Compare commits

...

25 Commits

Author SHA1 Message Date
Vxrtrauter a7c71b37f5 refactor: remove duplicate stripping 2026-03-04 21:00:37 +01:00
Vxrtrauter 8fb1bb9e8d Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-03-04 20:58:58 +01:00
Vxrtrauter 89fd8b4ebb fix: fix scraping for uztracker posts to support displaying authors properly 2026-03-04 20:58:56 +01:00
KeksNino 3915e114a3 fix: logging in uztracker scraper and search for non-ascii letters and remove unused imports 2026-03-04 20:28:55 +01:00
Vxrtrauter 34a50e1a1b refactor: rename scraping functions for consistency and improve data handling 2026-03-04 19:11:31 +01:00
KeksNino 90124fd9c8 fix: smaller author column in search tab 2026-03-04 14:07:43 +01:00
Vxrtrauter 4175f82967 feat: add windowCloseHelper function and integrate it into keyboard interrupt handling 2026-03-03 21:56:28 +01:00
Vxrtrauter 4bf2e9af81 feat: add download existence checks and improve log removal feedback 2026-03-01 23:14:10 +01:00
Vxrtrauter a6bb879db3 feat: enhance download logging with path handling and timeout mechanism 2026-02-28 04:01:00 +01:00
Vxrtrauter 7653748599 chore: remove unused file_list utility 2026-02-28 03:34:56 +01:00
Vxrtrauter 9bec3f9189 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-28 03:33:13 +01:00
Vxrtrauter 04d93b870a refactor: reorganize logging functionality and remove unused log files 2026-02-28 03:33:10 +01:00
KeksNino 664e660198 fix a bunch of lsp errors 2026-02-28 03:31:17 +01:00
Vxrtrauter 60019e0d2e refactor: update log imports and restructure logging functionality 2026-02-28 03:31:05 +01:00
Vxrtrauter 8238cfbe56 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-28 03:05:10 +01:00
Vxrtrauter 7ccff5c5a1 Refactor monkrus scraper to implement caching and improve post fetching logic 2026-02-28 03:05:09 +01:00
KeksNino 8abe92fc4f fix: hide seeders and leechers in uztracker and m0nkrus tab 2026-02-28 02:37:35 +01:00
Vxrtrauter 0a3b5eeee2 Remove unused asyncio import in uztracker.py 2026-02-27 23:59:59 +01:00
KeksNino 7662c861ea trigger github CI 2026-02-20 01:13:13 +01:00
KeksNino 2a8f305a76 fix exe not starting on windows 2026-02-19 19:28:24 +01:00
KeksNino 12223d18de add transparency to settings dialog on linux/macos 2026-02-17 00:29:55 +01:00
KeksNino 544733ce6b fix app icon in nuitka build process and remove settings pngs 2026-02-17 00:10:30 +01:00
KeksNino 7aee2bcfe9 remove include flags for assets in github workflows 2026-02-16 23:58:34 +01:00
KeksNino d274e64841 fix assets not showing up by loading them from base64 2026-02-16 23:49:21 +01:00
KeksNino 23c74f6487 fix: wrong assets path on windows 2026-02-16 02:34:44 +01:00
24 changed files with 250 additions and 234 deletions
-5
View File
@@ -126,7 +126,6 @@ jobs:
--enable-plugin=upx \ --enable-plugin=upx \
--upx-binary="${{ env.UPX_BINARY }}" \ --upx-binary="${{ env.UPX_BINARY }}" \
--noinclude-qt-translations \ --noinclude-qt-translations \
--include-data-dir=src/core/interface/assets=src/core/interface/assets \
--include-data-files=build_info.json=build_info.json \ --include-data-files=build_info.json=build_info.json \
--include-data-files=build_info.json=src/build_info.json \ --include-data-files=build_info.json=src/build_info.json \
--include-data-files=build_info.json=src/core/interface/build_info.json \ --include-data-files=build_info.json=src/core/interface/build_info.json \
@@ -141,8 +140,6 @@ jobs:
--add-data "build_info.json:." \ --add-data "build_info.json:." \
--add-data "build_info.json:src/core" \ --add-data "build_info.json:src/core" \
--add-data "build_info.json:src/core/interface" \ --add-data "build_info.json:src/core/interface" \
--add-data "src/core/interface/assets:src/core/interface/assets" \
--icon "src/core/interface/assets/logo.png" \
--exclude-module PyQt6 \ --exclude-module PyQt6 \
--exclude-module PyQt5 \ --exclude-module PyQt5 \
--hidden-import=backports.tarfile \ --hidden-import=backports.tarfile \
@@ -161,8 +158,6 @@ jobs:
--add-data "build_info.json:." \ --add-data "build_info.json:." \
--add-data "build_info.json:src/core" \ --add-data "build_info.json:src/core" \
--add-data "build_info.json:src/core/interface" \ --add-data "build_info.json:src/core/interface" \
--add-data "src/core/interface/assets:src/core/interface/assets" \
--icon "src/core/interface/assets/fr logo.png" \
--exclude-module PyQt6 \ --exclude-module PyQt6 \
--exclude-module PyQt5 \ --exclude-module PyQt5 \
--hidden-import=backports.tarfile \ --hidden-import=backports.tarfile \
+33 -5
View File
@@ -1,16 +1,28 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests
from core.utils.general.logs import consoleLog import time
def scrape_monkrus_telegram(query): cache = {
"data": [],
"last_fetched": 0
}
cache_expiry = 300
def _get_telegram_posts():
post = 100 post = 100
max_posts = 250 max_posts = 250
posts: list[dict] = [] posts = []
added = set() added = set()
current_time = time.time()
if cache["data"] and (current_time - cache["last_fetched"] < cache_expiry):
return cache["data"]
while post <= max_posts: while post <= max_posts:
url = f"https://t.me/s/real_monkrus/{post}" url = f"https://t.me/s/real_monkrus/{post}"
response = requests.get(url) response = requests.get(url, timeout=10)
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble") bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
@@ -25,7 +37,7 @@ def scrape_monkrus_telegram(query):
title = post_txt.b.text title = post_txt.b.text
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net")) link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
if query.lower() in title.lower() and link: if link:
post_url = link["href"] post_url = link["href"]
if post_url not in added: if post_url not in added:
added.add(post_url) added.add(post_url)
@@ -39,4 +51,20 @@ def scrape_monkrus_telegram(query):
post += 22 post += 22
posts.reverse() posts.reverse()
cache["data"] = posts
cache["last_fetched"] = current_time
return(posts) 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
+3 -3
View File
@@ -1,10 +1,10 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
def scrape_rutracker(search_text): def scrape_rutracker(query):
search = requests.get(f"{state.api_url}/search?q={search_text}") search = requests.get(f"{state.api_url}/search?q={query}")
consoleLog("Sent request to server") consoleLog("Sent request to server")
if search: if search:
try: try:
+28 -74
View File
@@ -1,84 +1,38 @@
import requests import requests
import asyncio
import threading
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from urllib.parse import urljoin
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.general.wrappers import run_thread
def scrape_uztracker(query):
base_url="https://uztracker.net/"
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}"
consoleLog(search_url)
posts = []
global url_uztracker
url_uztracker = "https://uztracker.net/tracker.php?nm="
def init_uztracker():
global up
global soup
try: try:
response = requests.get(url_uztracker, timeout=10) response = requests.get(search_url)
if response.status_code == 200: response.raise_for_status()
up = True
else:
up = False
consoleLog(f"Uztracker seems down, status code: {response.status_code}")
except requests.exceptions.RequestException as e:
consoleLog(f"Request Exception on {url_uztracker}:")
consoleLog(str(e))
consoleLog("Is the Site down?")
up = False
run_thread(threading.Thread(target=init_uztracker))
def scrape_uztracker(search):
if up:
search_url = url_uztracker + search
consoleLog(search_url)
result = False
global results
global resulttitles
results = []
resulttitles = []
try:
resultCount = 0
response = requests.get(search_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
for link in links:
if link.b:
resultCount += 1
results.append(link['href'])
resulttitles.append(link.b.text)
result = True
if result:
return resulttitles, results
if not result:
# add popup in gui for no result
return None
except requests.RequestException as e:
if result:
consoleLog(f"Failed to fetch {search_url}: {e}")
return None
else:
consoleLog("Error: Uztracker down")
return None, None
def get_post_title(post_url):
if up:
response = requests.get(post_url)
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
maintitle = soup.find(class_='tt-text') links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
if maintitle: for link in links:
return maintitle.text
else: theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
consoleLog("Program not found") url = urljoin(base_url, theme_link['href'])
else: title = theme_link.b.text
consoleLog("Error: Uztracker down") author_link = link.find('a', class_="med")
author = author_link.text.strip() if author_link else "Unknown"
posts.append(dict(
title=title,
url=url,
author=author
))
return posts
except requests.RequestException as e:
consoleLog(f"Failed to fetch {search_url}: {e}")
return None return None
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

+5 -1
View File
@@ -1,10 +1,11 @@
from core.utils.config.settings import save_settings from core.utils.config.settings import save_settings
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.interface.utils.tabhelper import general_tab from core.interface.utils.tabhelper import general_tab
from core.interface.utils.tabhelper import paths_tab from core.interface.utils.tabhelper import paths_tab
from core.interface.utils.tabhelper import network_tab from core.interface.utils.tabhelper import network_tab
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QPushButton, QPushButton,
@@ -31,6 +32,9 @@ def settings_dialog(self):
dialog.setLayout(QVBoxLayout()) dialog.setLayout(QVBoxLayout())
if state.window_transparency and platform.system() != "Windows" and dialog:
dialog.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
def close_settings(): def close_settings():
dialog.reject() dialog.reject()
+70 -53
View File
@@ -1,5 +1,5 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize from PySide6.QtCore import QPersistentModelIndex, Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QTableView, QTableView,
@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
QStyledItemDelegate, QStyledItemDelegate,
) )
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent, QGuiApplication
import darkdetect import darkdetect
import threading import threading
import platform import platform
@@ -29,8 +29,8 @@ import libtorrent as lt
import time import time
import sys import sys
import json import json
import asyncio import base64
from core.utils.general.logs import consoleLog, remove_download_log, flush_log_buffer from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_buffer
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.network.download import download_selected from core.utils.network.download import download_selected
@@ -38,7 +38,9 @@ from core.utils.network.update_checker import check_for_updates
from core.interface.utils.tabhelper import create_tab from core.interface.utils.tabhelper import create_tab
from core.interface.utils.searchhelper import return_pressed from core.interface.utils.searchhelper import return_pressed
from core.interface.dialogs.settings import settings_dialog from core.interface.dialogs.settings import settings_dialog
from core.network.libtorrent_misc import cleanup_session from core.interface.assets.base64_icons import settings_black_base64
from core.interface.assets.base64_icons import settings_white_base64
from core.interface.assets.base64_icons import logo_base64
from core.utils.general.shutdown import closehelper from core.utils.general.shutdown import closehelper
@@ -65,6 +67,9 @@ def download_update(latest_version):
sys.exit(0) sys.exit(0)
def windowCloseHelper():
QGuiApplication.quit()
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
@@ -73,21 +78,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
MainWindow._instance = self MainWindow._instance = self
self.log_signal.connect(self._on_log_signal) self.log_signal.connect(self._on_log_signal)
def get_asset_path(filename): pixmap = QPixmap()
base_path = os.path.dirname(os.path.abspath(__file__)) image_data = base64.b64decode(logo_base64)
asset_path = os.path.join(base_path, 'assets', filename) pixmap.loadFromData(image_data)
if os.path.exists(asset_path): self.setWindowIcon(QIcon(pixmap))
return asset_path
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
asset_path = os.path.join(sys._MEIPASS, 'core', 'interface', 'assets', filename)
if os.path.exists(asset_path):
return asset_path
return os.path.join('core', 'interface', 'assets', filename)
self.setWindowIcon(QIcon(get_asset_path("logo.png")))
if platform.system() == "Windows": if platform.system() == "Windows":
try: try:
@@ -153,51 +147,57 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
flush_log_buffer() flush_log_buffer()
# Table Widget for Item List def create_tracker_table(headers):
self.qtablewidget = QTableWidget() table = QTableWidget()
table.setColumnCount(len(headers))
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
table.verticalHeader().setVisible(False)
table.setHorizontalHeaderLabels(headers)
self.qtablewidget.setColumnCount(4) header = table.horizontalHeader()
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.qtablewidget.verticalHeader().setVisible(False) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"]) header.resizeSection(1, 500)
header.setStretchLastSection(False)
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"])
header = self.qtablewidget.horizontalHeader() state.tracker_list.update({"rutracker": self.rutrackerlist, "uztracker": self.uztrackerlist, "m0nkrus": self.monkruslist})
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.setStretchLastSection(False)
self.qtablewidget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.rutrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.qtablewidget.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.rutrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
header = self.qtablewidget.horizontalHeader() self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
header.resizeSection(1, 500)
container = QWidget() container = QWidget()
containerLayout = QVBoxLayout() containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar) containerLayout.addWidget(self.searchbar)
containerLayout.addWidget(self.qtablewidget) containerLayout.addWidget(state.tracker_list[state.tracker])
class DownloadModel(QAbstractTableModel): class DownloadModel(QAbstractTableModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.headers = ["Action", "Name", "Status", "Progress", "Speed", "Size", "Total Size"] self.headers = ["Action", "Name", "Status", "Progress", "Speed", "Size", "Total Size"]
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
return len(state.active_downloads) return len(state.active_downloads)
def columnCount(self, parent=QModelIndex()): def columnCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()):
return len(self.headers) return len(self.headers)
def headerData(self, section, orientation, role=Qt.DisplayRole): def headerData(self, section, orientation, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal: if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self.headers[section] return self.headers[section]
return None return None
def data(self, index, role=Qt.DisplayRole): def data(self, index: QModelIndex | QPersistentModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
col = index.column() col = index.column()
if index.row() >= len(state.active_downloads) or index.row() < 0: if index.row() >= len(state.active_downloads) or index.row() < 0:
@@ -259,7 +259,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
else: else:
return "" if status.paused else "Stalled" return "" if status.paused else "Stalled"
if role == Qt.UserRole and index.column() == 0: if role == Qt.ItemDataRole.UserRole and index.column() == 0:
magnet_link = list(state.active_downloads.keys())[index.row()] magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link] magnetdl = state.active_downloads[magnet_link]
return magnetdl.status().paused return magnetdl.status().paused
@@ -359,7 +359,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
delegate.clicked.connect(on_pause_resume_clicked) delegate.clicked.connect(on_pause_resume_clicked)
# download button triggers # download button triggers
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles)))) self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles))))
container.setLayout(containerLayout) container.setLayout(containerLayout)
self.setCentralWidget(container) self.setCentralWidget(container)
@@ -379,15 +379,16 @@ 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.horizontal_layout.addWidget(self.qtablewidget) self.tracker_widget = state.tracker_list[state.tracker]
self.horizontal_layout.addWidget(self.tracker_widget)
self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout) self.tab1 = create_tab("Search", self.searchbar, state.tracker_list[state.tracker], 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)
if state.image_path is not None and os.path.exists(state.image_path): if state.image_path is not None and os.path.exists(state.image_path):
self.image = QImage(state.image_path) self.image = QImage(state.image_path)
self.image = self.image.scaledToWidth(300, Qt.SmoothTransformation) self.image = self.image.scaledToWidth(300, Qt.TransformationMode.SmoothTransformation)
self.pixmap = QPixmap.fromImage(self.image) self.pixmap = QPixmap.fromImage(self.image)
self.overlay_label = QLabel(self) self.overlay_label = QLabel(self)
self.overlay_label.setPixmap(self.pixmap) self.overlay_label.setPixmap(self.pixmap)
@@ -418,10 +419,18 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.settings_btn = QtWidgets.QToolButton() self.settings_btn = QtWidgets.QToolButton()
self.settings_btn.setIconSize(QSize(21, 21)) self.settings_btn.setIconSize(QSize(21, 21))
self.settings_btn.setStyleSheet("background: transparent;") self.settings_btn.setStyleSheet("background: transparent;")
if darkdetect.isDark(): if darkdetect.isDark():
self.settings_btn.setIcon(QIcon(get_asset_path("settings_white.png"))) settings_white_pixmap = QPixmap()
settings_white = base64.b64decode(settings_white_base64)
settings_white_pixmap.loadFromData(settings_white)
self.settings_btn.setIcon(QIcon(settings_white_pixmap))
else: else:
self.settings_btn.setIcon(QIcon(get_asset_path("settings_black.png"))) settings_black_pixmap = QPixmap()
settings_black = base64.b64decode(settings_black_base64)
settings_black_pixmap.loadFromData(settings_black)
self.settings_btn.setIcon(QIcon(settings_black_pixmap))
self.settings_btn.setToolTip("Settings") self.settings_btn.setToolTip("Settings")
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.settings_btn.clicked.connect(lambda: settings_dialog(self)) self.settings_btn.clicked.connect(lambda: settings_dialog(self))
@@ -506,12 +515,20 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def set_tracker(self, _): def set_tracker(self, _):
state.tracker = self.tracker_list.currentText() state.tracker = self.tracker_list.currentText()
while self.horizontal_layout.count():
item = self.horizontal_layout.takeAt(0)
if item.widget():
item.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:
self.qtablewidget.hide() state.tracker_list[state.tracker].hide()
self.emptyResults.show() self.emptyResults.show()
else: else:
self.qtablewidget.show() state.tracker_list[state.tracker].show()
self.emptyResults.hide() self.emptyResults.hide()
def show_empty_downloads(self): def show_empty_downloads(self):
@@ -525,8 +542,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# thank you claude # thank you claude
def resizeEvent(self, event): def resizeEvent(self, event):
super().resizeEvent(event) super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width() table_width = state.tracker_list[state.tracker].viewport().width()
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3)) 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():
+28 -45
View File
@@ -1,11 +1,17 @@
from PySide6.QtWidgets import QTableWidgetItem from PySide6.QtWidgets import QTableWidgetItem
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker from core.data.scrapers.rutracker import scrape_rutracker
from core.data.scrapers.monkrus import scrape_monkrus_telegram from core.data.scrapers.monkrus import scrape_m0nkrus
from core.utils.network.jsonhandler import split_data, format_data, format_data_m0nkrus from core.utils.network.jsonhandler import split_data, format_data, format_data_minimal
from core.utils.data.state import state from core.utils.data.state import state
scrapers = {
"uztracker": scrape_uztracker,
"rutracker": scrape_rutracker,
"m0nkrus": scrape_m0nkrus
}
def return_pressed(self): def return_pressed(self):
search_text = self.searchbar.text() search_text = self.searchbar.text()
if search_text == "": if search_text == "":
@@ -13,71 +19,48 @@ def return_pressed(self):
return return
consoleLog(f"User searched for: {search_text}") consoleLog(f"User searched for: {search_text}")
if state.tracker == "uztracker": if state.tracker == "rutracker":
response = scrape_uztracker(search_text)
if response:
state.post_titles, state.post_urls = response
self.qtablewidget.clear()
if state.post_titles and len(state.post_titles) > 0:
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
self.show_empty_results(False)
else:
consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear()
self.show_empty_results(True)
else:
consoleLog(f"No response from uztracker")
self.qtablewidget.clear()
self.show_empty_results(True)
elif state.tracker == "rutracker":
response = scrape_rutracker(search_text) response = scrape_rutracker(search_text)
if response: if response:
_, state.posts, _, _, cached = split_data(response) _, state.posts, _, _, cached = split_data(response)
if state.posts == []: if state.posts == []:
consoleLog(f"No Results for {search_text}") consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear() state.tracker_list[state.tracker].clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts) state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts)
self.show_empty_results(False) self.show_empty_results(False)
self.qtablewidget.clear() state.tracker_list[state.tracker].clear()
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"]) state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
self.qtablewidget.setRowCount(len(state.post_titles)) state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author): for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author)) state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
for i, seeders in enumerate(state.post_seeders): for i, seeders in enumerate(state.post_seeders):
self.qtablewidget.setItem(i, 2, QTableWidgetItem(seeders)) state.tracker_list[state.tracker].setItem(i, 2, QTableWidgetItem(seeders))
for i, leechers in enumerate(state.post_leechers): for i, leechers in enumerate(state.post_leechers):
self.qtablewidget.setItem(i, 3, QTableWidgetItem(leechers)) state.tracker_list[state.tracker].setItem(i, 3, QTableWidgetItem(leechers))
if state.debug == True: if state.debug == True:
consoleLog(f"Response Cached: {cached}") consoleLog(f"Response Cached: {cached}")
else: else:
consoleLog(f"No response from rutracker") consoleLog(f"No response from rutracker")
self.qtablewidget.clear() state.tracker_list[state.tracker].clear()
self.show_empty_results(True) self.show_empty_results(True)
elif state.tracker == "m0nkrus": elif state.tracker is not None:
state.posts = scrape_monkrus_telegram(search_text) state.posts = scrapers[state.tracker](search_text)
if state.posts == []: if state.posts == []:
consoleLog(f"No Results for {search_text}") consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear() state.tracker_list[state.tracker].clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts) state.post_author, state.post_titles, state.post_urls = format_data_minimal(state.posts)
self.show_empty_results(False) self.show_empty_results(False)
self.qtablewidget.clear() state.tracker_list[state.tracker].clear()
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"]) state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"])
self.qtablewidget.setRowCount(len(state.post_titles)) state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author): for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author)) state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
+1 -1
View File
@@ -1,4 +1,4 @@
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.data.state import state from core.utils.data.state import state
import psutil import psutil
+1 -1
View File
@@ -4,7 +4,7 @@ from core.network.interface import get_interface_ip
import threading import threading
import libtorrent as lt import libtorrent as lt
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
global loop_running global loop_running
+2 -2
View File
@@ -1,6 +1,6 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import update_download_completed_by_hash from core.utils.logging.logs import update_download_completed_by_hash
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from plyer import notification from plyer import notification
import libtorrent as lt import libtorrent as lt
import time import time
+1 -1
View File
@@ -1,4 +1,4 @@
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.network.libtorrent_int import add_download from core.network.libtorrent_int import add_download
def add_magnet(uri): def add_magnet(uri):
+1 -1
View File
@@ -1,6 +1,6 @@
from core.network.libtorrent_int import update_settings from core.network.libtorrent_int import update_settings
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from .config import create_config from .config import create_config
+1
View File
@@ -7,6 +7,7 @@ class Download:
title: str title: str
url: str url: str
magnet_uri: str magnet_uri: str
path: str
completed: bool completed: bool
@dataclass @dataclass
+7 -5
View File
@@ -1,4 +1,5 @@
from PySide6.QtCore import QObject, Signal from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QTableWidget
from typing import Optional, Any, List, Dict from typing import Optional, Any, List, Dict
from pathlib import Path from pathlib import Path
@@ -7,9 +8,9 @@ class AppState(QObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.posts: List[Dict[str, Any]] = [] self.posts: list[Any] | None = None
self.post_titles: List[str] = [] self.post_titles: Optional[List] = None
self.post_urls: List[str] = [] self.post_urls: Optional[List] = None
self.post_author: List[str] = [] self.post_author: List[str] = []
self.post_seeders: List[str] = [] self.post_seeders: List[str] = []
self.post_leechers: List[str] = [] self.post_leechers: List[str] = []
@@ -19,6 +20,7 @@ class AppState(QObject):
self.debug: bool = False self.debug: bool = False
self.autoresume: bool = True self.autoresume: bool = True
self.tracker: str = "rutracker" self.tracker: str = "rutracker"
self.tracker_list: dict[str, QTableWidget] = {}
self.api_url: str = "https://api.michijackson.xyz" self.api_url: str = "https://api.michijackson.xyz"
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
@@ -27,11 +29,11 @@ class AppState(QObject):
self.max_downloads: int = 10 self.max_downloads: int = 10
self.settings_path: str = "" self.settings_path: str = ""
self.dl_session: Any = None self.dl_session: Any = None
self.active_downloads: List[str] = {} self.active_downloads: Dict = {}
self.window_transparency: bool = False self.window_transparency: bool = False
self.interfaces: List = [] self.interfaces: List = []
self.active_interfaces: List = [] self.active_interfaces: List = []
self.bound_interface: str = None self.bound_interface: Any = None
@property @property
def image_path(self) -> str: def image_path(self) -> str:
+1 -1
View File
@@ -1,7 +1,7 @@
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.network.jsonhandler import format_data from core.utils.network.jsonhandler import format_data
@@ -1,6 +1,8 @@
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog, remove_download_log
from core.utils.network.download import run_download_direct from core.utils.network.download import run_download_direct
import os
def split_data(data): def split_data(data):
count = data.count count = data.count
@@ -16,6 +18,11 @@ def check_completed(downloads, resume):
run_download_direct(download.magnet_uri) run_download_direct(download.magnet_uri)
consoleLog(f"Resuming {download.title}") consoleLog(f"Resuming {download.title}")
def check_downloads(downloads):
for download in downloads:
if os.path.exists(download.path):
consoleLog(f"Existing Download: {download.title}")
else:
consoleLog(f"Inexistent Download: {download.title}")
remove_download_log(download.magnet_uri)
@@ -5,6 +5,7 @@ from datetime import datetime
import json import json
import os import os
import re import re
import time
_log_buffer = [] _log_buffer = []
@@ -22,6 +23,20 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
else: else:
downloads = [] downloads = []
magnetdl = state.active_downloads.get(magnet_uri)
if magnetdl:
status = magnetdl.status()
timeout = 60
start = time.time()
while not status.has_metadata and (time.time() - start) < timeout:
time.sleep(0.5)
status = magnetdl.status()
torrent_name = status.name
save_path = status.save_path
consoleLog(f"Torrent name (has_metadata={status.has_metadata}): {torrent_name}")
path = os.path.join(save_path, torrent_name)
else:
path = os.path.join(state.download_path, title)
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads): if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
if magnet_uri in state.active_downloads: if magnet_uri in state.active_downloads:
@@ -37,7 +52,8 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
title=title, title=title,
url=url, url=url,
magnet_uri=magnet_uri, magnet_uri=magnet_uri,
completed=completed, path=path,
completed=completed
)) ))
consoleLog(f"Added {title} to Log File") consoleLog(f"Added {title} to Log File")
@@ -61,10 +77,12 @@ def remove_download_log(magnet_uri) -> DownloadList:
else: else:
downloads = [] downloads = []
magnet_link = (magnet_uri or "").strip() magnet_link = (magnet_uri or "").strip()
title = next((getattr(d, 'title', 'Unknown') for d in downloads if (getattr(d, 'magnet_uri', None) or '').strip() == magnet_link or (getattr(d, 'url', None) or '').strip() == magnet_link))
downloads = [d for d in downloads if (getattr(d, 'magnet_uri', None) or "").strip() != magnet_link and (getattr(d, 'url', None) or "").strip() != magnet_link] downloads = [d for d in downloads if (getattr(d, 'magnet_uri', None) or "").strip() != magnet_link and (getattr(d, 'url', None) or "").strip() != magnet_link]
consoleLog(f"Removed {magnet_link} from Log File") consoleLog(f"Removed {title} from Log File")
download_list = DownloadList(data=downloads, count=len(downloads)) download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file: with open(downloads_file, "w") as file:
+4 -3
View File
@@ -1,10 +1,11 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.data.tracker import get_item_url from core.utils.data.tracker import get_item_url
from core.utils.data.tracker import get_magnet_link from core.utils.data.tracker import get_magnet_link
from core.network.libtorrent_wrapper import add_download 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.general.logs import add_download_log from core.utils.logging.logs import add_download_log
import time
import threading import threading
@@ -21,8 +22,8 @@ def run_download(item, posts, post_titles):
consoleLog(f"Selected URL: {post_url}") consoleLog(f"Selected URL: {post_url}")
magnet_uri = get_magnet_link(post_url) magnet_uri = get_magnet_link(post_url)
add_download_log(item, post_url, magnet_uri, False)
add_download(magnet_uri) add_download(magnet_uri)
add_download_log(item, post_url, magnet_uri, False)
def run_download_direct(magnet_uri): def run_download_direct(magnet_uri):
add_download(magnet_uri) add_download(magnet_uri)
+3 -3
View File
@@ -20,16 +20,16 @@ def format_data(data):
return post_titles, post_links, post_author, post_seeders, post_leechers return post_titles, post_links, post_author, post_seeders, post_leechers
def format_data_m0nkrus(data): def format_data_minimal(data):
post_author = [post["author"] for post in data] post_author = [post["author"] for post in data]
post_titles = [post["title"] for post in data] post_titles = [post["title"] for post in data]
post_links = [post["url"] for post in data] post_links = [post["url"] for post in data]
return post_titles, post_links, post_author return post_author, post_titles, post_links
# https://open.spotify.com/track/70vjXI7oBou5buTtgiJeRW?si=25640cc4243c4bf3 # https://open.spotify.com/track/0F0fMcIdL2FmcC9eYqwazX
+1 -1
View File
@@ -1,6 +1,6 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
def check_for_updates(): def check_for_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases" url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
+10 -15
View File
@@ -1,13 +1,12 @@
from core.interface.gui import MainWindow from core.interface.gui import MainWindow, windowCloseHelper
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.network.libtorrent_misc import send_notification, update_log from core.network.libtorrent_misc import send_notification, update_log
from core.utils.general.logs import get_download_logs from core.utils.logging.logs import get_download_logs
from core.utils.general.shutdown import closehelper, shutdown_event from core.utils.general.shutdown import closehelper, shutdown_event
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.general.loghandler import split_data, check_completed from core.utils.logging.loghandler import split_data, check_completed, check_downloads
from core.network.interface import list_interfaces, init_interfaces from core.network.interface import list_interfaces, init_interfaces
from core.network.libtorrent_int import update_bound_interface
from core.utils.config.config import read_config from core.utils.config.config import read_config
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
@@ -15,15 +14,9 @@ import qdarktheme
import threading import threading
import platform import platform
import signal import signal
import argparse
import sys import sys
import time import time
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
def run_gui(): def run_gui():
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
qdarktheme.setup_theme("auto") qdarktheme.setup_theme("auto")
@@ -33,21 +26,20 @@ def run_gui():
widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"}) qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"})
from core.utils.general.logs import set_main_window from core.utils.logging.logs import set_main_window
set_main_window(widget) set_main_window(widget)
widget.show() widget.show()
sys.exit(app.exec()) sys.exit(app.exec())
def keyboardinterrupthandler(signum, frame): def keyboardinterrupthandler(signum, frame):
closehelper() closehelper()
windowCloseHelper()
if __name__ == "__main__": def main():
start_time = time.perf_counter() start_time = time.perf_counter()
read_config() read_config()
logs = get_download_logs() logs = get_download_logs()
_, downloads = split_data(logs) _, downloads = split_data(logs)
if args.debug:
state.debug = args.debug # override of read_config
signal.signal(signal.SIGINT, keyboardinterrupthandler) signal.signal(signal.SIGINT, keyboardinterrupthandler)
consoleLog("Starting SoftwareManager...") consoleLog("Starting SoftwareManager...")
consoleLog("Fetching Network Interfaces...") consoleLog("Fetching Network Interfaces...")
@@ -61,7 +53,10 @@ if __name__ == "__main__":
consoleLog("Started Thread: update_log") consoleLog("Started Thread: update_log")
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume))) run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
consoleLog("Started Thread: check_completed") consoleLog("Started Thread: check_completed")
check_downloads(downloads)
elapsed = time.perf_counter() - start_time elapsed = time.perf_counter() - start_time
consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI") consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI")
run_gui() run_gui()
if __name__ == "__main__":
main()