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:
+3
-220
@@ -1,220 +1,3 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# Created by venv; see https://docs.python.org/3/library/venv.html
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[codz]
|
*.exe
|
||||||
*$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
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ plyer==2.1.0
|
|||||||
psutil==7.1.0
|
psutil==7.1.0
|
||||||
libtorrent==2.0.11
|
libtorrent==2.0.11
|
||||||
libtorrent-windows-dll==0.0.3
|
libtorrent-windows-dll==0.0.3
|
||||||
|
fuzzyfinder==2.3.0
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import requests
|
import requests
|
||||||
import time
|
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 = {
|
cache = {
|
||||||
"data": [],
|
"data": [],
|
||||||
@@ -31,7 +35,7 @@ def _get_telegram_posts():
|
|||||||
|
|
||||||
for bubble in bubbles:
|
for bubble in bubbles:
|
||||||
post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text")
|
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
|
continue
|
||||||
|
|
||||||
title = post_txt.b.text
|
title = post_txt.b.text
|
||||||
@@ -42,9 +46,9 @@ def _get_telegram_posts():
|
|||||||
if post_url not in added:
|
if post_url not in added:
|
||||||
added.add(post_url)
|
added.add(post_url)
|
||||||
posts.append(dict(
|
posts.append(dict(
|
||||||
|
title=title,
|
||||||
author="m0nkrus",
|
author="m0nkrus",
|
||||||
id=len(posts) + 1,
|
id=len(posts) + 1,
|
||||||
title=title,
|
|
||||||
url=post_url
|
url=post_url
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -67,4 +71,19 @@ def scrape_m0nkrus(query):
|
|||||||
filtered_post["id"] = len(filtered_posts) + 1
|
filtered_post["id"] = len(filtered_posts) + 1
|
||||||
filtered_posts.append(filtered_post)
|
filtered_posts.append(filtered_post)
|
||||||
|
|
||||||
return filtered_posts
|
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
|
import requests
|
||||||
from core.utils.data.state import state
|
from core.utils.data.state import state
|
||||||
from core.utils.logging.logs import consoleLog
|
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):
|
def scrape_rutracker(query):
|
||||||
search = requests.get(f"{state.api_url}/search?q={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:
|
if search:
|
||||||
try:
|
_, data, _, _, _ = split_data(search.text)
|
||||||
return search.text
|
|
||||||
except Exception:
|
|
||||||
consoleLog("No results found / No response from server")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
consoleLog("[core.data.scrapers.rutracker] No search Text, returning nothing")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# This function utilizes the SoftwareManager server - source code can be found under the SoftwareManager-Server repository.
|
def get_magnet(post: Dict):
|
||||||
|
_, post_links, _, _, _ = format_data([post])
|
||||||
|
return get_magnet_link(post_links[0])
|
||||||
|
|
||||||
|
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 bs4 import BeautifulSoup
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from core.utils.logging.logs import consoleLog
|
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):
|
def scrape_uztracker(query):
|
||||||
base_url="https://uztracker.net/"
|
base_url="https://uztracker.net/"
|
||||||
@@ -26,12 +30,27 @@ def scrape_uztracker(query):
|
|||||||
|
|
||||||
posts.append(dict(
|
posts.append(dict(
|
||||||
title=title,
|
title=title,
|
||||||
|
author=author,
|
||||||
url=url,
|
url=url,
|
||||||
author=author
|
|
||||||
))
|
))
|
||||||
|
|
||||||
return posts
|
return posts
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
consoleLog(f"Failed to fetch {search_url}: {e}")
|
consoleLog(f"Failed to fetch {search_url}: {e}")
|
||||||
return None
|
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,
|
QHeaderView,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QTableWidget,
|
QTableWidget,
|
||||||
|
QTableWidgetItem,
|
||||||
QTextEdit,
|
QTextEdit,
|
||||||
QStyledItemDelegate,
|
QStyledItemDelegate,
|
||||||
QStatusBar
|
QStatusBar
|
||||||
@@ -189,15 +190,24 @@ def download_update(latest_version):
|
|||||||
def windowCloseHelper():
|
def windowCloseHelper():
|
||||||
QGuiApplication.quit()
|
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):
|
class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||||
log_signal = Signal(str) # Thread-safe signal for logging
|
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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
MainWindow._instance = self
|
MainWindow._instance = self
|
||||||
self.log_signal.connect(self._on_log_signal)
|
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()
|
pixmap = QPixmap()
|
||||||
image_data = base64.b64decode(logo_base64)
|
image_data = base64.b64decode(logo_base64)
|
||||||
@@ -273,68 +283,30 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
flush_log_buffer()
|
flush_log_buffer()
|
||||||
|
|
||||||
class TrackerHoverDelegate(QStyledItemDelegate):
|
# create initial tracker table
|
||||||
def paint(self, painter, option, index):
|
state.trackertable = QTableWidget()
|
||||||
if index.row() == MainWindow._instance._tracker_hovered_row:
|
state.trackertable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||||
painter.save()
|
state.trackertable.verticalHeader().setVisible(False)
|
||||||
painter.fillRect(option.rect, _theme_colors()["hover"])
|
state.trackertable.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
painter.restore()
|
state.trackertable.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
super().paint(painter, option, index)
|
model = state.trackertable.model()
|
||||||
|
model.dataChanged.disconnect()
|
||||||
|
state.trackertable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
|
||||||
self._tracker_hover_delegate = TrackerHoverDelegate(self)
|
state.trackertable.setStyleSheet(_table_stylesheet("QTableWidget"))
|
||||||
|
|
||||||
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()
|
state.trackertable.setItemDelegate(TrackerHoverDelegate(state.trackertable))
|
||||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
state.trackertable.viewport().installEventFilter(self)
|
||||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
state.trackertable.setMouseTracking(True)
|
||||||
header.resizeSection(1, 500)
|
state.trackertable.viewport().setMouseTracking(True)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
container = QWidget()
|
container = QWidget()
|
||||||
containerLayout = QVBoxLayout()
|
containerLayout = QVBoxLayout()
|
||||||
|
containerLayout.addWidget(self.searchbar)
|
||||||
|
containerLayout.addWidget(state.trackertable)
|
||||||
search_row = QHBoxLayout()
|
search_row = QHBoxLayout()
|
||||||
search_row.addWidget(self.searchbar)
|
search_row.addWidget(self.searchbar)
|
||||||
containerLayout.addLayout(search_row)
|
|
||||||
containerLayout.addWidget(state.tracker_list[state.tracker])
|
|
||||||
|
|
||||||
class DownloadModel(QAbstractTableModel):
|
class DownloadModel(QAbstractTableModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -568,8 +540,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.downloadList.setItemDelegateForColumn(0, delegate)
|
self.downloadList.setItemDelegateForColumn(0, delegate)
|
||||||
delegate.clicked.connect(on_pause_resume_clicked)
|
delegate.clicked.connect(on_pause_resume_clicked)
|
||||||
|
|
||||||
# download button triggers
|
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),))))
|
||||||
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].selectedItems(), state.posts, state.post_titles))))
|
|
||||||
|
|
||||||
container.setLayout(containerLayout)
|
container.setLayout(containerLayout)
|
||||||
self.setCentralWidget(container)
|
self.setCentralWidget(container)
|
||||||
@@ -589,10 +560,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
self.horizontal_layout = QHBoxLayout()
|
self.horizontal_layout = QHBoxLayout()
|
||||||
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
||||||
self.tracker_widget = state.tracker_list[state.tracker]
|
self.horizontal_layout.addWidget(state.trackertable)
|
||||||
self.horizontal_layout.addWidget(self.tracker_widget)
|
|
||||||
|
|
||||||
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.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)
|
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 = 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.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.tracker_list.activated.connect(self.set_tracker)
|
self.tracker_list.activated.connect(self.set_tracker)
|
||||||
self.corner_layout.addWidget(self.tracker_list)
|
self.corner_layout.addWidget(self.tracker_list)
|
||||||
@@ -705,16 +675,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
def _start_search(self):
|
def _start_search(self):
|
||||||
self.searchbar.setEnabled(False)
|
self.searchbar.setEnabled(False)
|
||||||
def _search_thread():
|
def _search_thread():
|
||||||
try:
|
return_pressed(self)
|
||||||
return_pressed(self)
|
|
||||||
finally:
|
|
||||||
self.search_finished_signal.emit()
|
|
||||||
run_thread(threading.Thread(target=_search_thread))
|
run_thread(threading.Thread(target=_search_thread))
|
||||||
|
|
||||||
def _on_search_finished(self):
|
|
||||||
self.searchbar.setEnabled(True)
|
|
||||||
self.searchbar.setFocus()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_log(text):
|
def add_log(text):
|
||||||
if MainWindow._instance:
|
if MainWindow._instance:
|
||||||
@@ -727,6 +690,28 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.consoleLog.verticalScrollBar().maximum()
|
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):
|
def update_image_overlay(self, new_image_path):
|
||||||
self.image = QImage(new_image_path)
|
self.image = QImage(new_image_path)
|
||||||
self.pixmap = QPixmap.fromImage(self.image)
|
self.pixmap = QPixmap.fromImage(self.image)
|
||||||
@@ -784,9 +769,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
self.rutrackerlist.clearSelection()
|
state.trackertable.clearSelection()
|
||||||
self.uztrackerlist.clearSelection()
|
|
||||||
self.monkruslist.clearSelection()
|
|
||||||
self.downloadList.clearSelection()
|
self.downloadList.clearSelection()
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
@@ -801,21 +784,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
def eventFilter(self, obj, event):
|
def eventFilter(self, obj, event):
|
||||||
try:
|
try:
|
||||||
for tbl in (self.rutrackerlist, self.uztrackerlist, self.monkruslist):
|
if obj == state.trackertable.viewport():
|
||||||
if obj == tbl.viewport():
|
if event.type() == QEvent.Type.MouseMove:
|
||||||
if event.type() == QEvent.Type.MouseMove:
|
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
||||||
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
idx = state.trackertable.indexAt(pos)
|
||||||
idx = tbl.indexAt(pos)
|
new_row = idx.row() if idx.isValid() else -1
|
||||||
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:
|
||||||
if new_row != self._tracker_hovered_row or tbl is not self._tracker_hovered_table:
|
self._tracker_hovered_row = new_row
|
||||||
self._tracker_hovered_row = new_row
|
self._tracker_hovered_table = state.trackertable
|
||||||
self._tracker_hovered_table = tbl
|
state.trackertable.viewport().update()
|
||||||
tbl.viewport().update()
|
elif event.type() == QEvent.Type.Leave:
|
||||||
elif event.type() == QEvent.Type.Leave:
|
self._tracker_hovered_row = -1
|
||||||
self._tracker_hovered_row = -1
|
self._tracker_hovered_table = None
|
||||||
self._tracker_hovered_table = None
|
state.trackertable.viewport().update()
|
||||||
tbl.viewport().update()
|
|
||||||
break
|
|
||||||
if obj == self.downloadList.viewport():
|
if obj == self.downloadList.viewport():
|
||||||
if event.type() == QEvent.Type.MouseMove:
|
if event.type() == QEvent.Type.MouseMove:
|
||||||
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
|
||||||
@@ -839,22 +820,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.downloadList.viewport().update()
|
self.downloadList.viewport().update()
|
||||||
|
|
||||||
def set_tracker(self, _):
|
def set_tracker(self, _):
|
||||||
old_tracker = state.tracker
|
state.currenttracker = self.tracker_list.currentText()
|
||||||
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)
|
|
||||||
|
|
||||||
def show_empty_results(self, show: bool):
|
def show_empty_results(self, show: bool):
|
||||||
if show:
|
if show:
|
||||||
state.tracker_list[state.tracker].hide()
|
state.trackertable.hide()
|
||||||
self.emptyResults.show()
|
self.emptyResults.show()
|
||||||
else:
|
else:
|
||||||
state.tracker_list[state.tracker].show()
|
state.trackertable.show()
|
||||||
self.emptyResults.hide()
|
self.emptyResults.hide()
|
||||||
|
|
||||||
def show_empty_downloads(self):
|
def show_empty_downloads(self):
|
||||||
@@ -865,11 +838,28 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.emptyDownload.show()
|
self.emptyDownload.show()
|
||||||
self.downloadList.hide()
|
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):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
table_width = state.tracker_list[state.tracker].viewport().width()
|
self._resize_tracker_columns()
|
||||||
state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3))
|
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QContextMenuEvent):
|
def contextMenuEvent(self, event: QContextMenuEvent):
|
||||||
if self.downloadList.underMouse():
|
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.utils.logging.logs import consoleLog
|
||||||
from core.data.scrapers.uztracker import scrape_uztracker
|
from core.data.scrapers.rutracker import init_rutracker
|
||||||
from core.data.scrapers.rutracker import scrape_rutracker
|
from core.data.scrapers.uztracker import init_uztracker
|
||||||
from core.data.scrapers.monkrus import scrape_m0nkrus
|
from core.data.scrapers.monkrus import init_m0nkrus
|
||||||
from core.utils.network.jsonhandler import split_data, format_data, format_data_minimal
|
from core.data.scrapers.steamrip import init_steamrip
|
||||||
from core.utils.data.state import state
|
from core.utils.data.state import state
|
||||||
|
|
||||||
scrapers = {
|
init_rutracker()
|
||||||
"uztracker": scrape_uztracker,
|
init_uztracker()
|
||||||
"rutracker": scrape_rutracker,
|
init_m0nkrus()
|
||||||
"m0nkrus": scrape_m0nkrus
|
init_steamrip()
|
||||||
}
|
|
||||||
|
|
||||||
def return_pressed(self):
|
def return_pressed(self):
|
||||||
|
self.show_empty_results(False)
|
||||||
search_text = self.searchbar.text()
|
search_text = self.searchbar.text()
|
||||||
if search_text == "":
|
if search_text == "":
|
||||||
consoleLog("Error: Can't search for nothing")
|
consoleLog("Error: Can't search for nothing")
|
||||||
return
|
return
|
||||||
consoleLog(f"User searched for: {search_text}")
|
consoleLog(f"User searched for: {search_text}")
|
||||||
|
|
||||||
if state.tracker == "rutracker":
|
tracker = state.trackers[state.currenttracker]
|
||||||
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)
|
|
||||||
|
|
||||||
elif state.tracker is not None:
|
scrapefunc: function = tracker["scrapeFunc"]
|
||||||
state.posts = scrapers[state.tracker](search_text)
|
|
||||||
if not state.posts:
|
state.posts = scrapefunc(search_text)
|
||||||
consoleLog(f"No Results for {search_text}")
|
|
||||||
state.tracker_list[state.tracker].clear()
|
from core.interface.gui import MainWindow
|
||||||
self.show_empty_results(True)
|
MainWindow._instance.search_results_signal.emit(tracker["headers"])
|
||||||
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))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from PySide6.QtCore import QObject, Signal
|
from PySide6.QtCore import QObject, Signal
|
||||||
from PySide6.QtWidgets import QTableWidget
|
from PySide6.QtWidgets import QTableWidget
|
||||||
from typing import Optional, Any, List, Dict
|
from typing import Any, List, Dict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
class AppState(QObject):
|
class AppState(QObject):
|
||||||
@@ -8,20 +8,26 @@ class AppState(QObject):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.posts: list[Any] | None = None
|
self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seederm leecher
|
||||||
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.version: str = "dev"
|
self.version: str = "dev"
|
||||||
self._image_path: str = ""
|
self._image_path: str = ""
|
||||||
self.ignore_updates: bool = False
|
self.ignore_updates: bool = False
|
||||||
self.debug: bool = False
|
self.debug: bool = False
|
||||||
self.autoresume: bool = True
|
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.api_url: str = "https://api.michijackson.xyz"
|
||||||
|
self.seeded_magnets: set = set()
|
||||||
self.download_path: str = str(Path.home() / "Downloads")
|
self.download_path: str = str(Path.home() / "Downloads")
|
||||||
self.up_speed_limit: int = 0
|
self.up_speed_limit: int = 0
|
||||||
self.down_speed_limit: int = 0
|
self.down_speed_limit: int = 0
|
||||||
@@ -30,7 +36,6 @@ class AppState(QObject):
|
|||||||
self.settings_path: str = ""
|
self.settings_path: str = ""
|
||||||
self.dl_session: Any = None
|
self.dl_session: Any = None
|
||||||
self.active_downloads: Dict = {}
|
self.active_downloads: Dict = {}
|
||||||
self.seeded_magnets: set = set()
|
|
||||||
self.window_transparency: bool = False
|
self.window_transparency: bool = False
|
||||||
self.interfaces: List = []
|
self.interfaces: List = []
|
||||||
self.active_interfaces: List = []
|
self.active_interfaces: List = []
|
||||||
|
|||||||
@@ -1,31 +1,6 @@
|
|||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from core.utils.data.state import state
|
|
||||||
from core.utils.logging.logs import consoleLog
|
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):
|
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.logging.logs import consoleLog
|
||||||
from core.utils.data.tracker import get_item_url
|
from core.network.libtorrent_wrapper import add_magnet
|
||||||
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.general.wrappers import run_thread
|
||||||
from core.utils.logging.logs import add_download_log
|
from core.utils.logging.logs import add_download_log
|
||||||
from core.network.libtorrent_int import add_seed
|
from core.network.libtorrent_int import add_seed
|
||||||
|
from core.utils.data.state import state
|
||||||
|
from urllib.parse import urlparse, unquote
|
||||||
import threading
|
import threading
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
def download_selected(items, posts, post_titles):
|
def download_selected(items: list[QTableWidgetItem]):
|
||||||
if not items:
|
if not items:
|
||||||
consoleLog("No item selected for download.")
|
consoleLog("No item selected for download.")
|
||||||
return
|
return
|
||||||
@@ -17,24 +21,57 @@ def download_selected(items, posts, post_titles):
|
|||||||
if item.column() != 0:
|
if item.column() != 0:
|
||||||
continue
|
continue
|
||||||
text = item.text()
|
text = item.text()
|
||||||
if text and text not in seen:
|
if text != "" and text not in seen:
|
||||||
seen.add(text)
|
seen.add(text)
|
||||||
consoleLog(f"Downloading {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):
|
def get_direct_filename(url: str, headers) -> str:
|
||||||
post_url = get_item_url(item, posts, post_titles)
|
cd = headers.get('content-disposition', '')
|
||||||
consoleLog(f"Selected URL: {post_url}")
|
if cd:
|
||||||
magnet_uri = get_magnet_link(post_url)
|
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):
|
path = urlparse(url).path
|
||||||
add_download_log(item, post_url, magnet_uri, False)
|
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):
|
def run_download_direct(magnet_uri):
|
||||||
consoleLog(f"Direct download: {magnet_uri[:60]}")
|
consoleLog(f"Direct download: {magnet_uri[:60]}")
|
||||||
add_download(magnet_uri)
|
add_magnet(magnet_uri)
|
||||||
add_download_log("Direct Download", "", magnet_uri, False)
|
add_download_log("Direct Download", "", magnet_uri, False)
|
||||||
|
|
||||||
def seed_magnet(magnet_uri, file_path):
|
def seed_magnet(magnet_uri, file_path):
|
||||||
consoleLog(f"Seeding: {magnet_uri[:60]}")
|
consoleLog(f"Seeding: {magnet_uri[:60]}")
|
||||||
add_seed(magnet_uri, file_path)
|
add_seed(magnet_uri, file_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user