From 886ee8f0f04363fdc2d0271203b9cd093cd2604e Mon Sep 17 00:00:00 2001 From: nosch Date: Wed, 25 Feb 2026 23:22:40 +0100 Subject: [PATCH 01/18] installed fuzzyfinder, scraped the links, started UI --- requirements.txt | 1 + src/core/data/scrapers/example.html | 116 +++++++++++++++++++++++++++ src/core/data/scrapers/steamrip.py | 24 ++++++ src/core/interface/utils/fzhelper.py | 28 +++++++ 4 files changed, 169 insertions(+) create mode 100644 src/core/data/scrapers/example.html create mode 100644 src/core/data/scrapers/steamrip.py create mode 100644 src/core/interface/utils/fzhelper.py diff --git a/requirements.txt b/requirements.txt index 0fecfdd..dec79d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ plyer==2.1.0 psutil==7.1.0 libtorrent==2.0.11 libtorrent-windows-dll==0.0.3 +fuzzyfinder==2.3.0 diff --git a/src/core/data/scrapers/example.html b/src/core/data/scrapers/example.html new file mode 100644 index 0000000..80152b1 --- /dev/null +++ b/src/core/data/scrapers/example.html @@ -0,0 +1,116 @@ + Games List » SteamRIP

All Games List

Back to top button
+ + + + + \ No newline at end of file diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py new file mode 100644 index 0000000..d55b28a --- /dev/null +++ b/src/core/data/scrapers/steamrip.py @@ -0,0 +1,24 @@ + +from bs4 import BeautifulSoup +import requests +import re +#from core.utils.general.logs import consoleLog + +def scrape_steamrip_links(): + url = f"https://steamrip.com/games-list-page/" + response = requests.get(url) + soup = BeautifulSoup(response.text, "html.parser") + games = soup.find_all("li", class_="az-list-item") + + if len(games) == 0: + return [] + + links = [] + for gamehtml in games: + link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) + links += re.findall('(?<=href=")[^"]*', link.__str__()) + + return links + +if __name__ == "__main__": + print(scrape_steamrip_links()) diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py new file mode 100644 index 0000000..ce01128 --- /dev/null +++ b/src/core/interface/utils/fzhelper.py @@ -0,0 +1,28 @@ +import fuzzyfinder +from PySide6 import QtCore, QtWidgets, QtGui + +class FuzzySearchWindow(QtWidgets.QWidget): + """ + The dict should contain a: + name : str + THIS IS REQUIRED + + all other things will be displayed alphabetically after it. + All elements should have the same Keys, eg when one element has "sources" : "buzzheavier" all other elements should as well have a key called "sources", though the value can be "MegaDB" + + All elements are strings, e.g. "count" : "1" + + The key will be at the Top of the Table + The value will be listed for each element + """ + def __init__(self, options: list[dict[str, str]]): + super().__init__() + + self.opts: list[dict[str, str]] = options + + self.searchbox = QtWidgets.QTextEdit() + self.searchbox.placeholderText = "Search for a game" + + self.table = QtWidgets.QTableWidget() + self.table.setHorizontalHeader(self.opts[0].keys()) + From bef019cf8cc3b796b259924f1ee4b4a023c1ecc5 Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 13:32:56 +0100 Subject: [PATCH 02/18] a fuzzyfind demo works, use python -m core.interface.utils.fzhelper --- .gitignore | 222 +----------------- .../scrapers/example.html => example.html | 0 src/core/data/scrapers/steamrip.py | 33 ++- src/core/interface/utils/fzhelper.py | 55 ++++- 4 files changed, 81 insertions(+), 229 deletions(-) rename src/core/data/scrapers/example.html => example.html (100%) diff --git a/.gitignore b/.gitignore index 9bd9aa6..49b53dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,220 +1,2 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[codz] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ - -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: -.ruff_cache/ - -# PyPI configuration file -.pypirc - -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore - -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ - - -# IDE's and editors -.vscode/ -.idea/ -*.sublime-workspace -*.sublime-project -*.code-workspace -*.swp - -# Local build files -build-local.ps1 -build-local.bat \ No newline at end of file +# Created by venv; see https://docs.python.org/3/library/venv.html +* diff --git a/src/core/data/scrapers/example.html b/example.html similarity index 100% rename from src/core/data/scrapers/example.html rename to example.html diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index d55b28a..e6275cd 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -4,21 +4,40 @@ import requests import re #from core.utils.general.logs import consoleLog -def scrape_steamrip_links(): - url = f"https://steamrip.com/games-list-page/" - response = requests.get(url) - soup = BeautifulSoup(response.text, "html.parser") +def scrape_steamrip_links(text = None): + if text is None: + url = f"https://steamrip.com/games-list-page/" + response = requests.get(url) + text = response.text + + soup = BeautifulSoup(text, "html.parser") games = soup.find_all("li", class_="az-list-item") if len(games) == 0: return [] links = [] - for gamehtml in games: + for i, gamehtml in enumerate(games): + if i == 0: print(gamehtml) link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) links += re.findall('(?<=href=")[^"]*', link.__str__()) - return links + + # construct the list[dict[str,str]] + + ret = [] + + for link in links: + ret.append({"name" : link}) + + return ret + +def offline_scrape_steamrip_links(): + text = "" + with open("example.html", encoding = "utf-8") as f: + text=f.read() + + return scrape_steamrip_links(text) if __name__ == "__main__": - print(scrape_steamrip_links()) + print(offline_scrape_steamrip_links()) \ No newline at end of file diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index ce01128..a2c032c 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -1,5 +1,6 @@ import fuzzyfinder from PySide6 import QtCore, QtWidgets, QtGui +from sys import exit class FuzzySearchWindow(QtWidgets.QWidget): """ @@ -20,9 +21,59 @@ class FuzzySearchWindow(QtWidgets.QWidget): self.opts: list[dict[str, str]] = options - self.searchbox = QtWidgets.QTextEdit() + self.searchbox = QtWidgets.QLineEdit() self.searchbox.placeholderText = "Search for a game" self.table = QtWidgets.QTableWidget() - self.table.setHorizontalHeader(self.opts[0].keys()) + headers = self.opts[1].keys() + print(headers) + self.table.setColumnCount(len(headers)) + self.table.setHorizontalHeaderLabels(headers) + + self.table.setRowCount(len(self.opts)) + for row_index, row_data in enumerate(self.opts): + for col_index, key in enumerate(headers): + value = row_data.get(key, "")[1:-1].replace("-", " ") + item = QtWidgets.QTableWidgetItem(value) + self.table.setItem(row_index, col_index, item) + + self.table.resizeColumnsToContents() + + self.searchbox.textChanged.connect(self.filter_table) + + + + layout = QtWidgets.QGridLayout(self) + layout.addWidget(self.searchbox, 0, 0) + layout.addWidget(self.table, 1, 0) + + layout.setRowStretch(0, 0) + layout.setRowStretch(1, 1) + + def filter_table(self): + + searchquery = self.searchbox.text() + + options = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x["name"]) + + for row_index, row_data in enumerate(options): + for col_index, key in enumerate(row_data.keys()): + value = row_data.get(key, "") + self.table.setItem( + row_index, + col_index, + QtWidgets.QTableWidgetItem(value) + ) + +if __name__ == "__main__": + + import core.data.scrapers.steamrip as sr + + app = QtWidgets.QApplication([]) + widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links()) + widget.resize(600,400) + widget.show() + + + exit(app.exec()) \ No newline at end of file From 7e2b4cb46b64ff885b385fcadddffcd849e65ab1 Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 13:36:13 +0100 Subject: [PATCH 03/18] moved the offline example --- example.html => src/example.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename example.html => src/example.html (100%) diff --git a/example.html b/src/example.html similarity index 100% rename from example.html rename to src/example.html From 2066c860011f53c1f4e0643717212800923958f1 Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 14:08:07 +0100 Subject: [PATCH 04/18] now displaying the actual names of the game, not the links, links are in the 'link' key. --- src/core/data/scrapers/steamrip.py | 13 +++++++++---- src/core/interface/utils/fzhelper.py | 23 ++++++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index e6275cd..f9da50c 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -17,18 +17,23 @@ def scrape_steamrip_links(text = None): return [] links = [] - for i, gamehtml in enumerate(games): - if i == 0: print(gamehtml) + names = [] + for gamehtml in games: link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) links += re.findall('(?<=href=")[^"]*', link.__str__()) + name = gamehtml.find("a", href=lambda x: x and x.startswith("/")) + names += re.findall('(?<=\/">)[^<]*', name.__str__()) + + print(names) + # construct the list[dict[str,str]] ret = [] - for link in links: - ret.append({"name" : link}) + for i in range(len(names)-1): + ret.append({"name" : names[i], "link" : links[i]}) return ret diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index a2c032c..6fd2570 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -2,7 +2,7 @@ import fuzzyfinder from PySide6 import QtCore, QtWidgets, QtGui from sys import exit -class FuzzySearchWindow(QtWidgets.QWidget): +class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere """ The dict should contain a: name : str @@ -15,17 +15,26 @@ class FuzzySearchWindow(QtWidgets.QWidget): The key will be at the Top of the Table The value will be listed for each element + + keys in ignorekeys will be ignored and not displayed, e.g. if oyu have links in the database which arent relevant """ - def __init__(self, options: list[dict[str, str]]): + def __init__(self, options: list[dict[str, str]], ignorekeys: list[str] = []): super().__init__() self.opts: list[dict[str, str]] = options + self.ignorekeys = ignorekeys self.searchbox = QtWidgets.QLineEdit() self.searchbox.placeholderText = "Search for a game" self.table = QtWidgets.QTableWidget() - headers = self.opts[1].keys() + + headers = [] + for key in self.opts[1].keys(): + if key not in self.ignorekeys: + headers.append(key) + + print(headers) self.table.setColumnCount(len(headers)) @@ -34,7 +43,9 @@ class FuzzySearchWindow(QtWidgets.QWidget): self.table.setRowCount(len(self.opts)) for row_index, row_data in enumerate(self.opts): for col_index, key in enumerate(headers): - value = row_data.get(key, "")[1:-1].replace("-", " ") + if key in self.ignorekeys: + continue + value = row_data.get(key, "") item = QtWidgets.QTableWidgetItem(value) self.table.setItem(row_index, col_index, item) @@ -59,6 +70,8 @@ class FuzzySearchWindow(QtWidgets.QWidget): for row_index, row_data in enumerate(options): for col_index, key in enumerate(row_data.keys()): + if key in self.ignorekeys: + continue value = row_data.get(key, "") self.table.setItem( row_index, @@ -71,7 +84,7 @@ if __name__ == "__main__": import core.data.scrapers.steamrip as sr app = QtWidgets.QApplication([]) - widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links()) + widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links(), ["link"]) widget.resize(600,400) widget.show() From e354ef50cf771001e965925d067ee0bdd858dce9 Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 14:21:26 +0100 Subject: [PATCH 05/18] made it more user frendly using a placeholdertext --- src/core/data/scrapers/steamrip.py | 2 -- src/core/interface/utils/fzhelper.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index f9da50c..ab52afb 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -24,8 +24,6 @@ def scrape_steamrip_links(text = None): name = gamehtml.find("a", href=lambda x: x and x.startswith("/")) names += re.findall('(?<=\/">)[^<]*', name.__str__()) - print(names) - # construct the list[dict[str,str]] diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index 6fd2570..8f26477 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -25,7 +25,7 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere self.ignorekeys = ignorekeys self.searchbox = QtWidgets.QLineEdit() - self.searchbox.placeholderText = "Search for a game" + self.searchbox.setPlaceholderText("Search for a game on Steamrip") self.table = QtWidgets.QTableWidget() From 1c3fb95e401f3b7c5e6680a997bbf11ca5da3e6c Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 16:53:54 +0100 Subject: [PATCH 06/18] vikingfile cloudflare turnstyle-captcha bypassed --- requirements.txt | 14 ++++++++++++++ src/core/interface/utils/fzhelper.py | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/requirements.txt b/requirements.txt index dec79d8..6d70221 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,17 @@ psutil==7.1.0 libtorrent==2.0.11 libtorrent-windows-dll==0.0.3 fuzzyfinder==2.3.0 +cffi==2.0.0 +h11==0.16.0 +outcome==1.3.0.post0 +pycparser==3.0 +pysocks==1.7.1 +selenium==4.41.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +trio==0.33.0 +trio-websocket==0.12.2 +undetected-chromedriver==3.5.5 +websocket-client==1.9.0 +websockets==16.0 +wsproto==1.3.2 \ No newline at end of file diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index 8f26477..bfe975d 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -28,6 +28,10 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere self.searchbox.setPlaceholderText("Search for a game on Steamrip") self.table = QtWidgets.QTableWidget() + self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # disable editing + self.table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) # no selection highlight + self.table.setFocusPolicy(QtCore.Qt.NoFocus) + self.searchbox.setFocus() headers = [] for key in self.opts[1].keys(): From d6a388652b5d9f33858ba7fb8a1bd48ed7e81bc0 Mon Sep 17 00:00:00 2001 From: nosch Date: Thu, 26 Feb 2026 18:12:12 +0100 Subject: [PATCH 07/18] accidentally included a pycache --- .../data/scrapers/providers/buzzheavier.py | 22 ++++++++++++++ src/core/data/scrapers/providers/gofile.py | 30 +++++++++++++++++++ .../data/scrapers/providers/vikingfile.py | 25 ++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/core/data/scrapers/providers/buzzheavier.py create mode 100644 src/core/data/scrapers/providers/gofile.py create mode 100644 src/core/data/scrapers/providers/vikingfile.py diff --git a/src/core/data/scrapers/providers/buzzheavier.py b/src/core/data/scrapers/providers/buzzheavier.py new file mode 100644 index 0000000..b7d36bd --- /dev/null +++ b/src/core/data/scrapers/providers/buzzheavier.py @@ -0,0 +1,22 @@ +from bs4 import BeautifulSoup +import requests + +def scrape_buzzheavier(url): + + response = requests.get(url) + response.raise_for_status() + + download_url = url + '/download' + headers = { + 'hx-current-url': url, + 'hx-request': 'true', + 'referer': url + } + head_response = requests.head(download_url, headers=headers, allow_redirects=False) + hx_redirect = head_response.headers.get('hx-redirect') + return hx_redirect + +if __name__ == "__main__": + example = "https://buzzheavier.com/irzewxdjt5ah" + download = scrape_buzzheavier("https://buzzheavier.com/irzewxdjt5ah") + print(download) \ No newline at end of file diff --git a/src/core/data/scrapers/providers/gofile.py b/src/core/data/scrapers/providers/gofile.py new file mode 100644 index 0000000..0ea6527 --- /dev/null +++ b/src/core/data/scrapers/providers/gofile.py @@ -0,0 +1,30 @@ +import requests +import re + +def scrape_gofile(url): + + filetoken = re.findall(r"(?<=https...gofile.io\/d\/).*", url)[0] + session = requests.Session() + + acc = session.post("https://api.gofile.io/accounts") + token = acc.json()["data"]["token"] + + headers = { + "Authorization": f"Bearer {token}", + "X-Website-Token": "4fd6sg89d7s6", + } + + r = session.get( + f"https://api.gofile.io/contents/{filetoken}", + headers=headers, + ) + + temp: dict = r.json()["data"]["children"] + child = [key for key in temp.keys()] + + + return temp[child[0]]["link"] + + +out = scrape_gofile("https://gofile.io/d/tvvzAD") +print(out) \ No newline at end of file diff --git a/src/core/data/scrapers/providers/vikingfile.py b/src/core/data/scrapers/providers/vikingfile.py new file mode 100644 index 0000000..1a3145a --- /dev/null +++ b/src/core/data/scrapers/providers/vikingfile.py @@ -0,0 +1,25 @@ +import undetected_chromedriver as uc +from selenium.webdriver.common.by import By + +import requests + +def scrape_vikingfile(url): + + options = uc.ChromeOptions() + + driver = uc.Chrome(options=options, version_main=145) + driver.get(url) + + while driver.find_element(By.ID, "download-link").get_attribute("href") is None: + continue + + link = driver.find_element(By.ID, "download-link").get_attribute("href") + + driver.quit() + + return link + +if __name__ == "__main__": + example = "https://vik1ngfile.site/f/4txMf0RghN" + downloadlink = scrape_vikingfile(example) + print(downloadlink) \ No newline at end of file From 29bac99359f9bc13b1cf7b017c1d1ba2e4013f0a Mon Sep 17 00:00:00 2001 From: nosch Date: Fri, 27 Feb 2026 10:02:11 +0100 Subject: [PATCH 08/18] steamrip scrapes downloadlinks for the provider - steamrip connection --- .gitignore | 3 +- .../{providers => provider}/buzzheavier.py | 0 .../{providers => provider}/gofile.py | 0 src/core/data/scrapers/provider/megadb.py | 32 +++++++++++++++ .../{providers => provider}/vikingfile.py | 0 src/core/data/scrapers/steamrip.py | 39 +++++++++++++++++-- 6 files changed, 70 insertions(+), 4 deletions(-) rename src/core/data/scrapers/{providers => provider}/buzzheavier.py (100%) rename src/core/data/scrapers/{providers => provider}/gofile.py (100%) create mode 100644 src/core/data/scrapers/provider/megadb.py rename src/core/data/scrapers/{providers => provider}/vikingfile.py (100%) diff --git a/.gitignore b/.gitignore index 49b53dc..6f64177 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Created by venv; see https://docs.python.org/3/library/venv.html -* +__pycache__/ +*.exe diff --git a/src/core/data/scrapers/providers/buzzheavier.py b/src/core/data/scrapers/provider/buzzheavier.py similarity index 100% rename from src/core/data/scrapers/providers/buzzheavier.py rename to src/core/data/scrapers/provider/buzzheavier.py diff --git a/src/core/data/scrapers/providers/gofile.py b/src/core/data/scrapers/provider/gofile.py similarity index 100% rename from src/core/data/scrapers/providers/gofile.py rename to src/core/data/scrapers/provider/gofile.py diff --git a/src/core/data/scrapers/provider/megadb.py b/src/core/data/scrapers/provider/megadb.py new file mode 100644 index 0000000..decaa4e --- /dev/null +++ b/src/core/data/scrapers/provider/megadb.py @@ -0,0 +1,32 @@ +import undetected_chromedriver as uc +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException +import time + + +options = uc.ChromeOptions() +options.set_capability("goog:loggingPrefs", {"performance": "ALL"}) + +driver = uc.Chrome(options=options) +driver.get("https://megadb.net/qp0yi1v1p6nl") + +WebDriverWait(driver, 20).until( + EC.frame_to_be_available_and_switch_to_it( + (By.XPATH, "//iframe[@title='reCAPTCHA']") + ) +) + +checkboxui = WebDriverWait(driver, 20).until( + EC.presence_of_element_located((By.ID, "recaptcha-anchor")) +) + +checkboxelement = driver.find_element(By.ID, "recaptcha-anchor") + +while checkboxelement.get_attribute("aria-checked") == "false": + time.sleep(1) + +print("captcha is solved") + +driver.quit() \ No newline at end of file diff --git a/src/core/data/scrapers/providers/vikingfile.py b/src/core/data/scrapers/provider/vikingfile.py similarity index 100% rename from src/core/data/scrapers/providers/vikingfile.py rename to src/core/data/scrapers/provider/vikingfile.py diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index ab52afb..089b95b 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -20,9 +20,9 @@ def scrape_steamrip_links(text = None): names = [] for gamehtml in games: link = gamehtml.find("a", href=lambda x: x and x.startswith("/")) - links += re.findall('(?<=href=")[^"]*', link.__str__()) + links += re.findall(r'(?<=href=")[^"]*', link.__str__()) name = gamehtml.find("a", href=lambda x: x and x.startswith("/")) - names += re.findall('(?<=\/">)[^<]*', name.__str__()) + names += re.findall(r'(?<=\/">)[^<]*', name.__str__()) @@ -42,5 +42,38 @@ def offline_scrape_steamrip_links(): return scrape_steamrip_links(text) +def scrape_steamrip_game_downloads(gamelink): + url = "https://steamrip.com" + gamelink + response = requests.get(url) + + soup = BeautifulSoup(response.text, "html.parser") + + download_link_elements = soup.find_all("a",class_="shortc-button") + + download_links = ["b", "g", "v", "m"] + + for download_link in download_link_elements: + pure = download_link.attrs.get("href") + if pure[2] == "b": + download_links[0] = "https:" + pure + if pure[2] == "g": + download_links[1] = "https:" + pure + if pure[2] == "v": + download_links[2] = "https:" + pure + if pure[2] == "m": + download_links[3] = "https:" + pure + + ret = [] + + for link in download_links: + if len(link) != 1: + ret.append(link) + + + + return ret + + if __name__ == "__main__": - print(offline_scrape_steamrip_links()) \ No newline at end of file + #print(offline_scrape_steamrip_links()) + print(scrape_steamrip_game_downloads("/r-e-p-o-free-download/")) \ No newline at end of file From b9dc6cc18daf589e20db0db1f7746ba29bf40a23 Mon Sep 17 00:00:00 2001 From: nosch Date: Fri, 27 Feb 2026 23:27:45 +0100 Subject: [PATCH 09/18] im not dealing with megadb and fichier, as there are always better alternatives. if not ill add a way to select a downloaded .rar, though the links will be given to the user --- src/core/data/scrapers/provider/fichier.py | 21 ++++++++++++++++++ src/core/data/scrapers/provider/gofile.py | 2 +- src/core/data/scrapers/provider/megadb.py | 9 ++++---- src/core/data/scrapers/steamrip.py | 2 +- src/core/interface/utils/fzhelper.py | 25 +++++++++++++++++----- 5 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 src/core/data/scrapers/provider/fichier.py diff --git a/src/core/data/scrapers/provider/fichier.py b/src/core/data/scrapers/provider/fichier.py new file mode 100644 index 0000000..7a54af5 --- /dev/null +++ b/src/core/data/scrapers/provider/fichier.py @@ -0,0 +1,21 @@ +import requests +from bs4 import BeautifulSoup + +def scrape_fichier(url: str): + response = requests.get(url) + + soup = BeautifulSoup(response.text, "html.parser") + + btn = soup.find_all("a", class_="ok") + + print(response.text) + + link = btn[0].get("href") + + return link + + +if __name__ == "__main__": + print(scrape_fichier("https://1fichier.com/?ai6vzcfi1r79q9rebc3a")) + + diff --git a/src/core/data/scrapers/provider/gofile.py b/src/core/data/scrapers/provider/gofile.py index 0ea6527..7ee9ddf 100644 --- a/src/core/data/scrapers/provider/gofile.py +++ b/src/core/data/scrapers/provider/gofile.py @@ -27,4 +27,4 @@ def scrape_gofile(url): out = scrape_gofile("https://gofile.io/d/tvvzAD") -print(out) \ No newline at end of file +print(out) diff --git a/src/core/data/scrapers/provider/megadb.py b/src/core/data/scrapers/provider/megadb.py index decaa4e..4e9c6de 100644 --- a/src/core/data/scrapers/provider/megadb.py +++ b/src/core/data/scrapers/provider/megadb.py @@ -2,18 +2,17 @@ import undetected_chromedriver as uc from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC -from selenium.common.exceptions import TimeoutException import time options = uc.ChromeOptions() options.set_capability("goog:loggingPrefs", {"performance": "ALL"}) -driver = uc.Chrome(options=options) +driver = uc.Chrome(options=options, version_main=145) driver.get("https://megadb.net/qp0yi1v1p6nl") WebDriverWait(driver, 20).until( - EC.frame_to_be_available_and_switch_to_it( + EC.frame_to_be_available_and_switch_to_it( (By.XPATH, "//iframe[@title='reCAPTCHA']") ) ) @@ -27,6 +26,8 @@ checkboxelement = driver.find_element(By.ID, "recaptcha-anchor") while checkboxelement.get_attribute("aria-checked") == "false": time.sleep(1) + + print("captcha is solved") -driver.quit() \ No newline at end of file +driver.quit() diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 089b95b..1eef2ad 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -76,4 +76,4 @@ def scrape_steamrip_game_downloads(gamelink): if __name__ == "__main__": #print(offline_scrape_steamrip_links()) - print(scrape_steamrip_game_downloads("/r-e-p-o-free-download/")) \ No newline at end of file + print(scrape_steamrip_game_downloads("/r-e-p-o-free-download/")) diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index bfe975d..3fd6f0b 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -1,6 +1,8 @@ +from typing import Generator import fuzzyfinder from PySide6 import QtCore, QtWidgets, QtGui from sys import exit +from copy import deepcopy class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere """ @@ -18,11 +20,13 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere keys in ignorekeys will be ignored and not displayed, e.g. if oyu have links in the database which arent relevant """ - def __init__(self, options: list[dict[str, str]], ignorekeys: list[str] = []): + def __init__(self, options: list[dict[str, str]], ignorekeys: list[str] = [], columnClicklShouldReturn = 1): super().__init__() self.opts: list[dict[str, str]] = options + self.filtered_opts = deepcopy(self.opts) self.ignorekeys = ignorekeys + self.returncolumn = columnClicklShouldReturn self.searchbox = QtWidgets.QLineEdit() self.searchbox.setPlaceholderText("Search for a game on Steamrip") @@ -57,7 +61,7 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere self.searchbox.textChanged.connect(self.filter_table) - + self.table.cellClicked.connect(self.cellClick) layout = QtWidgets.QGridLayout(self) layout.addWidget(self.searchbox, 0, 0) @@ -70,9 +74,12 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere searchquery = self.searchbox.text() - options = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x["name"]) + options: Generator = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x["name"]) + + self.filtered_opts = [] for row_index, row_data in enumerate(options): + self.filtered_opts.append(row_data) for col_index, key in enumerate(row_data.keys()): if key in self.ignorekeys: continue @@ -83,14 +90,22 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere QtWidgets.QTableWidgetItem(value) ) + def cellClick(self, row: int, column: int): + item = self.table.item(row, self.returncolumn) + + if item is not None: + value = item.text() + print(value) + + if __name__ == "__main__": import core.data.scrapers.steamrip as sr app = QtWidgets.QApplication([]) - widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links(), ["link"]) + widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links()) widget.resize(600,400) widget.show() - exit(app.exec()) \ No newline at end of file + exit(app.exec()) From 889c50e201e3440f32691aa96e5fabc14cb8cc36 Mon Sep 17 00:00:00 2001 From: nosch Date: Sat, 28 Feb 2026 17:58:50 +0100 Subject: [PATCH 10/18] worked on gui implementation and cleaned up a bit --- src/core/data/scrapers/steamrip.py | 7 -- src/core/interface/gui.py | 24 ++--- src/core/interface/utils/fzhelper.py | 2 +- src/core/interface/utils/searchhelper.py | 17 ++++ src/core/utils/data/state.py | 2 +- src/example.html | 116 ----------------------- 6 files changed, 29 insertions(+), 139 deletions(-) delete mode 100644 src/example.html diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 1eef2ad..0fa667d 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -35,13 +35,6 @@ def scrape_steamrip_links(text = None): return ret -def offline_scrape_steamrip_links(): - text = "" - with open("example.html", encoding = "utf-8") as f: - text=f.read() - - return scrape_steamrip_links(text) - def scrape_steamrip_game_downloads(gamelink): url = "https://steamrip.com" + gamelink response = requests.get(url) diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index 5654b84..f1efad8 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -144,7 +144,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): flush_log_buffer() - def create_tracker_table(headers): + def create_tracker_table(headers, key): table = QTableWidget() table.setColumnCount(len(headers)) table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) @@ -156,21 +156,17 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 500) header.setStretchLastSection(False) + + table.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + table.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - return table + state.tracker_list.update({key: table}) - self.rutrackerlist = create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"]) - self.uztrackerlist = create_tracker_table(["Post Title", "Author"]) - self.monkruslist = create_tracker_table(["Post Title", "Author"]) - - state.tracker_list.update({"rutracker": self.rutrackerlist, "uztracker": self.uztrackerlist, "m0nkrus": self.monkruslist}) - - self.rutrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - self.rutrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - - self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"], "rutracker") + create_tracker_table(["Post Title", "Author"], "uztracker") + create_tracker_table(["Post Title", "Author"], "m0nkrus") + create_tracker_table(["Game", "Availible Downloads"], "steamrip") container = QWidget() containerLayout = QVBoxLayout() @@ -408,7 +404,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.tracker_list = QComboBox() - self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"]) + self.tracker_list.addItems(list(state.tracker_list.keys())) self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor) self.tracker_list.activated.connect(self.set_tracker) self.corner_layout.addWidget(self.tracker_list) diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index 3fd6f0b..5c4e261 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -103,7 +103,7 @@ if __name__ == "__main__": import core.data.scrapers.steamrip as sr app = QtWidgets.QApplication([]) - widget = FuzzySearchWindow(sr.offline_scrape_steamrip_links()) + widget = FuzzySearchWindow(sr.scrape_steamrip_links()) widget.resize(600,400) widget.show() diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py index b38bc2b..ee32ac7 100644 --- a/src/core/interface/utils/searchhelper.py +++ b/src/core/interface/utils/searchhelper.py @@ -3,8 +3,10 @@ from core.utils.logging.logs import consoleLog from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.rutracker import scrape_rutracker from core.data.scrapers.monkrus import scrape_monkrus_telegram +from core.data.scrapers.steamrip import scrape_steamrip_links, scrape_steamrip_game_downloads from core.utils.network.jsonhandler import split_data, format_data, format_data_m0nkrus from core.utils.data.state import state +import fuzzyfinder def return_pressed(self): search_text = self.searchbar.text() @@ -81,3 +83,18 @@ def return_pressed(self): state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author)) for i, title in enumerate(state.post_titles): state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title)) + + elif state.tracker == "steamrip": + games = scrape_steamrip_links() + + options: Generator = fuzzyfinder.main.fuzzyfinder(search_text, self.opts, accessor=lambda x: x["name"]) + + filtered_opts = [*options] + + if filtered_opts == []: + consoleLog(f"No results for {search_text}") + state.tracker_list[state.tracker].clear() + self.show_empty_results(True) + else: + state + diff --git a/src/core/utils/data/state.py b/src/core/utils/data/state.py index 0d38ce8..86732fc 100644 --- a/src/core/utils/data/state.py +++ b/src/core/utils/data/state.py @@ -8,7 +8,7 @@ class AppState(QObject): def __init__(self): super().__init__() - self.posts: List[Dict[str, Any]] = [] + self.posts: List[Dict[str, Any]] = [] # title, url, author, seeders, leachers self.post_titles: Optional[List] = None self.post_urls: Optional[List] = None self.post_author: List[str] = [] diff --git a/src/example.html b/src/example.html deleted file mode 100644 index 80152b1..0000000 --- a/src/example.html +++ /dev/null @@ -1,116 +0,0 @@ - Games List » SteamRIP

All Games List

Back to top button
- - - - - \ No newline at end of file From c76d179daa096102a2c1a39d76a7f97f7ab49d84 Mon Sep 17 00:00:00 2001 From: nosch Date: Sat, 7 Mar 2026 16:52:27 +0100 Subject: [PATCH 11/18] shayaa needs to fix rutracker NOW --- src/core/data/scrapers/monkrus.py | 13 ++++++- src/core/data/scrapers/rutracker.py | 10 +++++- src/core/data/scrapers/steamrip.py | 9 ++++- src/core/data/scrapers/uztracker.py | 10 +++++- src/core/interface/gui.py | 37 +++++++++---------- src/core/interface/utils/fzhelper.py | 9 ++--- src/core/interface/utils/searchhelper.py | 2 +- src/core/utils/data/state.py | 45 ++++++++++++++++++------ 8 files changed, 95 insertions(+), 40 deletions(-) diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py index 67e64f3..c7af80f 100644 --- a/src/core/data/scrapers/monkrus.py +++ b/src/core/data/scrapers/monkrus.py @@ -2,6 +2,14 @@ from bs4 import BeautifulSoup import requests import time +Metadata = { + "headers" : ["Post Title", "Author"], + "name" : "m0nkrus", +} + +def get_Metadata(): + return Metadata + cache = { "data": [], "last_fetched": 0 @@ -67,4 +75,7 @@ def scrape_m0nkrus(query): filtered_post["id"] = len(filtered_posts) + 1 filtered_posts.append(filtered_post) - return filtered_posts \ No newline at end of file + return filtered_posts + +if __name__ == "__main__": + print(scrape_m0nkrus("adobe")) diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py index bf67bb5..2266ead 100644 --- a/src/core/data/scrapers/rutracker.py +++ b/src/core/data/scrapers/rutracker.py @@ -2,6 +2,14 @@ import requests from core.utils.data.state import state from core.utils.logging.logs import consoleLog +Metadata = { + "headers" : ["Post Title", "Author", "Seeders", "Leechers"], + "name" : "rutracker", +} + +def get_Metadata(): + return Metadata + def scrape_rutracker(query): search = requests.get(f"{state.api_url}/search?q={query}") consoleLog("Sent request to server") @@ -9,7 +17,7 @@ def scrape_rutracker(query): try: return search.text except Exception: - consoleLog("No results found / No response from server") + consoleLog(f"No results found / No response from server, exception: {Exception}") return None else: return None diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 0fa667d..8d0076e 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -2,7 +2,14 @@ from bs4 import BeautifulSoup import requests import re -#from core.utils.general.logs import consoleLog + +Metadata = { + "headers" : ["Game", "Availible Downloads"], + "name" : "steamrip", +} + +def get_Metadata(): + return Metadata def scrape_steamrip_links(text = None): if text is None: diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py index 956629f..a110e00 100644 --- a/src/core/data/scrapers/uztracker.py +++ b/src/core/data/scrapers/uztracker.py @@ -3,6 +3,14 @@ from bs4 import BeautifulSoup from urllib.parse import urljoin from core.utils.logging.logs import consoleLog +Metadata = { + "headers" : ["Post Title", "Author"], + "name" : "uztracker", +} + +def get_Metadata(): + return Metadata + def scrape_uztracker(query): base_url="https://uztracker.net/" search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}" @@ -32,4 +40,4 @@ def scrape_uztracker(query): except requests.RequestException as e: consoleLog(f"Failed to fetch {search_url}: {e}") - return None \ No newline at end of file + return None diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index 7639c6d..c75f6aa 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -147,30 +147,25 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): flush_log_buffer() - def create_tracker_table(headers, key): - table = QTableWidget() - table.setColumnCount(len(headers)) - table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) - table.verticalHeader().setVisible(False) - table.setHorizontalHeaderLabels(headers) + # create tracker tables + state.trackertable + table = QTableWidget() + table.setColumnCount(len(headers)) + table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + table.verticalHeader().setVisible(False) + table.setHorizontalHeaderLabels(headers) - header = table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) - header.resizeSection(1, 500) - header.setStretchLastSection(False) - - table.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - table.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + header = table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) + header.resizeSection(1, 500) + header.setStretchLastSection(False) + + table.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + table.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - state.tracker_list.update({key: table}) + state.tracker_list.update({key: table}) - - create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"], "rutracker") - create_tracker_table(["Post Title", "Author"], "uztracker") - create_tracker_table(["Post Title", "Author"], "m0nkrus") - create_tracker_table(["Game", "Availible Downloads"], "steamrip") - container = QWidget() containerLayout = QVBoxLayout() containerLayout.addWidget(self.searchbar) diff --git a/src/core/interface/utils/fzhelper.py b/src/core/interface/utils/fzhelper.py index 5c4e261..9aba5d8 100644 --- a/src/core/interface/utils/fzhelper.py +++ b/src/core/interface/utils/fzhelper.py @@ -7,7 +7,7 @@ from copy import deepcopy class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere """ The dict should contain a: - name : str + $(keytosortby) : str THIS IS REQUIRED all other things will be displayed alphabetically after it. @@ -20,13 +20,14 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere keys in ignorekeys will be ignored and not displayed, e.g. if oyu have links in the database which arent relevant """ - def __init__(self, options: list[dict[str, str]], ignorekeys: list[str] = [], columnClicklShouldReturn = 1): + def __init__(self, options: list[dict[str, str]], ignorekeys: list[str] = [], columnClicklShouldReturn = 1, keytosortby = "name"): super().__init__() self.opts: list[dict[str, str]] = options self.filtered_opts = deepcopy(self.opts) self.ignorekeys = ignorekeys self.returncolumn = columnClicklShouldReturn + self.sortkey = keytosortby self.searchbox = QtWidgets.QLineEdit() self.searchbox.setPlaceholderText("Search for a game on Steamrip") @@ -74,7 +75,7 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere searchquery = self.searchbox.text() - options: Generator = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x["name"]) + options: Generator = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x[self.sortkey]) self.filtered_opts = [] @@ -90,7 +91,7 @@ class FuzzySearchWindow(QtWidgets.QWidget): # should work everywhere QtWidgets.QTableWidgetItem(value) ) - def cellClick(self, row: int, column: int): + def cellClick(self, row: int, _: int): item = self.table.item(row, self.returncolumn) if item is not None: diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py index d3e4f14..9787c12 100644 --- a/src/core/interface/utils/searchhelper.py +++ b/src/core/interface/utils/searchhelper.py @@ -19,7 +19,7 @@ def return_pressed(self): return consoleLog(f"User searched for: {search_text}") - if state.tracker == "rutracker": + if state.currenttracker == "rutracker": response = scrape_rutracker(search_text) if response: _, state.posts, _, _, cached = split_data(response) diff --git a/src/core/utils/data/state.py b/src/core/utils/data/state.py index 1e33a70..6077010 100644 --- a/src/core/utils/data/state.py +++ b/src/core/utils/data/state.py @@ -1,6 +1,6 @@ from PySide6.QtCore import QObject, Signal from PySide6.QtWidgets import QTableWidget -from typing import Optional, Any, List, Dict +from typing import Any, List, Dict from pathlib import Path class AppState(QObject): @@ -8,20 +8,27 @@ class AppState(QObject): def __init__(self): super().__init__() - self.posts: list[Any] | None = None - self.post_titles: Optional[List] = None - self.post_urls: Optional[List] = None - self.post_author: List[str] = [] - self.post_seeders: List[str] = [] - self.post_leechers: List[str] = [] + self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seederm leecher self.version: str = "dev" self._image_path: str = "" self.ignore_updates: bool = False self.debug: bool = False self.autoresume: bool = True - self.tracker: str = "rutracker" - self.tracker_list: dict[str, QTableWidget] = {} + + self.currenttracker: str = "rutracker" + self.trackertable: QTableWidget + self.trackers: Dict[str,Dict[str,Any]] # each tracker should add itself here, will be listed in order of module initialisation + ''' + an example: + "rutracker" : { + "name" : "rutracker", # name of the tracker + "posts" : [post1, post2, ...], # all the posts, will be in self.posts, + "headers" : ["author", "title"], # keys shown in the table + "sortkey" : "name" # the key to sort posts by # currently only usefull to steamrip + } + ''' self.api_url: str = "https://api.michijackson.xyz" + self.seeded_magnets: set = set() self.download_path: str = str(Path.home() / "Downloads") self.up_speed_limit: int = 0 self.down_speed_limit: int = 0 @@ -30,12 +37,30 @@ class AppState(QObject): self.settings_path: str = "" self.dl_session: Any = None self.active_downloads: Dict = {} - self.seeded_magnets: set = set() self.window_transparency: bool = False self.interfaces: List = [] self.active_interfaces: List = [] self.bound_interface: Any = None + def update_tracker_table(self, newtracker: str): + + if newtracker not in self.trackers.keys(): + raise ValueError("the selected tracker is not initialised.") + + self.trackertable.setColumnCount(self.trackers[self.currenttracker][]) + + for row_index, row_data in enumerate(self.trackers[self.currenttracker].values()): + for col_index, key in enumerate(row_data.keys()): + if key in self.trackers[self.currenttracker]["ignorekeys"]: + continue + value = row_data.get(key, "") + self.trackertable.setItem( + row_index, + col_index, + QtWidgets.QTableWidgetItem(value) + ) + + @property def image_path(self) -> str: return self._image_path From 003dd0e3aafb8b3c5f31ac226275b7de100f17f2 Mon Sep 17 00:00:00 2001 From: nosch Date: Sat, 7 Mar 2026 19:30:24 +0100 Subject: [PATCH 12/18] a semiworking version, line 343 in gui.py is commented cuz it needs work, the table is also a bit screwed --- src/core/data/scrapers/monkrus.py | 18 ++++-- src/core/data/scrapers/rutracker.py | 32 ++++----- src/core/data/scrapers/steamrip.py | 52 +++++++++++---- src/core/data/scrapers/uztracker.py | 16 +++-- src/core/interface/gui.py | 55 ++++++---------- src/core/interface/utils/searchhelper.py | 82 ++++++------------------ src/core/utils/data/state.py | 24 +------ 7 files changed, 121 insertions(+), 158 deletions(-) diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py index c7af80f..d41c020 100644 --- a/src/core/data/scrapers/monkrus.py +++ b/src/core/data/scrapers/monkrus.py @@ -1,11 +1,8 @@ from bs4 import BeautifulSoup import requests import time +from core.utils.data.state import state -Metadata = { - "headers" : ["Post Title", "Author"], - "name" : "m0nkrus", -} def get_Metadata(): return Metadata @@ -77,5 +74,14 @@ def scrape_m0nkrus(query): return filtered_posts -if __name__ == "__main__": - print(scrape_m0nkrus("adobe")) + +Metadata = { + "name" : "m0nkrus", + "headers" : ["Post Title", "Author"], + "searchkey" : None, + "scrapeFunc" : scrape_m0nkrus, + "scrapeSearches" : True, +} + +if __name__ != "__main__": + state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py index 2266ead..5641cdf 100644 --- a/src/core/data/scrapers/rutracker.py +++ b/src/core/data/scrapers/rutracker.py @@ -1,27 +1,29 @@ import requests from core.utils.data.state import state from core.utils.logging.logs import consoleLog +from core.utils.network.jsonhandler import split_data -Metadata = { - "headers" : ["Post Title", "Author", "Seeders", "Leechers"], - "name" : "rutracker", -} - -def get_Metadata(): - return Metadata def scrape_rutracker(query): search = requests.get(f"{state.api_url}/search?q={query}") - consoleLog("Sent request to server") + consoleLog("[core.data.scrapers.rutracker] Sent request to server") if search: - try: - return search.text - except Exception: - consoleLog(f"No results found / No response from server, exception: {Exception}") - return None + _, data, _, _, _ = split_data(search.text) + + return data else: - return None + consoleLog("[core.data.scrapers.rutracker] No search Text, returning nothing") + return [] +Metadata = { + "name" : "rutracker", + "headers" : ["Post Title", "Author", "Seeders", "Leechers"], + "searchkey" : None, + "scrapeFunc" : scrape_rutracker, + "scrapeSearches" : True, +} -# This function utilizes the SoftwareManager server - source code can be found under the SoftwareManager-Server repository. + +if __name__ != "__main__": + state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 8d0076e..52aaae1 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -1,21 +1,33 @@ from bs4 import BeautifulSoup +from fuzzyfinder.main import fuzzyfinder import requests import re +import time +from typing import Generator +from core.utils.data.state import state -Metadata = { - "headers" : ["Game", "Availible Downloads"], - "name" : "steamrip", + +cache = { + "data": [], + "last_fetched": 0 } +cache_expiry = 300 + def get_Metadata(): return Metadata -def scrape_steamrip_links(text = None): - if text is None: - url = f"https://steamrip.com/games-list-page/" - response = requests.get(url) - text = response.text +def scrape_steamrip_links(): + + current_time = time.time() + + if cache["data"] != [] and (current_time - cache["last_fetched"] < cache_expiry): + return cache["data"] + + url = f"https://steamrip.com/games-list-page/" + response = requests.get(url) + text = response.text soup = BeautifulSoup(text, "html.parser") games = soup.find_all("li", class_="az-list-item") @@ -50,7 +62,8 @@ def scrape_steamrip_game_downloads(gamelink): download_link_elements = soup.find_all("a",class_="shortc-button") - download_links = ["b", "g", "v", "m"] + + download_links = ["buzzheavier", "gofile", "vikingfile", "megadb"] for download_link in download_link_elements: pure = download_link.attrs.get("href") @@ -69,11 +82,28 @@ def scrape_steamrip_game_downloads(gamelink): if len(link) != 1: ret.append(link) - + cache["data"] = ret return ret +def filter_steamrip(query: str): + + options: Generator = fuzzyfinder(query, scrape_steamrip_links(), accessor=lambda x: x["name"]) + + return list(options) + + + + +Metadata = { + "name" : "steamrip", + "headers" : ["Game", "Available Downloads"], + "scrapeFunc" : filter_steamrip, +} + +if __name__ != "__main__": + state.trackers.update({Metadata["name"] : Metadata}) + if __name__ == "__main__": - #print(offline_scrape_steamrip_links()) print(scrape_steamrip_game_downloads("/r-e-p-o-free-download/")) diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py index a110e00..0746a2d 100644 --- a/src/core/data/scrapers/uztracker.py +++ b/src/core/data/scrapers/uztracker.py @@ -2,11 +2,8 @@ import requests from bs4 import BeautifulSoup from urllib.parse import urljoin from core.utils.logging.logs import consoleLog +from core.utils.data.state import state -Metadata = { - "headers" : ["Post Title", "Author"], - "name" : "uztracker", -} def get_Metadata(): return Metadata @@ -41,3 +38,14 @@ def scrape_uztracker(query): except requests.RequestException as e: consoleLog(f"Failed to fetch {search_url}: {e}") return None + +Metadata = { + "name" : "uztracker", + "headers" : ["Post Title", "Author"], + "searchkey" : None, + "scrapeFunc" : scrape_uztracker, + "scrapeSearches" : True, +} + +if __name__ != "__main__": + state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index c75f6aa..fc9b209 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -147,29 +147,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): flush_log_buffer() - # create tracker tables - state.trackertable - table = QTableWidget() - table.setColumnCount(len(headers)) - table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) - table.verticalHeader().setVisible(False) - table.setHorizontalHeaderLabels(headers) + # create initial tracker table + state.trackertable = QTableWidget() + state.trackertable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + state.trackertable.verticalHeader().setVisible(False) + state.trackertable.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + state.trackertable.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + + - header = table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) - header.resizeSection(1, 500) - header.setStretchLastSection(False) - - table.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - table.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - - state.tracker_list.update({key: table}) - container = QWidget() containerLayout = QVBoxLayout() containerLayout.addWidget(self.searchbar) - containerLayout.addWidget(state.tracker_list[state.tracker]) + containerLayout.addWidget(state.trackertable) class DownloadModel(QAbstractTableModel): def __init__(self): @@ -350,7 +340,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): delegate.clicked.connect(on_pause_resume_clicked) # download button triggers - self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles)))) + #! THIS IS TEMPORARY self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts, state.posts)))) container.setLayout(containerLayout) self.setCentralWidget(container) @@ -370,10 +360,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.horizontal_layout = QHBoxLayout() self.horizontal_layout.addWidget(self.emptyResults, stretch=3) - self.tracker_widget = state.tracker_list[state.tracker] + self.tracker_widget = state.trackertable self.horizontal_layout.addWidget(self.tracker_widget) + self.horizontal_layout.addWidget(state.trackertable) - self.tab1 = create_tab("Search", self.searchbar, state.tracker_list[state.tracker], self.tabs, self.dlbutton, self.horizontal_layout) + self.tab1 = create_tab("Search", self.searchbar, state.trackertable, self.tabs, self.dlbutton, self.horizontal_layout) # self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None) self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) @@ -402,7 +393,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.tracker_list = QComboBox() - self.tracker_list.addItems(list(state.tracker_list.keys())) + self.tracker_list.addItems(list(state.trackers.keys())) self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor) self.tracker_list.activated.connect(self.set_tracker) self.corner_layout.addWidget(self.tracker_list) @@ -504,22 +495,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): event.accept() def set_tracker(self, _): - old_tracker = state.tracker - state.tracker = self.tracker_list.currentText() - - old_widget = state.tracker_list[old_tracker] - self.horizontal_layout.removeWidget(old_widget) - old_widget.setParent(None) - - tracker_widget = state.tracker_list[state.tracker] - self.horizontal_layout.addWidget(tracker_widget) + state.currenttracker = self.tracker_list.currentText() def show_empty_results(self, show: bool): if show: - state.tracker_list[state.tracker].hide() + state.trackertable.hide() self.emptyResults.show() else: - state.tracker_list[state.tracker].show() + state.trackertable.show() self.emptyResults.hide() def show_empty_downloads(self): @@ -533,8 +516,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): # thank you claude def resizeEvent(self, event): super().resizeEvent(event) - table_width = state.tracker_list[state.tracker].viewport().width() - state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3)) + table_width = state.trackertable.viewport().width() + state.trackertable.setColumnWidth(1, int(table_width * 0.3)) def contextMenuEvent(self, event: QContextMenuEvent): if self.downloadList.underMouse(): diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py index 9787c12..5898bef 100644 --- a/src/core/interface/utils/searchhelper.py +++ b/src/core/interface/utils/searchhelper.py @@ -1,16 +1,10 @@ -from PySide6.QtWidgets import QTableWidgetItem +from PySide6.QtWidgets import QTableWidgetItem, QHeaderView from core.utils.logging.logs import consoleLog from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.rutracker import scrape_rutracker from core.data.scrapers.monkrus import scrape_m0nkrus +from core.data.scrapers.steamrip import filter_steamrip from core.utils.data.state import state -import fuzzyfinder - -scrapers = { - "uztracker": scrape_uztracker, - "rutracker": scrape_rutracker, - "m0nkrus": scrape_m0nkrus -} def return_pressed(self): search_text = self.searchbar.text() @@ -19,63 +13,23 @@ def return_pressed(self): return consoleLog(f"User searched for: {search_text}") - if state.currenttracker == "rutracker": - response = scrape_rutracker(search_text) - if response: - _, state.posts, _, _, cached = split_data(response) - if state.posts == []: - consoleLog(f"No Results for {search_text}") - state.tracker_list[state.tracker].clear() - self.show_empty_results(True) - else: - state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts) - self.show_empty_results(False) - state.tracker_list[state.tracker].clear() - state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"]) - state.tracker_list[state.tracker].setRowCount(len(state.post_titles)) - for i, author in enumerate(state.post_author): - state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author)) - for i, title in enumerate(state.post_titles): - state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title)) - for i, seeders in enumerate(state.post_seeders): - state.tracker_list[state.tracker].setItem(i, 2, QTableWidgetItem(seeders)) - for i, leechers in enumerate(state.post_leechers): - state.tracker_list[state.tracker].setItem(i, 3, QTableWidgetItem(leechers)) - if state.debug == True: - consoleLog(f"Response Cached: {cached}") - else: - consoleLog(f"No response from rutracker") - state.tracker_list[state.tracker].clear() - self.show_empty_results(True) + tracker = state.trackers[state.currenttracker] - elif state.tracker is not None: - state.posts = scrapers[state.tracker](search_text) - if state.posts == []: - consoleLog(f"No Results for {search_text}") - state.tracker_list[state.tracker].clear() - self.show_empty_results(True) - else: - state.post_author, state.post_titles, state.post_urls = format_data_minimal(state.posts) - self.show_empty_results(False) - state.tracker_list[state.tracker].clear() - state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"]) - state.tracker_list[state.tracker].setRowCount(len(state.post_titles)) - for i, author in enumerate(state.post_author): - state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author)) - for i, title in enumerate(state.post_titles): - state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title)) + scrapefunc: function = tracker["scrapeFunc"] + + state.posts = scrapefunc(search_text) - elif state.tracker == "steamrip": - games = scrape_steamrip_links() + state.trackertable.clear() - options: Generator = fuzzyfinder.main.fuzzyfinder(search_text, self.opts, accessor=lambda x: x["name"]) - - filtered_opts = [*options] - - if filtered_opts == []: - consoleLog(f"No results for {search_text}") - state.tracker_list[state.tracker].clear() - self.show_empty_results(True) - else: - state + if state.posts == []: + self.show_empty_results(True) + + state.trackertable.setColumnCount(len(tracker["headers"])) + state.trackertable.setHorizontalHeaderLabels(tracker["headers"]) + state.trackertable.setRowCount(len(state.posts)) + for x, rowdata in enumerate(state.posts): + for y, data in enumerate(rowdata.values()): + state.trackertable.setItem(x, y, QTableWidgetItem(data)) + + consoleLog("[searchhelper] updated table") diff --git a/src/core/utils/data/state.py b/src/core/utils/data/state.py index 6077010..6d135c8 100644 --- a/src/core/utils/data/state.py +++ b/src/core/utils/data/state.py @@ -17,14 +17,13 @@ class AppState(QObject): self.currenttracker: str = "rutracker" self.trackertable: QTableWidget - self.trackers: Dict[str,Dict[str,Any]] # each tracker should add itself here, will be listed in order of module initialisation + self.trackers: Dict[str,Dict[str,Any]] = {}# each tracker should add itself here ''' an example: "rutracker" : { "name" : "rutracker", # name of the tracker - "posts" : [post1, post2, ...], # all the posts, will be in self.posts, "headers" : ["author", "title"], # keys shown in the table - "sortkey" : "name" # the key to sort posts by # currently only usefull to steamrip + "scrapeFunc" : function, } ''' self.api_url: str = "https://api.michijackson.xyz" @@ -42,25 +41,6 @@ class AppState(QObject): self.active_interfaces: List = [] self.bound_interface: Any = None - def update_tracker_table(self, newtracker: str): - - if newtracker not in self.trackers.keys(): - raise ValueError("the selected tracker is not initialised.") - - self.trackertable.setColumnCount(self.trackers[self.currenttracker][]) - - for row_index, row_data in enumerate(self.trackers[self.currenttracker].values()): - for col_index, key in enumerate(row_data.keys()): - if key in self.trackers[self.currenttracker]["ignorekeys"]: - continue - value = row_data.get(key, "") - self.trackertable.setItem( - row_index, - col_index, - QtWidgets.QTableWidgetItem(value) - ) - - @property def image_path(self) -> str: return self._image_path From d8fec74de14b4097477ede4acaeb3ed0f3b684c5 Mon Sep 17 00:00:00 2001 From: nosch Date: Sun, 8 Mar 2026 01:42:59 +0100 Subject: [PATCH 13/18] every menu works, now the downloads need fixing --- src/core/data/scrapers/monkrus.py | 2 +- src/core/data/scrapers/rutracker.py | 16 ++++++- src/core/data/scrapers/steamrip.py | 2 +- src/core/data/scrapers/uztracker.py | 2 +- src/core/interface/gui.py | 59 ++++++++++++++++++++---- src/core/interface/utils/searchhelper.py | 19 ++------ src/core/utils/network/download.py | 2 +- 7 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py index d41c020..b6baa5b 100644 --- a/src/core/data/scrapers/monkrus.py +++ b/src/core/data/scrapers/monkrus.py @@ -47,9 +47,9 @@ def _get_telegram_posts(): if post_url not in added: added.add(post_url) posts.append(dict( + title=title, author="m0nkrus", id=len(posts) + 1, - title=title, url=post_url )) diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py index 5641cdf..46255fb 100644 --- a/src/core/data/scrapers/rutracker.py +++ b/src/core/data/scrapers/rutracker.py @@ -9,8 +9,22 @@ def scrape_rutracker(query): consoleLog("[core.data.scrapers.rutracker] Sent request to server") if search: _, data, _, _, _ = split_data(search.text) + + sorteddata = [] - return data + for entry in data: + sorteddata.append( + { + "title" : entry["title"], + "author" : entry["author"], + "seeders" : entry["seeders"], + "leechers" : entry["leechers"], + "url" : entry["url"], + "id" : entry["id"], + } + ) + + return sorteddata else: consoleLog("[core.data.scrapers.rutracker] No search Text, returning nothing") return [] diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 52aaae1..961ca59 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -98,7 +98,7 @@ def filter_steamrip(query: str): Metadata = { "name" : "steamrip", - "headers" : ["Game", "Available Downloads"], + "headers" : ["Game"], "scrapeFunc" : filter_steamrip, } diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py index 0746a2d..f66d415 100644 --- a/src/core/data/scrapers/uztracker.py +++ b/src/core/data/scrapers/uztracker.py @@ -29,8 +29,8 @@ def scrape_uztracker(query): posts.append(dict( title=title, + author=author, url=url, - author=author )) return posts diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index 87bc723..825db3b 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -13,6 +13,7 @@ from PySide6.QtWidgets import ( QHeaderView, QMessageBox, QTableWidget, + QTableWidgetItem, QTextEdit, QStyledItemDelegate, QStatusBar @@ -162,11 +163,13 @@ def windowCloseHelper(): class MainWindow(QtWidgets.QMainWindow, QWidget): log_signal = Signal(str) # Thread-safe signal for logging + search_results_signal = Signal(list) # and searching def __init__(self): super().__init__() MainWindow._instance = self self.log_signal.connect(self._on_log_signal) + self.search_results_signal.connect(self._on_search_results) pixmap = QPixmap() image_data = base64.b64decode(logo_base64) @@ -248,8 +251,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): state.trackertable.verticalHeader().setVisible(False) state.trackertable.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) state.trackertable.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) - - + model = state.trackertable.model() + model.dataChanged.disconnect() + state.trackertable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) + class TrackerHoverDelegate(QStyledItemDelegate): def paint(self, painter, option, index): if index.row() == MainWindow._instance._tracker_hovered_row: @@ -491,8 +496,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.downloadList.setItemDelegateForColumn(0, delegate) delegate.clicked.connect(on_pause_resume_clicked) - # download button triggers - #! THIS IS TEMPORARY self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts, state.posts)))) + #! FIX THIS + + self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts, state.trackers[state.currenttracker]["headers"])))) container.setLayout(containerLayout) self.setCentralWidget(container) @@ -636,6 +642,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.consoleLog.verticalScrollBar().maximum() ) + def _on_search_results(self, headers): + + if state.posts is None or state.posts == []: + self.show_empty_results(True) + return + else: + self.show_empty_results(False) + + state.trackertable.clearContents() + state.trackertable.setColumnCount(len(headers)) + state.trackertable.setHorizontalHeaderLabels(headers) + state.trackertable.setRowCount(len(state.posts)) + for x, rowdata in enumerate(state.posts): + for y, data in enumerate(rowdata.values()): + state.trackertable.setItem(x, y, QTableWidgetItem(str(data))) + + self._resize_tracker_columns() + def update_image_overlay(self, new_image_path): self.image = QImage(new_image_path) self.pixmap = QPixmap.fromImage(self.image) @@ -693,9 +717,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): def mousePressEvent(self, event): - self.rutrackerlist.clearSelection() - self.uztrackerlist.clearSelection() - self.monkruslist.clearSelection() + state.trackertable.clearSelection() self.downloadList.clearSelection() super().mousePressEvent(event) @@ -764,11 +786,28 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.emptyDownload.show() self.downloadList.hide() - # thank you claude + # thanks claude + def _resize_tracker_columns(self): + header = state.trackertable.horizontalHeader() + col_count = header.count() + if col_count == 0: + return + + for i in range(col_count): + state.trackertable.resizeColumnToContents(i) + + content_widths = [header.sectionSize(i) for i in range(col_count)] + total_content_width = sum(content_widths) + remaining = state.trackertable.viewport().width() - total_content_width + extra = max(0, remaining) // col_count + + header.setSectionResizeMode(QHeaderView.ResizeMode.Fixed) + for i in range(col_count): + header.resizeSection(i, content_widths[i] + extra) + def resizeEvent(self, event): super().resizeEvent(event) - table_width = state.trackertable.viewport().width() - state.trackertable.setColumnWidth(1, int(table_width * 0.3)) + self._resize_tracker_columns() def contextMenuEvent(self, event: QContextMenuEvent): if self.downloadList.underMouse(): diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py index ec36a7a..7b04010 100644 --- a/src/core/interface/utils/searchhelper.py +++ b/src/core/interface/utils/searchhelper.py @@ -1,12 +1,13 @@ from PySide6.QtWidgets import QTableWidgetItem, QHeaderView from core.utils.logging.logs import consoleLog -from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.rutracker import scrape_rutracker +from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.monkrus import scrape_m0nkrus from core.data.scrapers.steamrip import filter_steamrip from core.utils.data.state import state def return_pressed(self): + self.show_empty_results(False) search_text = self.searchbar.text() if search_text == "": consoleLog("Error: Can't search for nothing") @@ -18,18 +19,6 @@ def return_pressed(self): scrapefunc: function = tracker["scrapeFunc"] state.posts = scrapefunc(search_text) - - state.trackertable.clearContents() - if state.posts == []: - self.show_empty_results(True) - - state.trackertable.setColumnCount(len(tracker["headers"])) - state.trackertable.setHorizontalHeaderLabels(tracker["headers"]) - state.trackertable.setRowCount(len(state.posts)) - - for x, rowdata in enumerate(state.posts): - for y, data in enumerate(rowdata.values()): - state.trackertable.setItem(x, y, QTableWidgetItem(data)) - - consoleLog("[searchhelper] updated table") + from core.interface.gui import MainWindow + MainWindow._instance.search_results_signal.emit(tracker["headers"]) diff --git a/src/core/utils/network/download.py b/src/core/utils/network/download.py index 69082b0..66cdb17 100644 --- a/src/core/utils/network/download.py +++ b/src/core/utils/network/download.py @@ -37,4 +37,4 @@ def run_download_direct(magnet_uri): def seed_magnet(magnet_uri, file_path): consoleLog(f"Seeding: {magnet_uri[:60]}") - add_seed(magnet_uri, file_path) \ No newline at end of file + add_seed(magnet_uri, file_path) From ffdd656b9ca7391bd76ae14ab97016af56522c39 Mon Sep 17 00:00:00 2001 From: nosch Date: Sun, 8 Mar 2026 01:47:33 +0100 Subject: [PATCH 14/18] fixed a slight issue in the gui --- src/core/interface/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index 825db3b..acb469b 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -647,8 +647,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): if state.posts is None or state.posts == []: self.show_empty_results(True) return - else: - self.show_empty_results(False) state.trackertable.clearContents() state.trackertable.setColumnCount(len(headers)) @@ -660,6 +658,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self._resize_tracker_columns() + self.show_empty_results(False) + def update_image_overlay(self, new_image_path): self.image = QImage(new_image_path) self.pixmap = QPixmap.fromImage(self.image) From 7cdae312cee6af7423a59c2e8d9acc78c5c76791 Mon Sep 17 00:00:00 2001 From: nosch Date: Sun, 8 Mar 2026 15:54:57 +0100 Subject: [PATCH 15/18] preparing to merge --- src/core/interface/utils/searchhelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/interface/utils/searchhelper.py b/src/core/interface/utils/searchhelper.py index 7b04010..b1e548d 100644 --- a/src/core/interface/utils/searchhelper.py +++ b/src/core/interface/utils/searchhelper.py @@ -5,6 +5,7 @@ from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.monkrus import scrape_m0nkrus from core.data.scrapers.steamrip import filter_steamrip from core.utils.data.state import state +from core.interface.gui import MainWindow def return_pressed(self): self.show_empty_results(False) @@ -20,5 +21,4 @@ def return_pressed(self): state.posts = scrapefunc(search_text) - from core.interface.gui import MainWindow MainWindow._instance.search_results_signal.emit(tracker["headers"]) From 56735c4344c8fbc411ceaf3bdc871a9dc3946953 Mon Sep 17 00:00:00 2001 From: nosch Date: Sun, 8 Mar 2026 22:29:28 +0100 Subject: [PATCH 16/18] worked on downloads, magnets are next --- src/core/data/scrapers/monkrus.py | 13 +++++++------ src/core/data/scrapers/rutracker.py | 15 ++++++++++----- src/core/data/scrapers/steamrip.py | 18 ++++++++++++++++++ src/core/data/scrapers/uztracker.py | 7 ++++--- src/core/interface/gui.py | 25 +++++++++++++++++-------- src/core/utils/data/tracker.py | 25 ------------------------- src/core/utils/network/download.py | 9 +++++---- 7 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py index e3757b4..81913b4 100644 --- a/src/core/data/scrapers/monkrus.py +++ b/src/core/data/scrapers/monkrus.py @@ -2,10 +2,9 @@ from bs4 import BeautifulSoup import requests import time from core.utils.data.state import state - - -def get_Metadata(): - return Metadata +from core.utils.network.jsonhandler import format_data +from core.utils.data.tracker import get_magnet_link +from typing import Dict cache = { "data": [], @@ -74,13 +73,15 @@ def scrape_m0nkrus(query): return filtered_posts +def get_magnet(post: Dict): + _, post_links, _, _, _ = format_data([post]) + return post_links[0] Metadata = { "name" : "m0nkrus", "headers" : ["Post Title", "Author"], - "searchkey" : None, "scrapeFunc" : scrape_m0nkrus, - "scrapeSearches" : True, + "" } def init_m0nkrus(): diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py index 32be176..23d1e57 100644 --- a/src/core/data/scrapers/rutracker.py +++ b/src/core/data/scrapers/rutracker.py @@ -1,8 +1,8 @@ import requests from core.utils.data.state import state from core.utils.logging.logs import consoleLog -from core.utils.network.jsonhandler import split_data - +from core.utils.network.jsonhandler import split_data, format_data +from typing import Dict def scrape_rutracker(query): search = requests.get(f"{state.api_url}/search?q={query}") @@ -28,13 +28,18 @@ def scrape_rutracker(query): else: consoleLog("[core.data.scrapers.rutracker] No search Text, returning nothing") return [] - + + +def get_magnet_link(post: Dict): + _, post_links, _, _, _ = format_data([post]) + return post_links[0] + Metadata = { "name" : "rutracker", "headers" : ["Post Title", "Author", "Seeders", "Leechers"], - "searchkey" : None, "scrapeFunc" : scrape_rutracker, - "scrapeSearches" : True, + "linkFunc" : get_magnet_link, + "isMagnet" : True, } diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 96621a9..8bfafe2 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -1,11 +1,14 @@ +import os from bs4 import BeautifulSoup from fuzzyfinder.main import fuzzyfinder import requests import re import time +import webbrowser from typing import Generator from core.utils.data.state import state +from core.utils.logging.logs import consoleLog cache = { @@ -86,6 +89,21 @@ def scrape_steamrip_game_downloads(gamelink): return ret +def get_download_link(post: Dict): + url = post["link"] + links = scrape_steamrip_game_downloads(url) + best = "" + for link in links: + if link[0] != "h": + best = link + break + + if links.index(best) < 2: + return best + else: + consoleLog("cant scrape this cuz of captcha... opening in the browser") + webbrowser.open(best) + return None def filter_steamrip(query: str): diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py index 43c3baa..8be0b3b 100644 --- a/src/core/data/scrapers/uztracker.py +++ b/src/core/data/scrapers/uztracker.py @@ -3,11 +3,9 @@ from bs4 import BeautifulSoup from urllib.parse import urljoin from core.utils.logging.logs import consoleLog from core.utils.data.state import state +from typing import Dict -def get_Metadata(): - return Metadata - def scrape_uztracker(query): base_url="https://uztracker.net/" search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}" @@ -48,6 +46,9 @@ Metadata = { "scrapeFunc" : scrape_uztracker, "scrapeSearches" : True, } +def get_magnet_link(post: Dict): + item = "https://uztracker.net/" + post["url"].lstrip("./") + return item def init_uztracker(): state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index 1815954..f831cc8 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -187,6 +187,15 @@ def download_update(latest_version): def windowCloseHelper(): QGuiApplication.quit() +class TrackerHoverDelegate(QStyledItemDelegate): + def paint(self, painter, option, index): + if index.row() == MainWindow._instance._tracker_hovered_row: + painter.save() + painter.fillRect(option.rect, _theme_colors()["hover"]) + painter.restore() + super().paint(painter, option, index) + + class MainWindow(QtWidgets.QMainWindow, QWidget): log_signal = Signal(str) # Thread-safe signal for logging search_results_signal = Signal(list) # and searching @@ -281,15 +290,13 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): model.dataChanged.disconnect() state.trackertable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) - class TrackerHoverDelegate(QStyledItemDelegate): - def paint(self, painter, option, index): - if index.row() == MainWindow._instance._tracker_hovered_row: - painter.save() - painter.fillRect(option.rect, _theme_colors()["hover"]) - painter.restore() - super().paint(painter, option, index) + state.trackertable.setStyleSheet(_table_stylesheet("QTableWidget")) + + state.trackertable.setItemDelegate(TrackerHoverDelegate(state.trackertable)) state.trackertable.viewport().installEventFilter(self) + state.trackertable.setMouseTracking(True) + state.trackertable.viewport().setMouseTracking(True) container = QWidget() containerLayout = QVBoxLayout() @@ -526,7 +533,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): #! FIX THIS - self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts, state.trackers[state.currenttracker]["headers"])))) + self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts)))) container.setLayout(containerLayout) self.setCentralWidget(container) @@ -695,6 +702,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.show_empty_results(False) self.searchbar.setEnabled(True) self.searchbar.setFocus() + state.trackertable.setItemDelegate(TrackerHoverDelegate(state.trackertable)) + state.trackertable.viewport().installEventFilter(self) def update_image_overlay(self, new_image_path): self.image = QImage(new_image_path) diff --git a/src/core/utils/data/tracker.py b/src/core/utils/data/tracker.py index 79ed98f..f70756b 100644 --- a/src/core/utils/data/tracker.py +++ b/src/core/utils/data/tracker.py @@ -1,31 +1,6 @@ import requests from bs4 import BeautifulSoup -from core.utils.data.state import state from core.utils.logging.logs import consoleLog -from core.utils.network.jsonhandler import format_data - - - -def get_item_url(item, posts, post_titles): # softwarelist currentitem, post list (dict), post titles list - post_index = post_titles.index(item) - if 0 <= post_index < len(post_titles): - if state.tracker == "uztracker": - item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./") - consoleLog(f"Found post URL: {item}") - return item - if state.tracker == "rutracker": - item_dict = posts[post_index] - _, post_links, _, _, _ = format_data([item_dict]) - consoleLog(f"Found post URL: {post_links[0]}") - return post_links[0] - if state.tracker == "m0nkrus": - item_dict = posts[post_index] - _, post_links, _, _, _,= format_data([item_dict]) - consoleLog(f"Found post URL: {post_links[0]}") - return post_links[0] - - return None - def get_magnet_link(post_url): diff --git a/src/core/utils/network/download.py b/src/core/utils/network/download.py index 66cdb17..9f043e0 100644 --- a/src/core/utils/network/download.py +++ b/src/core/utils/network/download.py @@ -1,14 +1,15 @@ +from PySide6.QtWidgets import QTableWidgetItem from core.utils.logging.logs import consoleLog -from core.utils.data.tracker import get_item_url from core.utils.data.tracker import get_magnet_link from core.network.libtorrent_wrapper import add_download from core.utils.general.wrappers import run_thread from core.utils.logging.logs import add_download_log from core.network.libtorrent_int import add_seed +from core.utils.data.state import state import threading -def download_selected(items, posts, post_titles): +def download_selected(items: list[QTableWidgetItem]): if not items: consoleLog("No item selected for download.") return @@ -17,10 +18,10 @@ def download_selected(items, posts, post_titles): if item.column() != 0: continue text = item.text() - if text and text not in seen: + if text != "" and text not in seen: seen.add(text) consoleLog(f"Downloading {text}") - run_thread(threading.Thread(target=run_download, args=(text, posts, post_titles))) + run_thread(threading.Thread(target=run_download, args=(text, state.posts[item.row()]))) def run_download(item, posts, post_titles): post_url = get_item_url(item, posts, post_titles) From 39522a40682260134d03afaf496a748280619c28 Mon Sep 17 00:00:00 2001 From: nosch Date: Mon, 9 Mar 2026 16:59:05 +0100 Subject: [PATCH 17/18] download is in download.py, needs to move --- src/core/data/scrapers/monkrus.py | 7 +-- src/core/data/scrapers/provider/gofile.py | 14 +++--- src/core/data/scrapers/rutracker.py | 7 +-- src/core/data/scrapers/steamrip.py | 15 ++++-- src/core/data/scrapers/uztracker.py | 12 +++-- src/core/interface/gui.py | 4 +- src/core/utils/network/download.py | 56 +++++++++++++++++++---- 7 files changed, 79 insertions(+), 36 deletions(-) diff --git a/src/core/data/scrapers/monkrus.py b/src/core/data/scrapers/monkrus.py index 81913b4..e48a5df 100644 --- a/src/core/data/scrapers/monkrus.py +++ b/src/core/data/scrapers/monkrus.py @@ -35,7 +35,7 @@ def _get_telegram_posts(): for bubble in bubbles: post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text") - if not post_txt or not post_txt.b: + if not post_txt is None or not post_txt.b is None: continue title = post_txt.b.text @@ -75,13 +75,14 @@ def scrape_m0nkrus(query): def get_magnet(post: Dict): _, post_links, _, _, _ = format_data([post]) - return post_links[0] + return get_magnet_link(post_links[0]) Metadata = { "name" : "m0nkrus", "headers" : ["Post Title", "Author"], "scrapeFunc" : scrape_m0nkrus, - "" + "linkFunc" : get_magnet, + "isMagnet" : True, } def init_m0nkrus(): diff --git a/src/core/data/scrapers/provider/gofile.py b/src/core/data/scrapers/provider/gofile.py index 7ee9ddf..42574af 100644 --- a/src/core/data/scrapers/provider/gofile.py +++ b/src/core/data/scrapers/provider/gofile.py @@ -1,5 +1,6 @@ import requests import re +from core.utils.logging.loghandler import consoleLog def scrape_gofile(url): @@ -18,13 +19,12 @@ def scrape_gofile(url): f"https://api.gofile.io/contents/{filetoken}", headers=headers, ) + try: - temp: dict = r.json()["data"]["children"] - child = [key for key in temp.keys()] + temp: dict = r.json()["data"]["children"] + child = [key for key in temp.keys()] + return temp[child[0]]["link"] + except: + consoleLog("gofile didnt auth you, either the api has changed\n or you sent to many requests, please try again later.\n If it still doesnt work please open an issue on Github.") - return temp[child[0]]["link"] - - -out = scrape_gofile("https://gofile.io/d/tvvzAD") -print(out) diff --git a/src/core/data/scrapers/rutracker.py b/src/core/data/scrapers/rutracker.py index 23d1e57..ab235ef 100644 --- a/src/core/data/scrapers/rutracker.py +++ b/src/core/data/scrapers/rutracker.py @@ -1,6 +1,7 @@ import requests from core.utils.data.state import state from core.utils.logging.logs import consoleLog +from core.utils.data.tracker import get_magnet_link from core.utils.network.jsonhandler import split_data, format_data from typing import Dict @@ -30,15 +31,15 @@ def scrape_rutracker(query): return [] -def get_magnet_link(post: Dict): +def get_magnet(post: Dict): _, post_links, _, _, _ = format_data([post]) - return post_links[0] + return get_magnet_link(post_links[0]) Metadata = { "name" : "rutracker", "headers" : ["Post Title", "Author", "Seeders", "Leechers"], "scrapeFunc" : scrape_rutracker, - "linkFunc" : get_magnet_link, + "linkFunc" : get_magnet, "isMagnet" : True, } diff --git a/src/core/data/scrapers/steamrip.py b/src/core/data/scrapers/steamrip.py index 8bfafe2..65c29dc 100644 --- a/src/core/data/scrapers/steamrip.py +++ b/src/core/data/scrapers/steamrip.py @@ -1,12 +1,13 @@ -import os from bs4 import BeautifulSoup from fuzzyfinder.main import fuzzyfinder import requests import re import time import webbrowser -from typing import Generator +from core.data.scrapers.provider.buzzheavier import scrape_buzzheavier +from core.data.scrapers.provider.gofile import scrape_gofile +from typing import Generator, Dict from core.utils.data.state import state from core.utils.logging.logs import consoleLog @@ -94,12 +95,14 @@ def get_download_link(post: Dict): links = scrape_steamrip_game_downloads(url) best = "" for link in links: - if link[0] != "h": + if link[0] == "h": best = link break - if links.index(best) < 2: - return best + if links.index(best) == 0: + return scrape_buzzheavier(best) + elif links.index(best) == 1: + return scrape_gofile(best) else: consoleLog("cant scrape this cuz of captcha... opening in the browser") webbrowser.open(best) @@ -118,6 +121,8 @@ Metadata = { "name" : "steamrip", "headers" : ["Game"], "scrapeFunc" : filter_steamrip, + "linkFunc" : get_download_link, + "isMagnet" : False, } def init_steamrip(): diff --git a/src/core/data/scrapers/uztracker.py b/src/core/data/scrapers/uztracker.py index 8be0b3b..f6395ef 100644 --- a/src/core/data/scrapers/uztracker.py +++ b/src/core/data/scrapers/uztracker.py @@ -3,6 +3,7 @@ from bs4 import BeautifulSoup from urllib.parse import urljoin from core.utils.logging.logs import consoleLog from core.utils.data.state import state +from core.utils.data.tracker import get_magnet_link from typing import Dict @@ -39,16 +40,17 @@ def scrape_uztracker(query): consoleLog(f"Failed to fetch {search_url}: {e}") return None +def get_magnet(post: Dict): + item = "https://uztracker.net/" + post["url"].lstrip("./") + return get_magnet_link(item) + Metadata = { "name" : "uztracker", "headers" : ["Post Title", "Author"], - "searchkey" : None, "scrapeFunc" : scrape_uztracker, - "scrapeSearches" : True, + "linkFunc" : get_magnet, + "isMagnet" : True, } -def get_magnet_link(post: Dict): - item = "https://uztracker.net/" + post["url"].lstrip("./") - return item def init_uztracker(): state.trackers.update({Metadata["name"] : Metadata}) diff --git a/src/core/interface/gui.py b/src/core/interface/gui.py index dea7146..6b7f127 100644 --- a/src/core/interface/gui.py +++ b/src/core/interface/gui.py @@ -540,9 +540,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.downloadList.setItemDelegateForColumn(0, delegate) delegate.clicked.connect(on_pause_resume_clicked) - #! FIX THIS - - self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.currentItem(), state.posts)))) + self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),)))) container.setLayout(containerLayout) self.setCentralWidget(container) diff --git a/src/core/utils/network/download.py b/src/core/utils/network/download.py index 9f043e0..fcba5a2 100644 --- a/src/core/utils/network/download.py +++ b/src/core/utils/network/download.py @@ -1,12 +1,15 @@ from PySide6.QtWidgets import QTableWidgetItem from core.utils.logging.logs import consoleLog -from core.utils.data.tracker import get_magnet_link -from core.network.libtorrent_wrapper import add_download +from core.network.libtorrent_wrapper import add_magnet from core.utils.general.wrappers import run_thread from core.utils.logging.logs import add_download_log from core.network.libtorrent_int import add_seed from core.utils.data.state import state +from urllib.parse import urlparse, unquote import threading +import re +import requests +import random def download_selected(items: list[QTableWidgetItem]): @@ -21,19 +24,52 @@ def download_selected(items: list[QTableWidgetItem]): if text != "" and text not in seen: seen.add(text) consoleLog(f"Downloading {text}") - run_thread(threading.Thread(target=run_download, args=(text, state.posts[item.row()]))) + run_thread(threading.Thread(target=run_download, args=(state.posts[item.row()],))) -def run_download(item, posts, post_titles): - post_url = get_item_url(item, posts, post_titles) - consoleLog(f"Selected URL: {post_url}") - magnet_uri = get_magnet_link(post_url) +def get_direct_filename(url: str, headers) -> str: + cd = headers.get('content-disposition', '') + if cd: + match = re.search(r'filename\*=UTF-8\'\'(.+)', cd) # RFC 5987 encoded + if match: + return unquote(match.group(1)) + match = re.search(r'filename="?([^";\n]+)"?', cd) + if match: + return match.group(1).strip() - if add_download(magnet_uri): - add_download_log(item, post_url, magnet_uri, False) + path = urlparse(url).path + name = path.split('/')[-1] + if name: + return unquote(name) + + return f"download{random.randint(1,9999)}" #avoid collision + +def filedownload(url): + try: + with requests.get(url, stream=True, timeout=30) as r: + r.raise_for_status() + name = get_direct_filename(url, r.headers) + + with open(state.download_path + f"/{name}", 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + + consoleLog(f"Finished downloading {name}") + except Exception as e: + consoleLog(f"Error downloading: {e}") + +def run_download(post): + linkfunc = state.trackers[state.currenttracker]["linkFunc"] + ismagnet = state.trackers[state.currenttracker]["isMagnet"] + link = linkfunc(post) + + if ismagnet: + add_magnet(link) + else: + filedownload(link) def run_download_direct(magnet_uri): consoleLog(f"Direct download: {magnet_uri[:60]}") - add_download(magnet_uri) + add_magnet(magnet_uri) add_download_log("Direct Download", "", magnet_uri, False) def seed_magnet(magnet_uri, file_path): From c5e72332dc41ace71c20101f6735a68307a9a945 Mon Sep 17 00:00:00 2001 From: nosch Date: Mon, 9 Mar 2026 17:03:01 +0100 Subject: [PATCH 18/18] cleaned requirements.txt --- requirements.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6d70221..dec79d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,17 +10,3 @@ psutil==7.1.0 libtorrent==2.0.11 libtorrent-windows-dll==0.0.3 fuzzyfinder==2.3.0 -cffi==2.0.0 -h11==0.16.0 -outcome==1.3.0.post0 -pycparser==3.0 -pysocks==1.7.1 -selenium==4.41.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -trio==0.33.0 -trio-websocket==0.12.2 -undetected-chromedriver==3.5.5 -websocket-client==1.9.0 -websockets==16.0 -wsproto==1.3.2 \ No newline at end of file