Compare commits

..

149 Commits

Author SHA1 Message Date
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
32 changed files with 1197 additions and 238 deletions
+143
View File
@@ -0,0 +1,143 @@
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-${{ github.sha }}-linux
asset_name: SoftwareManager-dev-${{ github.sha }}-linux
- os: windows-latest
artifact_name: SoftwareManager-dev-${{ github.sha }}-windows.exe
asset_name: SoftwareManager-dev-${{ github.sha }}-windows.exe
- os: macos-latest
artifact_name: SoftwareManager-dev-${{ github.sha }}-macos
asset_name: SoftwareManager-dev-${{ github.sha }}-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
SHORT_SHA="${{ steps.version.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)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}
draft: false
prerelease: true
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
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
+3 -5
View File
@@ -1,14 +1,11 @@
# SoftwareManager - A Download Manager/Launcher
## Description
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 (impl. soon)
- UI Theming
- Bandwidth/Download speed limiting
- Start downloads via a predefined magnet URI.
- View detailed download information (name, speed, and status). (improved impl. soon)
- View detailed download information (name, speed, and status).
## Installation
1. Download the binary file for your operating system from releases
@@ -33,3 +30,4 @@ SoftwareManager is a Python-based GUI/TUI tool that simplifies downloading and m
---
**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
+20
View File
@@ -0,0 +1,20 @@
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 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.
+93
View File
@@ -0,0 +1,93 @@
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
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?")
up = False
asyncio.run(init_uztracker())
async def scrape_uztracker(search, max_results=450):
if up:
result = False
global results
global resulttitles
results = []
resulttitles = []
per_page = 50
for start in range(0, max_results, per_page):
search_url = url_uztracker + search + f"&start={start}"
if state.debug:
print(search_url)
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
else:
print("Error: Uztracker down")
return None, None
if not results:
return None, None
return resulttitles, results
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:
print("Error: Uztracker down")
return None
-21
View File
@@ -1,21 +0,0 @@
import aria2p
import os
import subprocess
def run_aria2p():
aria2 = aria2p.API(
aria2p.Client(
host="http://localhost",
port=6800,
secret=""
)
)
downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads")
subprocess.Popen(["aria2c", "--enable-rpc", "--rpc-listen-all=true", f"--dir={downloads_dir}"])
return aria2
-19
View File
@@ -1,19 +0,0 @@
from .aria2p_server import run_aria2p
import aria2p
import subprocess
import os
# from .scraper import uri
def start_client():
global aria2
aria2 = run_aria2p()
def add_magnet(uri):
aria2.add_magnet(uri)
def terminate():
return
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+123
View File
@@ -0,0 +1,123 @@
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)
print(thread_box.value())
layout.addWidget(cancel_btn)
layout.addWidget(save_btn)
dialog.layout().addLayout(layout)
dialog.exec()
+190
View File
@@ -0,0 +1,190 @@
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,
)
from PySide6.QtGui import QIcon, QAction, QCloseEvent
import darkdetect
import threading
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.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
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(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")
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()
container = QWidget()
containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar)
containerLayout.addWidget(self.softwareList)
containerLayout.addWidget(self.post_author_list)
self.softwareList.addItems(searchresults)
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)
# download button triggers
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.softwareList.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.softwareList, stretch=3)
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
self.horizontal_layout.addWidget(self.post_author_list)
self.tab1 = create_tab("Search", self.searchbar, self.softwareList, 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"])
self.tracker_list.activated.connect(self.set_tracker)
if darkdetect.isDark():
settings_action = QAction(QIcon("core/interface/assets/settings_dark.png"), "Settings", self)
else:
settings_action = QAction(QIcon("core/interface/assets/settings.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.softwareList.hide()
self.emptyResults.show()
else:
self.emptyResults.hide()
self.softwareList.show()
+42
View File
@@ -0,0 +1,42 @@
import asyncio
from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker
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 = asyncio.run(scrape_uztracker(search_text))
if state.tracker == "rutracker":
response = scrape_rutracker(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)
if state.posts == []:
if state.debug:
print(f"No Results found for \"{search_text}\"")
self.softwareList.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)
if state.debug == True:
print(f"Cached: {cached}")
+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)
+11
View File
@@ -0,0 +1,11 @@
from .aria2_integration import run_aria2p
def start_client():
global aria2
aria2 = run_aria2p()
def add_magnet(uri):
aria2.add_magnet(uri)
-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}"}
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")
+23
View File
@@ -0,0 +1,23 @@
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
main.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)
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
create_config()
restart_aria2c()
close()
+22
View File
@@ -0,0 +1,22 @@
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]
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="")
+39
View File
@@ -0,0 +1,39 @@
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): # oftwarelist 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("./")
return item
if state.tracker == "rutracker":
item_dict = posts[post_index]
_, post_links, _ = format_data([item_dict])
return post_links[0]
return None
def get_magnet_link(post_url):
try:
response = requests.get(post_url) # eventually impl. cloudscraper
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("Killed Aria2c")
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"network {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
-96
View File
@@ -1,96 +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:"?
+40 -57
View File
@@ -1,64 +1,47 @@
from ui.clinterface import asciititle;
from core.uztracker_scraper import scrape_uztracker
from core.uztracker_scraper import get_magnet_link
from core.downloading.download import start_client
from core.downloading.download import add_magnet
import os
def search():
asciititle()
searchquery = input("Enter Search Query: ")
result = scrape_uztracker(searchquery)
if result is None:
clear()
asciititle()
print("No results found for your Query.\n")
return
return result
def select_result(result):
resulttitles, resultlinks = result
clear()
asciititle()
for i, title in enumerate(resulttitles, 1):
print(f"({i}) {title}")
selection = input("\n Enter the Number of the Program you want to download: ")
try:
index = int(selection) - 1
if 0 <= index < len(resultlinks): # 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/" + resultlinks[index].lstrip("./")
post_url = selected
return selected
else:
print("Invalid Selection.")
except ValueError:
print(Exception)
return
def clear():
os.system('cls' if os.name == 'nt' else 'clear')
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.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 atexit
import sys
# Debug Output
state.debug = True
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__":
clear()
results = search()
selected = select_result(results)
print(selected)
uri = get_magnet_link(selected)
start_client()
add_magnet(uri)
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()
+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
-15
View File
@@ -1,15 +0,0 @@
def asciititle():
print(f"""
_____ __ _ __ __
/ ____| / _| | | \/ |
| (___ ___ | |_| |___ ____ _ _ __ ___| \ / | __ _ _ __ __ _ __ _ ___ _ __
\___ \ / _ \| _| __\ \ /\ / / _` | '__/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
____) | (_) | | | |_ \ V V / (_| | | | __/ | | | (_| | | | | (_| | (_| | __/ |
|_____/ \___/|_| \__| \_/\_/ \__,_|_| \___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_|
__/ |
|___/
""")