Compare commits

...

179 Commits

Author SHA1 Message Date
Vxrtrauter a11c1d7129 added dev release for version check 2025-10-19 03:23:37 +02:00
Vxrtrauter dcc78ade44 fixed qtablewidget colors 2025-10-19 03:13:08 +02:00
KeksNino 10e80ab4b6 fix row count 2025-10-19 02:58:02 +02:00
KeksNino 3290ff3fac switch to spreadsheet view in softwareList
QTableWidget
2025-10-19 02:56:10 +02:00
Vxrtrauter d5d99d6a0e fix debug outputs for update checker 2025-10-18 23:55:25 +02:00
Vxrtrauter 3960612f94 fix settings icon not showing up 2025-10-18 23:47:54 +02:00
KeksNino a37a47b65b add update checker file for windows 2025-10-18 22:40:25 +02:00
KeksNino 76b26664f2 add updater for windows (experimental) 2025-10-18 22:37:05 +02:00
KeksNino c8a1e72107 remove test.txt 2025-10-18 22:09:22 +02:00
KeksNino 7c505f1402 this is a test 2025-10-18 16:45:09 +02:00
KeksNino 55e4f86a39 update workflows 2025-10-18 15:59:50 +02:00
KeksNino bd71b0e645 update workflows 2025-10-18 15:55:48 +02:00
KeksNino 2a86e4b774 update workflows 2025-10-18 15:50:16 +02:00
shayaa d17b4bd44a add & impl. m0nkrus scraperM 2025-10-18 02:32:33 +02:00
Vxrtrauter 10c935e852 fix tracker.py to handle m0nkrus scraper for downloads 2025-10-18 02:31:00 +02:00
Vxrtrauter bd8320a26c improve monkrus scraper for more results 2025-10-18 02:25:15 +02:00
Vxrtrauter c99ce339e0 fully impl. monkrus scraper (+ add search logic to scraper) 2025-10-18 01:49:00 +02:00
Vxrtrauter 1822a150d7 finnish monkrus scraper (probably) 2025-10-18 01:33:12 +02:00
Vxrtrauter 7e5e3033e6 add scraping logic 2025-10-18 01:22:11 +02:00
Vxrtrauter 1cab671a13 add more testing stuff 2025-10-18 01:08:36 +02:00
Vxrtrauter 5637e80657 added some more testing stuff 2025-10-17 21:23:17 +02:00
Vxrtrauter 2f78b812ba add besic scraping with message retrieving logic; still featuring full testing & debug outpus 2025-10-17 20:23:44 +02:00
Vxrtrauter 5af9e79ecc Refractor searchhelper to finally be readable, reverd uztracker scraper to old format since i didn't like it 2025-10-16 22:55:52 +02:00
shayaa 4dadc9ba50 Add debug feature 2025-10-16 15:55:39 +02:00
Vxrtrauter 719c4e86e8 improved cache debug 2025-10-16 15:53:05 +02:00
Vxrtrauter 999c8e9ba4 improved debug output 2025-10-16 15:49:00 +02:00
Vxrtrauter 3f6889da56 improve debug messages 2025-10-16 15:40:43 +02:00
Vxrtrauter 69ac739be6 add debug arg 2025-10-16 15:36:10 +02:00
shayaa 141cc8fadc Update README.md 2025-10-14 23:13:14 +02:00
shayaa 80c6ece72e Update README.md 2025-10-14 23:12:34 +02:00
shayaa 3f0f454578 revert some workflow changes 2025-10-14 10:52:58 +02:00
Vxrtrauter 45e614ed86 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-14 10:42:52 +02:00
Vxrtrauter 43baeed7d3 Added "No Results" Text to GUI 2025-10-14 10:42:41 +02:00
shayaa e34ff32dbd Update README.md 2025-10-14 10:31:45 +02:00
shayaa 7990480843 fix dev workflow issues 2025-10-14 10:30:45 +02:00
shayaa 000278ec6b update dev workflow to use dev tag for releases 2025-10-14 10:23:28 +02:00
shayaa 931faacb0b update dev workflow to release builds publicly as pre-release 2025-10-14 10:16:42 +02:00
Vxrtrauter 6de5337e77 fix: update no search result handling to work 2025-10-14 09:06:51 +02:00
Vxrtrauter 991ea47b40 remove unnessecary 2025-10-12 20:46:06 +02:00
Vxrtrauter adc0d1c552 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-12 20:27:03 +02:00
Vxrtrauter 5215fb2e1b pribably fixed notifications 2025-10-12 20:26:34 +02:00
KeksNino de2fc1ca42 remove unnecessary comments 2025-10-12 20:09:38 +02:00
KeksNino 6e2860cb67 add downloads tab functionality 2025-10-12 20:08:35 +02:00
KeksNino 65a5b8cdbf hide aria2 metadata in state.downloads 2025-10-12 17:06:00 +02:00
Vxrtrauter 30e920675d update github actions 2025-10-12 15:25:39 +02:00
Vxrtrauter 7d77a88245 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-12 02:13:25 +02:00
Vxrtrauter d9314c979e fix aria2 window showing up for binaries, added error handling for aria2 killing 2025-10-12 02:13:18 +02:00
shayaa 4bd6761592 Update dev.yml 2025-10-12 01:47:43 +02:00
Vxrtrauter e41b142023 fix filenames 2025-10-12 01:39:32 +02:00
Vxrtrauter 97b604eec1 add write permissions for workflow 2025-10-12 01:31:49 +02:00
Vxrtrauter 94c0673714 update workflow to hide console 2025-10-12 01:28:32 +02:00
Vxrtrauter 00c6babc40 rename github workflows 2025-10-12 01:26:28 +02:00
Vxrtrauter 7cd342effc update github workflows 2025-10-12 01:26:03 +02:00
Vxrtrauter ac8f48ed56 ci: set up build workflow for SoftwareManager dev releases 2025-10-12 01:24:54 +02:00
Vxrtrauter f59c8227c2 update github workflow 2025-10-12 01:23:33 +02:00
Vxrtrauter 8c787db331 eventually fix github actions? 2025-10-12 01:20:26 +02:00
Vxrtrauter a0f772def8 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-12 01:07:55 +02:00
Vxrtrauter 0537bc23e3 fix aria2 process killing completely (cross-platform) 2025-10-12 01:07:53 +02:00
KeksNino 962ce8984c add downloading items to download tab 2025-10-12 00:50:40 +02:00
Vxrtrauter f358d5462f refractor files and improve aria2 process killing, needs improvement 2025-10-11 23:52:25 +02:00
Vxrtrauter 251f9f40de cleanup gui file, splitting and general improvements 2025-10-11 22:40:11 +02:00
Vxrtrauter 9d0033dcb5 refractor: add create_tab to utils of interface 2025-10-11 22:26:15 +02:00
Vxrtrauter 92a6fbc9b5 gui cleanup 2025-10-11 21:39:12 +02:00
Vxrtrauter 77343e6300 fixed shutdown issues, added shutdown event for download notification 2025-10-11 20:49:42 +02:00
Vxrtrauter c151b87da9 fixer api thread error spam message for notification 2025-10-11 20:41:16 +02:00
Vxrtrauter a9450435d7 fixed error messages spamming in console + added debug messages 2025-10-11 20:35:21 +02:00
KeksNino e4c50e20df fixed notifications (kinda) 2025-10-11 15:31:31 +02:00
KeksNino b5d944278b show post author (very ugly as of now) 2025-10-11 00:03:34 +02:00
KeksNino 223dcd9a4c temporarily disable notifications because of bug 2025-10-10 23:10:09 +02:00
Vxrtrauter 12f7712af3 unnessary fixes 2025-10-10 22:23:04 +02:00
Vxrtrauter 108ba9ab31 partial fix: fix function call for download progress 2025-10-10 22:15:24 +02:00
Vxrtrauter 9a7985696c add threading to download bar to prevent freezing 2025-10-10 22:03:06 +02:00
Vxrtrauter ea9288dcf7 refractor gui.py 2025-10-10 21:52:49 +02:00
Vxrtrauter 2659b1da79 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-10 21:52:09 +02:00
Vxrtrauter 1af30922d2 refactor: update GUI layout and improve user experience 2025-10-10 21:49:37 +02:00
KeksNino 335845b9dd add notification function (not working) 2025-10-10 21:48:09 +02:00
KeksNino 573889721f add download speed limit setting 2025-10-10 19:44:33 +02:00
KeksNino d7d454a213 add download path setting 2025-10-10 19:10:11 +02:00
Vxrtrauter b45a4895f5 fully impl settings file read & write 2025-10-10 15:21:48 +02:00
Vxrtrauter f1c92448e7 refactor, improve config creation 2025-10-10 15:05:36 +02:00
Vxrtrauter c06fac0f55 fixed settings write bug and add a comment-style title 2025-10-10 14:10:11 +02:00
KeksNino b0596e9710 create config file. doesnt load yet 2025-10-10 02:41:49 +02:00
Vxrtrauter 62b8506d13 Update everything to work with new API + Reorganize file structurem functions and common logic 2025-10-09 22:12:10 +02:00
Vxrtrauter 53b2bb912e INCOMPLETE, DONT PUSH:
refactor: reorganize state management and update import paths across modules and start changing json parsing
2025-10-09 16:42:18 +02:00
Vxrtrauter 5da7989157 refactor: consolidate aria2 process management into state and remove global variables 2025-10-09 15:55:39 +02:00
Vxrtrauter 13d0384a64 refactor: replace global tracker usage with state management in GUI and tracker utilities 2025-10-09 15:25:59 +02:00
Vxrtrauter 171a61b8ae refactor: update import paths and reorganize utility modules 2025-10-09 15:21:15 +02:00
Vxrtrauter 1f4fbb845a refactor: streamline scraper functions to utilize centralized state management 2025-10-09 15:10:18 +02:00
Vxrtrauter 1b0a00068c refactor: reorganize tracker utility functions and add settings management 2025-10-09 14:45:12 +02:00
Vxrtrauter 70035cc255 refactor: remove unnecessary comments from get_post_title function 2025-10-09 14:33:51 +02:00
Vxrtrauter 26b091319b chore: update dependencies in requirements.txt 2025-10-09 14:33:22 +02:00
Vxrtrauter d2a2212b15 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-09 14:31:27 +02:00
Vxrtrauter 0184b10a5f refactor: move get_item_index and get_magnet_link functions to tracker_utils.py 2025-10-09 14:31:21 +02:00
KeksNino 662751d7db tried to made uztracker scraper async
not currently working
2025-10-09 14:17:37 +02:00
Vxrtrauter cdb9257859 rename settings pngs according to color and update gui to use new asset names 2025-10-07 15:49:14 +02:00
Vxrtrauter 03ee9c299a Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-06 03:41:52 +02:00
Vxrtrauter 6c54692155 add comments for myself (splitting soon) 2025-10-06 03:41:33 +02:00
KeksNino 199b59b7da add feature to change api server url 2025-10-06 02:54:07 +02:00
Vxrtrauter 2428bddb9f refactor: pass server URL to scrape_rutracker function 2025-10-06 02:43:41 +02:00
KeksNino 2cc4b9e815 add api server url and fix check if website is up 2025-10-06 02:40:32 +02:00
KeksNino dfc44e3534 fix settings icon path 2025-10-06 02:22:30 +02:00
KeksNino a568e14db4 Update main.yml 2025-10-06 01:40:34 +02:00
Vxrtrauter 817ea56001 add __init__.py files to core, data, and network directories 2025-10-06 01:20:03 +02:00
Vxrtrauter d3063cd148 update gitignore (github template + additions) 2025-10-06 01:01:56 +02:00
Vxrtrauter 2d8876868f Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-10-06 00:56:17 +02:00
Vxrtrauter 1c5460de94 improve overall folder structure and update files according to structure 2025-10-06 00:56:15 +02:00
KeksNino 6a12cb4314 fix check to see if website is up 2025-10-06 00:55:25 +02:00
Vxrtrauter 2d264d7d1c improve folder structure for scrapers 2025-10-06 00:43:36 +02:00
KeksNino 1ac9496e46 change check to see if website is up 2025-10-06 00:42:46 +02:00
KeksNino 0c61ac5319 remove unnecessary comments and soup object 2025-10-06 00:35:37 +02:00
Vxrtrauter 6a34c15457 move assets to ui 2025-10-06 00:24:27 +02:00
Vxrtrauter b925503562 add gui 2025-10-06 00:19:58 +02:00
Vxrtrauter bae3667cd9 remove gui 2025-10-06 00:19:46 +02:00
KeksNino ca28097dd9 add more search results for uztracker 2025-10-06 00:10:54 +02:00
KeksNino 7c9bad2451 added download button widget into search tab 2025-10-05 23:27:34 +02:00
Vxrtrauter b8af01927d fix: update server URL to include HTTPS in rutracker scraper 2025-10-05 23:16:51 +02:00
Vxrtrauter b284a06708 fix: update server URL in rutracker scraper 2025-10-02 01:19:49 +02:00
Vxrtrauter 9394b5fa01 feat: add downloads tab and update UI components 2025-09-25 15:24:15 +02:00
shayaa 3c7450cee1 fix: rm unused imports 2025-09-21 02:25:01 +02:00
KeksNino eedb5ded17 added progress bar 2025-09-21 02:16:17 +02:00
KeksNino 8a856f0dad better tab implementation (not done yet) 2025-09-21 00:54:43 +02:00
KeksNino 01217c21af initial implementation of tabs 2025-09-18 19:20:42 +02:00
KeksNino 59f092f937 Revert "initial implementation of tabs"
This reverts commit ba6fb26e8ef32c56483de7969e9074da6507aee8.
2025-09-18 19:20:42 +02:00
KeksNino 154d06caaf initial implementation of tabs 2025-09-18 19:20:42 +02:00
Vxrtrauter 4ee1f366aa Add "No Results" result on no results &cleanup (NOTE: STILL REQUIRES ERROR HANDLING & OVERALL IMPROVEMENT) 2025-09-16 23:10:11 +02:00
Vxrtrauter 9fbaca0b2b fix: rm unaccessed imports 2025-09-15 23:38:29 +02:00
Vxrtrauter 75ff4e4ecd improve performance and avoic freezes using threading; code cleanup, remove unnecessary functions 2025-09-13 02:13:57 +02:00
Vxrtrauter 66d43faad1 add threads setting in settings menu + add aria2c restart function in gui.py 2025-09-13 01:50:37 +02:00
Vxrtrauter 1c28d8e232 Fixed Aria2 Errors, Automatically kill it, make it work with VPNs 2025-09-12 00:54:00 +02:00
Vxrtrauter 71ab31b537 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-11 20:48:43 +02:00
Vxrtrauter af1488d980 fix: add validation for empty search input in return_pressed method 2025-09-11 20:48:37 +02:00
shayaa 32c9f4a391 Update README.md 2025-09-11 20:43:19 +02:00
Vxrtrauter 664ac9b48f fix: update server URL and implement threading for search functionality 2025-09-11 20:24:25 +02:00
Vxrtrauter 7035eec0b5 add error handling for no search result on rutracker + debug outputs 2025-09-10 22:45:28 +02:00
Vxrtrauter bdad958e25 fix: correct dark mode detection function call 2025-09-09 13:42:36 +02:00
Vxrtrauter a95cc197be fix: correct function call for dark mode detection 2025-09-09 13:26:46 +02:00
Vxrtrauter 05c60104c4 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-09 13:05:43 +02:00
Vxrtrauter c1ee2b7c60 add tracker selection dropdown menu and impl functionality 2025-09-09 13:05:41 +02:00
Vxrtrauter 8033c56145 improve structure, code cleanup and improved switching between uz & rutracker 2025-09-09 01:31:00 +02:00
shayaa a3471fd100 Update README.md 2025-09-08 23:51:20 +02:00
KeksNino cf46ee1c78 fixed github merge issue 2025-09-08 21:33:34 +02:00
KeksNino e3ca73e600 added rutracker as option. check full message
IMPORTANT: doesnt have error handling fo no result

will add a gui option in the future
2025-09-08 21:28:42 +02:00
Vxrtrauter 8eacbc4bc5 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-07 22:28:55 +02:00
Vxrtrauter 9b9b50db9b rm old scaper files 2025-09-07 22:28:51 +02:00
KeksNino 764002f4de Update README.md 2025-09-07 20:16:19 +02:00
Vxrtrauter 72a892d707 Refactor scrape_uztracker call in GUI to handle no results more gracefully and improve debug output 2025-09-06 01:10:48 +02:00
Vxrtrauter 01303234d6 Refactor GUI to utilize get_item_index for improved item selection and download handling 2025-09-06 01:00:12 +02:00
Vxrtrauter 87380f57af Refactor init_uztracker to be asynchronous for improved performance 2025-09-06 00:32:25 +02:00
Vxrtrauter b701e75bdf Refactor uztracker scraper to handle site availability checks and improve error handling 2025-09-05 19:10:46 +02:00
KeksNino 922897ebc5 removed unneccesary gui stuff 2025-09-05 17:24:21 +02:00
Vxrtrauter 9578326a1a Refactor button connection comments for clarity in GUI interactions 2025-09-05 00:04:01 +02:00
Vxrtrauter bf6e638519 Add debug print statement for retrieved magnet link 2025-09-04 20:39:54 +02:00
Vxrtrauter addd432229 Implement debug mode for scraping and GUI interactions 2025-09-04 20:36:59 +02:00
KeksNino 9d6e54b451 added more of settings dialog 2025-09-04 19:28:13 +02:00
KeksNino 6aa43ae2c5 added 1% of settings menu 2025-09-04 18:48:07 +02:00
Vxrtrauter 5929c218b7 Add pyqtdarktheme to requirements for improved theme support 2025-09-03 22:26:56 +02:00
Vxrtrauter 7edec4ace5 Files Cleanup 2025-09-03 22:21:03 +02:00
Vxrtrauter 7cc79e69cb Code Cleanup 2025-09-03 22:16:30 +02:00
Vxrtrauter 4ddccc2341 Fix global settings action button icon and improve theme handling in GUI 2025-09-03 22:04:36 +02:00
Vxrtrauter 09a3a5a7a2 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-03 21:42:47 +02:00
Vxrtrauter 391e4c372b Refactor asset paths for settings icons in the GUI and reorganize main.py structure 2025-09-03 21:42:34 +02:00
KeksNino 9793558b7c fix github actions workflow 2025-09-03 21:24:45 +02:00
Vxrtrauter 5bfa06239a Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-03 21:23:38 +02:00
Vxrtrauter 25e1647a13 Fix import paths for uztracker_scraper and downloading modules 2025-09-03 21:23:35 +02:00
KeksNino e39c386e55 add github actions workflow 2025-09-03 21:22:43 +02:00
Vxrtrauter 70c7042b09 Start download client when a magnet link is added 2025-09-03 21:09:41 +02:00
Vxrtrauter 8716000057 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-03 21:06:55 +02:00
Vxrtrauter 0307f0fa82 Fix method call for getting item index in download functionality 2025-09-03 21:06:51 +02:00
KeksNino 091a534ceb fix wrong elements being aligned right 2025-09-03 20:56:48 +02:00
KeksNino aa31b85365 Update README.md 2025-09-03 19:37:14 +02:00
KeksNino 908e559805 added requirements.txt 2025-09-03 19:21:23 +02:00
KeksNino cdde3e12d9 added settings button 2025-09-03 19:03:35 +02:00
Vxrtrauter 60fd806f1e Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2025-09-03 14:28:10 +02:00
Vxrtrauter e80bd9569f Implement download functionality in GUI and connect button to item selection 2025-09-03 14:20:45 +02:00
KeksNino ebdedb2536 readded print selected software function
and also returns the selected software
2025-08-20 17:37:50 +02:00
KeksNino a94048188e removed unused imports and add download button click in gui 2025-08-01 16:15:07 +02:00
KeksNino 69892861a5 added other files from main-cli 2025-07-29 19:53:41 +02:00
KeksNino bf3516ee02 updated main branch 2025-07-29 19:52:14 +02:00
KeksNino e48375bce1 Rebrand in README 2025-07-29 19:41:02 +02:00
33 changed files with 1439 additions and 307 deletions
+142
View File
@@ -0,0 +1,142 @@
name: Build Executables (Dev)
on:
push:
branches:
- main
workflow_dispatch:
inputs:
version:
description: "Dev release version (e.g., v1.0.0-dev)"
required: true
default: "v1.0.0"
permissions:
contents: write
env:
PYTHON_VERSION: "3.11"
APP_NAME: "SoftwareManager"
jobs:
build:
name: Build ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
artifact_name: SoftwareManager-dev-linux
asset_name: SoftwareManager-dev-linux
- os: windows-latest
artifact_name: SoftwareManager-dev-windows.exe
asset_name: SoftwareManager-dev-windows.exe
- os: macos-latest
artifact_name: SoftwareManager-dev-macos
asset_name: SoftwareManager-dev-macos
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-shape0 \
libxcb-util1
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: Build executable with PyInstaller
run: pyinstaller --onefile --windowed --name ${{ env.APP_NAME }} main.py
- name: Make executable (Unix)
if: runner.os != 'Windows'
run: chmod +x dist/${{ env.APP_NAME }}
- name: Rename artifact
shell: bash
run: |
cd dist
mv ${{ env.APP_NAME }}${{ runner.os == 'Windows' && '.exe' || '' }} ${{ matrix.artifact_name }}
ls -la
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-build
path: dist/${{ matrix.artifact_name }}
retention-days: 1
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
if: success()
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
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
cp artifacts/ubuntu-latest-build/SoftwareManager-dev-linux release/SoftwareManager-dev-linux
cp artifacts/windows-latest-build/SoftwareManager-dev-windows.exe release/SoftwareManager-dev-windows.exe
cp artifacts/macos-latest-build/SoftwareManager-dev-macos release/SoftwareManager-dev-macos
chmod +x release/SoftwareManager-dev-linux release/SoftwareManager-dev-macos
ls -la release/
- 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: false
files: |
release/SoftwareManager-dev-linux
release/SoftwareManager-dev-windows.exe
release/SoftwareManager-dev-macos
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+216 -2
View File
@@ -1,2 +1,216 @@
__pycache__
.venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# IDE's and editors
.vscode/
.idea/
*.sublime-workspace
*.sublime-project
*.code-workspace
*.swp
+26 -45
View File
@@ -1,63 +1,44 @@
# PirateLauncher - A Download Manager/Launcher
## Description
PirateLauncher is a Python-based GUI/TUI tool that simplifies downloading and managing pirated software from various sources.
# SoftwareManager - A Download Manager/Launcher
SoftwareManager is a Python-based GUI/TUI tool that simplifies downloading and managing free software from various sources.
## Features
- VPN Binding (impl. soon)
- Bandwidth/Download speed limiting
- Start downloads via a predefined magnet URI.
- View detailed download information (name, speed, and status).
## Requirements
- Python 3.x
- `aria2c` (installed and available in your PATH)
- Python libraries:
- `aria2p`
- `keyboard`
## Installation
1. Download the binary file for your operating system from releases
https://github.com/KeksPirates/SoftwareManager/releases
2. Run the file
## Manual Installation
1. Ensure Python 3.x is installed on your system.
2. Install Aria2:
2. Install Dependencies:
```bash
sudo apt install aria2 # For Debian/Ubuntu/Mint
paru/yay -S aria2 # For Arch from AUR
brew install aria2 # For macOS
choco install aria2 # For Windows
pip install -r requirements.txt
```
4. Install the required Python libraries:
3. Run main.py:
```bash
pip install aria2p keyboard
python3 main.py
```
## Usage
1. Clone or download PirateLauncher to your local machine.
2. Run the launcher:
```bash
python pirate_launcher.py
```
3. Use the following keyboard controls:
- Press **`d`** to start/resume a download.
- Press **`s`** to pause all downloads.
- Press **`q`** to quit the launcher.
## Configuration
- To use a different magnet URI, update the `magnet_uri` variable in the script.
- Ensure the RPC port (default: 6800) is open and accessible.
## Example
Run the following command to launch PirateLauncher and control downloads:
```bash
python pirate_launcher.py
```
Press `d` to start the predefined download, `s` to pause, and `q` to quit.
## Notes
- Aria2 must be installed and accessible from your command line.
- 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:** PirateLauncher is intended for legal and ethical use only. Ensure compliance with applicable laws and regulations when using this tool.
**Disclaimer:** SoftwareManager is intended for legal and ethical use only. Ensure compliance with applicable laws and regulations when using this tool.
View File
View File
+41
View File
@@ -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)
+22
View File
@@ -0,0 +1,22 @@
import requests
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
except Exception:
if state.debug:
print("No results found / No response from server")
return None
else:
return None
# This function utilizes the SoftwareManager server - source code can be found under the "server" branch.
+88
View File
@@ -0,0 +1,88 @@
import requests
import asyncio
import aiohttp
from bs4 import BeautifulSoup
from core.utils.data.state import state
global url_uztracker
url_uztracker = "https://uztracker.net/tracker.php?nm="
async 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
if state.debug:
print(f"Uztracker seems down, status code: {response.status_code}")
except requests.exceptions.RequestException as e:
if state.debug:
print(f"\nRequest Exception on {url_uztracker}:")
print(e)
print("\nIs the Site down?")
up = False
asyncio.run(init_uztracker())
def scrape_uztracker(search):
if up:
search_url = url_uztracker + search
if state.debug:
print(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 and state.debug:
print(f"Failed to fetch {search_url}: {e}")
return None
else:
if state.debug:
print("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:
if state.debug:
print("Program not found")
else:
if state.debug:
print("Error: Uztracker down")
return None
-65
View File
@@ -1,65 +0,0 @@
import aria2p
import subprocess
import keyboard
import os
# from .scraper import uri
aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret=""
)
)
download_paused = False
def pauseDownload(_):
global download_paused
if download_paused == True:
print("Error: Download already paused.")
else:
print("Download is being paused...")
download_paused = True
aria2.pause_all()
def startDownload(_):
global download_paused
if download_paused == True:
aria2.resume_all()
print("Download Resumed!")
download_paused = False
else:
print("Download Started!")
download = aria2.add_magnet(uri)
download_paused = False
def terminate():
return
# print("Enter Download URL: ")
# uri = input()
#Magnet Download
# downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads")
# subprocess.Popen(["aria2c", "--enable-rpc", "--rpc-listen-all=true", f"--dir={downloads_dir}"])
# magnet_uri = "magnet:?xt=urn:btih:7FBF03CCC9115260DC50102E5A31E83E91422A79&dn=Ready+or+Not%3A+LSPD+Bundle+%28v66884+%2B+4+DLCs%2FBonuses+%2B+Windows+7+Fix%2C+MULTi12%29+%5BFitGirl+Repack%2C+Selective+Download+-+from+34.7+GB%5D&tr=udp%3A%2F%2Fopentor.net%3A6969&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.theoks.net%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.ccp.ovh%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=https%3A%2F%2Ftracker.tamersunion.org%3A443%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bt4g.com%3A2095%2Fannounce&tr=udp%3A%2F%2Fbt2.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.filemail.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker1.bt.moack.co.kr%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=http%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce"
# #Torrent Download
# downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads")
# subprocess.Popen(["aria2c", "--enable-rpc", "--rpc-listen-all=true", f"--dir={downloads_dir}"])
# torrent_path = "C:/Users/Downloads/Ready or Not.torrent"
#Direct Download
downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads")
subprocess.Popen(["aria2c", "--enable-rpc", "--rpc-listen-all=true", f"--dir={downloads_dir}"])
uri
downloads = aria2.get_downloads()
for download in downloads:
print(download.name, downloads.download_speed, download.status)
keyboard.on_press_key("d", startDownload, suppress=False)
keyboard.on_press_key("s", pauseDownload, suppress=False)
keyboard.on_press_key("q", pauseDownload, terminate)
keyboard.wait("q")
-68
View File
@@ -1,68 +0,0 @@
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLayoutItem, QLineEdit, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem, QLabel, QPushButton
import sys
from uztracker_scraper import scrape_uztracker
class MainWindow(QtWidgets.QMainWindow, QWidget):
def __init__(self):
super().__init__()
searchresults = []
self.setWindowTitle("Software Manager")
self.setGeometry(100, 100, 800, 600)
self.controls = QWidget()
self.controlsLayout = QVBoxLayout()
# Widgets
self.searchbar = QLineEdit()
self.searchbar.setPlaceholderText("Search for software...")
self.searchbar.setClearButtonEnabled(True)
self.searchbar.setMinimumHeight(30)
self.searchbar.returnPressed.connect(self.return_pressed)
self.button = QtWidgets.QPushButton("Download")
self.softwareList = QListWidget()
container = QWidget()
containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar)
containerLayout.addWidget(self.softwareList)
self.softwareList.addItems(searchresults)
containerLayout.addWidget(self.button)
self.button.clicked.connect(lambda: print(f"Downloading {self.softwareList.currentItem().text()}"), )
container.setLayout(containerLayout)
self.setCentralWidget(container)
containerLayout.setAlignment(Qt.AlignmentFlag.AlignBottom)
def return_pressed(self):
search_text = self.searchbar.text()
self.postnames, self.postlinks = scrape_uztracker(search_text)
self.softwareList.clear()
if self.postnames:
self.softwareList.addItems(self.postnames)
print("User searched for:", search_text)
def run_gui():
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
sys.exit(app.exec())
if __name__ == "__main__":
run_gui()
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+122
View File
@@ -0,0 +1,122 @@
from core.utils.config.settings import save_settings
from core.utils.data.state import state
from PySide6 import QtWidgets
from PySide6.QtWidgets import (
QLineEdit,
QPushButton,
QWidget,
QVBoxLayout,
QDialog,
QLabel,
QHBoxLayout,
QSpinBox,
)
def settings_dialog(self):
if state.debug:
print("Settings dialog opened")
dialog = QDialog(self)
dialog.setWindowTitle("Settings")
dialog.setFixedSize(800, 400)
dialog.setLayout(QVBoxLayout())
dialog.layout().addWidget(QLabel("Settings"))
def close_settings():
dialog.reject()
##################
# THREAD SETTING #
##################
thread_box = QSpinBox()
thread_box.setMinimum(1)
thread_box.setMaximum(16)
thread_box.setValue(state.aria2_threads)
# container for tight space
thread_container = QWidget()
thread_layout = QHBoxLayout()
thread_layout.addWidget(QLabel("Threads:"))
thread_layout.addWidget(thread_box)
thread_container.setLayout(thread_layout)
thread_container.setMaximumHeight(80)
# Dimensions
thread_box.setFixedWidth(90)
thread_box.setFixedHeight(30)
dialog.layout().addWidget(thread_container)
##################
# SERVER SETTING #
##################
api_url_container = QWidget()
api_url_layout = QHBoxLayout()
api_url = QLineEdit()
api_url_layout.addWidget(QLabel("API Server URL:"))
api_url_layout.addWidget(api_url)
api_url_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
api_url_container.setLayout(api_url_layout)
api_url.setText(state.api_url)
dialog.layout().addWidget(api_url_container)
#################
# DOWNLOAD PATH #
#################
download_path_container = QWidget()
download_path_layout = QHBoxLayout()
download_path = QLineEdit()
download_path_layout.addWidget(QLabel("Download Path:"))
download_path_layout.addWidget(download_path)
download_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
download_path_container.setLayout(download_path_layout)
download_path.setText(state.download_path)
dialog.layout().addWidget(download_path_container)
##################
# SPEED LIMITING #
##################
speed_limit_container = QWidget()
speed_limit_layout = QHBoxLayout()
speed_limit_layout.addWidget(QLabel("Max Download Speed (KiB, 0 for unlimited): "))
speed_limit = QSpinBox()
speed_limit.setMinimum(0)
speed_limit.setMaximum(10000000)
speed_limit.setValue(state.speed_limit)
speed_limit_container.setLayout(speed_limit_layout)
speed_limit_layout.addWidget(speed_limit)
speed_limit.setFixedWidth(180)
speed_limit.setFixedHeight(30)
dialog.layout().addWidget(speed_limit_container)
###############
# SAVE/CANCEL #
###############
layout = QHBoxLayout()
save_btn = QPushButton("Save")
cancel_btn = QPushButton("Cancel")
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)
layout.addWidget(cancel_btn)
layout.addWidget(save_btn)
dialog.layout().addLayout(layout)
dialog.exec()
+243
View File
@@ -0,0 +1,243 @@
from PySide6 import QtWidgets
from PySide6 import QtCore
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel
from PySide6.QtWidgets import (
QLineEdit,
QTableView,
QWidget,
QVBoxLayout,
QListWidget,
QToolBar,
QLabel,
QHBoxLayout,
QComboBox,
QTabWidget,
QProgressBar,
QHeaderView,
QMessageBox,
QTableWidget,
QTableWidgetItem
)
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QPalette
import darkdetect
import threading
import platform
import requests as r
import os
import subprocess
import time
import sys
from core.utils.config.settings import save_settings
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
def download_update(latest_version):
filename = "SoftwareManager-dev-windows.exe"
url = "https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-windows.exe"
print("Downloading update...")
if os.path.exists(filename):
os.remove(filename)
response = r.get(url, allow_redirects=True)
with open(filename, "wb") as f:
f.write(response.content)
if not os.path.exists(filename):
raise FileNotFoundError(f"Executable not found")
subprocess.Popen([filename], shell=True)
time.sleep(0.5)
save_settings(version=latest_version)
sys.exit(0)
class MainWindow(QtWidgets.QMainWindow, QWidget):
def __init__(self):
super().__init__()
# Check for updates on Windows
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)
self.controls = QWidget()
self.controlsLayout = QVBoxLayout()
# Widgets
self.searchbar = QLineEdit()
self.searchbar.setPlaceholderText("Search for software...")
self.searchbar.setClearButtonEnabled(True)
self.searchbar.setMinimumHeight(30)
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.libraryList = QListWidget()
self.emptyResults = QLabel("No Results")
self.emptyResults.setAlignment(Qt.AlignCenter)
self.emptyResults.hide()
self.download_model = None
self.downloadList = QTableView()
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.qtablewidget)
class DownloadModel(QAbstractTableModel):
def __init__(self):
super().__init__()
self.headers = ["Name", "Status", "Progress", "Speed", "Size", "Total Size"]
def rowCount(self, parent=QModelIndex()):
return len(state.downloads)
def columnCount(self, parent=QModelIndex()):
return len(self.headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.headers[section]
return None
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
download = state.downloads[index.row()]
col = index.column()
if col == 0:
return download.name
elif col == 1:
return getattr(download, 'status', 'Downloading')
elif col == 2:
return f"{int(download.progress)}%"
elif col == 3:
return f"{download.download_speed_string()}"
elif col == 4:
pass
elif col == 5:
return f"{download.total_length_string()}"
return None
self.download_model = DownloadModel()
self.downloadList.setModel(self.download_model)
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.qtablewidget.currentItem(), state.posts, state.post_titles))))
container.setLayout(containerLayout)
self.setCentralWidget(container)
# Tabs
self.tabs = QTabWidget()
self.horizontal_layout = QHBoxLayout()
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
self.horizontal_layout.addWidget(self.qtablewidget)
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)
containerLayout.setAlignment(Qt.AlignmentFlag.AlignBottom)
toolbar = QToolBar("Main Toolbar")
self.addToolBar(toolbar)
toolbar.setLayoutDirection(Qt.RightToLeft)
self.tracker_list = QComboBox()
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_white.png"), "Settings", self)
else:
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self)
settings_action.triggered.connect(lambda: settings_dialog(self))
toolbar.addAction(settings_action)
toolbar.addWidget(self.tracker_list)
toolbar.setMovable(False)
self.progressbar.setValue(0)
containerLayout.addWidget(self.progressbar)
self.progress_timer = QTimer()
self.progress_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.update_progress)))
self.progress_timer.start(1000)
self.download_timer = QTimer()
self.download_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.download_list_update)))
self.download_timer.start(500)
def download_list_update(self):
if self.download_model:
self.download_model.layoutAboutToBeChanged.emit()
self.download_model.layoutChanged.emit()
def closeEvent(self, event: QCloseEvent):
closehelper()
event.accept()
def set_tracker(self, _):
state.tracker = self.tracker_list.currentText()
def update_progress(self):
progress = dlprogress()
self.progressbar.setValue(progress)
def show_empty_results(self, show: bool):
if show:
self.qtablewidget.hide()
self.emptyResults.show()
else:
self.emptyResults.hide()
self.qtablewidget.show()
+85
View File
@@ -0,0 +1,85 @@
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
if state.debug:
print("User searched for:", search_text)
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:
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 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))
if state.debug == True:
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))
+17
View File
@@ -0,0 +1,17 @@
from PySide6.QtWidgets import QWidget, QVBoxLayout
def create_tab(title, searchbar, software_list, tabs, dlbutton, layout2):
tab = QWidget()
layout = QVBoxLayout()
if searchbar:
layout.addWidget(searchbar)
if layout2:
layout.addLayout(layout2)
else:
layout.addWidget(software_list)
if dlbutton:
layout.addWidget(dlbutton)
tab.setLayout(layout)
tabs.addTab(tab, title)
return tab
View File
+75
View File
@@ -0,0 +1,75 @@
import aria2p
import subprocess
from core.utils.data.state import state
from plyer import notification
import time
import sys
def run_aria2p():
state.aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret=""
)
)
return state.aria2
def aria2server():
download_path = state.download_path
speed_limit = state.speed_limit
cmd = [
"aria2c",
"--enable-rpc",
"--disable-ipv6", # added this since it caused problems with vpns
"--rpc-listen-all",
"--rpc-listen-port=6800",
f"--dir={download_path}",
f"--max-download-limit={speed_limit * 1024}",
"-x", str(state.aria2_threads),
"-s", str(state.aria2_threads),
]
aria2server = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
)
return aria2server
def dlprogress():
try:
state.downloads = [download for download in state.aria2.get_downloads() if not download.is_metadata]
downloads = state.downloads
if downloads:
for download in downloads:
progress = download.progress_string(0)
progress_int = int(progress.strip('%'))
return progress_int
return 0
except:
return 0
def send_notification(shutdown_event):
notified = set()
while not shutdown_event.is_set():
try:
for d in state.aria2.get_downloads():
if d.is_metadata:
continue
if d.progress == 100 and d.gid not in notified:
notification.notify(
title="Download finished",
message=f"{d.name} has finished downloading.",
timeout=4
)
notified.add(d.gid)
state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata]
except Exception:
pass
time.sleep(5)
+16
View File
@@ -0,0 +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")
-23
View File
@@ -1,23 +0,0 @@
from bs4 import BeautifulSoup
import requests
badphrases = {}
badphrases.update({chr(i) for i in range(ord('A'), ord('Z') + 1)})
rutracker_url = "https://rutracker.org/forum/index.php"
def fetch_rutracker_software(url):
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, "html.parser")
links = soup.find_all("a")
games_dict = {}
for link in links:
href = link.get("href", "").lower()
text = link.text.strip()
if text:
games_dict[text] = href
return games_dict
else:
print(f"Failed to retrieve the Rutracker page. Status code: {response.status_code}")
return {}
View File
+45
View File
@@ -0,0 +1,45 @@
import os
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}",
"version": f"{state.version}"}
if platform.system() == "Windows":
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
else:
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
settings_path = os.path.join(config_dir, "SoftwareManager")
os.makedirs(settings_path, exist_ok=True)
with open(os.path.join(settings_path, "config.yml"), 'w') as cf:
config.write(cf)
def read_config():
config = configparser.ConfigParser()
if platform.system() == "Windows":
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
else:
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
settings_path = os.path.join(config_dir, "SoftwareManager")
config_file = os.path.join(settings_path, "config.yml")
if not os.path.exists(config_file):
create_config()
config.read(config_file)
state.debug = config.getboolean("General", "debug")
state.api_url = config.get("General", "api_url")
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.version = config.get("General", "version")
+50
View File
@@ -0,0 +1,50 @@
from core.utils.data.state import state
from .config import create_config
def restart_aria2c():
import main # had to do this because of circle import :(
import signal
import atexit
import time
try:
main.kill_aria2server()
except Exception as e:
print(f"Warning: aria2server kill failed: {e}")
try:
if state.aria2process:
state.aria2process.wait(timeout=3)
except Exception as e:
print(f"Warning: aria2process wait failed: {e}")
time.sleep(0.5)
try:
state.aria2process = main.run_aria2server()
except Exception as e:
print(f"Error: failed to restart aria2server: {e}")
return
try:
signal.signal(signal.SIGINT, main.keyboardinterrupthandler)
atexit.unregister(main.kill_aria2server)
atexit.register(main.kill_aria2server)
except Exception as e:
print(f"Warning: signal/atexit setup failed: {e}")
def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_path=None, speed_limit=None, version=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
if version is not None:
state.version = version
create_config()
restart_aria2c()
close()
+23
View File
@@ -0,0 +1,23 @@
from dataclasses import dataclass
from typing import Optional, Any, List, Dict
from pathlib import Path
@dataclass
class AppState:
posts: List[Dict[str, Any]]
post_titles: List[str]
post_urls: List[str]
post_author: List[str]
downloads: List[str]
version: str
debug: bool = False
tracker: str = "rutracker"
api_url: str = "https://api.michijackson.xyz"
download_path: str = str(Path.home() / "Downloads")
speed_limit: int = 0
aria2process: Optional[Any] = None
aria2: Any = None
aria2p: Any = None
aria2_threads: int = 4
state = AppState(posts=[], post_titles=[], post_urls=[], post_author=[], downloads=[], download_path="", version="")
+52
View File
@@ -0,0 +1,52 @@
import requests
from bs4 import BeautifulSoup
from core.utils.data.state import state
from core.utils.network.jsonhandler import format_data
def get_item_url(item, posts, post_titles): # softwarelist currentitem, post list (dict), post titles list
post_index = post_titles.index(item)
if 0 <= post_index < len(post_titles):
if state.tracker == "uztracker":
item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./")
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
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:'))
if magnet_link:
if state.debug:
print("Magnet Link Retrieved: ", magnet_link['href'])
return magnet_link['href']
else:
if state.debug:
print("Magnet Link not Found!")
except requests.RequestException as e:
if state.debug:
print(f"Failed to fetch {post_url}: {e}")
return None
+31
View File
@@ -0,0 +1,31 @@
from core.utils.data.state import state
import threading
import psutil
import sys
shutdown_event = threading.Event()
def kill_aria2server():
if sys.platform.startswith("win"):
process = "aria2c.exe"
else:
process = "aria2c"
if state.aria2process:
state.aria2process.kill()
if state.debug:
print("Killed Aria2")
try:
for proc in psutil.process_iter():
if proc.name() == process:
proc.kill()
if state.debug:
print(f"Killed Aria2c (PID {proc.pid})")
except psutil.NoSuchProcess:
pass
def closehelper():
shutdown_event.set()
kill_aria2server()
+2
View File
@@ -0,0 +1,2 @@
def run_thread(thread):
thread.start()
+29
View File
@@ -0,0 +1,29 @@
from core.utils.data.state import state
from core.utils.data.tracker import get_item_url
from core.utils.data.tracker import get_magnet_link
from core.network.aria2_wrapper import start_client
from core.network.aria2_wrapper import add_magnet
from core.utils.general.wrappers import run_thread
import threading
def download_selected(item, posts, post_titles):
if item is not None:
if state.debug:
print(f"Downloading {item.text()}")
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
else:
if state.debug:
print("No item selected for download.")
def run_download(item, posts, post_titles):
post_url = get_item_url(item, posts, post_titles)
if state.debug:
print("Selected URL: ", post_url)
magnet_uri = get_magnet_link(post_url)
start_client()
add_magnet(magnet_uri)
+23
View File
@@ -0,0 +1,23 @@
import json
def split_data(data):
p_json = json.loads(data)
count = p_json["count"]
posts = p_json["data"]
query = p_json["query"]
success = p_json["success"]
cached = p_json["cached"]
return count, posts, query, success, cached
def format_data(data):
post_author = [post["author"] for post in data]
post_titles = [post["title"] for post in data]
post_links = [post["url"] for post in data]
return post_titles, post_links, post_author
# https://open.spotify.com/track/70vjXI7oBou5buTtgiJeRW?si=25640cc4243c4bf3
+36
View File
@@ -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
-102
View File
@@ -1,102 +0,0 @@
import requests
from bs4 import BeautifulSoup
global url_uztracker
url_uztracker = "https://uztracker.net/tracker.php?nm=" # placeholder url for now
response = requests.get(url_uztracker) # get da content from da url
soup = BeautifulSoup(response.content, 'html.parser') # create bs object
def scrape_uztracker(search):
search_url = url_uztracker + search
print(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
# print(f"Result found: ({resultCount}) {link.b.text} | {link['href']}")
results.append(link['href'])
resulttitles.append(link.b.text)
result = True
if result:
return resulttitles, results
if not result:
print(f'No Results found for "{search}"')
return
except requests.RequestException as e:
print(f"Failed to fetch {search_url}: {e}")
return None
def get_post_title(post_url):
response = requests.get(post_url)
soup = BeautifulSoup(response.text, 'html.parser')
maintitle = soup.find(class_='tt-text')
if maintitle:
return maintitle.text
else:
print("Program not found")
def select_program():
selection = input("Enter the Number of the Program you want to download: ")
try:
index = int(selection) - 1
if 0 <= index < len(results): # note for myself: py starts counting at 0; check if number is not negative, check if number is not more than list length.
selected = "https://uztracker.net/" + results[index].lstrip("./")
post_url = selected
return selected
else:
print("Invalid Selection.")
except ValueError:
print(Exception)
return
def get_magnet_link(post_url):
try:
response = requests.get(post_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:'))
if magnet_link:
return magnet_link['href']
else:
print("Magnet Link not Found!")
except requests.RequestException as e:
print(f"Failed to fetch {post_url}: {e}")
# What x and x.startswith('magnet:') Does
# x - check if x exists (not None/empty)
# and - if x exists, THEN check the next part
# x.startswith('magnet:') - does x start with "magnet:"?
if __name__ == "__main__":
search_input = input("Enter the Name of the Program you want to search for: ")
selected = scrape_uztracker(search_input)
maintitle = get_post_title(selected)
magnetlink = get_magnet_link(selected)
print(maintitle)
print(magnetlink)
+45 -2
View File
@@ -1,6 +1,49 @@
from core.gui import run_gui
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 closehelper, shutdown_event
from core.utils.general.wrappers import run_thread
from core.utils.config.config import read_config
from PySide6 import QtWidgets
import qdarktheme
import darkdetect
import threading
import signal
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
def run_gui():
app = QtWidgets.QApplication([])
if darkdetect.isDark:
app.setStyleSheet(qdarktheme.load_stylesheet("dark"))
else:
app.setStyleSheet(qdarktheme.load_stylesheet("light"))
widget = MainWindow()
widget.show()
sys.exit(app.exec())
def run_aria2server():
aria2process = aria2server()
return aria2process
def keyboardinterrupthandler(signum, frame):
closehelper()
if __name__ == "__main__":
read_config()
state.debug = args.debug # override of read_config
if state.debug:
print("Starting Aria2 Server")
state.aria2process = run_aria2server()
signal.signal(signal.SIGINT, keyboardinterrupthandler)
run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True))
if state.debug:
print("Launching GUI")
run_gui()
run_gui()
+10
View File
@@ -0,0 +1,10 @@
requests==2.32.2
PySide6==6.9.2
beautifulsoup4==4.13.5
darkdetect==0.8.0
aria2p==0.12.1
pyinstaller==6.15.0
pyqtdarktheme==0.1.7
aiohttp==3.13.0
plyer==2.1.0
psutil==7.1.0