Advertisement
cevoj35548

FMHY Dupe Checker Updated 1.9

Dec 27th, 2022 (edited)
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.94 KB | None | 0 0
  1. """
  2. Written in Python. Run this command for dependencies:
  3. pip install requests PyQt5 pyperclip fake-headers
  4. """
  5.  
  6. import requests
  7. from fake_headers import Headers
  8. import re
  9. from threading import Thread, Event
  10. from contextlib import suppress
  11. from pyperclip import copy
  12. from PyQt5 import QtCore, QtGui, QtWidgets
  13. from PyQt5.QtCore import pyqtSignal
  14. from requests.exceptions import ReadTimeout, ConnectionError
  15. import time
  16. import csv
  17.  
  18. # fake headers
  19. headers = Headers(headers=True)
  20.  
  21. # wiki scrape
  22. elapsed = time.perf_counter()
  23. url_regex = re.compile(r'(?:https?|ftp|file):\/\/(?:ww(?:w|\d+)\.)?((?:[\w_-]+(?:\.[\w_-]+)+)[\w.,@?^=%&:\/~+#-]*[\w@?^=%&~+-])')
  24. wiki = set(re.findall(url_regex, requests.get("https://raw.githubusercontent.com/nbats/FMHYedit/main/single-page").text))
  25. print(f'Wiki scraped in {time.perf_counter() - elapsed:0.4f} sec. Found {len(wiki)} links.')
  26.  
  27.  
  28. def handle_req(url, item, callback):
  29.     try:
  30.         resp = requests.get(url, headers=headers.generate(), timeout=10)
  31.         if resp is None: resp = 'Failed'
  32.     except ReadTimeout:
  33.         callback(url, 'Timeout', item)
  34.     except ConnectionError:
  35.         callback(url, 'Error', item)
  36.     except Exception as e:
  37.         callback(url, str(e), item)
  38.     else:
  39.         callback(url, resp, item)
  40.  
  41. def async_request(*args):
  42.     thread = Thread(target=handle_req, args=args, daemon=True)
  43.     thread.start()
  44.  
  45.  
  46. class httpTestSignalWrapper(QtCore.QObject):
  47.     signal = pyqtSignal(str, object, object)
  48.    
  49. class checkLinksSignalWrapper(QtCore.QObject):
  50.     signal = pyqtSignal()
  51.  
  52.  
  53. class Ui_MainWindow(object):
  54.     group_url_regex = re.compile(r'((?:https?|ftp|file):\/\/(?:ww(?:w|\d+)\.)?)((?:[\w_-]+(?:\.[\w_-]+)+)[\w.,@?^=%&:\/~+#-]*[\w@?^=%&~+-])')
  55.    
  56.     def setupUi(self, MainWindow):
  57.         # blob of code up ahead from pyuic5. needed to keep this in one file
  58.         MainWindow.setObjectName("MainWindow")
  59.         MainWindow.resize(750, 450)
  60.         self.centralwidget = QtWidgets.QWidget(MainWindow)
  61.         self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
  62.         self.gridLayout.setContentsMargins(9, 3, 9, 0)
  63.         self.horizontalLayout = QtWidgets.QHBoxLayout()
  64.         self.label = QtWidgets.QLabel(self.centralwidget)
  65.         font = QtGui.QFont()
  66.         font.setFamily("Segoe UI Variable Display")
  67.         font.setPointSize(14)
  68.         self.label.setFont(font)
  69.         self.horizontalLayout.addWidget(self.label)
  70.         spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
  71.         self.horizontalLayout.addItem(spacerItem)
  72.         self.label_2 = QtWidgets.QLabel(self.centralwidget)
  73.         font = QtGui.QFont()
  74.         font.setFamily("Segoe UI Variable Display")
  75.         font.setPointSize(14)
  76.         self.label_2.setFont(font)
  77.         self.horizontalLayout.addWidget(self.label_2)
  78.         self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2)
  79.         self.inputBox = QtWidgets.QPlainTextEdit(self.centralwidget)
  80.         font = QtGui.QFont()
  81.         font.setFamily("Calibri")
  82.         font.setPointSize(11)
  83.         self.inputBox.setFont(font)
  84.         self.inputBox.setFrameShape(QtWidgets.QFrame.NoFrame)
  85.         self.gridLayout.addWidget(self.inputBox, 1, 0, 1, 1)
  86.         self.gridFrame = QtWidgets.QFrame(self.centralwidget)
  87.         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
  88.         sizePolicy.setHorizontalStretch(0)
  89.         sizePolicy.setVerticalStretch(0)
  90.         sizePolicy.setHeightForWidth(self.gridFrame.sizePolicy().hasHeightForWidth())
  91.         self.gridFrame.setSizePolicy(sizePolicy)
  92.         self.gridLayout_3 = QtWidgets.QGridLayout(self.gridFrame)
  93.         self.gridLayout_3.setContentsMargins(1, 1, 1, 4)
  94.         self.gridLayout_3.setVerticalSpacing(4)
  95.         self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
  96.         self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
  97.         self.copyDupes = QtWidgets.QPushButton(self.gridFrame)
  98.         self.copyDupes.setEnabled(False)
  99.         self.copyDupes.setFont(font)
  100.         self.horizontalLayout_2.addWidget(self.copyDupes)
  101.         self.copyValid = QtWidgets.QPushButton(self.gridFrame)
  102.         self.copyValid.setEnabled(False)
  103.         self.copyValid.setFont(font)
  104.         self.horizontalLayout_2.addWidget(self.copyValid)
  105.         self.copyTested = QtWidgets.QPushButton(self.gridFrame)
  106.         self.copyTested.setEnabled(False)
  107.         self.copyTested.setFont(font)
  108.         self.horizontalLayout_2.addWidget(self.copyTested)
  109.         self.checkSelected = QtWidgets.QPushButton(self.gridFrame)
  110.         self.checkSelected.setVisible(False)
  111.         self.checkSelected.setFont(font)
  112.         self.horizontalLayout_2.addWidget(self.checkSelected)
  113.         self.exportCsv = QtWidgets.QPushButton(self.gridFrame)
  114.         self.exportCsv.setFlat(True)
  115.         font = QtGui.QFont()
  116.         font.setFamily("Segoe MDL2 Assets")
  117.         font.setPointSize(13)
  118.         self.exportCsv.setFont(font)
  119.         self.exportCsv.setText("\uE896")
  120.         self.exportCsv.setFixedSize(30, 30)
  121.         self.horizontalLayout_2.addWidget(self.exportCsv)
  122.         self.gridLayout_3.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
  123.         self.outputTree = QtWidgets.QTreeWidget(self.gridFrame)
  124.         self.outputTree.setFrameShape(QtWidgets.QFrame.NoFrame)
  125.         self.outputTree.header().setSectionsMovable(False)
  126.         self.outputTree.setColumnCount(3)
  127.         self.outputTree.setColumnWidth(0, 50)
  128.         self.outputTree.setColumnWidth(1, 180)
  129.         # MainWindow.resizeEvent = lambda event: \
  130.         #     self.outputTree.setColumnWidth(1, 100 + (event.size().width() - 750) / 2)
  131.         # multi select tree
  132.         self.outputTree.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
  133.         self.outputTree.setRootIsDecorated(False)
  134.         self.gridLayout_3.addWidget(self.outputTree, 0, 0, 1, 1)
  135.         self.gridLayout.addWidget(self.gridFrame, 1, 1, 1, 1)
  136.         MainWindow.setCentralWidget(self.centralwidget)
  137.        
  138.         # status code font
  139.         self.status_font = QtGui.QFont()
  140.         self.status_font.setFamily("Calibri")
  141.         self.status_font.setPointSize(10)
  142.        
  143.         # status code colors
  144.         self.status_colors = {
  145.             range(200, 300): '#31cd64',
  146.             range(300, 400): '#33a7ff',
  147.             range(400, 500): '#fda92a',
  148.             range(500, 600): '#fc4f52',
  149.         }
  150.  
  151.         # connections
  152.         self.copyDupes.clicked.connect(lambda: copy('\n'.join(self.duped_links)))
  153.         self.copyValid.clicked.connect(lambda: copy('\n'.join(self.valid_links)))
  154.         self.copyTested.clicked.connect(lambda: copy('\n'.join(self.getTestedLinks())))
  155.         self.exportCsv.clicked.connect(self.exportCsvDialog)
  156.         self.checkSelected.clicked.connect(self._testSelectedLinks)
  157.         self.inputBox.textChanged.connect(self.checkLinks)
  158.         self.outputTree.itemSelectionChanged.connect(self.onSelection)
  159.         self.http_test_sig = httpTestSignalWrapper()        
  160.         self.http_test_sig.signal.connect(self.finishTest)
  161.         self.call_back_checkLinks = checkLinksSignalWrapper()
  162.         self.call_back_checkLinks.signal.connect(self.checkLinks)
  163.        
  164.         self.testing_items = set()
  165.         self.tested_items = {}
  166.         self._is_free = Event()
  167.         self._is_free.set()
  168.         self.line_thread = None
  169.         self._new_event = False
  170.  
  171.         self.retranslateUi(MainWindow)
  172.         QtCore.QMetaObject.connectSlotsByName(MainWindow)
  173.    
  174.     def exportCsvDialog(self):
  175.         file_dialog = QtWidgets.QFileDialog()
  176.         file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
  177.         file_dialog.setNameFilter("CSV (*.csv)")
  178.         file_dialog.setDefaultSuffix("csv")
  179.         # show dialog
  180.         if file_dialog.exec_():
  181.             file_path = file_dialog.selectedFiles()[0]
  182.             links = re.findall(self.group_url_regex, self.inputBox.toPlainText())
  183.             try:
  184.                 with open(file_path, 'w', newline='') as csvfile:
  185.                     writer = csv.writer(csvfile, dialect='excel', quoting=csv.QUOTE_MINIMAL)
  186.                     writer.writerow(['Request URL', 'Unique?', '# Redirects', 'Status', 'Reason'])
  187.                     for link in links:
  188.                         full_link = ''.join(link)
  189.                         if full_link in self.tested_items:
  190.                             if type(self.tested_items[full_link]) is str:
  191.                                 _redirects = _status_code = ''
  192.                                 _reason = self.tested_items[full_link]
  193.                             else:
  194.                                 _redirects = str(len(self.tested_items[full_link].history))
  195.                                 _reason = self.tested_items[full_link].reason
  196.                                 _status_code = '=CONCAT('+', " > ", '.join(
  197.                                 f'HYPERLINK("{r.url}", "{r.status_code}")'
  198.                                 for r in (
  199.                                     *self.tested_items[full_link].history,
  200.                                     self.tested_items[full_link])
  201.                                 )+')'
  202.                         else:
  203.                             _reason = _redirects = _status_code = ''
  204.                         writer.writerow([
  205.                             full_link,
  206.                             'FALSE' if link[1] in wiki else 'TRUE',
  207.                             _redirects,
  208.                             _status_code,
  209.                             _reason
  210.                         ])
  211.             except PermissionError:
  212.                 QtWidgets.QMessageBox.critical(self.centralwidget, "Error", "Permission denied")
  213.    
  214.     def getTestedLinks(self):
  215.         return [
  216.             l for l in self.tested_items
  217.                 if l in self.valid_links and type(self.tested_items[l]) is not str
  218.                 and self.tested_items[l].status_code in range(200, 300)
  219.             ]
  220.    
  221.     def finishTest(self, url, resp, item):
  222.         if url in self.testing_items:
  223.             self.testing_items.remove(url)
  224.             self.tested_items[url] = resp
  225.             self.copyTested.setEnabled(True)
  226.         try:
  227.             item.text(2)
  228.         except RuntimeError:
  229.             return  # item was deleted
  230.         widget = QtWidgets.QWidget()
  231.         widget.setLayout(layout := QtWidgets.QHBoxLayout())
  232.         layout.setContentsMargins(0, 0, 0, 0)
  233.         layout.setSpacing(2)
  234.         layout.setAlignment(QtCore.Qt.AlignLeft)
  235.         self.outputTree.setItemWidget(item, 2, widget)
  236.         item.setText(2, "")
  237.         if type(resp) is str:
  238.             label = QtWidgets.QLabel(f' {resp} ')
  239.             label.setFont(self.status_font)
  240.             label.setStyleSheet('background-color: #A12729; color: white; border-radius: 6px;')
  241.             layout.addWidget(label)
  242.             return
  243.         for r in (*resp.history, resp):
  244.             label = QtWidgets.QLabel(f" {r.status_code} ")
  245.             color = next((self.status_colors[k] for k in self.status_colors if r.status_code in k), '#000000')
  246.             label.setStyleSheet(f'background-color: {color}; color: white; border-radius: 6px;')
  247.             label.setToolTip(r.url)
  248.             label.setToolTipDuration(-1)
  249.             label.setFont(self.status_font)
  250.             layout.addWidget(label)
  251.    
  252.     def _testSelectedLinks(self):
  253.         selected = self.getRanItems()
  254.         self.testing_items.update([i.text(1) for i in selected])  # remember tested items
  255.         for item in selected:
  256.             item.setText(2, "Testing...")
  257.         self.outputTree.clearSelection()
  258.         self.checkSelected.setVisible(False)
  259.         for item in selected:
  260.             async_request(item.text(1), item, self.http_test_sig.signal.emit)
  261.        
  262.     def getRanItems(self):
  263.         return [i for i in self.outputTree.selectedItems()
  264.                 if i.text(1) not in {*self.tested_items, *self.testing_items}]
  265.        
  266.     def onSelection(self):
  267.         if selected := self.getRanItems():
  268.             self.checkSelected.setText(QtCore.QCoreApplication.translate("MainWindow", f"Test ({len(selected)}) \U0001F50D"))
  269.             self.checkSelected.setVisible(True)
  270.         else:
  271.             self.checkSelected.setVisible(False)
  272.  
  273.     def _waitForEvent(self):
  274.         self._new_event = True
  275.         self._is_free.wait()
  276.         self.call_back_checkLinks.signal.emit()
  277.  
  278.     def checkLinks(self):
  279.         if not self._is_free.is_set():
  280.             if self.line_thread and self.line_thread.is_alive():
  281.                 return
  282.             self.line_thread = Thread(target=self._waitForEvent, daemon=True)
  283.             self.line_thread.start()
  284.             return
  285.         self._is_free.clear()
  286.         text = self.inputBox.toPlainText()
  287.         if text:
  288.             self.inputBox.setPlaceholderText('')
  289.         else:
  290.             self.inputBox.setPlaceholderText(self._placeholderText)
  291.         self.outputTree.clear()
  292.         self.copyValid.setEnabled(False)
  293.         self.copyDupes.setEnabled(False)
  294.         self.copyTested.setEnabled(False)
  295.         self.exportCsv.setEnabled(False)
  296.         self.checkSelected.setVisible(False)
  297.        
  298.         links = re.findall(self.group_url_regex, text)
  299.         self.valid_links, self.duped_links, self.tested_links = [], [], []
  300.         # populate tree
  301.         for n, link in enumerate(links):
  302.             if self._new_event:
  303.                 self._new_event = False
  304.                 self._is_free.set()
  305.                 return
  306.             item = QtWidgets.QTreeWidgetItem(self.outputTree)
  307.             full_link = ''.join(link)
  308.             item.setText(1, full_link)
  309.             if len(links) > 100 and not n % 10:
  310.                 # process in chunks to allow for UI updates
  311.                 QtWidgets.QApplication.processEvents()
  312.             with suppress(RuntimeError):
  313.                 if full_link in self.tested_items:
  314.                     self.finishTest(full_link, self.tested_items[full_link], item)
  315.                 elif full_link in self.testing_items:
  316.                     item.setText(2, "Testing...")
  317.                 if link[1] in wiki:
  318.                     item.setText(0, "\u274C")
  319.                     for _ in range(3):
  320.                         item.setBackground(_, QtGui.QColor(255, 128, 0))
  321.                     self.duped_links.append(full_link)
  322.                 else:
  323.                     item.setText(0, "\u2705")
  324.                     self.valid_links.append(full_link)
  325.         # toggle buttons
  326.         self.copyValid.setEnabled(bool(self.valid_links))
  327.         self.copyDupes.setEnabled(bool(self.duped_links))
  328.         self.copyTested.setEnabled(bool(self.getTestedLinks()))
  329.         self.exportCsv.setEnabled(True)
  330.         # handle copy buttons
  331.         self._is_free.set()
  332.  
  333.     def retranslateUi(self, MainWindow):
  334.         # Set text (with translations)
  335.         _translate = QtCore.QCoreApplication.translate
  336.         MainWindow.setWindowTitle(_translate("MainWindow", "Dupe Checker v1.9"))
  337.         self.label.setText(_translate("MainWindow", "FMHY Dupe Tester"))
  338.         self.label_2.setText(_translate("MainWindow", "by cevoj35548"))
  339.         self._placeholderText = _translate("MainWindow", "Paste a list of links here")
  340.         self.inputBox.setPlaceholderText(self._placeholderText)
  341.         self.copyValid.setText(_translate("MainWindow", "Copy \u2705"))
  342.         self.copyDupes.setText(_translate("MainWindow", "Copy \u274C"))
  343.         self.copyTested.setText(_translate("MainWindow", "Copy \U0001F50D"))
  344.         self.checkSelected.setText(_translate("MainWindow", "Test \U0001F50D"))
  345.         self.outputTree.headerItem().setText(0, _translate("MainWindow", "Check"))
  346.         self.outputTree.headerItem().setText(1, _translate("MainWindow", "Link"))
  347.         self.outputTree.headerItem().setText(2, _translate("MainWindow", "Status"))
  348.  
  349.  
  350. if __name__ == "__main__":
  351.     import sys
  352.     app = QtWidgets.QApplication(sys.argv)
  353.     MainWindow = QtWidgets.QMainWindow()
  354.     ui = Ui_MainWindow()
  355.     ui.setupUi(MainWindow)
  356.     MainWindow.show()
  357.     sys.exit(app.exec_())
  358.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement