mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4b7067484 | |||
| 77c2e9cd9e | |||
| 290388a7dc | |||
| 3b51552313 | |||
| 9e7607dd36 | |||
| 616552063d | |||
| 9eb70af1a1 | |||
| 4d4187ce63 | |||
| cac2ad3ab1 | |||
| cdbf9165d2 | |||
| 23e44cb128 | |||
| 0a591e1cda | |||
| c0f5060451 | |||
| c6940f0178 | |||
| 2afb3e7716 | |||
| 325eda54fa | |||
| 53d4a240a0 | |||
| a56969f19d | |||
| 2beabbb1ba |
@@ -106,7 +106,7 @@ jobs:
|
||||
run: |
|
||||
python -c "
|
||||
from PIL import Image
|
||||
img = Image.open('src/core/interface/assets/logo.png')
|
||||
img = Image.open('src/interface/assets/logo.png')
|
||||
img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])
|
||||
print('Icon converted successfully')
|
||||
"
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
name: Build Executables (Test)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
APP_NAME: "SoftwareManager"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false # keep other platforms building even if one fails
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-linux
|
||||
- os: windows-latest
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-windows
|
||||
- os: macos-latest
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-macos
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.9.7"
|
||||
enable-cache: true
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install Pillow
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: 1
|
||||
|
||||
- name: Install Nuitka (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
run: uv pip install nuitka
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: 1
|
||||
|
||||
- name: Generate version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
SHORT_SHA="${{ github.sha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
BRANCH="${{ github.head_ref || github.ref_name }}"
|
||||
BRANCH_SLUG="${BRANCH//\//-}" # replace slashes with dashes
|
||||
VERSION="${BRANCH_SLUG}-${SHORT_SHA}-test"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "Generated version: $VERSION"
|
||||
|
||||
- name: Inject build info
|
||||
shell: bash
|
||||
run: |
|
||||
cat > build_info.json << EOF
|
||||
{
|
||||
"version": "${{ env.VERSION }}"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Install UPX (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sL https://github.com/upx/upx/releases/download/v5.1.0/upx-5.1.0-win64.zip -o upx.zip
|
||||
7z x upx.zip -oupx-dir
|
||||
UPX_DIR="$(pwd)/upx-dir/upx-5.1.0-win64"
|
||||
echo "$UPX_DIR" >> $GITHUB_PATH
|
||||
export PATH="$UPX_DIR:$PATH"
|
||||
echo "UPX_BINARY=$UPX_DIR/upx.exe" >> $GITHUB_ENV
|
||||
upx --version | head -1
|
||||
|
||||
- name: Convert icon for Windows
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "
|
||||
from PIL import Image
|
||||
img = Image.open('src/interface/assets/logo.png')
|
||||
img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])
|
||||
print('Icon converted successfully')
|
||||
"
|
||||
|
||||
- name: Build with Nuitka (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
python -m nuitka \
|
||||
--standalone \
|
||||
--assume-yes-for-downloads \
|
||||
--windows-console-mode=disable \
|
||||
--windows-icon-from-ico=app_icon.ico \
|
||||
--enable-plugin=pyside6 \
|
||||
--enable-plugin=upx \
|
||||
--upx-binary="${{ env.UPX_BINARY }}" \
|
||||
--noinclude-qt-translations \
|
||||
--include-data-files=build_info.json=build_info.json \
|
||||
--output-filename=${{ env.APP_NAME }}.exe \
|
||||
--output-dir=nuitka-build \
|
||||
src/main.py
|
||||
|
||||
- name: Build with PyInstaller (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
pyinstaller --onefile --windowed \
|
||||
--add-data "build_info.json:." \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
--hidden-import=backports \
|
||||
--hidden-import=jaraco.context \
|
||||
--hidden-import=jaraco.text \
|
||||
--hidden-import=jaraco.functools \
|
||||
--name ${{ env.APP_NAME }} \
|
||||
src/main.py
|
||||
|
||||
- name: Build with PyInstaller (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
pyinstaller --onefile \
|
||||
--add-data "build_info.json:." \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
--hidden-import=backports \
|
||||
--hidden-import=jaraco.context \
|
||||
--hidden-import=jaraco.text \
|
||||
--hidden-import=jaraco.functools \
|
||||
--name ${{ env.APP_NAME }} \
|
||||
src/main.py
|
||||
|
||||
- name: Stage artifact
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist-final
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }}
|
||||
else
|
||||
chmod +x dist/${{ env.APP_NAME }}
|
||||
mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }}
|
||||
fi
|
||||
ls -la dist-final/
|
||||
|
||||
- name: Create ZIP (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "dist-final/${{ env.APP_NAME }}/*" -DestinationPath "dist-final/${{ matrix.artifact_name }}.zip"
|
||||
|
||||
- name: Upload artifact (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-build
|
||||
path: dist-final/${{ matrix.artifact_name }}.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload artifact (Linux/macOS)
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-build
|
||||
path: dist-final/${{ matrix.artifact_name }}
|
||||
retention-days: 7
|
||||
@@ -62,7 +62,6 @@ SoftwareManager is a Python-based GUI tool that simplifies searching and downloa
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=KeksPirates/SoftwareManager&type=date&legend=top-left" />
|
||||
</picture>
|
||||
</a>
|
||||
---
|
||||
|
||||
**Disclaimer:** SoftwareManager is intended for legal and ethical use only. Ensure compliance with applicable laws and regulations when using this tool.
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
from core.utils.data.tracker import get_magnet_link
|
||||
from core.utils.data.state import state
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import requests
|
||||
import time
|
||||
|
||||
cache = {
|
||||
"data": [],
|
||||
"last_fetched": 0
|
||||
}
|
||||
|
||||
cache_expiry = 300
|
||||
|
||||
def _get_telegram_posts():
|
||||
post = 100
|
||||
max_posts = 250
|
||||
posts = []
|
||||
added = set()
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
if cache["data"] and (current_time - cache["last_fetched"] < cache_expiry):
|
||||
return cache["data"]
|
||||
|
||||
while post <= max_posts:
|
||||
url = f"https://t.me/s/real_monkrus/{post}"
|
||||
response = requests.get(url, timeout=10)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
|
||||
|
||||
if not bubbles:
|
||||
break
|
||||
|
||||
for bubble in bubbles:
|
||||
post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text")
|
||||
if post_txt is None or post_txt.b is None:
|
||||
continue
|
||||
|
||||
title = post_txt.b.text
|
||||
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
|
||||
|
||||
if link:
|
||||
post_url = link["href"]
|
||||
if post_url not in added:
|
||||
added.add(post_url)
|
||||
posts.append(dict(
|
||||
title=title,
|
||||
author="m0nkrus",
|
||||
id=len(posts) + 1,
|
||||
url=post_url
|
||||
))
|
||||
|
||||
post += 22
|
||||
|
||||
posts.reverse()
|
||||
|
||||
cache["data"] = posts
|
||||
cache["last_fetched"] = current_time
|
||||
|
||||
return(posts)
|
||||
|
||||
def scrape_m0nkrus(query):
|
||||
posts = _get_telegram_posts()
|
||||
filtered_posts = []
|
||||
|
||||
for post in posts:
|
||||
if query.lower() in post["title"].lower():
|
||||
filtered_post = post.copy()
|
||||
filtered_post["id"] = len(filtered_posts) + 1
|
||||
filtered_posts.append(filtered_post)
|
||||
|
||||
return filtered_posts
|
||||
|
||||
def get_magnet(post: Dict):
|
||||
return get_magnet_link(post["url"])
|
||||
|
||||
Metadata = {
|
||||
"name" : "m0nkrus",
|
||||
"headers" : ["Post Title", "Author"],
|
||||
"scrapeFunc" : scrape_m0nkrus,
|
||||
"linkFunc" : get_magnet,
|
||||
"isMagnet" : True,
|
||||
}
|
||||
|
||||
def init_m0nkrus():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
@@ -1,52 +0,0 @@
|
||||
from core.utils.network.jsonhandler import split_data, format_data
|
||||
from core.utils.data.tracker import get_magnet_link
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from typing import Dict
|
||||
import requests
|
||||
|
||||
def scrape_rutracker(query):
|
||||
search = requests.get(f"{state.api_url}/search?q={query}", timeout=15)
|
||||
consoleLog("Sent request to server")
|
||||
if search:
|
||||
_, data, _, success, cached = split_data(search.text)
|
||||
if cached:
|
||||
consoleLog("Server response cached")
|
||||
if success:
|
||||
sorted_data = []
|
||||
|
||||
for entry in data:
|
||||
sorted_data.append(
|
||||
{
|
||||
"title" : entry["title"],
|
||||
"author" : entry["author"],
|
||||
"seeders" : entry["seeders"],
|
||||
"leechers" : entry["leechers"],
|
||||
"url" : entry["url"],
|
||||
"id" : entry["id"],
|
||||
}
|
||||
)
|
||||
|
||||
return sorted_data
|
||||
else:
|
||||
consoleLog("Scraping failed server-side, unable to fetch posts from rutracker")
|
||||
return []
|
||||
else:
|
||||
consoleLog("No response from server, returning nothing")
|
||||
return []
|
||||
|
||||
|
||||
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})
|
||||
@@ -1,122 +0,0 @@
|
||||
from core.data.scrapers.provider.buzzheavier import scrape_buzzheavier
|
||||
from core.data.scrapers.provider.gofile import scrape_gofile
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import webbrowser
|
||||
import requests
|
||||
import time
|
||||
import re
|
||||
|
||||
|
||||
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)):
|
||||
ret.append({"title" : names[i], "url" : 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"]
|
||||
|
||||
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
|
||||
|
||||
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["url"]
|
||||
links = scrape_steamrip_game_downloads(url)
|
||||
best = ""
|
||||
for link in links:
|
||||
if link[0] == "h":
|
||||
best = link
|
||||
break
|
||||
|
||||
if links.index(best) == 0:
|
||||
link = scrape_buzzheavier(best)
|
||||
return link
|
||||
elif links.index(best) == 1:
|
||||
link, headers = scrape_gofile(best)
|
||||
return link, headers
|
||||
else:
|
||||
consoleLog("Unable to retrieve download link due to captcha, launching browser...")
|
||||
webbrowser.open(best)
|
||||
return None, None
|
||||
|
||||
def filter_steamrip(query: str):
|
||||
games = scrape_steamrip_links()
|
||||
|
||||
filtered_games = [
|
||||
game for game in games
|
||||
if query.lower() in game["title"].lower()
|
||||
]
|
||||
|
||||
return filtered_games
|
||||
|
||||
Metadata = {
|
||||
"name" : "steamrip",
|
||||
"headers" : ["Game"],
|
||||
"scrapeFunc" : filter_steamrip,
|
||||
"linkFunc" : get_download_link,
|
||||
"isMagnet" : False,
|
||||
}
|
||||
|
||||
def init_steamrip():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
@@ -1,55 +0,0 @@
|
||||
from core.utils.data.tracker import get_magnet_link
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from urllib.parse import urljoin
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import requests
|
||||
|
||||
|
||||
def scrape_uztracker(query):
|
||||
base_url="https://uztracker.net/"
|
||||
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}"
|
||||
consoleLog(search_url)
|
||||
posts = []
|
||||
|
||||
try:
|
||||
response = requests.get(search_url)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
|
||||
for link in links:
|
||||
|
||||
theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
|
||||
if not theme_link or not theme_link.b:
|
||||
continue
|
||||
url = urljoin(base_url, theme_link['href'])
|
||||
title = theme_link.b.text
|
||||
author_link = link.find('a', class_="med")
|
||||
author = author_link.text.strip() if author_link else "Unknown"
|
||||
|
||||
posts.append(dict(
|
||||
title=title,
|
||||
author=author,
|
||||
url=url,
|
||||
))
|
||||
|
||||
return posts
|
||||
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"Failed to fetch {search_url}: {e}")
|
||||
return None
|
||||
|
||||
def get_magnet(post: Dict):
|
||||
return get_magnet_link(post["url"])
|
||||
|
||||
Metadata = {
|
||||
"name" : "uztracker",
|
||||
"headers" : ["Post Title", "Author"],
|
||||
"scrapeFunc" : scrape_uztracker,
|
||||
"linkFunc" : get_magnet,
|
||||
"isMagnet" : True,
|
||||
}
|
||||
|
||||
def init_uztracker():
|
||||
state.trackers.update({Metadata["name"] : Metadata})
|
||||
@@ -1,26 +0,0 @@
|
||||
from core.data.scrapers.rutracker import init_rutracker
|
||||
from core.data.scrapers.uztracker import init_uztracker
|
||||
from core.data.scrapers.steamrip import init_steamrip
|
||||
from core.data.scrapers.monkrus import init_m0nkrus
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
|
||||
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}")
|
||||
|
||||
tracker = state.trackers[state.currenttracker]
|
||||
scrapefunc = tracker["scrapeFunc"]
|
||||
state.posts = scrapefunc(search_text)
|
||||
|
||||
from core.interface.gui import MainWindow
|
||||
MainWindow._instance.search_results_signal.emit(tracker["headers"])
|
||||
@@ -1,56 +0,0 @@
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
||||
|
||||
|
||||
def create_tab(title, searchbar, software_list, tabs, dlbutton, layout2):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
if searchbar:
|
||||
layout.addWidget(searchbar)
|
||||
if layout2:
|
||||
layout.addLayout(layout2)
|
||||
else:
|
||||
layout.addWidget(software_list)
|
||||
if dlbutton:
|
||||
layout.addWidget(dlbutton)
|
||||
tab.setLayout(layout)
|
||||
tabs.addTab(tab, title)
|
||||
return tab
|
||||
|
||||
#################
|
||||
# SETTINGS TABS #
|
||||
#################
|
||||
def general_tab(title, autoresume, update_checkbox, transparent_window, tabs):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(autoresume)
|
||||
layout.addWidget(update_checkbox)
|
||||
layout.addWidget(transparent_window)
|
||||
layout.addStretch()
|
||||
tab.setLayout(layout)
|
||||
tabs.addTab(tab, title)
|
||||
return tab
|
||||
|
||||
def paths_tab(title, download_path, image_path, tabs):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(download_path)
|
||||
layout.addWidget(image_path)
|
||||
layout.addStretch()
|
||||
tab.setLayout(layout)
|
||||
tabs.addTab(tab, title)
|
||||
return tab
|
||||
|
||||
|
||||
def network_tab(title, network_interface, max_connections, max_downloads, up_speed_limit, down_speed_limit, api_url, tabs):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(network_interface)
|
||||
layout.addWidget(max_connections)
|
||||
layout.addWidget(max_downloads)
|
||||
layout.addWidget(up_speed_limit)
|
||||
layout.addWidget(down_speed_limit)
|
||||
layout.addWidget(api_url)
|
||||
layout.addStretch()
|
||||
tab.setLayout(layout)
|
||||
tabs.addTab(tab, title)
|
||||
return tab
|
||||
@@ -1,4 +1,4 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from utils.logging.logs import consoleLog
|
||||
import requests
|
||||
import re
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from data.sources.rutracker import RutrackerScraper
|
||||
from data.sources.uztracker import UztrackerScraper
|
||||
from data.sources.steamrip import SteamripScraper
|
||||
from data.sources.monkrus import MonkrusScraper
|
||||
|
||||
# Register Scrapers here
|
||||
SCRAPERS = [
|
||||
RutrackerScraper(),
|
||||
SteamripScraper(),
|
||||
MonkrusScraper(),
|
||||
UztrackerScraper(),
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
from utils.data.tracker import get_magnet_link
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
class MonkrusScraper:
|
||||
name = "m0nkrus"
|
||||
headers = ["Post Title", "Author"]
|
||||
is_magnet = True
|
||||
|
||||
def __init__(self):
|
||||
self.cache = { "data": [], "last_fetched": 0 }
|
||||
self.cache_expiry = 300
|
||||
|
||||
def _get_telegram_posts(self):
|
||||
post = 100
|
||||
max_posts = 250
|
||||
posts = []
|
||||
added = set()
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
if self.cache["data"] and (current_time - self.cache["last_fetched"] < self.cache_expiry):
|
||||
return self.cache["data"]
|
||||
|
||||
while post <= max_posts:
|
||||
url = f"https://t.me/s/real_monkrus/{post}"
|
||||
response = requests.get(url, timeout=10)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
|
||||
|
||||
if not bubbles:
|
||||
break
|
||||
|
||||
for bubble in bubbles:
|
||||
post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text")
|
||||
if post_txt is None or post_txt.b is None:
|
||||
continue
|
||||
|
||||
title = post_txt.b.text
|
||||
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
|
||||
|
||||
if link:
|
||||
post_url = link["href"]
|
||||
if post_url not in added:
|
||||
added.add(post_url)
|
||||
posts.append(dict(
|
||||
title=title,
|
||||
author="m0nkrus",
|
||||
id=len(posts) + 1,
|
||||
url=post_url
|
||||
))
|
||||
|
||||
post += 22
|
||||
|
||||
posts.reverse()
|
||||
|
||||
self.cache["data"] = posts
|
||||
self.cache["last_fetched"] = current_time
|
||||
|
||||
return(posts)
|
||||
|
||||
def search(self, query):
|
||||
posts = self._get_telegram_posts()
|
||||
filtered_posts = []
|
||||
|
||||
for post in posts:
|
||||
if query.lower() in post["title"].lower():
|
||||
filtered_post = post.copy()
|
||||
filtered_post["id"] = len(filtered_posts) + 1
|
||||
filtered_posts.append(filtered_post)
|
||||
|
||||
return filtered_posts
|
||||
|
||||
def get_download_link(self, post: Dict):
|
||||
return get_magnet_link(post["url"])
|
||||
@@ -0,0 +1,46 @@
|
||||
from utils.network.jsonhandler import split_data, format_data
|
||||
from utils.data.tracker import get_magnet_link
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from typing import Dict
|
||||
import requests
|
||||
|
||||
class RutrackerScraper:
|
||||
name = "rutracker"
|
||||
headers = ["Post Title", "Author", "Seeders", "Leechers"]
|
||||
is_magnet = True
|
||||
|
||||
def search(self, query):
|
||||
search = requests.get(f"{state.api_url}/search?q={query}", timeout=15)
|
||||
consoleLog("Sent request to server")
|
||||
if search:
|
||||
_, data, _, success, cached = split_data(search.text)
|
||||
if cached:
|
||||
consoleLog("Server response cached")
|
||||
if success:
|
||||
sorted_data = []
|
||||
|
||||
for entry in data:
|
||||
sorted_data.append(
|
||||
{
|
||||
"title" : entry["title"],
|
||||
"author" : entry["author"],
|
||||
"seeders" : entry["seeders"],
|
||||
"leechers" : entry["leechers"],
|
||||
"url" : entry["url"],
|
||||
"id" : entry["id"],
|
||||
}
|
||||
)
|
||||
|
||||
return sorted_data
|
||||
else:
|
||||
consoleLog("Scraping failed server-side, unable to fetch posts from rutracker")
|
||||
return []
|
||||
else:
|
||||
consoleLog("No response from server, returning nothing")
|
||||
return []
|
||||
|
||||
def get_download_link(self, post: Dict):
|
||||
_, post_links, _, _, _ = format_data([post])
|
||||
return get_magnet_link(post_links[0])
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
from data.hosts.buzzheavier import scrape_buzzheavier
|
||||
from data.hosts.gofile import scrape_gofile
|
||||
from utils.logging.logs import consoleLog
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import webbrowser
|
||||
import requests
|
||||
import time
|
||||
import re
|
||||
|
||||
class SteamripScraper:
|
||||
name = "steamrip"
|
||||
headers = ["Game"]
|
||||
is_magnet = False
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.cache = { "data": [], "last_fetched": 0 }
|
||||
self.cache_expiry = 300
|
||||
|
||||
def scrape_steamrip_links(self):
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
if self.cache["data"] != [] and (current_time - self.cache["last_fetched"] < self.cache_expiry):
|
||||
return self.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)):
|
||||
ret.append({"title" : names[i], "url" : links[i]})
|
||||
|
||||
return ret
|
||||
|
||||
def scrape_steamrip_game_downloads(self, 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"]
|
||||
|
||||
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
|
||||
|
||||
ret = []
|
||||
|
||||
for link in download_links:
|
||||
if len(link) != 1:
|
||||
ret.append(link)
|
||||
|
||||
self.cache["data"] = ret
|
||||
|
||||
return ret
|
||||
|
||||
def get_download_link(self, post: Dict):
|
||||
url = post["url"]
|
||||
links = self.scrape_steamrip_game_downloads(url)
|
||||
best = ""
|
||||
for link in links:
|
||||
if link[0] == "h":
|
||||
best = link
|
||||
break
|
||||
|
||||
if links.index(best) == 0:
|
||||
link = scrape_buzzheavier(best)
|
||||
return link
|
||||
elif links.index(best) == 1:
|
||||
link, headers = scrape_gofile(best)
|
||||
return link, headers
|
||||
else:
|
||||
consoleLog("Unable to retrieve download link due to captcha, launching browser...")
|
||||
webbrowser.open(best)
|
||||
return None, None
|
||||
|
||||
def search(self, query: str):
|
||||
games = self.scrape_steamrip_links()
|
||||
|
||||
filtered_games = [
|
||||
game for game in games
|
||||
if query.lower() in game["title"].lower()
|
||||
]
|
||||
|
||||
return filtered_games
|
||||
@@ -0,0 +1,50 @@
|
||||
from utils.data.tracker import get_magnet_link
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from urllib.parse import urljoin
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Dict
|
||||
import requests
|
||||
|
||||
class UztrackerScraper:
|
||||
name = "uztracker"
|
||||
headers = ["Post Title", "Author"]
|
||||
is_magnet = True
|
||||
|
||||
|
||||
def search(self, query):
|
||||
base_url="https://uztracker.net/"
|
||||
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}"
|
||||
consoleLog(search_url)
|
||||
posts = []
|
||||
|
||||
try:
|
||||
response = requests.get(search_url)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
|
||||
for link in links:
|
||||
|
||||
theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
|
||||
if not theme_link or not theme_link.b:
|
||||
continue
|
||||
url = urljoin(base_url, theme_link['href'])
|
||||
title = theme_link.b.text
|
||||
author_link = link.find('a', class_="med")
|
||||
author = author_link.text.strip() if author_link else "Unknown"
|
||||
|
||||
posts.append(dict(
|
||||
title=title,
|
||||
author=author,
|
||||
url=url,
|
||||
))
|
||||
|
||||
return posts
|
||||
|
||||
except requests.RequestException as e:
|
||||
consoleLog(f"Failed to fetch {search_url}: {e}")
|
||||
return None
|
||||
|
||||
def get_download_link(self, post: Dict):
|
||||
return get_magnet_link(post["url"])
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -1,7 +1,7 @@
|
||||
from core.utils.logging.logs import consoleLog, remove_download_log
|
||||
from utils.logging.logs import consoleLog, remove_download_log
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from core.utils.data.state import state
|
||||
from PySide6.QtCore import Qt, QPoint
|
||||
from utils.data.state import state
|
||||
from PySide6 import QtWidgets
|
||||
import subprocess
|
||||
import platform
|
||||
@@ -1,9 +1,9 @@
|
||||
from core.interface.dialogs.pauseresumedelegate import PauseResumeDelegate
|
||||
from core.interface.dialogs.hoverrowdelegate import HoverRowDelegate
|
||||
from core.interface.dialogs.downloadmodel import DownloadModel
|
||||
from core.interface.dialogs.theme import _table_stylesheet
|
||||
from interface.dialogs.pauseresumedelegate import PauseResumeDelegate
|
||||
from interface.dialogs.hoverrowdelegate import HoverRowDelegate
|
||||
from interface.dialogs.downloadmodel import DownloadModel
|
||||
from interface.dialogs.theme import _table_stylesheet
|
||||
from PySide6.QtWidgets import QTableView, QHeaderView
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from utils.logging.logs import consoleLog
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
import libtorrent as lt
|
||||
import subprocess
|
||||
import platform
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
from core.interface.dialogs.theme import _theme_colors
|
||||
from interface.dialogs.theme import _theme_colors
|
||||
from PySide6.QtWidgets import QStyledItemDelegate
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from PySide6.QtCore import QEvent
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
from core.interface.dialogs.theme import _theme_colors
|
||||
from interface.dialogs.theme import _theme_colors
|
||||
from PySide6.QtWidgets import QStyledItemDelegate
|
||||
from PySide6 import QtWidgets
|
||||
|
||||
class HoverRowDelegate(QStyledItemDelegate):
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
from core.interface.gui import MainWindow
|
||||
from interface.gui import MainWindow
|
||||
|
||||
opt = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
|
||||
@@ -1,6 +1,6 @@
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from core.utils.data.state import state
|
||||
from PySide6.QtWidgets import QLabel
|
||||
from utils.data.state import state
|
||||
from PySide6.QtCore import Qt
|
||||
import os
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
from core.interface.dialogs.theme import _theme_colors, SVG_PLAY, SVG_PAUSE, SVG_FOLDER
|
||||
from interface.dialogs.theme import _theme_colors, SVG_PLAY, SVG_PAUSE, SVG_FOLDER
|
||||
from PySide6.QtWidgets import QStyledItemDelegate, QWidget, QHBoxLayout
|
||||
from core.interface.utils.svghelper import svg_icon
|
||||
from interface.utils.svghelper import svg_icon
|
||||
from PySide6.QtCore import Qt, QSize, Signal
|
||||
from core.utils.data.state import state
|
||||
from utils.data.state import state
|
||||
from PySide6 import QtWidgets
|
||||
import libtorrent as lt
|
||||
|
||||
@@ -10,7 +10,7 @@ class PauseResumeDelegate(QStyledItemDelegate):
|
||||
clicked = Signal(int)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
from core.interface.gui import MainWindow
|
||||
from interface.gui import MainWindow
|
||||
|
||||
opt = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
|
||||
@@ -1,11 +1,9 @@
|
||||
from core.interface.utils.tabhelper import general_tab
|
||||
from core.interface.utils.tabhelper import network_tab
|
||||
from core.utils.config.settings import save_settings
|
||||
from core.interface.utils.tabhelper import paths_tab
|
||||
from core.interface.utils.svghelper import svg_icon
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from interface.utils.tabhelper import create_tab
|
||||
from utils.config.settings import save_settings
|
||||
from interface.utils.svghelper import svg_icon
|
||||
from utils.logging.logs import consoleLog
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
from utils.data.state import state
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtWidgets import (
|
||||
QLineEdit,
|
||||
@@ -19,7 +17,6 @@ from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QFileDialog,
|
||||
QComboBox,
|
||||
QTabWidget,
|
||||
)
|
||||
|
||||
import platform
|
||||
@@ -46,7 +43,7 @@ def settings_dialog(self):
|
||||
update_checkbox_container = QWidget()
|
||||
update_checkbox_layout = QHBoxLayout()
|
||||
|
||||
# ignore updates checkbox
|
||||
# Ignore updates checkbox
|
||||
|
||||
if platform.system() == "Windows":
|
||||
update_checkbox = QCheckBox()
|
||||
@@ -57,7 +54,7 @@ def settings_dialog(self):
|
||||
update_checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked))
|
||||
update_checkbox_layout.addWidget(update_checkbox)
|
||||
|
||||
# auto-resume downloads checkbox
|
||||
# Auto-resume downloads checkbox
|
||||
|
||||
autoresume_container = QWidget()
|
||||
autoresume_layout = QHBoxLayout()
|
||||
@@ -71,7 +68,7 @@ def settings_dialog(self):
|
||||
autoresume_layout.addWidget(autoresume_checkbox)
|
||||
|
||||
|
||||
# transparent window checkbox
|
||||
# Transparent window checkbox
|
||||
transparent_window_container = QWidget()
|
||||
transparent_window_layout = QHBoxLayout()
|
||||
|
||||
@@ -84,10 +81,8 @@ def settings_dialog(self):
|
||||
transparent_window_checkbox.toggled.connect(lambda checked: setattr(state, 'window_transparency', checked))
|
||||
transparent_window_layout.addWidget(transparent_window_checkbox)
|
||||
|
||||
##################
|
||||
# SERVER SETTING #
|
||||
##################
|
||||
|
||||
# API Server Setting
|
||||
api_url_container = QWidget()
|
||||
api_url_layout = QHBoxLayout()
|
||||
|
||||
@@ -98,10 +93,8 @@ def settings_dialog(self):
|
||||
api_url_container.setLayout(api_url_layout)
|
||||
api_url.setText(state.api_url)
|
||||
|
||||
#################
|
||||
# DOWNLOAD PATH #
|
||||
#################
|
||||
|
||||
# Download Path
|
||||
download_path_container = QWidget()
|
||||
download_path_layout = QHBoxLayout()
|
||||
|
||||
@@ -133,10 +126,8 @@ def settings_dialog(self):
|
||||
download_path_layout.addWidget(browse_button)
|
||||
browse_button.clicked.connect(browse_download_path)
|
||||
|
||||
###############
|
||||
# IMAGE PATH #
|
||||
###############
|
||||
|
||||
# Image Path
|
||||
image_path_container = QWidget()
|
||||
image_path_layout = QHBoxLayout()
|
||||
|
||||
@@ -167,10 +158,8 @@ def settings_dialog(self):
|
||||
image_path_layout.addWidget(browse_button)
|
||||
browse_button.clicked.connect(browse_image_path)
|
||||
|
||||
##################
|
||||
# SPEED LIMITING #
|
||||
##################
|
||||
|
||||
# Speed Limiting
|
||||
down_speed_limit_container = QWidget()
|
||||
down_speed_limit_layout = QHBoxLayout()
|
||||
|
||||
@@ -197,10 +186,8 @@ def settings_dialog(self):
|
||||
up_speed_limit.setFixedWidth(180)
|
||||
up_speed_limit.setFixedHeight(30)
|
||||
|
||||
######################
|
||||
# CONNECTION CONFIGS #
|
||||
######################
|
||||
|
||||
|
||||
# Connection Configs
|
||||
max_connections_container = QWidget()
|
||||
max_connections_layout = QHBoxLayout()
|
||||
|
||||
@@ -214,10 +201,8 @@ def settings_dialog(self):
|
||||
max_connections.setFixedWidth(180)
|
||||
max_connections.setFixedHeight(30)
|
||||
|
||||
####################
|
||||
# DOWNLOAD CONFIGS #
|
||||
####################
|
||||
|
||||
# Download Configs
|
||||
max_downloads_container = QWidget()
|
||||
max_downloads_layout = QHBoxLayout()
|
||||
|
||||
@@ -231,10 +216,8 @@ def settings_dialog(self):
|
||||
max_downloads.setFixedWidth(180)
|
||||
max_downloads.setFixedHeight(30)
|
||||
|
||||
#####################
|
||||
# INTERFACE BINDING #
|
||||
#####################
|
||||
|
||||
# Interface Binding
|
||||
interface_container = QWidget()
|
||||
interface_layout = QHBoxLayout()
|
||||
|
||||
@@ -257,10 +240,7 @@ def settings_dialog(self):
|
||||
interface_container.setLayout(interface_layout)
|
||||
|
||||
|
||||
###############
|
||||
# SAVE/CANCEL #
|
||||
###############
|
||||
|
||||
# Save / Cancel buttons
|
||||
layout = QHBoxLayout()
|
||||
|
||||
save_btn = QPushButton("Save")
|
||||
@@ -281,12 +261,12 @@ def settings_dialog(self):
|
||||
layout.addWidget(cancel_btn)
|
||||
layout.addWidget(save_btn)
|
||||
|
||||
self.tabs = QTabWidget()
|
||||
self.tab1 = general_tab("General", autoresume_container, update_checkbox_container, transparent_window_container, self.tabs)
|
||||
self.tab2 = paths_tab("Paths", download_path_container, image_path_container, self.tabs)
|
||||
self.tab3 = network_tab("Network", interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container, self.tabs)
|
||||
tabs = QtWidgets.QTabWidget()
|
||||
create_tab("General", [autoresume_container, update_checkbox_container, transparent_window_container], tabs=tabs, stretch=True)
|
||||
create_tab("Paths", [download_path_container, image_path_container], tabs=tabs, stretch=True)
|
||||
create_tab("Network", [interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container], tabs=tabs, stretch=True)
|
||||
|
||||
dialog_layout.addWidget(self.tabs)
|
||||
dialog_layout.addWidget(tabs)
|
||||
dialog_layout.addLayout(layout)
|
||||
|
||||
dialog.exec()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
from core.interface.dialogs.theme import _theme_colors
|
||||
from interface.dialogs.theme import _theme_colors
|
||||
from PySide6.QtWidgets import QStyledItemDelegate
|
||||
|
||||
class TrackerHoverDelegate(QStyledItemDelegate):
|
||||
@@ -1,4 +1,4 @@
|
||||
from core.interface.dialogs.theme import _table_stylesheet
|
||||
from interface.dialogs.theme import _table_stylesheet
|
||||
from PySide6.QtWidgets import QTableWidget
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import Qt
|
||||
@@ -1,8 +1,8 @@
|
||||
from core.utils.network.update_checker import get_updates
|
||||
from core.utils.network.updater import download_update
|
||||
from utils.network.update_checker import get_updates
|
||||
from PySide6.QtWidgets import QDialog, QMessageBox
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.network.updater import download_update
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import json
|
||||
@@ -24,7 +24,7 @@ def _get_build_info_path() -> (Path | None):
|
||||
def get_version() -> None:
|
||||
# Get build info filepath
|
||||
build_info_path = _get_build_info_path()
|
||||
if os.path.exists(build_info_path):
|
||||
if build_info_path and os.path.exists(build_info_path):
|
||||
with open(build_info_path, "r") as f:
|
||||
build_info = json.load(f)
|
||||
state.version = build_info.get("version")
|
||||
@@ -46,3 +46,4 @@ class UpdateDialog(QDialog):
|
||||
response = msg.exec_()
|
||||
if response == QMessageBox.StandardButton.Ok:
|
||||
download_update(assets)
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
from core.interface.dialogs.trackerhoverdelegate import TrackerHoverDelegate
|
||||
from core.interface.dialogs.elideditemdelegate import ElidedItemDelegate
|
||||
from core.interface.dialogs.trackertable import _create_tracker_table
|
||||
from core.interface.dialogs.downloadlist import _create_download_list
|
||||
from core.interface.assets.base64_icons import settings_white_base64
|
||||
from core.interface.assets.base64_icons import settings_black_base64
|
||||
from core.interface.dialogs.downloadlist import download_list_update
|
||||
from core.interface.dialogs.update import get_version, UpdateDialog
|
||||
from core.utils.logging.logs import consoleLog, flush_log_buffer
|
||||
from core.interface.dialogs.downloadmodel import DownloadModel
|
||||
from core.interface.utils.searchhelper import return_pressed
|
||||
from core.interface.dialogs.settings import settings_dialog
|
||||
from core.interface.assets.base64_icons import logo_base64
|
||||
from core.interface.dialogs.eventfilter import eventFilter
|
||||
from core.utils.network.download import download_selected
|
||||
from core.interface.utils.tabhelper import create_tab
|
||||
from core.utils.general.shutdown import closehelper
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.interface.dialogs.image import Image
|
||||
from core.utils.data.state import state
|
||||
from interface.dialogs.trackerhoverdelegate import TrackerHoverDelegate
|
||||
from interface.dialogs.elideditemdelegate import ElidedItemDelegate
|
||||
from interface.dialogs.trackertable import _create_tracker_table
|
||||
from interface.dialogs.downloadlist import _create_download_list
|
||||
from interface.assets.base64_icons import settings_white_base64
|
||||
from interface.assets.base64_icons import settings_black_base64
|
||||
from interface.dialogs.downloadlist import download_list_update
|
||||
from interface.dialogs.update import get_version, UpdateDialog
|
||||
from utils.logging.logs import consoleLog, flush_log_buffer
|
||||
from interface.dialogs.downloadmodel import DownloadModel
|
||||
from interface.dialogs.settings import settings_dialog
|
||||
from interface.assets.base64_icons import logo_base64
|
||||
from interface.dialogs.eventfilter import eventFilter
|
||||
from utils.network.download import download_selected
|
||||
from interface.utils.searchhelper import run_search
|
||||
from interface.utils.tabhelper import create_tab
|
||||
from utils.general.shutdown import closehelper
|
||||
from utils.general.wrappers import run_thread
|
||||
from interface.dialogs.image import Image
|
||||
from utils.data.state import state
|
||||
|
||||
import core.interface.dialogs.contextmenu
|
||||
import interface.dialogs.contextmenu
|
||||
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import (
|
||||
@@ -86,7 +86,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
consoleLog(f"Could not set app ID: {e}")
|
||||
|
||||
# Get current version
|
||||
# core.interface.dialogs.update
|
||||
# interface.dialogs.update
|
||||
get_version()
|
||||
UpdateDialog()
|
||||
|
||||
@@ -137,7 +137,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
containerLayout.addWidget(self.searchbar)
|
||||
containerLayout.addWidget(state.trackertable)
|
||||
|
||||
# core.interface.dialogs.hoverrowdelegate
|
||||
# interface.dialogs.hoverrowdelegate
|
||||
self.download_model = DownloadModel()
|
||||
self.downloadList = _create_download_list(self)
|
||||
self.downloadList.viewport().installEventFilter(self)
|
||||
@@ -164,8 +164,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.horizontal_layout.addWidget(state.trackertable)
|
||||
|
||||
# Tabs
|
||||
self.tab1 = create_tab("Search", self.searchbar, state.trackertable, self.tabs, self.dlbutton, self.horizontal_layout)
|
||||
self.tab2 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
|
||||
create_tab("Search", [self.searchbar, self.horizontal_layout, self.dlbutton], tabs=self.tabs, stretch=False)
|
||||
create_tab("Downloads", [self.emptyDownload, self.downloadList], tabs=self.tabs, stretch=False)
|
||||
|
||||
# Corner Widget (Settings Button, Tracker list, Tab button container)
|
||||
self.corner_widget = QWidget()
|
||||
@@ -232,7 +232,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.statusbar.addPermanentWidget(self.version, Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
# (\u2193) Arrow down, (\u2191) Arrow up
|
||||
self.speed_label = QLabel("\u2193 0.0 kB/s \u2191 0.0 kB/s")
|
||||
self.speed_label = QLabel("↓ 0.0 kB/s ↑ 0.0 kB/s")
|
||||
self.speed_label.setStyleSheet("padding-right: 6px;")
|
||||
self.statusbar.addPermanentWidget(self.speed_label)
|
||||
|
||||
@@ -246,7 +246,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.active_timer.timeout.connect(self.show_empty_downloads)
|
||||
self.active_timer.start(500)
|
||||
|
||||
self._context_menu = core.interface.dialogs.contextmenu.ContextMenu(self)
|
||||
self._context_menu = interface.dialogs.contextmenu.ContextMenu(self)
|
||||
self.image_overlay = Image(self)
|
||||
|
||||
def _apply_default_headers(self, table):
|
||||
@@ -292,10 +292,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
table.setItemDelegateForColumn(i, self._tracker_hover_delegate)
|
||||
|
||||
def _start_search(self):
|
||||
self.searchbar.setEnabled(False)
|
||||
def _search_thread():
|
||||
try:
|
||||
return_pressed(self)
|
||||
run_search(self)
|
||||
except Exception as e:
|
||||
consoleLog(f"Search error: {e}", True)
|
||||
self.search_results_signal.emit([])
|
||||
@@ -384,7 +383,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
closehelper()
|
||||
event.accept()
|
||||
from core.utils.general.shutdown import force_exit
|
||||
from utils.general.shutdown import force_exit
|
||||
force_exit()
|
||||
|
||||
def _invalidate_hover_row(self, row):
|
||||
@@ -0,0 +1,27 @@
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from data.sources import SCRAPERS
|
||||
|
||||
for scraper in SCRAPERS:
|
||||
state.trackers[scraper.name] = scraper
|
||||
|
||||
def run_search(self) -> None:
|
||||
state.trackertable.setRowCount(0)
|
||||
self.show_empty_results(False)
|
||||
|
||||
search_text = self.searchbar.text()
|
||||
# Check for empty search
|
||||
if search_text == "":
|
||||
consoleLog("Error: Can't search for nothing")
|
||||
return
|
||||
|
||||
state.posts = []
|
||||
self.searchbar.setEnabled(False)
|
||||
consoleLog(f"User searched for: {search_text}")
|
||||
|
||||
# Get current tracker and call its search function
|
||||
scraper = state.trackers[state.currenttracker]
|
||||
state.posts = scraper.search(search_text)
|
||||
|
||||
# Update GUI headers to match the current scraper
|
||||
self.search_results_signal.emit(scraper.headers)
|
||||
@@ -1,16 +1,13 @@
|
||||
|
||||
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import Qt, QByteArray
|
||||
from PySide6.QtGui import QIcon, QPixmap, QPainter
|
||||
from PySide6.QtCore import Qt, QByteArray
|
||||
from PySide6.QtSvg import QSvgRenderer
|
||||
from PySide6 import QtWidgets
|
||||
import darkdetect
|
||||
|
||||
|
||||
# Check if darkmode is enabled
|
||||
def _is_dark_mode():
|
||||
return darkdetect.isDark()
|
||||
|
||||
|
||||
def svg_icon(svg_str, size=20):
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if app:
|
||||
@@ -0,0 +1,22 @@
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLayout
|
||||
|
||||
# Tab creation helper function
|
||||
def create_tab(title, items, tabs, stretch):
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
|
||||
for item in items:
|
||||
if item is None:
|
||||
continue
|
||||
|
||||
if isinstance(item, QLayout):
|
||||
layout.addLayout(item)
|
||||
else:
|
||||
layout.addWidget(item)
|
||||
|
||||
if stretch:
|
||||
layout.addStretch()
|
||||
|
||||
tab.setLayout(layout)
|
||||
tabs.addTab(tab, title)
|
||||
return tab
|
||||
+15
-13
@@ -1,15 +1,16 @@
|
||||
from core.network.libtorrent_misc import send_notification, update_log, check_deleted_files
|
||||
from core.utils.logging.loghandler import split_data, check_completed, check_downloads
|
||||
from core.network.interface import list_interfaces, init_interfaces
|
||||
from core.utils.logging.logs import get_download_logs
|
||||
from core.utils.logging.logs import set_main_window
|
||||
from core.utils.general.shutdown import closehelper
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.utils.general.shutdown import force_exit
|
||||
from core.utils.config.config import read_config
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.interface.gui import MainWindow
|
||||
from core.utils.data.state import state
|
||||
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
|
||||
from utils.logging.loghandler import split_data, check_completed, check_downloads
|
||||
from network.interface import list_interfaces, init_interfaces
|
||||
from utils.logging.logs import get_download_logs
|
||||
from utils.logging.logs import set_main_window
|
||||
from network.libtorrent_int import check_space
|
||||
from utils.general.shutdown import closehelper
|
||||
from utils.general.wrappers import run_thread
|
||||
from utils.general.shutdown import force_exit
|
||||
from utils.config.config import read_config
|
||||
from utils.logging.logs import consoleLog
|
||||
from interface.gui import MainWindow
|
||||
from utils.data.state import state
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import Qt
|
||||
import qdarktheme
|
||||
@@ -59,6 +60,7 @@ def main():
|
||||
# Start background non-daemon threads
|
||||
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
|
||||
run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
|
||||
run_thread(threading.Thread(target=check_space, args=()))
|
||||
# Finish counting startup time
|
||||
elapsed = time.perf_counter() - start_time
|
||||
consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI")
|
||||
@@ -66,4 +68,4 @@ def main():
|
||||
run_gui()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
+5
-4
@@ -1,9 +1,10 @@
|
||||
from utils.logging.logs import consoleLog, add_download_log
|
||||
from utils.data.state import state
|
||||
from typing import Optional
|
||||
from core.utils.data.state import state
|
||||
from core.utils.logging.logs import consoleLog, add_download_log
|
||||
|
||||
from .handle import DirectDownloadHandle
|
||||
from .utils import (
|
||||
|
||||
from network.direct_download.handle import DirectDownloadHandle
|
||||
from network.direct_download.utils import (
|
||||
sanitize_filename,
|
||||
extract_filename_from_url,
|
||||
detect_filename_from_headers,
|
||||
@@ -1,8 +1,8 @@
|
||||
from core.utils.logging.logs import consoleLog, update_download_completed
|
||||
from network.direct_download.status import DirectDownloadStatus, ChunkSpec
|
||||
from utils.logging.logs import consoleLog, update_download_completed
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from .status import DirectDownloadStatus, ChunkSpec
|
||||
from core.utils.data.state import state
|
||||
from .utils import format_size
|
||||
from network.direct_download.utils import format_size
|
||||
from utils.data.state import state
|
||||
from typing import Optional
|
||||
import libtorrent as lt
|
||||
import threading
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from urllib.parse import urlparse, unquote
|
||||
from utils.logging.logs import consoleLog
|
||||
from typing import Optional
|
||||
import requests
|
||||
import os
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
import psutil
|
||||
|
||||
def get_net_interfaces():
|
||||
@@ -1,7 +1,7 @@
|
||||
from core.network.interface import get_interface_ip
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from network.interface import get_interface_ip
|
||||
from utils.general.wrappers import run_thread
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
import libtorrent as lt
|
||||
import threading
|
||||
import platform
|
||||
@@ -21,6 +21,25 @@ def get_free_space_mb(dirname):
|
||||
st = os.statvfs(dirname)
|
||||
return st.f_bavail * st.f_frsize
|
||||
|
||||
def check_space():
|
||||
while not state.shutdown_event.is_set():
|
||||
for _, magnetdl in list(state.active_downloads.items()):
|
||||
try:
|
||||
status = magnetdl.status()
|
||||
except RuntimeError:
|
||||
continue
|
||||
|
||||
if status.state == lt.torrent_status.downloading:
|
||||
free_space = get_free_space_mb(state.download_path)
|
||||
total_size = status.total_wanted
|
||||
|
||||
if free_space < total_size:
|
||||
consoleLog(f"Not enough free space to continue downloading: {status.name}")
|
||||
magnetdl.pause()
|
||||
break
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
def init_session():
|
||||
if state.dl_session is not None:
|
||||
return
|
||||
@@ -60,7 +79,6 @@ def add_download(magnet_uri):
|
||||
state.active_downloads = {}
|
||||
|
||||
init_session()
|
||||
|
||||
free_space = get_free_space_mb(state.download_path)
|
||||
|
||||
if magnet_uri in state.active_downloads:
|
||||
@@ -91,7 +109,6 @@ def add_download(magnet_uri):
|
||||
params.save_path = state.download_path
|
||||
|
||||
handle = state.dl_session.add_torrent(params)
|
||||
|
||||
while not handle.has_metadata():
|
||||
time.sleep(1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from core.utils.logging.logs import update_download_completed_by_hash
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import update_download_completed_by_hash
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from plyer import notification
|
||||
import libtorrent as lt
|
||||
import time
|
||||
@@ -17,9 +17,6 @@ def cleanup_session():
|
||||
state.dl_session = None
|
||||
state.active_downloads.clear()
|
||||
|
||||
|
||||
|
||||
|
||||
def send_notification(shutdown_event):
|
||||
notified = set()
|
||||
while not shutdown_event.is_set():
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.network.libtorrent_int import add_download
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from network.libtorrent_int import add_download
|
||||
from utils.logging.logs import consoleLog
|
||||
|
||||
|
||||
def add_magnet(uri):
|
||||
@@ -1,4 +1,4 @@
|
||||
from core.utils.data.state import state
|
||||
from utils.data.state import state
|
||||
import configparser
|
||||
import platform
|
||||
import os
|
||||
@@ -1,7 +1,8 @@
|
||||
from core.network.libtorrent_int import update_settings
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from .config import create_config
|
||||
from network.libtorrent_int import update_settings
|
||||
from utils.config.config import create_config
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
|
||||
|
||||
|
||||
def save_settings(close=lambda: None, apiurl=None, download_path=None, down_speed_limit=None, up_speed_limit=None, image_path=None, autoresume=None, max_connections=None, max_downloads=None, bound_interface=None):
|
||||
@@ -9,43 +9,44 @@ class AppState(QObject):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
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.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()
|
||||
# General
|
||||
self.version: str = "Unknown"
|
||||
self.debug: bool = False
|
||||
self.main_window: Any = None
|
||||
self.loop_running: bool = False
|
||||
self.shutdown_event = threading.Event()
|
||||
self.log_buffer: List[str] = []
|
||||
|
||||
# Paths
|
||||
self.settings_path: str = ""
|
||||
self._image_path: str = ""
|
||||
self.download_path: str = str(Path.home() / "Downloads")
|
||||
|
||||
# GUI
|
||||
self.window_transparency: bool = False
|
||||
self.trackertable: QTableWidget
|
||||
self.interfaces: List = []
|
||||
self.active_interfaces: List = []
|
||||
self.bound_interface: Any = None
|
||||
|
||||
# Trackers / Scraping
|
||||
self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seeders, leechers
|
||||
self.currenttracker: str = "rutracker"
|
||||
self.api_url: str = "https://api.michijackson.xyz"
|
||||
self.trackers: Dict[str,Dict[str,Any]] = {}
|
||||
|
||||
# LibTorrent / Download related stuff
|
||||
self.dl_session: Any = None
|
||||
self.active_downloads: Dict = {}
|
||||
self.seeded_magnets: set = set()
|
||||
self.ignore_updates: bool = False
|
||||
self.autoresume: bool = True
|
||||
self.up_speed_limit: int = 0
|
||||
self.down_speed_limit: int = 0
|
||||
self.max_connections: int = 200
|
||||
self.max_downloads: int = 10
|
||||
self.settings_path: str = ""
|
||||
self.dl_session: Any = None
|
||||
self.active_downloads: Dict = {}
|
||||
self.window_transparency: bool = False
|
||||
self.interfaces: List = []
|
||||
self.active_interfaces: List = []
|
||||
self.bound_interface: Any = None
|
||||
self.log_buffer: List[str] = []
|
||||
self.downloads_lock = threading.RLock()
|
||||
self.main_window: Any = None
|
||||
self.loop_running: bool = False
|
||||
self.shutdown_event = threading.Event()
|
||||
|
||||
@property
|
||||
def image_path(self) -> str:
|
||||
@@ -1,4 +1,4 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from utils.logging.logs import consoleLog
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from core.network.libtorrent_misc import cleanup_session
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from network.libtorrent_misc import cleanup_session
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
import os
|
||||
|
||||
def closehelper():
|
||||
@@ -1,4 +1,4 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from utils.logging.logs import consoleLog
|
||||
|
||||
def run_thread(thread):
|
||||
target_name = thread._target.__name__
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.utils.network.download import run_download_direct, seed_magnet
|
||||
from core.utils.logging.logs import consoleLog, remove_download_log
|
||||
from utils.network.download import run_download_direct, seed_magnet
|
||||
from utils.logging.logs import consoleLog, remove_download_log
|
||||
import os
|
||||
|
||||
def split_data(data):
|
||||
@@ -18,7 +18,7 @@ def check_completed(downloads, resume):
|
||||
run_download_direct(download.magnet_uri, download.title)
|
||||
consoleLog(f"Resuming Magnet: {download.title}")
|
||||
elif download.url:
|
||||
from core.network.direct_download import add_direct_download
|
||||
from network.direct_download import add_direct_download
|
||||
add_direct_download(download.url, download.title)
|
||||
consoleLog(f"Resuming Direct Download: {download.title}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from core.utils.data.models import Download, DownloadList
|
||||
from core.utils.data.state import state
|
||||
from utils.data.models import Download, DownloadList
|
||||
from utils.data.state import state
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime
|
||||
import json
|
||||
@@ -243,7 +243,7 @@ def set_main_window(window):
|
||||
def flush_log_buffer(): # credits to claude
|
||||
if state.log_buffer:
|
||||
try:
|
||||
from core.interface.gui import MainWindow
|
||||
from interface.gui import MainWindow
|
||||
for log_entry in state.log_buffer:
|
||||
MainWindow.add_log(log_entry)
|
||||
state.log_buffer = []
|
||||
@@ -256,7 +256,7 @@ def consoleLog(text, printAnyways = False):
|
||||
formatted_text = f"[{current_time}] {text}"
|
||||
|
||||
try:
|
||||
from core.interface.gui import MainWindow
|
||||
from interface.gui import MainWindow
|
||||
if not MainWindow.add_log(formatted_text):
|
||||
state.log_buffer.append(formatted_text)
|
||||
except Exception:
|
||||
@@ -1,11 +1,11 @@
|
||||
from core.network.direct_download import add_direct_download
|
||||
from core.network.libtorrent_wrapper import add_magnet
|
||||
from core.utils.logging.logs import add_download_log
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.network.libtorrent_int import add_seed
|
||||
from network.direct_download import add_direct_download
|
||||
from network.libtorrent_wrapper import add_magnet
|
||||
from PySide6.QtWidgets import QTableWidgetItem
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import add_download_log
|
||||
from utils.general.wrappers import run_thread
|
||||
from network.libtorrent_int import add_seed
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from PySide6.QtCore import Qt
|
||||
from typing import Optional
|
||||
import threading
|
||||
@@ -31,9 +31,9 @@ def download_selected(items: list[QTableWidgetItem]):
|
||||
run_thread(threading.Thread(target=run_download, args=(post,)))
|
||||
|
||||
def run_download(post, headers: Optional[dict] = None):
|
||||
linkfunc = state.trackers[state.currenttracker]["linkFunc"]
|
||||
ismagnet = state.trackers[state.currenttracker]["isMagnet"]
|
||||
result = linkfunc(post)
|
||||
link_method = state.trackers[state.currenttracker].get_download_link
|
||||
ismagnet = state.trackers[state.currenttracker].is_magnet
|
||||
result = link_method(post)
|
||||
|
||||
if ismagnet:
|
||||
link = result
|
||||
@@ -1,9 +1,8 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
def get_updates():
|
||||
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases/latest"
|
||||
try:
|
||||
@@ -1,6 +1,6 @@
|
||||
from core.network.direct_download.handle import DirectDownloadHandle
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from network.direct_download.handle import DirectDownloadHandle
|
||||
from utils.logging.logs import consoleLog
|
||||
from utils.data.state import state
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6 import QtWidgets
|
||||
import libtorrent as lt
|
||||
Reference in New Issue
Block a user