mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Merge pull request #40 from noschXL/main
This commit is contained in:
+2
-219
@@ -1,220 +1,3 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
# Created by venv; see https://docs.python.org/3/library/venv.html
|
||||
__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
|
||||
*.exe
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
import time
|
||||
from core.utils.data.state import state
|
||||
from core.utils.network.jsonhandler import format_data
|
||||
from core.utils.data.tracker import get_magnet_link
|
||||
from typing import Dict
|
||||
|
||||
cache = {
|
||||
"data": [],
|
||||
@@ -31,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
|
||||
@@ -42,9 +46,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
|
||||
))
|
||||
|
||||
@@ -68,3 +72,18 @@ def scrape_m0nkrus(query):
|
||||
filtered_posts.append(filtered_post)
|
||||
|
||||
return filtered_posts
|
||||
|
||||
def get_magnet(post: Dict):
|
||||
_, post_links, _, _, _ = format_data([post])
|
||||
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():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import requests
|
||||
import re
|
||||
from core.utils.logging.loghandler import consoleLog
|
||||
|
||||
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,
|
||||
)
|
||||
try:
|
||||
|
||||
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.")
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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
|
||||
import time
|
||||
|
||||
|
||||
options = uc.ChromeOptions()
|
||||
options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
|
||||
|
||||
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(
|
||||
(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()
|
||||
@@ -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)
|
||||
@@ -1,19 +1,48 @@
|
||||
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
|
||||
|
||||
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("No results found / No response from server")
|
||||
return None
|
||||
_, data, _, _, _ = split_data(search.text)
|
||||
|
||||
sorteddata = []
|
||||
|
||||
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:
|
||||
return None
|
||||
consoleLog("[core.data.scrapers.rutracker] No search Text, returning nothing")
|
||||
return []
|
||||
|
||||
|
||||
def get_magnet(post: Dict):
|
||||
_, post_links, _, _, _ = format_data([post])
|
||||
return get_magnet_link(post_links[0])
|
||||
|
||||
# This function utilizes the SoftwareManager server - source code can be found under the SoftwareManager-Server repository.
|
||||
Metadata = {
|
||||
"name" : "rutracker",
|
||||
"headers" : ["Post Title", "Author", "Seeders", "Leechers"],
|
||||
"scrapeFunc" : scrape_rutracker,
|
||||
"linkFunc" : get_magnet,
|
||||
"isMagnet" : True,
|
||||
}
|
||||
|
||||
|
||||
def init_rutracker():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from fuzzyfinder.main import fuzzyfinder
|
||||
import requests
|
||||
import re
|
||||
import time
|
||||
import webbrowser
|
||||
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
|
||||
|
||||
|
||||
cache = {
|
||||
"data": [],
|
||||
"last_fetched": 0
|
||||
}
|
||||
|
||||
cache_expiry = 300
|
||||
|
||||
def get_Metadata():
|
||||
return Metadata
|
||||
|
||||
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")
|
||||
|
||||
if len(games) == 0:
|
||||
return []
|
||||
|
||||
links = []
|
||||
names = []
|
||||
for gamehtml in games:
|
||||
link = gamehtml.find("a", href=lambda x: x and x.startswith("/"))
|
||||
links += re.findall(r'(?<=href=")[^"]*', link.__str__())
|
||||
name = gamehtml.find("a", href=lambda x: x and x.startswith("/"))
|
||||
names += re.findall(r'(?<=\/">)[^<]*', name.__str__())
|
||||
|
||||
|
||||
|
||||
# construct the list[dict[str,str]]
|
||||
|
||||
ret = []
|
||||
|
||||
for i in range(len(names)-1):
|
||||
ret.append({"name" : names[i], "link" : links[i]})
|
||||
|
||||
return ret
|
||||
|
||||
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 = ["buzzheavier", "gofile", "vikingfile", "megadb"]
|
||||
|
||||
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)
|
||||
|
||||
cache["data"] = ret
|
||||
|
||||
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) == 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)
|
||||
return None
|
||||
|
||||
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"],
|
||||
"scrapeFunc" : filter_steamrip,
|
||||
"linkFunc" : get_download_link,
|
||||
"isMagnet" : False,
|
||||
}
|
||||
|
||||
def init_steamrip():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(scrape_steamrip_game_downloads("/r-e-p-o-free-download/"))
|
||||
@@ -2,6 +2,10 @@ 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
|
||||
from core.utils.data.tracker import get_magnet_link
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def scrape_uztracker(query):
|
||||
base_url="https://uztracker.net/"
|
||||
@@ -26,8 +30,8 @@ def scrape_uztracker(query):
|
||||
|
||||
posts.append(dict(
|
||||
title=title,
|
||||
author=author,
|
||||
url=url,
|
||||
author=author
|
||||
))
|
||||
|
||||
return posts
|
||||
@@ -35,3 +39,18 @@ def scrape_uztracker(query):
|
||||
except requests.RequestException as e:
|
||||
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"],
|
||||
"scrapeFunc" : scrape_uztracker,
|
||||
"linkFunc" : get_magnet,
|
||||
"isMagnet" : True,
|
||||
}
|
||||
|
||||
def init_uztracker():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
|
||||
+92
-102
@@ -13,6 +13,7 @@ from PySide6.QtWidgets import (
|
||||
QHeaderView,
|
||||
QMessageBox,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QTextEdit,
|
||||
QStyledItemDelegate,
|
||||
QStatusBar
|
||||
@@ -189,15 +190,24 @@ 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_finished_signal = Signal() # Thread-safe signal for search completion
|
||||
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_finished_signal.connect(self._on_search_finished)
|
||||
self.search_results_signal.connect(self._on_search_results)
|
||||
|
||||
pixmap = QPixmap()
|
||||
image_data = base64.b64decode(logo_base64)
|
||||
@@ -273,68 +283,30 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
|
||||
flush_log_buffer()
|
||||
|
||||
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)
|
||||
# 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)
|
||||
model = state.trackertable.model()
|
||||
model.dataChanged.disconnect()
|
||||
state.trackertable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
||||
|
||||
self._tracker_hover_delegate = TrackerHoverDelegate(self)
|
||||
|
||||
def create_tracker_table(headers):
|
||||
table = QTableWidget()
|
||||
table.setColumnCount(len(headers))
|
||||
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
table.verticalHeader().setVisible(False)
|
||||
table.setHorizontalHeaderLabels(headers)
|
||||
table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
|
||||
header = table.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||
header.resizeSection(1, 500)
|
||||
header.setStretchLastSection(False)
|
||||
header.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||||
header.setHighlightSections(False)
|
||||
|
||||
table.setShowGrid(False)
|
||||
table.setStyleSheet(_table_stylesheet("QTableWidget"))
|
||||
|
||||
table.setMouseTracking(True)
|
||||
table.viewport().setMouseTracking(True)
|
||||
table.setItemDelegate(self._tracker_hover_delegate)
|
||||
|
||||
return table
|
||||
state.trackertable.setStyleSheet(_table_stylesheet("QTableWidget"))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
self.monkruslist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
self.monkruslist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
|
||||
for tbl in (self.rutrackerlist, self.uztrackerlist, self.monkruslist):
|
||||
tbl.viewport().installEventFilter(self)
|
||||
state.trackertable.setItemDelegate(TrackerHoverDelegate(state.trackertable))
|
||||
state.trackertable.viewport().installEventFilter(self)
|
||||
state.trackertable.setMouseTracking(True)
|
||||
state.trackertable.viewport().setMouseTracking(True)
|
||||
|
||||
container = QWidget()
|
||||
containerLayout = QVBoxLayout()
|
||||
|
||||
containerLayout.addWidget(self.searchbar)
|
||||
containerLayout.addWidget(state.trackertable)
|
||||
search_row = QHBoxLayout()
|
||||
search_row.addWidget(self.searchbar)
|
||||
containerLayout.addLayout(search_row)
|
||||
containerLayout.addWidget(state.tracker_list[state.tracker])
|
||||
|
||||
class DownloadModel(QAbstractTableModel):
|
||||
def __init__(self):
|
||||
@@ -568,8 +540,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.downloadList.setItemDelegateForColumn(0, delegate)
|
||||
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].selectedItems(), state.posts, state.post_titles))))
|
||||
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),))))
|
||||
|
||||
container.setLayout(containerLayout)
|
||||
self.setCentralWidget(container)
|
||||
@@ -589,10 +560,9 @@ 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.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)
|
||||
|
||||
@@ -621,7 +591,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
|
||||
|
||||
self.tracker_list = QComboBox()
|
||||
self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"])
|
||||
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)
|
||||
@@ -705,16 +675,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
def _start_search(self):
|
||||
self.searchbar.setEnabled(False)
|
||||
def _search_thread():
|
||||
try:
|
||||
return_pressed(self)
|
||||
finally:
|
||||
self.search_finished_signal.emit()
|
||||
return_pressed(self)
|
||||
run_thread(threading.Thread(target=_search_thread))
|
||||
|
||||
def _on_search_finished(self):
|
||||
self.searchbar.setEnabled(True)
|
||||
self.searchbar.setFocus()
|
||||
|
||||
@staticmethod
|
||||
def add_log(text):
|
||||
if MainWindow._instance:
|
||||
@@ -727,6 +690,28 @@ 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
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
self.pixmap = QPixmap.fromImage(self.image)
|
||||
@@ -784,9 +769,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)
|
||||
|
||||
@@ -801,21 +784,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
try:
|
||||
for tbl in (self.rutrackerlist, self.uztrackerlist, self.monkruslist):
|
||||
if obj == tbl.viewport():
|
||||
if event.type() == QEvent.Type.MouseMove:
|
||||
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
||||
idx = tbl.indexAt(pos)
|
||||
new_row = idx.row() if idx.isValid() else -1
|
||||
if new_row != self._tracker_hovered_row or tbl is not self._tracker_hovered_table:
|
||||
self._tracker_hovered_row = new_row
|
||||
self._tracker_hovered_table = tbl
|
||||
tbl.viewport().update()
|
||||
elif event.type() == QEvent.Type.Leave:
|
||||
self._tracker_hovered_row = -1
|
||||
self._tracker_hovered_table = None
|
||||
tbl.viewport().update()
|
||||
break
|
||||
if obj == state.trackertable.viewport():
|
||||
if event.type() == QEvent.Type.MouseMove:
|
||||
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
||||
idx = state.trackertable.indexAt(pos)
|
||||
new_row = idx.row() if idx.isValid() else -1
|
||||
if new_row != self._tracker_hovered_row or state.trackertable is not self._tracker_hovered_table:
|
||||
self._tracker_hovered_row = new_row
|
||||
self._tracker_hovered_table = state.trackertable
|
||||
state.trackertable.viewport().update()
|
||||
elif event.type() == QEvent.Type.Leave:
|
||||
self._tracker_hovered_row = -1
|
||||
self._tracker_hovered_table = None
|
||||
state.trackertable.viewport().update()
|
||||
if obj == self.downloadList.viewport():
|
||||
if event.type() == QEvent.Type.MouseMove:
|
||||
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
||||
@@ -839,22 +820,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.downloadList.viewport().update()
|
||||
|
||||
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):
|
||||
@@ -865,11 +838,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.tracker_list[state.tracker].viewport().width()
|
||||
state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3))
|
||||
self._resize_tracker_columns()
|
||||
|
||||
def contextMenuEvent(self, event: QContextMenuEvent):
|
||||
if self.downloadList.underMouse():
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
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
|
||||
"""
|
||||
The dict should contain a:
|
||||
$(keytosortby) : 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
|
||||
|
||||
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, 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")
|
||||
|
||||
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():
|
||||
if key not in self.ignorekeys:
|
||||
headers.append(key)
|
||||
|
||||
|
||||
|
||||
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):
|
||||
if key in self.ignorekeys:
|
||||
continue
|
||||
value = row_data.get(key, "")
|
||||
item = QtWidgets.QTableWidgetItem(value)
|
||||
self.table.setItem(row_index, col_index, item)
|
||||
|
||||
self.table.resizeColumnsToContents()
|
||||
|
||||
self.searchbox.textChanged.connect(self.filter_table)
|
||||
|
||||
self.table.cellClicked.connect(self.cellClick)
|
||||
|
||||
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: Generator = fuzzyfinder.main.fuzzyfinder(searchquery, self.opts, accessor=lambda x: x[self.sortkey])
|
||||
|
||||
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
|
||||
value = row_data.get(key, "")
|
||||
self.table.setItem(
|
||||
row_index,
|
||||
col_index,
|
||||
QtWidgets.QTableWidgetItem(value)
|
||||
)
|
||||
|
||||
def cellClick(self, row: int, _: 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.scrape_steamrip_links())
|
||||
widget.resize(600,400)
|
||||
widget.show()
|
||||
|
||||
|
||||
exit(app.exec())
|
||||
@@ -1,66 +1,29 @@
|
||||
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.utils.network.jsonhandler import split_data, format_data, format_data_minimal
|
||||
from core.data.scrapers.rutracker import init_rutracker
|
||||
from core.data.scrapers.uztracker import init_uztracker
|
||||
from core.data.scrapers.monkrus import init_m0nkrus
|
||||
from core.data.scrapers.steamrip import init_steamrip
|
||||
from core.utils.data.state import state
|
||||
|
||||
scrapers = {
|
||||
"uztracker": scrape_uztracker,
|
||||
"rutracker": scrape_rutracker,
|
||||
"m0nkrus": scrape_m0nkrus
|
||||
}
|
||||
init_rutracker()
|
||||
init_uztracker()
|
||||
init_m0nkrus()
|
||||
init_steamrip()
|
||||
|
||||
def return_pressed(self):
|
||||
self.show_empty_results(False)
|
||||
search_text = self.searchbar.text()
|
||||
if search_text == "":
|
||||
consoleLog("Error: Can't search for nothing")
|
||||
return
|
||||
consoleLog(f"User searched for: {search_text}")
|
||||
|
||||
if state.tracker == "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 not 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)
|
||||
|
||||
from core.interface.gui import MainWindow
|
||||
MainWindow._instance.search_results_signal.emit(tracker["headers"])
|
||||
|
||||
@@ -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,26 @@ 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
|
||||
'''
|
||||
an example:
|
||||
"rutracker" : {
|
||||
"name" : "rutracker", # name of the tracker
|
||||
"headers" : ["author", "title"], # keys shown in the table
|
||||
"scrapeFunc" : function,
|
||||
}
|
||||
'''
|
||||
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,7 +36,6 @@ 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 = []
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
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.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, posts, post_titles):
|
||||
def download_selected(items: list[QTableWidgetItem]):
|
||||
if not items:
|
||||
consoleLog("No item selected for download.")
|
||||
return
|
||||
@@ -17,22 +21,55 @@ 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=(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):
|
||||
|
||||
Reference in New Issue
Block a user