mirror of
https://github.com/fmhy/dupe-checker.git
synced 2026-03-11 08:55:39 +00:00
Code refactoring, toggle third-party testing api (1.17)
This commit is contained in:
parent
68ab8404d3
commit
c7198f7764
2 changed files with 300 additions and 155 deletions
323
FmhyChecker.pyw
323
FmhyChecker.pyw
|
|
@ -1,37 +1,53 @@
|
|||
"""
|
||||
FMHY Dupe Checker
|
||||
v1.17
|
||||
"""
|
||||
|
||||
|
||||
__author__ = "cevoj"
|
||||
__version__ = "1.17"
|
||||
|
||||
|
||||
# networking
|
||||
import grequests
|
||||
import requests
|
||||
import urllib3
|
||||
from fake_headers import Headers
|
||||
from http.client import responses as status_codes
|
||||
from requests.exceptions import ConnectTimeout, ReadTimeout, ConnectionError, SSLError
|
||||
|
||||
# gui
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
import ctypes as ct
|
||||
import darkdetect
|
||||
from pyperclip import copy
|
||||
|
||||
# builtins
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from threading import Thread, Event
|
||||
from contextlib import suppress
|
||||
from pyperclip import copy
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
from requests.exceptions import ReadTimeout, ConnectionError
|
||||
import time
|
||||
import csv
|
||||
import darkdetect
|
||||
from base64 import b64decode
|
||||
import ctypes as ct
|
||||
from queue import Queue
|
||||
from dataclasses import dataclass
|
||||
from http.client import responses as status_codes
|
||||
from typing import Union
|
||||
|
||||
|
||||
# disable ssl warnings
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
# fake headers
|
||||
headers = Headers()
|
||||
headers = Headers(headers=True)
|
||||
# use Queue to limit number of concurrent requests
|
||||
dist_cnxns = Queue(maxsize=3)
|
||||
# check for dark mode
|
||||
DARK_MODE = darkdetect.isDark()
|
||||
dist_cnxns = Queue(maxsize=3)
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
def resource_path(relative_path) -> str:
|
||||
# wrapper to retrieve the absolute path from a relative path
|
||||
try:
|
||||
base_path = sys._MEIPASS
|
||||
|
|
@ -49,13 +65,13 @@ class StatusResp:
|
|||
|
||||
|
||||
class LinkTest:
|
||||
chunk_size = 50 # number of links to test at once
|
||||
statusapi_url = b64decode('aHR0cHM6Ly9iYWNrZW5kLmh0dHBzdGF0dXMuaW8vYXBp').decode()
|
||||
chunk_size = 50 # number of links to test at once
|
||||
statusapi_url = b64decode('aHR0cHM6Ly9iYWNrZW5kLmh0dHBzdGF0dXMuaW8vYXBp').decode()
|
||||
statusapi_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
# 'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Referer': b64decode('aHR0cHM6Ly9odHRwc3RhdHVzLmlvLw==').decode(),
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'Origin': b64decode('aHR0cHM6Ly9odHRwc3RhdHVzLmlv').decode(),
|
||||
|
|
@ -71,14 +87,47 @@ class LinkTest:
|
|||
'userName': '', 'passWord': '', 'headerName': '', 'headerValue': '',
|
||||
'strictSSL': True,
|
||||
'canonicalDomain': False,
|
||||
'additionalSubdomains': ['www',],
|
||||
'additionalSubdomains': ['www'],
|
||||
'followRedirect': True,
|
||||
'throttleRequests': 100,
|
||||
'escapeCharacters': False,
|
||||
}
|
||||
error_names = {
|
||||
SSLError: 'SSL Error',
|
||||
ReadTimeout: 'Timeout',
|
||||
ConnectTimeout: 'Timeout',
|
||||
ConnectionError: 'Connection Error',
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def handle_req(urls, items, callback, error_sig):
|
||||
# process the request & send back to main event loop
|
||||
dist_cnxns.put(1) # wait for when <3 instances of handle_req are running. blocks if full
|
||||
for item in items:
|
||||
item.setText(2, 'Testing...')
|
||||
reqs = [
|
||||
grequests.head(url, headers=headers.generate(), timeout=5, allow_redirects=True, verify=False)
|
||||
for url in urls
|
||||
]
|
||||
resps = grequests.imap(reqs, size=len(reqs), exception_handler=lambda _, e: e)
|
||||
for (item, url, resp) in zip(items, urls, resps):
|
||||
if not resp:
|
||||
err = 'Connection Failed'
|
||||
elif isinstance(resp, Exception):
|
||||
err = LinkTest.error_names.get(resp.__class__, resp.__class__.__name__)
|
||||
else:
|
||||
err = None
|
||||
statusresp = StatusResp(
|
||||
url = url,
|
||||
status_code = 0 if err else resp.status_code,
|
||||
reason = err or status_codes[resp.status_code],
|
||||
history = [] if err else (*resp.history, resp),
|
||||
)
|
||||
callback(item, statusresp)
|
||||
dist_cnxns.get() # release next in queue
|
||||
|
||||
@staticmethod
|
||||
def thirdparty_req(urls, items, callback, error_sig):
|
||||
# process the request & send back to main event loop
|
||||
dist_cnxns.put(1) # wait for when <3 requests are running. blocks if full
|
||||
for item in items:
|
||||
|
|
@ -96,35 +145,44 @@ class LinkTest:
|
|||
error_sig(f'An unknown error occurred. Please try again.\n\n{e}')
|
||||
for (item, url, resp) in zip(items, urls, data):
|
||||
try:
|
||||
callback(item, LinkTest.build_status_resp(resp, url))
|
||||
callback(item, LinkTest.thirdp_status_resp(resp, url))
|
||||
except Exception as e:
|
||||
print('Error:', e, resp)
|
||||
dist_cnxns.get() # release next in queue
|
||||
|
||||
@staticmethod
|
||||
def build_status_resp(resp, url=None) -> StatusResp:
|
||||
def thirdp_status_resp(resp, url=None) -> StatusResp:
|
||||
return StatusResp(
|
||||
url = url or resp.get('url', 'Failed'),
|
||||
status_code = resp['statusCode'] if type(resp.get('statusCode')) is int else 0,
|
||||
reason = resp.get('errorMessage') or status_codes[resp['statusCode']],
|
||||
history = [LinkTest.build_status_resp(r)
|
||||
history = [LinkTest.thirdp_status_resp(r)
|
||||
for r in resp.get('fullRedirectChain', [])],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def async_request(*args):
|
||||
thread = Thread(target=LinkTest.handle_req, args=args, daemon=True)
|
||||
def async_request(*args, use_third_party=False):
|
||||
thread = Thread(
|
||||
target=(LinkTest.handle_req, LinkTest.thirdparty_req)[use_third_party],
|
||||
args=args,
|
||||
daemon=True
|
||||
)
|
||||
# spawn thread to handle request
|
||||
thread.start()
|
||||
|
||||
|
||||
class UI(QMainWindow):
|
||||
# signals
|
||||
checkLinks_callback = pyqtSignal() # signal to callback checkLinks if it was called but already running
|
||||
finish_test_sig = pyqtSignal(object, StatusResp) # signal to call finishTest
|
||||
error_sig = pyqtSignal(str) # signal to call error_msg
|
||||
error_sig = pyqtSignal(str) # signal to call UI.errorMsg
|
||||
# regex for slicing links into groups ( <protocol://> <domain/path> <?leading info> )
|
||||
# i only check if group 1 is in the wiki to determine if the link is unique, then add the full link
|
||||
grouped_wiki_regex = re.compile(r'((?:https?|ftp|file):\/\/(?:ww(?:w|\d+)\.)?)((?:[\w_-]+(?:\.[\w_-]+)+)[\w.,@?^=%&:\/~+#-]*[\w@?^=%&~+-])')
|
||||
grouped_wiki_regex = re.compile(
|
||||
r'((?:https?|ftp|file):\/\/(?:ww(?:w|\d+)\.)?)((?:[\w_-]+(?:\.[\w_-]+)+)[\w.,@?^=%&:\/~+#-]*[\w@?^=%&~+-])'
|
||||
)
|
||||
# check for dark mode
|
||||
DARK_MODE = darkdetect.isDark()
|
||||
|
||||
def __init__(self):
|
||||
super(UI, self).__init__()
|
||||
|
|
@ -132,10 +190,10 @@ class UI(QMainWindow):
|
|||
|
||||
self.setWindowIcon(QtGui.QIcon(resource_path('assets/icon.ico')))
|
||||
# palette coloring
|
||||
if DARK_MODE:
|
||||
if UI.DARK_MODE:
|
||||
self._highlight_col = QtGui.QColor(157, 93, 24)
|
||||
dark_palette()
|
||||
dark_title_bar(int(self.winId()))
|
||||
UI.darkPalette()
|
||||
UI.darkTitleBar(int(self.winId()))
|
||||
self.outputTree.setFont(self.inputBox.font())
|
||||
else:
|
||||
self._highlight_col = QtGui.QColor(255, 128, 0)
|
||||
|
|
@ -174,13 +232,17 @@ class UI(QMainWindow):
|
|||
self.copyValid.clicked.connect(lambda: copy('\n'.join(self.valid_links)))
|
||||
self.copyTested.clicked.connect(lambda: copy('\n'.join(self.getTestedLinks())))
|
||||
self.exportCsv.clicked.connect(self.exportCsvDialog)
|
||||
self.checkSelected.clicked.connect(self.test_selected_links)
|
||||
self.checkSelected.clicked.connect(self.testSelectedLinks)
|
||||
self.inputBox.textChanged.connect(self.checkLinks)
|
||||
self.outputTree.itemSelectionChanged.connect(self.onSelection)
|
||||
# signals
|
||||
self.finish_test_sig.connect(self.finishTest)
|
||||
self.checkLinks_callback.connect(self.checkLinks)
|
||||
self.error_sig.connect(error_msg)
|
||||
self.error_sig.connect(UI.errorMsg)
|
||||
# display "useThirdParty" button when titleFrame is hovered
|
||||
self.useThirdParty.setVisible(False)
|
||||
self.titleFrame.enterEvent = lambda e: self.useThirdParty.setVisible(True)
|
||||
self.titleFrame.leaveEvent = lambda e: self.useThirdParty.setVisible(False)
|
||||
|
||||
self.testing_items = set()
|
||||
self.tested_items = {}
|
||||
|
|
@ -193,6 +255,17 @@ class UI(QMainWindow):
|
|||
splash.hide()
|
||||
self.show()
|
||||
|
||||
def searchText(self, text):
|
||||
# use wiki regex to search for links in text
|
||||
links = re.findall(self.grouped_wiki_regex, text)
|
||||
# remove duplicates
|
||||
seen = set()
|
||||
for link in links:
|
||||
if link[1] not in seen:
|
||||
seen.add(link[1])
|
||||
yield link
|
||||
del seen
|
||||
|
||||
# csv exporting
|
||||
def exportCsvDialog(self):
|
||||
file_dialog = QtWidgets.QFileDialog()
|
||||
|
|
@ -203,7 +276,7 @@ class UI(QMainWindow):
|
|||
if not file_dialog.exec_():
|
||||
return
|
||||
file_path = file_dialog.selectedFiles()[0]
|
||||
links = re.findall(self.grouped_wiki_regex, self.inputBox.toPlainText())
|
||||
links = self.searchText(self.inputBox.toPlainText())
|
||||
try:
|
||||
with open(file_path, 'w', newline='') as csvfile:
|
||||
self.writeCsvFile(csvfile, links)
|
||||
|
|
@ -214,7 +287,7 @@ class UI(QMainWindow):
|
|||
# write links to csv file
|
||||
writer = csv.writer(csvfile, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||
# csv header
|
||||
writer.writerow(['Request URL', 'Final URL', 'Unique?', '# Redirects', 'Status', 'Reason'])
|
||||
writer.writerow(['Request URL', 'Final URL', '# Redirects', 'Unique?', 'Status', 'Reason'])
|
||||
for link in links:
|
||||
full_link = ''.join(link)
|
||||
if full_link in self.tested_items:
|
||||
|
|
@ -238,25 +311,26 @@ class UI(QMainWindow):
|
|||
# if the link was not tested, set values to blank
|
||||
reason = redirects = final_url = status_code = ''
|
||||
# write row to csv
|
||||
writer.writerow([
|
||||
re.sub(r'[^\x00-\x7F]+', '?', full_link), # remove non ascii characters
|
||||
final_url,
|
||||
redirects,
|
||||
'FALSE' if link[1] in wiki else 'TRUE',
|
||||
redirects,
|
||||
status_code,
|
||||
reason
|
||||
])
|
||||
writer.writerow(
|
||||
[
|
||||
re.sub(r"[^\x00-\x7F]+", "?", full_link), # request url
|
||||
final_url, # final url
|
||||
redirects, # number of redirects
|
||||
"FALSE" if link[1] in wiki else "TRUE", # unique?
|
||||
status_code, # status code
|
||||
reason, # reason
|
||||
]
|
||||
)
|
||||
|
||||
# return a list of unique links that have 200 status codes
|
||||
def getTestedLinks(self) -> list:
|
||||
return [
|
||||
l for l in self.tested_items
|
||||
# if link is valid and not a message
|
||||
if l in self.valid_links and type(self.tested_items[l]) is not str
|
||||
# and status code was OK
|
||||
and self.tested_items[l].status_code in range(200, 300)
|
||||
]
|
||||
# if link is valid and not a message
|
||||
if l in self.valid_links and type(self.tested_items[l]) is not str
|
||||
# and status code was OK
|
||||
and self.tested_items[l].status_code in range(200, 300)
|
||||
]
|
||||
|
||||
# add the status code chain to the tree
|
||||
def finishTest(self, item, resp):
|
||||
|
|
@ -281,18 +355,22 @@ class UI(QMainWindow):
|
|||
item.setText(2, "") # remove the loading text
|
||||
# add the status code chain
|
||||
for r in resp.history or (resp,):
|
||||
color = next((self.status_colors[k] for k in self.status_colors if r.status_code in k), '#781C1E')
|
||||
color = next(
|
||||
(self.status_colors[k] for k in self.status_colors if r.status_code in k),
|
||||
"#781C1E",
|
||||
)
|
||||
text = str(r.status_code) if r.status_code else 'Error'
|
||||
text2 = r.reason
|
||||
tooltip = f'{r.reason} | {r.url}'
|
||||
self.add_status_label(layout, text, text2, color, tooltip)
|
||||
tooltip = f'{r.status_code or "Error:"} {r.reason} | {r.url}'
|
||||
self.addStatusLabel(layout, text, text2, color, tooltip)
|
||||
|
||||
def add_status_label(self, layout, text, text2, color, tooltip=None):
|
||||
def addStatusLabel(self, layout, text, text2, color, tooltip=None):
|
||||
# add label to the layout
|
||||
label = QtWidgets.QLabel(text)
|
||||
# create lighter color for hover
|
||||
light_color = QtGui.QColor(color).lighter().name()
|
||||
label.setStyleSheet(f'''
|
||||
label.setStyleSheet(
|
||||
f"""
|
||||
* {{
|
||||
background-color: {color};
|
||||
color: white;
|
||||
|
|
@ -302,7 +380,8 @@ class UI(QMainWindow):
|
|||
QLabel:hover:!pressed {{
|
||||
border: 2px solid {light_color};
|
||||
}}
|
||||
''')
|
||||
"""
|
||||
)
|
||||
label.setMouseTracking(True)
|
||||
# change text to text2 on hover
|
||||
label.enterEvent = lambda e: label.setText(text2)
|
||||
|
|
@ -313,7 +392,7 @@ class UI(QMainWindow):
|
|||
label.setToolTipDuration(-1)
|
||||
layout.addWidget(label)
|
||||
|
||||
def test_selected_links(self):
|
||||
def testSelectedLinks(self):
|
||||
# get selected tree items
|
||||
selected = self.getRanItems()
|
||||
self.testing_items.update([i.text(1) for i in selected]) # remember tested items
|
||||
|
|
@ -324,10 +403,16 @@ class UI(QMainWindow):
|
|||
self.checkSelected.setVisible(False)
|
||||
# send requests
|
||||
for index in range(0, len(selected), LinkTest.chunk_size):
|
||||
items = selected[index:index+LinkTest.chunk_size]
|
||||
items = selected[index : index + LinkTest.chunk_size]
|
||||
urls = [item.text(1) for item in items]
|
||||
try:
|
||||
LinkTest.async_request(urls, items, self.finish_test_sig.emit, self.error_sig.emit)
|
||||
LinkTest.async_request(
|
||||
urls,
|
||||
items,
|
||||
self.finish_test_sig.emit,
|
||||
self.error_sig.emit,
|
||||
use_third_party=self.useThirdParty.isChecked(),
|
||||
)
|
||||
except RuntimeError:
|
||||
return # item was deleted
|
||||
QtWidgets.QApplication.processEvents() # allow GUI to update
|
||||
|
|
@ -340,7 +425,9 @@ class UI(QMainWindow):
|
|||
def onSelection(self):
|
||||
# when an untested tree item is selected, show the "Test" button
|
||||
if selected := self.getRanItems():
|
||||
self.checkSelected.setText(QtCore.QCoreApplication.translate("MainWindow", f"Test ({len(selected)}) \U0001F50D"))
|
||||
self.checkSelected.setText(
|
||||
QtCore.QCoreApplication.translate("MainWindow", f"Test ({len(selected)}) \U0001F50D")
|
||||
)
|
||||
self.checkSelected.setVisible(True)
|
||||
else:
|
||||
self.checkSelected.setVisible(False)
|
||||
|
|
@ -374,7 +461,7 @@ class UI(QMainWindow):
|
|||
self.exportCsv.setEnabled(False)
|
||||
self.checkSelected.setVisible(False)
|
||||
# get all links from the input text using regex
|
||||
links = re.findall(self.grouped_wiki_regex, text)
|
||||
links = list(self.searchText(text))
|
||||
self.valid_links, self.duped_links, self.tested_links = [], [], []
|
||||
# enable progress bar
|
||||
self.progressBar.setMaximum(len(links))
|
||||
|
|
@ -427,11 +514,12 @@ class UI(QMainWindow):
|
|||
def retranslateUi(self):
|
||||
# set text (with translations)
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.setWindowTitle(_translate("MainWindow", "Dupe Checker v1.16.1"))
|
||||
self.setWindowTitle(_translate("MainWindow", f"Dupe Checker {__version__}"))
|
||||
self.label.setText(_translate("MainWindow", "FMHY Dupe Tester"))
|
||||
self.label_2.setText(_translate("MainWindow", "by cevoj"))
|
||||
self.label_2.setText(_translate("MainWindow", f"by {__author__}"))
|
||||
self._placeholderText = _translate("MainWindow", "Paste a list of links here...")
|
||||
self.inputBox.setPlaceholderText(self._placeholderText)
|
||||
self.useThirdParty.setText(_translate("MainWindow", f"Use {b64decode('aHR0cHN0YXR1cy5pbw==').decode()} API"))
|
||||
self.copyValid.setText(_translate("MainWindow", "Copy \u2705"))
|
||||
self.copyDupes.setText(_translate("MainWindow", "Copy \u274C"))
|
||||
self.copyTested.setText(_translate("MainWindow", "Copy \U0001F50D"))
|
||||
|
|
@ -439,43 +527,60 @@ class UI(QMainWindow):
|
|||
self.outputTree.headerItem().setText(0, _translate("MainWindow", "Check"))
|
||||
self.outputTree.headerItem().setText(1, _translate("MainWindow", "Link"))
|
||||
self.outputTree.headerItem().setText(2, _translate("MainWindow", "Status"))
|
||||
|
||||
@staticmethod
|
||||
def errorMsg(text):
|
||||
# build error message
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msg.setText(text)
|
||||
msg.setWindowIcon(QtGui.QIcon(resource_path('assets/icon.ico')))
|
||||
msg.setWindowTitle("Connection Error")
|
||||
if UI.DARK_MODE:
|
||||
UI.darkTitleBar(int(msg.winId()))
|
||||
msg.exec_()
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def darkTitleBar(hwnd):
|
||||
if (
|
||||
sys.platform != 'win32'
|
||||
or (version_num := sys.getwindowsversion()).major != 10
|
||||
):
|
||||
return
|
||||
set_window_attribute = ct.windll.dwmapi.DwmSetWindowAttribute
|
||||
if version_num.build >= 22000: # windows 11
|
||||
color = ct.c_int(0x2d2319)
|
||||
set_window_attribute(hwnd, 35, ct.byref(color), ct.sizeof(color))
|
||||
else:
|
||||
rendering_policy = 19 if version_num.build < 19041 else 20 # 19 before 20h1
|
||||
value = ct.c_int(True)
|
||||
set_window_attribute(hwnd, rendering_policy, ct.byref(value), ct.sizeof(value))
|
||||
|
||||
def dark_title_bar(hwnd):
|
||||
if (
|
||||
sys.platform != 'win32'
|
||||
or (version_num := sys.getwindowsversion()).major != 10
|
||||
):
|
||||
return
|
||||
set_window_attribute = ct.windll.dwmapi.DwmSetWindowAttribute
|
||||
if version_num.build >= 22000: # windows 11
|
||||
color = ct.c_int(0x2d2319)
|
||||
set_window_attribute(hwnd, 35, ct.byref(color), ct.sizeof(color))
|
||||
else:
|
||||
rendering_policy = 19 if version_num.build < 19041 else 20 # 19 before 20h1
|
||||
value = ct.c_int(True)
|
||||
set_window_attribute(hwnd, rendering_policy, ct.byref(value), ct.sizeof(value))
|
||||
|
||||
|
||||
def dark_palette():
|
||||
# create darker palette with Fusion style
|
||||
app.setStyle('Fusion')
|
||||
palette = QtGui.QPalette()
|
||||
palette.setColor(QtGui.QPalette.Window, QtGui.QColor(25,35,45))
|
||||
palette.setColor(QtGui.QPalette.Light, QtGui.QColor(39, 49, 58))
|
||||
palette.setColor(QtGui.QPalette.Dark, QtGui.QColor(39, 49, 58))
|
||||
palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.white)
|
||||
palette.setColor(QtGui.QPalette.Base, QtGui.QColor(39, 49, 58))
|
||||
palette.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(25,35,45))
|
||||
palette.setColor(QtGui.QPalette.ToolTipBase, QtCore.Qt.white)
|
||||
palette.setColor(QtGui.QPalette.ToolTipText, QtCore.Qt.white)
|
||||
palette.setColor(QtGui.QPalette.Text, QtCore.Qt.white)
|
||||
palette.setColor(QtGui.QPalette.Button, QtGui.QColor(25,35,45))
|
||||
palette.setColor(QtGui.QPalette.ButtonText, QtCore.Qt.white)
|
||||
palette.setColor(QtGui.QPalette.BrightText, QtCore.Qt.blue)
|
||||
palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(20, 129, 216))
|
||||
palette.setColor(QtGui.QPalette.HighlightedText, QtCore.Qt.white)
|
||||
app.setPalette(palette)
|
||||
@staticmethod
|
||||
def darkPalette():
|
||||
# create darker palette with Fusion style
|
||||
app.setStyle('Fusion')
|
||||
palette = QtGui.QPalette()
|
||||
cols = {
|
||||
QtGui.QPalette.Window: QtGui.QColor(25,35,45),
|
||||
QtGui.QPalette.Light: QtGui.QColor(39, 49, 58),
|
||||
QtGui.QPalette.Dark: QtGui.QColor(39, 49, 58),
|
||||
QtGui.QPalette.WindowText: QtCore.Qt.white,
|
||||
QtGui.QPalette.Base: QtGui.QColor(39, 49, 58),
|
||||
QtGui.QPalette.AlternateBase: QtGui.QColor(25,35,45),
|
||||
QtGui.QPalette.ToolTipBase: QtCore.Qt.white,
|
||||
QtGui.QPalette.ToolTipText: QtCore.Qt.white,
|
||||
QtGui.QPalette.Text: QtCore.Qt.white,
|
||||
QtGui.QPalette.Button: QtGui.QColor(25,35,45),
|
||||
QtGui.QPalette.ButtonText: QtCore.Qt.white,
|
||||
QtGui.QPalette.BrightText: QtCore.Qt.blue,
|
||||
QtGui.QPalette.Highlight: QtGui.QColor(20, 129, 216),
|
||||
QtGui.QPalette.HighlightedText: QtCore.Qt.white,
|
||||
}
|
||||
for (pal, color) in cols.items():
|
||||
palette.setColor(pal, color)
|
||||
app.setPalette(palette)
|
||||
|
||||
|
||||
class WikiScraper:
|
||||
|
|
@ -493,18 +598,25 @@ class WikiScraper:
|
|||
'''
|
||||
def __init__(self):
|
||||
self.URLS = {
|
||||
'https://raw.githubusercontent.com/nbats/FMHYedit/main/single-page': (self.handle_wiki, self.handle_b64),
|
||||
'https://gitlab.com/nbatman_/deleted-links/-/raw/main/deleted-links': (self.handle_list,),
|
||||
"https://raw.githubusercontent.com/nbats/FMHYedit/main/single-page": (
|
||||
self.handle_wiki,
|
||||
self.handle_b64,
|
||||
),
|
||||
"https://gitlab.com/nbatman_/deleted-links/-/raw/main/deleted-links": (
|
||||
self.handle_list,
|
||||
),
|
||||
}
|
||||
|
||||
def build_wiki_set(self) -> set:
|
||||
elapsed = time.perf_counter()
|
||||
try:
|
||||
resps = grequests.map([grequests.get(l) for l in self.URLS], size=len(self.URLS))
|
||||
resps = grequests.map(
|
||||
[grequests.get(l) for l in self.URLS], size=len(self.URLS)
|
||||
)
|
||||
except ConnectionError:
|
||||
# show connection error
|
||||
splash.hide()
|
||||
self.error_msg("Could not connect to the internet. Please check your connection and try again.")
|
||||
UI.errorMsg("Could not connect to the internet. Please check your connection and try again.")
|
||||
wiki = set()
|
||||
for resp, funcs in zip(resps, self.URLS.values()):
|
||||
for func in funcs:
|
||||
|
|
@ -525,23 +637,10 @@ class WikiScraper:
|
|||
|
||||
def handle_b64(self, text) -> set:
|
||||
data = '\n'.join(
|
||||
b64decode(m.strip('`')).decode()
|
||||
for m in re.findall(self.b64_regex, text)
|
||||
b64decode(m.strip('`')).decode() for m in re.findall(self.b64_regex, text)
|
||||
)
|
||||
return self.handle_list(data)
|
||||
|
||||
def error_msg(text):
|
||||
# build error message
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msg.setText(text)
|
||||
msg.setWindowIcon(QtGui.QIcon(resource_path('assets/icon.ico')))
|
||||
msg.setWindowTitle("Connection Error")
|
||||
if DARK_MODE:
|
||||
dark_title_bar(int(msg.winId()))
|
||||
msg.exec_()
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
|
|
|||
132
MainWindow.ui
132
MainWindow.ui
|
|
@ -24,49 +24,6 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Segoe UI Variable Display</family>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FMHY Dupe Tester</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Segoe UI Variable Display</family>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>by cevoj35548</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="inputBox">
|
||||
<property name="font">
|
||||
|
|
@ -328,6 +285,95 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QFrame" name="titleFrame">
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Segoe UI Variable Display</family>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FMHY Dupe Tester</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useThirdParty">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Segoe UI Variable Display</family>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use third-party API</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Segoe UI Variable Display</family>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>by cevoj</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
|||
Loading…
Reference in a new issue