Compare commits

..

3 Commits

Author SHA1 Message Date
Vxrtrauter 2fb9ad9e28 daily commit 2026-02-18 17:20:42 +01:00
Vxrtrauter 193188c383 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-19 17:19:26 +01:00
Vxrtrauter d9de5c6ff5 daily commit 2026-02-19 17:19:15 +01:00
20 changed files with 223 additions and 218 deletions
+5 -33
View File
@@ -1,28 +1,16 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests
import time from core.utils.general.logs import consoleLog
cache = { def scrape_monkrus_telegram(query):
"data": [],
"last_fetched": 0
}
cache_expiry = 300
def _get_telegram_posts():
post = 100 post = 100
max_posts = 250 max_posts = 250
posts = [] posts: list[dict] = []
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, timeout=10) response = requests.get(url)
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")
@@ -37,7 +25,7 @@ def _get_telegram_posts():
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 link: if query.lower() in title.lower() and 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)
@@ -51,20 +39,4 @@ def _get_telegram_posts():
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
+6 -4
View File
@@ -1,9 +1,10 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.logging.logs import consoleLog from core.utils.general.logs import consoleLog
def scrape_rutracker(query):
search = requests.get(f"{state.api_url}/search?q={query}") def scrape_rutracker(search_text):
search = requests.get(f"{state.api_url}/search?q={search_text}")
consoleLog("Sent request to server") consoleLog("Sent request to server")
if search: if search:
try: try:
@@ -11,9 +12,10 @@ def scrape_rutracker(query):
except Exception: except Exception:
consoleLog("No results found / No response from server") consoleLog("No results found / No response from server")
return None return None
else: else:
return None return None
# This function utilizes the SoftwareManager server - source code can be found under the SoftwareManager-Server repository. # This function utilizes the SoftwareManager server - source code can be found under the "server" branch.
+68 -19
View File
@@ -1,35 +1,84 @@
import requests import requests
import asyncio
import threading
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from urllib.parse import urljoin from core.utils.data.state import state
from core.utils.logging.logs import consoleLog from core.utils.general.logs import consoleLog
from core.utils.general.wrappers import run_thread
def scrape_uztracker(query):
base_url="https://uztracker.net/" global url_uztracker
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}" url_uztracker = "https://uztracker.net/tracker.php?nm="
def init_uztracker():
global up
global soup
try:
response = requests.get(url_uztracker, timeout=10)
if response.status_code == 200:
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) consoleLog(search_url)
posts = [] result = False
global results
global resulttitles
results = []
resulttitles = []
try: try:
resultCount = 0
response = requests.get(search_url) response = requests.get(search_url)
response.raise_for_status() response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_')) links = soup.find_all('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
for link in links: for link in links:
if link.b:
resultCount += 1
results.append(link['href'])
resulttitles.append(link.b.text)
result = True
theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic')) if result:
url = urljoin(base_url, theme_link['href']) return resulttitles, results
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( if not result:
title=title, # add popup in gui for no result
url=url, return None
author=author
))
return posts
except requests.RequestException as e: except requests.RequestException as e:
if result:
consoleLog(f"Failed to fetch {search_url}: {e}") consoleLog(f"Failed to fetch {search_url}: {e}")
return None 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')
maintitle = soup.find(class_='tt-text')
if maintitle:
return maintitle.text
else:
consoleLog("Program not found")
else:
consoleLog("Error: Uztracker down")
return None
+3 -3
View File
@@ -1,10 +1,10 @@
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.logging.logs import consoleLog from core.utils.general.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, Qt
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
@@ -32,7 +32,7 @@ def settings_dialog(self):
dialog.setLayout(QVBoxLayout()) dialog.setLayout(QVBoxLayout())
if state.window_transparency and platform.system() != "Windows" and dialog: if state.window_transparency and platform.system() != "Windows":
dialog.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) dialog.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
def close_settings(): def close_settings():
+36 -53
View File
@@ -1,5 +1,5 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets, QtCore, QtGui
from PySide6.QtCore import QPersistentModelIndex, Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize from PySide6.QtCore import 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, QGuiApplication from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent
import darkdetect import darkdetect
import threading import threading
import platform import platform
@@ -30,7 +30,7 @@ import time
import sys import sys
import json import json
import base64 import base64
from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_buffer from core.utils.general.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
@@ -41,6 +41,7 @@ from core.interface.dialogs.settings import settings_dialog
from core.interface.assets.base64_icons import settings_black_base64 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 settings_white_base64
from core.interface.assets.base64_icons import logo_base64 from core.interface.assets.base64_icons import logo_base64
from core.network.libtorrent_misc import cleanup_session
from core.utils.general.shutdown import closehelper from core.utils.general.shutdown import closehelper
@@ -67,9 +68,6 @@ 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
@@ -147,57 +145,51 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
flush_log_buffer() flush_log_buffer()
def create_tracker_table(headers): # Table Widget for Item List
table = QTableWidget() self.qtablewidget = QTableWidget()
table.setColumnCount(len(headers))
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
table.verticalHeader().setVisible(False)
table.setHorizontalHeaderLabels(headers)
header = table.horizontalHeader() self.qtablewidget.setColumnCount(4)
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.qtablewidget.verticalHeader().setVisible(False)
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.resizeSection(1, 500)
header.setStretchLastSection(False) header.setStretchLastSection(False)
return table self.qtablewidget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.qtablewidget.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
header = self.qtablewidget.horizontalHeader()
self.rutrackerlist = create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"]) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
self.uztrackerlist = create_tracker_table(["Post Title", "Author"]) header.resizeSection(1, 500)
self.monkruslist = create_tracker_table(["Post Title", "Author"])
state.tracker_list.update({"rutracker": self.rutrackerlist, "uztracker": self.uztrackerlist, "m0nkrus": self.monkruslist})
self.rutrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.rutrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
container = QWidget() container = QWidget()
containerLayout = QVBoxLayout() containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar) containerLayout.addWidget(self.searchbar)
containerLayout.addWidget(state.tracker_list[state.tracker]) containerLayout.addWidget(self.qtablewidget)
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 | QPersistentModelIndex = QModelIndex()) -> int: def rowCount(self, parent=QModelIndex()):
return len(state.active_downloads) return len(state.active_downloads)
def columnCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()): def columnCount(self, parent=QModelIndex()):
return len(self.headers) return len(self.headers)
def headerData(self, section, orientation, role: int = Qt.ItemDataRole.DisplayRole): def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.headers[section] return self.headers[section]
return None return None
def data(self, index: QModelIndex | QPersistentModelIndex, role: int = Qt.ItemDataRole.DisplayRole): def data(self, index, role=Qt.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole: if role == Qt.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 +251,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
else: else:
return "" if status.paused else "Stalled" return "" if status.paused else "Stalled"
if role == Qt.ItemDataRole.UserRole and index.column() == 0: if role == Qt.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 +351,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=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles)))) self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles))))
container.setLayout(containerLayout) container.setLayout(containerLayout)
self.setCentralWidget(container) self.setCentralWidget(container)
@@ -379,16 +371,15 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.horizontal_layout = QHBoxLayout() self.horizontal_layout = QHBoxLayout()
self.horizontal_layout.addWidget(self.emptyResults, stretch=3) self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
self.tracker_widget = state.tracker_list[state.tracker] self.horizontal_layout.addWidget(self.qtablewidget)
self.horizontal_layout.addWidget(self.tracker_widget)
self.tab1 = create_tab("Search", self.searchbar, state.tracker_list[state.tracker], self.tabs, self.dlbutton, self.horizontal_layout) self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, 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.TransformationMode.SmoothTransformation) self.image = self.image.scaledToWidth(300, Qt.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)
@@ -513,22 +504,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
event.accept() event.accept()
def set_tracker(self, _): def set_tracker(self, _):
old_tracker = state.tracker
state.tracker = self.tracker_list.currentText() state.tracker = self.tracker_list.currentText()
old_widget = state.tracker_list[old_tracker]
self.horizontal_layout.removeWidget(old_widget)
old_widget.setParent(None)
tracker_widget = state.tracker_list[state.tracker]
self.horizontal_layout.addWidget(tracker_widget)
def show_empty_results(self, show: bool): def show_empty_results(self, show: bool):
if show: if show:
state.tracker_list[state.tracker].hide() self.qtablewidget.hide()
self.emptyResults.show() self.emptyResults.show()
else: else:
state.tracker_list[state.tracker].show() self.qtablewidget.show()
self.emptyResults.hide() self.emptyResults.hide()
def show_empty_downloads(self): def show_empty_downloads(self):
@@ -542,8 +525,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 = state.tracker_list[state.tracker].viewport().width() table_width = self.qtablewidget.viewport().width()
state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3)) self.qtablewidget.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():
+45 -28
View File
@@ -1,17 +1,11 @@
from PySide6.QtWidgets import QTableWidgetItem from PySide6.QtWidgets import QTableWidgetItem
from core.utils.logging.logs import consoleLog from core.utils.general.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_m0nkrus from core.data.scrapers.monkrus import scrape_monkrus_telegram
from core.utils.network.jsonhandler import split_data, format_data, format_data_minimal from core.utils.network.jsonhandler import split_data, format_data, format_data_m0nkrus
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 == "":
@@ -19,48 +13,71 @@ def return_pressed(self):
return return
consoleLog(f"User searched for: {search_text}") consoleLog(f"User searched for: {search_text}")
if state.tracker == "rutracker": if state.tracker == "uztracker":
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}")
state.tracker_list[state.tracker].clear() self.qtablewidget.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)
state.tracker_list[state.tracker].clear() self.qtablewidget.clear()
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"]) self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
state.tracker_list[state.tracker].setRowCount(len(state.post_titles)) self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author): for i, author in enumerate(state.post_author):
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author)) self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
for i, seeders in enumerate(state.post_seeders): for i, seeders in enumerate(state.post_seeders):
state.tracker_list[state.tracker].setItem(i, 2, QTableWidgetItem(seeders)) self.qtablewidget.setItem(i, 2, QTableWidgetItem(seeders))
for i, leechers in enumerate(state.post_leechers): for i, leechers in enumerate(state.post_leechers):
state.tracker_list[state.tracker].setItem(i, 3, QTableWidgetItem(leechers)) self.qtablewidget.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")
state.tracker_list[state.tracker].clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
elif state.tracker is not None: elif state.tracker == "m0nkrus":
state.posts = scrapers[state.tracker](search_text) state.posts = scrape_monkrus_telegram(search_text)
if state.posts == []: if state.posts == []:
consoleLog(f"No Results for {search_text}") consoleLog(f"No Results for {search_text}")
state.tracker_list[state.tracker].clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
state.post_author, state.post_titles, state.post_urls = format_data_minimal(state.posts) state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts)
self.show_empty_results(False) self.show_empty_results(False)
state.tracker_list[state.tracker].clear() self.qtablewidget.clear()
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"]) self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
state.tracker_list[state.tracker].setRowCount(len(state.post_titles)) self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author): for i, author in enumerate(state.post_author):
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author)) self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
+1 -1
View File
@@ -1,4 +1,4 @@
from core.utils.logging.logs import consoleLog from core.utils.general.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.logging.logs import consoleLog from core.utils.general.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.logging.logs import update_download_completed_by_hash from core.utils.general.logs import update_download_completed_by_hash
from core.utils.logging.logs import consoleLog from core.utils.general.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.logging.logs import consoleLog from core.utils.general.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.logging.logs import consoleLog from core.utils.general.logs import consoleLog
from .config import create_config from .config import create_config
-1
View File
@@ -7,7 +7,6 @@ class Download:
title: str title: str
url: str url: str
magnet_uri: str magnet_uri: str
path: str
completed: bool completed: bool
@dataclass @dataclass
+5 -7
View File
@@ -1,5 +1,4 @@
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
@@ -8,9 +7,9 @@ class AppState(QObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.posts: list[Any] | None = None self.posts: List[Dict[str, Any]] = []
self.post_titles: Optional[List] = None self.post_titles: List[str] = []
self.post_urls: Optional[List] = None self.post_urls: List[str] = []
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] = []
@@ -20,7 +19,6 @@ 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
@@ -29,11 +27,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: Dict = {} self.active_downloads: List[str] = {}
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: Any = None self.bound_interface: str = 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.logging.logs import consoleLog from core.utils.general.logs import consoleLog
from core.utils.network.jsonhandler import format_data from core.utils.network.jsonhandler import format_data
@@ -1,8 +1,6 @@
from core.utils.logging.logs import consoleLog, remove_download_log from core.utils.general.logs import consoleLog
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
@@ -18,11 +16,6 @@ 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,7 +5,6 @@ from datetime import datetime
import json import json
import os import os
import re import re
import time
_log_buffer = [] _log_buffer = []
@@ -23,20 +22,6 @@ 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:
@@ -52,8 +37,7 @@ 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,
path=path, completed=completed,
completed=completed
)) ))
consoleLog(f"Added {title} to Log File") consoleLog(f"Added {title} to Log File")
@@ -77,12 +61,10 @@ 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 {title} from Log File") consoleLog(f"Removed {magnet_link} 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:
+8 -3
View File
@@ -1,9 +1,11 @@
from core.utils.logging.logs import consoleLog from core.utils.data.state import state
from core.utils.general.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.logging.logs import add_download_log from core.utils.general.logs import add_download_log
import threading import threading
@@ -19,8 +21,11 @@ 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(magnet_uri)
add_download_log(item, post_url, magnet_uri, False) add_download_log(item, post_url, magnet_uri, False)
add_download(magnet_uri)
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_minimal(data): def format_data_m0nkrus(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_author, post_titles, post_links return post_titles, post_links, post_author
# https://open.spotify.com/track/0F0fMcIdL2FmcC9eYqwazX # https://open.spotify.com/track/70vjXI7oBou5buTtgiJeRW?si=25640cc4243c4bf3
+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.logging.logs import consoleLog from core.utils.general.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"
+15 -10
View File
@@ -1,12 +1,13 @@
from core.interface.gui import MainWindow, windowCloseHelper from core.interface.gui import MainWindow
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.logging.logs import consoleLog from core.utils.general.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.logging.logs import get_download_logs from core.utils.general.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.logging.loghandler import split_data, check_completed, check_downloads from core.utils.general.loghandler import split_data, check_completed
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
@@ -14,9 +15,15 @@ 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")
@@ -26,20 +33,21 @@ 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.logging.logs import set_main_window from core.utils.general.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()
def main(): if __name__ == "__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...")
@@ -53,10 +61,7 @@ def 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()