mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20a310264f | |||
| aee8f16353 | |||
| 2354adb349 | |||
| 4c69a17f0e | |||
| 2bab6c3321 | |||
| 91482760b6 | |||
| 83fa23df58 | |||
| 41d28095c2 | |||
| de2e49e60b | |||
| dd6d16d833 | |||
| 7d0c8d16ec | |||
| 80750f010f | |||
| c93f6345c8 | |||
| a7132ed1cd | |||
| a2cff15d0d | |||
| 2eb384f7cf | |||
| 05a5b04507 | |||
| 87c974f124 | |||
| 95a687ddd9 | |||
| 813d2b31c4 | |||
| deb5ce5b88 | |||
| a11c1d7129 | |||
| dcc78ade44 | |||
| 10e80ab4b6 | |||
| 3290ff3fac | |||
| d5d99d6a0e | |||
| 3960612f94 | |||
| a37a47b65b | |||
| 76b26664f2 | |||
| c8a1e72107 | |||
| 7c505f1402 | |||
| 55e4f86a39 | |||
| bd71b0e645 | |||
| 2a86e4b774 | |||
| d17b4bd44a | |||
| 10c935e852 | |||
| bd8320a26c | |||
| c99ce339e0 | |||
| 1822a150d7 | |||
| 7e5e3033e6 | |||
| 1cab671a13 | |||
| 5637e80657 | |||
| 2f78b812ba | |||
| 5af9e79ecc | |||
| 4dadc9ba50 | |||
| 719c4e86e8 | |||
| 999c8e9ba4 | |||
| 3f6889da56 | |||
| 69ac739be6 | |||
| 141cc8fadc | |||
| 80c6ece72e |
+39
-31
@@ -35,6 +35,9 @@ jobs:
|
||||
asset_name: SoftwareManager-dev-${{ github.sha }}-macos
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
short_sha: ${{ steps.version.outputs.short_sha }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -66,7 +69,33 @@ jobs:
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
- name: Build executable with PyInstaller
|
||||
- name: Generate version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
COMMIT_SHA=${{ github.sha }}
|
||||
SHORT_SHA=${COMMIT_SHA:0:7}
|
||||
VERSION="${SHORT_SHA}-dev"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "Generated version: $VERSION"
|
||||
|
||||
- name: Inject commit hash into code (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$json = '{
|
||||
"version": "' + $env:VERSION + '"
|
||||
}'
|
||||
$json | Out-File -Encoding utf8 build_info.json
|
||||
|
||||
- name: Build executable with PyInstaller (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: pyinstaller --onefile --windowed --add-data "build_info.json:." --name ${{ env.APP_NAME }} main.py
|
||||
|
||||
- name: Build executable with PyInstaller (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: pyinstaller --onefile --windowed --name ${{ env.APP_NAME }} main.py
|
||||
|
||||
- name: Make executable (Unix)
|
||||
@@ -94,50 +123,29 @@ jobs:
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Display downloaded artifacts
|
||||
run: |
|
||||
echo "Downloaded artifacts:"
|
||||
find artifacts -type f
|
||||
ls -la artifacts/
|
||||
|
||||
- name: Generate version
|
||||
id: version
|
||||
run: |
|
||||
COMMIT_SHA=${{ github.sha }}
|
||||
SHORT_SHA=${COMMIT_SHA:0:7}
|
||||
VERSION="${SHORT_SHA}-dev"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
echo "Generated version: ${VERSION}"
|
||||
|
||||
- name: Prepare release files
|
||||
run: |
|
||||
mkdir -p release
|
||||
SHORT_SHA="${{ steps.version.outputs.short_sha }}"
|
||||
SHORT_SHA="${{ needs.build.outputs.short_sha }}"
|
||||
cp artifacts/ubuntu-latest-build/SoftwareManager-dev-*-linux release/SoftwareManager-dev-${SHORT_SHA}-linux
|
||||
cp artifacts/windows-latest-build/SoftwareManager-dev-*-windows.exe release/SoftwareManager-dev-${SHORT_SHA}-windows.exe
|
||||
cp artifacts/macos-latest-build/SoftwareManager-dev-*-macos release/SoftwareManager-dev-${SHORT_SHA}-macos
|
||||
chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos
|
||||
ls -la release/
|
||||
|
||||
- name: Create Prerelease (Public)
|
||||
- name: Create Release (Public)
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
tag_name: ${{ needs.build.outputs.version }}
|
||||
name: ${{ needs.build.outputs.version }}
|
||||
files: |
|
||||
release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-linux
|
||||
release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-windows.exe
|
||||
release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-macos
|
||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-linux
|
||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-windows.exe
|
||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -27,6 +27,17 @@ SoftwareManager is a Python-based GUI/TUI tool that simplifies downloading and m
|
||||
- If you want to host your own server, clone the server branch of this repo, enter your cookie for rutracker.org and run server.py. Detailed Instructions on how to get your rutracker cookie are in the README of its branch.
|
||||
- This tool is for educational purposes only. Use it responsibly.
|
||||
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://www.star-history.com/#KeksPirates/SoftwareManager&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=KeksPirates/SoftwareManager&type=date&theme=dark&legend=top-left" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=KeksPirates/SoftwareManager&type=date&legend=top-left" />
|
||||
<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.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
|
||||
def scrape_monkrus_telegram(query):
|
||||
post = 100
|
||||
max_posts = 250
|
||||
posts: list[dict] = []
|
||||
added = set()
|
||||
|
||||
while post <= max_posts:
|
||||
url = f"https://t.me/s/real_monkrus/{post}"
|
||||
response = requests.get(url)
|
||||
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 not post_txt or not post_txt.b:
|
||||
continue
|
||||
|
||||
title = post_txt.b.text
|
||||
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
|
||||
|
||||
if query.lower() in title.lower() and link:
|
||||
post_url = link["href"]
|
||||
if post_url not in added:
|
||||
added.add(post_url)
|
||||
posts.append(dict(
|
||||
author="m0nkrus",
|
||||
id=len(posts) + 1,
|
||||
title=title,
|
||||
url=post_url
|
||||
))
|
||||
|
||||
post += 22
|
||||
|
||||
posts.reverse()
|
||||
return(posts)
|
||||
@@ -4,6 +4,8 @@ from core.utils.data.state import state
|
||||
|
||||
def scrape_rutracker(search_text):
|
||||
search = requests.get(f"{state.api_url}/search/{search_text}")
|
||||
if state.debug:
|
||||
print("Sent request to server")
|
||||
if search:
|
||||
try:
|
||||
return search.text
|
||||
|
||||
@@ -17,62 +17,56 @@ async def init_uztracker():
|
||||
up = True
|
||||
else:
|
||||
up = False
|
||||
print(f"Uztracker seems down, status code: {response.status_code}")
|
||||
if state.debug:
|
||||
print(f"Uztracker seems down, status code: {response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\nRequest Exception on {url_uztracker}:")
|
||||
print(e)
|
||||
print("\nIs the Site down?")
|
||||
if state.debug:
|
||||
print(f"\nRequest Exception on {url_uztracker}:")
|
||||
print(e)
|
||||
print("\nIs the Site down?")
|
||||
up = False
|
||||
|
||||
asyncio.run(init_uztracker())
|
||||
|
||||
async def scrape_uztracker(search, max_results=450):
|
||||
def scrape_uztracker(search):
|
||||
if up:
|
||||
search_url = url_uztracker + search
|
||||
if state.debug:
|
||||
print(search_url)
|
||||
result = False
|
||||
global results
|
||||
global resulttitles
|
||||
results = []
|
||||
results = []
|
||||
resulttitles = []
|
||||
|
||||
per_page = 50
|
||||
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
|
||||
|
||||
for start in range(0, max_results, per_page):
|
||||
search_url = url_uztracker + search + f"&start={start}"
|
||||
if state.debug:
|
||||
print(search_url)
|
||||
if result:
|
||||
return resulttitles, results
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.get(search_url) as response:
|
||||
response.raise_for_status()
|
||||
text = await response.text()
|
||||
soup = BeautifulSoup(text, 'html.parser')
|
||||
links = soup.find_all('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
|
||||
resultCount = 0
|
||||
for link in links:
|
||||
if link.b:
|
||||
resultCount += 1
|
||||
results.append(link['href'])
|
||||
resulttitles.append(link.b.text)
|
||||
result = True
|
||||
|
||||
if resultCount == 0:
|
||||
# add popup in gui for no result
|
||||
break
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
if result:
|
||||
print(f"Failed to fetch {search_url}: {e}")
|
||||
return None
|
||||
if not result:
|
||||
# add popup in gui for no result
|
||||
return None
|
||||
|
||||
else:
|
||||
except requests.RequestException as e:
|
||||
if result and state.debug:
|
||||
print(f"Failed to fetch {search_url}: {e}")
|
||||
return None
|
||||
else:
|
||||
if state.debug:
|
||||
print("Error: Uztracker down")
|
||||
return None, None
|
||||
|
||||
if not results:
|
||||
return None, None
|
||||
|
||||
return resulttitles, results
|
||||
return None, None
|
||||
|
||||
def get_post_title(post_url):
|
||||
if up:
|
||||
@@ -85,7 +79,8 @@ def get_post_title(post_url):
|
||||
if state.debug:
|
||||
print("Program not found")
|
||||
else:
|
||||
print("Error: Uztracker down")
|
||||
if state.debug:
|
||||
print("Error: Uztracker down")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QHBoxLayout,
|
||||
QSpinBox,
|
||||
QCheckBox,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def settings_dialog(self):
|
||||
|
||||
if state.debug:
|
||||
@@ -29,7 +27,20 @@ def settings_dialog(self):
|
||||
|
||||
def close_settings():
|
||||
dialog.reject()
|
||||
|
||||
|
||||
checkbox_container = QWidget()
|
||||
checkbox_layout = QHBoxLayout()
|
||||
|
||||
# ignore updates checkbox
|
||||
checkbox = QCheckBox()
|
||||
checkbox_container.setLayout(checkbox_layout)
|
||||
checkbox_layout.addWidget(QLabel("Ignore Updates: "))
|
||||
checkbox_layout.addStretch()
|
||||
checkbox.setChecked(state.ignore_updates)
|
||||
checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked))
|
||||
checkbox_layout.addWidget(checkbox)
|
||||
dialog.layout().addWidget(checkbox_container)
|
||||
|
||||
##################
|
||||
# THREAD SETTING #
|
||||
##################
|
||||
@@ -114,10 +125,9 @@ def settings_dialog(self):
|
||||
save_btn.clicked.connect(lambda: save_settings(thread_box.value(), close_settings, api_url.text(), download_path.text(), speed_limit.value()))
|
||||
|
||||
cancel_btn.clicked.connect(dialog.reject)
|
||||
print(thread_box.value())
|
||||
layout.addWidget(cancel_btn)
|
||||
layout.addWidget(save_btn)
|
||||
|
||||
dialog.layout().addLayout(layout)
|
||||
|
||||
dialog.exec()
|
||||
dialog.exec()
|
||||
|
||||
+78
-17
@@ -14,25 +14,76 @@ from PySide6.QtWidgets import (
|
||||
QTabWidget,
|
||||
QProgressBar,
|
||||
QHeaderView,
|
||||
QMessageBox,
|
||||
QTableWidget,
|
||||
)
|
||||
|
||||
from PySide6.QtGui import QIcon, QAction, QCloseEvent
|
||||
import darkdetect
|
||||
import threading
|
||||
import platform
|
||||
import requests as r
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.utils.data.state import state
|
||||
from core.utils.network.download import download_selected
|
||||
from core.utils.network.update_checker import check_for_updates
|
||||
from core.utils.general.shutdown import closehelper
|
||||
from core.interface.utils.tabhelper import create_tab
|
||||
from core.interface.utils.searchhelper import return_pressed
|
||||
from core.interface.dialogs.settings import settings_dialog
|
||||
from core.network.aria2_integration import dlprogress
|
||||
|
||||
print(os.path.abspath(__file__))
|
||||
|
||||
def download_update(latest_version):
|
||||
old_filename = f"SoftwareManager-dev-{state.version.replace('-dev', '')}-windows.exe"
|
||||
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
|
||||
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
|
||||
|
||||
print("Downloading update...")
|
||||
if os.path.exists(old_filename):
|
||||
os.remove(old_filename)
|
||||
response = r.get(url, allow_redirects=True)
|
||||
with open(new_filename, "wb") as f:
|
||||
f.write(response.content)
|
||||
if not os.path.exists(new_filename):
|
||||
raise FileNotFoundError(f"Executable not found")
|
||||
subprocess.Popen([new_filename], shell=True)
|
||||
time.sleep(0.5)
|
||||
sys.exit(0)
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
searchresults = []
|
||||
|
||||
build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json")
|
||||
if 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")
|
||||
|
||||
# Check for updates on Windows
|
||||
if state.ignore_updates is False:
|
||||
if platform.system() == "Windows":
|
||||
result = check_for_updates()
|
||||
assets, latest_version = result
|
||||
|
||||
if assets:
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Information)
|
||||
msg.setWindowTitle("Update Available")
|
||||
msg.setText(f"A new version is available.")
|
||||
msg.setInformativeText("Please visit the GitHub releases page to download the latest version.")
|
||||
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Ignore)
|
||||
|
||||
response = msg.exec_()
|
||||
if response == QMessageBox.Ok:
|
||||
download_update(latest_version)
|
||||
|
||||
self.setWindowTitle("Software Manager")
|
||||
self.setGeometry(100, 100, 800, 600)
|
||||
@@ -48,8 +99,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.searchbar.returnPressed.connect(lambda: run_thread(threading.Thread(target=return_pressed, args=(self,)))) # Triggers data function thread on enter
|
||||
|
||||
self.dlbutton = QtWidgets.QPushButton("Download")
|
||||
self.softwareList = QListWidget()
|
||||
self.post_author_list = QListWidget()
|
||||
self.libraryList = QListWidget()
|
||||
|
||||
self.emptyResults = QLabel("No Results")
|
||||
@@ -62,14 +111,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.emptyLibrary = QLabel("No items in library.")
|
||||
self.emptyDownload = QLabel("No items in downloads.")
|
||||
self.progressbar = QProgressBar()
|
||||
self.qtablewidget = QTableWidget()
|
||||
|
||||
self.qtablewidget.setColumnCount(2)
|
||||
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.qtablewidget.verticalHeader().setVisible(False)
|
||||
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
|
||||
self.qtablewidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.qtablewidget.setAttribute(Qt.WA_TranslucentBackground)
|
||||
self.qtablewidget.viewport().setAttribute(Qt.WA_TranslucentBackground)
|
||||
|
||||
header = self.qtablewidget.horizontalHeader()
|
||||
header.setSectionResizeMode(1, QHeaderView.Fixed)
|
||||
header.resizeSection(1, 500)
|
||||
|
||||
container = QWidget()
|
||||
containerLayout = QVBoxLayout()
|
||||
containerLayout.addWidget(self.searchbar)
|
||||
|
||||
containerLayout.addWidget(self.softwareList)
|
||||
containerLayout.addWidget(self.post_author_list)
|
||||
self.softwareList.addItems(searchresults)
|
||||
containerLayout.addWidget(self.qtablewidget)
|
||||
|
||||
class DownloadModel(QAbstractTableModel):
|
||||
def __init__(self):
|
||||
@@ -111,9 +170,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.downloadList.horizontalHeader().setStretchLastSection(False)
|
||||
self.downloadList.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.downloadList.setSelectionBehavior(QTableView.SelectRows)
|
||||
self.downloadList.setAttribute(Qt.WA_TranslucentBackground)
|
||||
self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground)
|
||||
|
||||
# download button triggers
|
||||
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.softwareList.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)
|
||||
self.setCentralWidget(container)
|
||||
@@ -122,13 +183,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.tabs = QTabWidget()
|
||||
|
||||
self.horizontal_layout = QHBoxLayout()
|
||||
self.horizontal_layout.addWidget(self.softwareList, stretch=3)
|
||||
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
||||
self.horizontal_layout.addWidget(self.post_author_list)
|
||||
self.horizontal_layout.addWidget(self.qtablewidget)
|
||||
|
||||
self.tab1 = create_tab("Search", self.searchbar, self.softwareList, 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.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
|
||||
|
||||
|
||||
|
||||
containerLayout.addWidget(self.tabs)
|
||||
|
||||
@@ -139,14 +201,13 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
toolbar.setLayoutDirection(Qt.RightToLeft)
|
||||
|
||||
self.tracker_list = QComboBox()
|
||||
self.tracker_list.addItems(["rutracker", "uztracker"])
|
||||
self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"])
|
||||
self.tracker_list.activated.connect(self.set_tracker)
|
||||
|
||||
|
||||
if darkdetect.isDark():
|
||||
settings_action = QAction(QIcon("core/interface/assets/settings_dark.png"), "Settings", self)
|
||||
settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self)
|
||||
else:
|
||||
settings_action = QAction(QIcon("core/interface/assets/settings.png"), "Settings", self)
|
||||
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self)
|
||||
|
||||
settings_action.triggered.connect(lambda: settings_dialog(self))
|
||||
toolbar.addAction(settings_action)
|
||||
@@ -183,8 +244,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
|
||||
def show_empty_results(self, show: bool):
|
||||
if show:
|
||||
self.softwareList.hide()
|
||||
self.qtablewidget.hide()
|
||||
self.emptyResults.show()
|
||||
else:
|
||||
self.emptyResults.hide()
|
||||
self.softwareList.show()
|
||||
self.qtablewidget.show()
|
||||
|
||||
@@ -1,42 +1,85 @@
|
||||
import asyncio
|
||||
from PySide6.QtWidgets import QTableWidgetItem
|
||||
from core.data.scrapers.uztracker import scrape_uztracker
|
||||
from core.data.scrapers.rutracker import scrape_rutracker
|
||||
from core.data.scrapers.monkrus import scrape_monkrus_telegram
|
||||
from core.utils.network.jsonhandler import split_data, format_data
|
||||
from core.utils.data.state import state
|
||||
|
||||
def return_pressed(self):
|
||||
search_text = self.searchbar.text()
|
||||
if search_text == "":
|
||||
if state.debug:
|
||||
print("Error: Can't search for nothing")
|
||||
return
|
||||
search_text = self.searchbar.text()
|
||||
if search_text == "":
|
||||
if state.debug:
|
||||
print("User searched for:", search_text)
|
||||
if state.tracker == "uztracker":
|
||||
response = asyncio.run(scrape_uztracker(search_text))
|
||||
if state.tracker == "rutracker":
|
||||
response = scrape_rutracker(search_text)
|
||||
print("Error: Can't search for nothing")
|
||||
return
|
||||
if state.debug:
|
||||
print("User searched for:", search_text)
|
||||
|
||||
if state.tracker == "uztracker":
|
||||
response = scrape_uztracker(search_text)
|
||||
if response:
|
||||
if state.tracker == "uztracker":
|
||||
state.post_titles, state.post_urls, = response
|
||||
self.softwareList.clear()
|
||||
if state.post_titles:
|
||||
self.softwareList.addItems(state.post_titles)
|
||||
self.post_author_list.addItems(state.post_author)
|
||||
if state.tracker == "rutracker":
|
||||
_, state.posts, _, _, cached = split_data(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:
|
||||
if state.debug:
|
||||
print(f"No Results for {search_text}")
|
||||
self.qtablewidget.clear()
|
||||
self.show_empty_results(True)
|
||||
else:
|
||||
if state.debug:
|
||||
print(f"No response from uztracker")
|
||||
self.qtablewidget.clear()
|
||||
self.show_empty_results(True)
|
||||
|
||||
elif state.tracker == "rutracker":
|
||||
response = scrape_rutracker(search_text)
|
||||
if response:
|
||||
_, state.posts, _, _, cached = split_data(response)
|
||||
if state.posts == []:
|
||||
if state.debug:
|
||||
print(f"No Results found for \"{search_text}\"")
|
||||
self.softwareList.clear()
|
||||
print(f"No Results for {search_text}")
|
||||
self.qtablewidget.clear()
|
||||
self.show_empty_results(True)
|
||||
else:
|
||||
state.post_titles, _, state.post_author = format_data(state.posts)
|
||||
self.show_empty_results(False)
|
||||
self.post_author_list.clear()
|
||||
self.post_author_list.addItems(state.post_author)
|
||||
self.softwareList.clear()
|
||||
self.softwareList.addItems(state.post_titles)
|
||||
self.qtablewidget.clear()
|
||||
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))
|
||||
if state.debug == True:
|
||||
print(f"Cached: {cached}")
|
||||
print(f"Response Cached: {cached}")
|
||||
else:
|
||||
if state.debug:
|
||||
print(f"No response from rutracker")
|
||||
self.qtablewidget.clear()
|
||||
self.show_empty_results(True)
|
||||
|
||||
elif state.tracker == "m0nkrus":
|
||||
state.posts = scrape_monkrus_telegram(search_text)
|
||||
|
||||
if state.posts == []:
|
||||
if state.debug:
|
||||
print(f"No Results for {search_text}")
|
||||
self.qtablewidget.clear()
|
||||
self.show_empty_results(True)
|
||||
else:
|
||||
state.post_titles, _, state.post_author = format_data(state.posts)
|
||||
self.show_empty_results(False)
|
||||
self.qtablewidget.clear()
|
||||
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))
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from .aria2_integration import run_aria2p
|
||||
from core.utils.data.state import state
|
||||
|
||||
def start_client():
|
||||
global aria2
|
||||
aria2 = run_aria2p()
|
||||
if state.debug:
|
||||
print("Started Aria2p")
|
||||
|
||||
def add_magnet(uri):
|
||||
aria2.add_magnet(uri)
|
||||
if state.debug:
|
||||
print("Magnet URI added to Aria2")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import platform
|
||||
import configparser
|
||||
from core.utils.data.state import state
|
||||
|
||||
|
||||
def create_config():
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}"}
|
||||
config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}",
|
||||
"ignore_updates": f"{state.ignore_updates}"}
|
||||
|
||||
if platform.system() == "Windows":
|
||||
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
|
||||
@@ -42,4 +42,4 @@ def read_config():
|
||||
state.aria2_threads = config.getint("General", "aria2_threads")
|
||||
state.download_path = config.get("General", "download_path")
|
||||
state.speed_limit = config.getint("General", "speed_limit")
|
||||
|
||||
state.ignore_updates = config.getboolean("General", "ignore_updates")
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
from core.utils.data.state import state
|
||||
from core.utils.general.shutdown import kill_aria2server
|
||||
from .config import create_config
|
||||
|
||||
def restart_aria2c():
|
||||
import main # had to do this because of circle import :(
|
||||
import signal
|
||||
import atexit
|
||||
main.kill_aria2server()
|
||||
kill_aria2server()
|
||||
state.aria2process.wait()
|
||||
state.aria2process = main.run_aria2server()
|
||||
signal.signal(signal.SIGINT, main.keyboardinterrupthandler)
|
||||
atexit.unregister(main.kill_aria2server)
|
||||
atexit.register(main.kill_aria2server)
|
||||
atexit.unregister(kill_aria2server)
|
||||
atexit.register(kill_aria2server)
|
||||
|
||||
def save_settings(thread_count, close, apiurl, download_path, speed_limit):
|
||||
state.aria2_threads = thread_count
|
||||
state.api_url = apiurl
|
||||
state.download_path = download_path
|
||||
state.speed_limit = speed_limit
|
||||
def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_path=None, speed_limit=None):
|
||||
if thread_count is not None:
|
||||
state.aria2_threads = thread_count
|
||||
if apiurl is not None:
|
||||
state.api_url = apiurl
|
||||
if download_path is not None:
|
||||
state.download_path = download_path
|
||||
if speed_limit is not None:
|
||||
state.speed_limit = speed_limit
|
||||
|
||||
create_config()
|
||||
restart_aria2c()
|
||||
|
||||
@@ -9,6 +9,8 @@ class AppState:
|
||||
post_urls: List[str]
|
||||
post_author: List[str]
|
||||
downloads: List[str]
|
||||
version: str
|
||||
ignore_updates: bool = False
|
||||
debug: bool = False
|
||||
tracker: str = "rutracker"
|
||||
api_url: str = "https://api.michijackson.xyz"
|
||||
@@ -19,4 +21,4 @@ class AppState:
|
||||
aria2p: Any = None
|
||||
aria2_threads: int = 4
|
||||
|
||||
state = AppState(posts=[], post_titles=[], post_urls=[], post_author=[], downloads=[], download_path="")
|
||||
state = AppState(posts=[], post_titles=[], post_urls=[], post_author=[], downloads=[], download_path="", version="")
|
||||
|
||||
@@ -5,16 +5,27 @@ from core.utils.network.jsonhandler import format_data
|
||||
|
||||
|
||||
|
||||
def get_item_url(item, posts, post_titles): # oftwarelist currentitem, post list (dict), post titles list
|
||||
def get_item_url(item, posts, post_titles): # softwarelist currentitem, post list (dict), post titles list
|
||||
post_index = post_titles.index(item)
|
||||
if 0 <= post_index < len(post_titles):
|
||||
if state.tracker == "uztracker":
|
||||
item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./")
|
||||
if state.debug:
|
||||
print("Found post URL: ", item)
|
||||
return item
|
||||
if state.tracker == "rutracker":
|
||||
item_dict = posts[post_index]
|
||||
_, post_links, _ = format_data([item_dict])
|
||||
if state.debug:
|
||||
print("Found post URL: ", post_links[0])
|
||||
return post_links[0]
|
||||
if state.tracker == "m0nkrus":
|
||||
item_dict = posts[post_index]
|
||||
_, post_links, _ = format_data([item_dict])
|
||||
if state.debug:
|
||||
print("Found post URL: ", post_links[0])
|
||||
return post_links[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -22,6 +33,8 @@ def get_item_url(item, posts, post_titles): # oftwarelist currentitem, post list
|
||||
def get_magnet_link(post_url):
|
||||
try:
|
||||
response = requests.get(post_url) # eventually impl. cloudscraper
|
||||
if state.debug:
|
||||
print("Sent Request to retrieve Magnet Link...")
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:'))
|
||||
|
||||
@@ -22,7 +22,7 @@ def kill_aria2server():
|
||||
if proc.name() == process:
|
||||
proc.kill()
|
||||
if state.debug:
|
||||
print("Killed Aria2c")
|
||||
print(f"Killed Aria2c (PID {proc.pid})")
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import threading
|
||||
def download_selected(item, posts, post_titles):
|
||||
if item is not None:
|
||||
if state.debug:
|
||||
print(f"network {item.text()}")
|
||||
print(f"Downloading {item.text()}")
|
||||
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
|
||||
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import requests
|
||||
from core.utils.data.state import state
|
||||
|
||||
def check_for_updates():
|
||||
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Failed to fetch releases: {response.status_code}")
|
||||
exit(1)
|
||||
|
||||
releases = response.json()
|
||||
releases.sort(key=lambda r: r["published_at"], reverse=True)
|
||||
|
||||
latest_release = releases[0]
|
||||
latest_version = latest_release["name"]
|
||||
assets = latest_release["assets"]
|
||||
|
||||
|
||||
if state.version == "dev":
|
||||
if state.debug:
|
||||
print("Dev release detected, skipping version check")
|
||||
return None, None
|
||||
elif latest_version != state.version:
|
||||
if state.debug:
|
||||
print(f"New release available: {latest_version}")
|
||||
if assets:
|
||||
if state.debug:
|
||||
print("Assets:")
|
||||
for asset in assets:
|
||||
print(f" - {asset['name']}: {asset['browser_download_url']}")
|
||||
return assets, latest_version
|
||||
else:
|
||||
if state.debug:
|
||||
print("Already up-to-date.")
|
||||
return None, None
|
||||
@@ -2,7 +2,7 @@ from core.interface.gui import MainWindow
|
||||
from core.utils.data.state import state
|
||||
from core.network.aria2_integration import aria2server
|
||||
from core.network.aria2_integration import send_notification
|
||||
from core.utils.general.shutdown import kill_aria2server, closehelper, shutdown_event
|
||||
from core.utils.general.shutdown import closehelper, shutdown_event
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.utils.config.config import read_config
|
||||
from PySide6 import QtWidgets
|
||||
@@ -10,12 +10,13 @@ import qdarktheme
|
||||
import darkdetect
|
||||
import threading
|
||||
import signal
|
||||
import atexit
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
# Debug Output
|
||||
state.debug = True
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--debug", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
def run_gui():
|
||||
app = QtWidgets.QApplication([])
|
||||
@@ -36,6 +37,7 @@ def keyboardinterrupthandler(signum, frame):
|
||||
|
||||
if __name__ == "__main__":
|
||||
read_config()
|
||||
state.debug = args.debug # override of read_config
|
||||
if state.debug:
|
||||
print("Starting Aria2 Server")
|
||||
state.aria2process = run_aria2server()
|
||||
|
||||
Reference in New Issue
Block a user