mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8238cfbe56 | |||
| 7ccff5c5a1 | |||
| 8abe92fc4f | |||
| 0a3b5eeee2 | |||
| 7662c861ea | |||
| 2a8f305a76 | |||
| 12223d18de | |||
| 544733ce6b | |||
| 7aee2bcfe9 | |||
| d274e64841 | |||
| 23c74f6487 | |||
| 19ea09d035 | |||
| 92359d75e5 | |||
| 315a68c000 | |||
| 4b0ef44efd | |||
| 6dc28615a0 | |||
| 2ea9a329d1 | |||
| 9f8111ac73 | |||
| e5b809d6d4 | |||
| b573ca8fed |
+92
-32
@@ -15,7 +15,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.11"
|
PYTHON_VERSION: "3.12"
|
||||||
APP_NAME: "SoftwareManager"
|
APP_NAME: "SoftwareManager"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -57,7 +57,13 @@ jobs:
|
|||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
uv pip install -r requirements.txt
|
uv pip install -r requirements.txt
|
||||||
uv pip install pyinstaller backports.tarfile pillow
|
uv pip install Pillow
|
||||||
|
env:
|
||||||
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
|
- name: Install Nuitka (Windows only)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: uv pip install nuitka
|
||||||
env:
|
env:
|
||||||
UV_SYSTEM_PYTHON: 1
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
@@ -82,36 +88,58 @@ jobs:
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Build executable with PyInstaller (Windows)
|
- name: Install UPX (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pyinstaller --onefile --windowed \
|
curl -sL https://github.com/upx/upx/releases/download/v5.1.0/upx-5.1.0-win64.zip -o upx.zip
|
||||||
--add-data "build_info.json;." \
|
7z x upx.zip -oupx-dir
|
||||||
--add-data "build_info.json;core" \
|
UPX_DIR="$(pwd)/upx-dir/upx-5.1.0-win64"
|
||||||
--add-data "build_info.json;core/interface" \
|
echo "$UPX_DIR" >> $GITHUB_PATH
|
||||||
--add-data "core/interface/assets;core/interface/assets" \
|
export PATH="$UPX_DIR:$PATH"
|
||||||
--icon "core/interface/assets/logo.png" \
|
echo "UPX_BINARY=$UPX_DIR/upx.exe" >> $GITHUB_ENV
|
||||||
--exclude-module PyQt6 \
|
upx --version | head -1
|
||||||
--exclude-module PyQt5 \
|
|
||||||
--hidden-import=backports.tarfile \
|
|
||||||
--hidden-import=backports \
|
|
||||||
--hidden-import=jaraco.context \
|
|
||||||
--hidden-import=jaraco.text \
|
|
||||||
--hidden-import=jaraco.functools \
|
|
||||||
--name ${{ env.APP_NAME }} \
|
|
||||||
main.py
|
|
||||||
|
|
||||||
- name: Build executable with PyInstaller (Unix)
|
- name: Convert icon for Windows
|
||||||
if: runner.os != 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python -c "
|
||||||
|
from PIL import Image
|
||||||
|
img = Image.open('src/core/interface/assets/logo.png')
|
||||||
|
img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])
|
||||||
|
print('Icon converted successfully')
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Build with Nuitka (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python -m nuitka \
|
||||||
|
--onefile \
|
||||||
|
--onefile-no-compression \
|
||||||
|
--onefile-tempdir-spec="{CACHE_DIR}/${{ env.APP_NAME }}/${{ env.VERSION }}" \
|
||||||
|
--assume-yes-for-downloads \
|
||||||
|
--windows-console-mode=disable \
|
||||||
|
--windows-icon-from-ico=app_icon.ico \
|
||||||
|
--enable-plugin=pyside6 \
|
||||||
|
--enable-plugin=upx \
|
||||||
|
--upx-binary="${{ env.UPX_BINARY }}" \
|
||||||
|
--noinclude-qt-translations \
|
||||||
|
--include-data-files=build_info.json=build_info.json \
|
||||||
|
--include-data-files=build_info.json=src/build_info.json \
|
||||||
|
--include-data-files=build_info.json=src/core/interface/build_info.json \
|
||||||
|
--output-filename=${{ env.APP_NAME }}.exe \
|
||||||
|
src/main.py
|
||||||
|
|
||||||
|
- name: Build with PyInstaller (Linux)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pyinstaller --onefile --windowed \
|
pyinstaller --onefile --windowed \
|
||||||
--add-data "build_info.json:." \
|
--add-data "build_info.json:." \
|
||||||
--add-data "build_info.json:core" \
|
--add-data "build_info.json:src/core" \
|
||||||
--add-data "build_info.json:core/interface" \
|
--add-data "build_info.json:src/core/interface" \
|
||||||
--add-data "core/interface/assets:core/interface/assets" \
|
|
||||||
--icon "core/interface/assets/logo.png" \
|
|
||||||
--exclude-module PyQt6 \
|
--exclude-module PyQt6 \
|
||||||
--exclude-module PyQt5 \
|
--exclude-module PyQt5 \
|
||||||
--hidden-import=backports.tarfile \
|
--hidden-import=backports.tarfile \
|
||||||
@@ -120,24 +148,43 @@ jobs:
|
|||||||
--hidden-import=jaraco.text \
|
--hidden-import=jaraco.text \
|
||||||
--hidden-import=jaraco.functools \
|
--hidden-import=jaraco.functools \
|
||||||
--name ${{ env.APP_NAME }} \
|
--name ${{ env.APP_NAME }} \
|
||||||
main.py
|
src/main.py
|
||||||
|
|
||||||
- name: Make executable (Unix)
|
- name: Build with PyInstaller (macOS)
|
||||||
if: runner.os != 'Windows'
|
if: runner.os == 'macOS'
|
||||||
run: chmod +x dist/${{ env.APP_NAME }}
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pyinstaller --onefile \
|
||||||
|
--add-data "build_info.json:." \
|
||||||
|
--add-data "build_info.json:src/core" \
|
||||||
|
--add-data "build_info.json:src/core/interface" \
|
||||||
|
--exclude-module PyQt6 \
|
||||||
|
--exclude-module PyQt5 \
|
||||||
|
--hidden-import=backports.tarfile \
|
||||||
|
--hidden-import=backports \
|
||||||
|
--hidden-import=jaraco.context \
|
||||||
|
--hidden-import=jaraco.text \
|
||||||
|
--hidden-import=jaraco.functools \
|
||||||
|
--name ${{ env.APP_NAME }} \
|
||||||
|
src/main.py
|
||||||
|
|
||||||
- name: Rename artifact
|
- name: Rename artifact
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd dist
|
mkdir -p dist-final
|
||||||
mv ${{ env.APP_NAME }}${{ runner.os == 'Windows' && '.exe' || '' }} ${{ matrix.artifact_name }}
|
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||||
ls -la
|
mv ${{ env.APP_NAME }}.exe dist-final/${{ matrix.artifact_name }}
|
||||||
|
else
|
||||||
|
chmod +x dist/${{ env.APP_NAME }}
|
||||||
|
mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }}
|
||||||
|
fi
|
||||||
|
ls -la dist-final/
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.os }}-build
|
name: ${{ matrix.os }}-build
|
||||||
path: dist/${{ matrix.artifact_name }}
|
path: dist-final/${{ matrix.artifact_name }}
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@@ -162,9 +209,16 @@ jobs:
|
|||||||
chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos
|
chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos
|
||||||
ls -la release/
|
ls -la release/
|
||||||
|
|
||||||
|
- name: Generate checksums
|
||||||
|
run: |
|
||||||
|
cd release
|
||||||
|
sha256sum * > SHA256SUMS.txt
|
||||||
|
cat SHA256SUMS.txt
|
||||||
|
|
||||||
- name: Generate release notes
|
- name: Generate release notes
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA="${{ needs.build.outputs.short_sha }}"
|
SHORT_SHA="${{ needs.build.outputs.short_sha }}"
|
||||||
|
CHECKSUMS=$(cat release/SHA256SUMS.txt)
|
||||||
cat > release_notes.md << EOF
|
cat > release_notes.md << EOF
|
||||||
## Development Build
|
## Development Build
|
||||||
|
|
||||||
@@ -177,6 +231,11 @@ jobs:
|
|||||||
- Linux: \`SoftwareManager-dev-${SHORT_SHA}-linux\`
|
- Linux: \`SoftwareManager-dev-${SHORT_SHA}-linux\`
|
||||||
- Windows: \`SoftwareManager-dev-${SHORT_SHA}-windows.exe\`
|
- Windows: \`SoftwareManager-dev-${SHORT_SHA}-windows.exe\`
|
||||||
- macOS: \`SoftwareManager-dev-${SHORT_SHA}-macos\`
|
- macOS: \`SoftwareManager-dev-${SHORT_SHA}-macos\`
|
||||||
|
|
||||||
|
### SHA-256 Checksums
|
||||||
|
\`\`\`
|
||||||
|
${CHECKSUMS}
|
||||||
|
\`\`\`
|
||||||
EOF
|
EOF
|
||||||
cat release_notes.md
|
cat release_notes.md
|
||||||
|
|
||||||
@@ -190,5 +249,6 @@ jobs:
|
|||||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-linux
|
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-linux
|
||||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-windows.exe
|
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-windows.exe
|
||||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
|
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
|
||||||
|
release/SHA256SUMS.txt
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ SoftwareManager is a Python-based GUI tool that simplifies searching and downloa
|
|||||||
## Additional Information
|
## Additional Information
|
||||||
- SoftwareManager is still in Development and most likely contains issues. Please report any bugs you find via the Issues tab. Contributions are also greatly appreciated.
|
- SoftwareManager is still in Development and most likely contains issues. Please report any bugs you find via the Issues tab. Contributions are also greatly appreciated.
|
||||||
- MacOS Builds haven't been properly tested - We're thankful to receive feedback.
|
- MacOS Builds haven't been properly tested - We're thankful to receive feedback.
|
||||||
- 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 the server branch.
|
- If you want to host your own server, clone our [server repository](https://github.com/KeksPirates/SoftwareManager-Server), enter your cookie for rutracker.org and run server.py. Detailed Instructions on how to get your rutracker cookie are in the README of the server repo.
|
||||||
|
|
||||||
## Python Dependencies
|
## Python Dependencies
|
||||||
(also included in requirements.txt)
|
(also included in requirements.txt)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
requests==2.32.2
|
requests==2.32.2
|
||||||
PySide6==6.10.1
|
PySide6-Essentials==6.10.1
|
||||||
beautifulsoup4==4.13.5
|
beautifulsoup4==4.13.5
|
||||||
darkdetect==0.7.1
|
darkdetect==0.7.1
|
||||||
pyinstaller==6.15.0
|
pyinstaller==6.15.0
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
from core.utils.general.logs import consoleLog
|
from core.utils.general.logs import consoleLog
|
||||||
|
|
||||||
def scrape_monkrus_telegram(query):
|
cache = {
|
||||||
|
"data": [],
|
||||||
|
"last_fetched": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_expiry = 300
|
||||||
|
|
||||||
|
def _get_telegram_posts():
|
||||||
post = 100
|
post = 100
|
||||||
max_posts = 250
|
max_posts = 250
|
||||||
posts: list[dict] = []
|
posts = []
|
||||||
added = set()
|
added = set()
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
if cache["data"] and (current_time - cache["last_fetched"] < cache_expiry):
|
||||||
|
return cache["data"]
|
||||||
|
|
||||||
while post <= max_posts:
|
while post <= max_posts:
|
||||||
url = f"https://t.me/s/real_monkrus/{post}"
|
url = f"https://t.me/s/real_monkrus/{post}"
|
||||||
response = requests.get(url)
|
response = requests.get(url, timeout=10)
|
||||||
soup = BeautifulSoup(response.text, "html.parser")
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
|
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
|
||||||
|
|
||||||
@@ -25,7 +38,7 @@ def scrape_monkrus_telegram(query):
|
|||||||
title = post_txt.b.text
|
title = post_txt.b.text
|
||||||
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
|
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
|
||||||
|
|
||||||
if query.lower() in title.lower() and link:
|
if link:
|
||||||
post_url = link["href"]
|
post_url = link["href"]
|
||||||
if post_url not in added:
|
if post_url not in added:
|
||||||
added.add(post_url)
|
added.add(post_url)
|
||||||
@@ -39,4 +52,20 @@ def scrape_monkrus_telegram(query):
|
|||||||
post += 22
|
post += 22
|
||||||
|
|
||||||
posts.reverse()
|
posts.reverse()
|
||||||
|
|
||||||
|
cache["data"] = posts
|
||||||
|
cache["last_fetched"] = current_time
|
||||||
|
|
||||||
return(posts)
|
return(posts)
|
||||||
|
|
||||||
|
def scrape_monkrus_telegram(query):
|
||||||
|
posts = _get_telegram_posts()
|
||||||
|
filtered_posts = []
|
||||||
|
|
||||||
|
for post in posts:
|
||||||
|
if query.lower() in post["title"].lower():
|
||||||
|
filtered_post = post.copy()
|
||||||
|
filtered_post["id"] = len(filtered_posts) + 1
|
||||||
|
filtered_posts.append(filtered_post)
|
||||||
|
|
||||||
|
return filtered_posts
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import requests
|
import requests
|
||||||
import asyncio
|
|
||||||
import threading
|
import threading
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from core.utils.data.state import state
|
from core.utils.data.state import state
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -5,6 +5,7 @@ from core.interface.utils.tabhelper import general_tab
|
|||||||
from core.interface.utils.tabhelper import paths_tab
|
from core.interface.utils.tabhelper import paths_tab
|
||||||
from core.interface.utils.tabhelper import network_tab
|
from core.interface.utils.tabhelper import network_tab
|
||||||
from PySide6 import QtWidgets
|
from PySide6 import QtWidgets
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QLineEdit,
|
QLineEdit,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
@@ -31,6 +32,9 @@ def settings_dialog(self):
|
|||||||
|
|
||||||
dialog.setLayout(QVBoxLayout())
|
dialog.setLayout(QVBoxLayout())
|
||||||
|
|
||||||
|
if state.window_transparency and platform.system() != "Windows" and dialog:
|
||||||
|
dialog.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
def close_settings():
|
def close_settings():
|
||||||
dialog.reject()
|
dialog.reject()
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from PySide6 import QtWidgets
|
from PySide6 import QtWidgets, QtCore, QtGui
|
||||||
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
|
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QLineEdit,
|
QLineEdit,
|
||||||
@@ -29,6 +29,7 @@ import libtorrent as lt
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import base64
|
||||||
from core.utils.general.logs import consoleLog, remove_download_log, flush_log_buffer
|
from core.utils.general.logs import consoleLog, remove_download_log, flush_log_buffer
|
||||||
from core.utils.general.wrappers import run_thread
|
from core.utils.general.wrappers import run_thread
|
||||||
from core.utils.data.state import state
|
from core.utils.data.state import state
|
||||||
@@ -37,6 +38,9 @@ from core.utils.network.update_checker import check_for_updates
|
|||||||
from core.interface.utils.tabhelper import create_tab
|
from core.interface.utils.tabhelper import create_tab
|
||||||
from core.interface.utils.searchhelper import return_pressed
|
from core.interface.utils.searchhelper import return_pressed
|
||||||
from core.interface.dialogs.settings import settings_dialog
|
from core.interface.dialogs.settings import settings_dialog
|
||||||
|
from core.interface.assets.base64_icons import settings_black_base64
|
||||||
|
from core.interface.assets.base64_icons import settings_white_base64
|
||||||
|
from core.interface.assets.base64_icons import logo_base64
|
||||||
from core.network.libtorrent_misc import cleanup_session
|
from core.network.libtorrent_misc import cleanup_session
|
||||||
from core.utils.general.shutdown import closehelper
|
from core.utils.general.shutdown import closehelper
|
||||||
|
|
||||||
@@ -72,20 +76,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
MainWindow._instance = self
|
MainWindow._instance = self
|
||||||
self.log_signal.connect(self._on_log_signal)
|
self.log_signal.connect(self._on_log_signal)
|
||||||
|
|
||||||
def get_asset_path(filename):
|
pixmap = QPixmap()
|
||||||
if getattr(sys, 'frozen', False):
|
image_data = base64.b64decode(logo_base64)
|
||||||
base_path = sys._MEIPASS
|
pixmap.loadFromData(image_data)
|
||||||
else:
|
self.setWindowIcon(QIcon(pixmap))
|
||||||
base_path = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
asset_path = os.path.join(base_path, 'core', 'interface', 'assets', filename)
|
|
||||||
if os.path.exists(asset_path):
|
|
||||||
return asset_path
|
|
||||||
|
|
||||||
return os.path.join('core', 'interface', 'assets', filename)
|
|
||||||
|
|
||||||
self.setWindowIcon(QIcon(get_asset_path("logo.png")))
|
|
||||||
|
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
try:
|
try:
|
||||||
@@ -102,23 +96,22 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
state.version = build_info.get("version")
|
state.version = build_info.get("version")
|
||||||
|
|
||||||
# Check for updates on Windows
|
# Check for updates on Windows
|
||||||
if state.ignore_updates is False:
|
if state.ignore_updates is False and platform.system() == "Windows":
|
||||||
if platform.system() == "Windows":
|
result = check_for_updates()
|
||||||
result = check_for_updates()
|
if result != (None, None):
|
||||||
if result != (None, None):
|
assets, latest_version = result
|
||||||
assets, latest_version = result
|
|
||||||
|
|
||||||
if assets:
|
if assets:
|
||||||
msg = QMessageBox()
|
msg = QMessageBox()
|
||||||
msg.setIcon(QMessageBox.Icon.Information)
|
msg.setIcon(QMessageBox.Icon.Information)
|
||||||
msg.setWindowTitle("Update Available")
|
msg.setWindowTitle("Update Available")
|
||||||
msg.setText("A new version is available.")
|
msg.setText("A new version is available.")
|
||||||
msg.setInformativeText("Press Ok to download the update.")
|
msg.setInformativeText("Press Ok to download the update.")
|
||||||
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
|
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
|
||||||
|
|
||||||
response = msg.exec_()
|
response = msg.exec_()
|
||||||
if response == QMessageBox.StandardButton.Ok:
|
if response == QMessageBox.StandardButton.Ok:
|
||||||
download_update(latest_version)
|
download_update(latest_version)
|
||||||
|
|
||||||
self.setWindowTitle("Software Manager")
|
self.setWindowTitle("Software Manager")
|
||||||
self.setGeometry(100, 100, 800, 600)
|
self.setGeometry(100, 100, 800, 600)
|
||||||
@@ -152,32 +145,38 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
flush_log_buffer()
|
flush_log_buffer()
|
||||||
|
|
||||||
# Table Widget for Item List
|
def create_tracker_table(headers):
|
||||||
self.qtablewidget = QTableWidget()
|
table = QTableWidget()
|
||||||
|
table.setColumnCount(len(headers))
|
||||||
self.qtablewidget.setColumnCount(4)
|
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||||
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
|
table.verticalHeader().setVisible(False)
|
||||||
self.qtablewidget.verticalHeader().setVisible(False)
|
table.setHorizontalHeaderLabels(headers)
|
||||||
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
|
|
||||||
|
|
||||||
|
header = table.horizontalHeader()
|
||||||
|
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||||
|
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||||
|
header.resizeSection(1, 500)
|
||||||
|
header.setStretchLastSection(False)
|
||||||
|
|
||||||
header = self.qtablewidget.horizontalHeader()
|
return table
|
||||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
|
||||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
|
||||||
header.setStretchLastSection(False)
|
|
||||||
|
|
||||||
self.qtablewidget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
self.rutrackerlist = create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"])
|
||||||
self.qtablewidget.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
self.uztrackerlist = create_tracker_table(["Post Title", "Author"])
|
||||||
|
self.monkruslist = create_tracker_table(["Post Title", "Author"])
|
||||||
|
|
||||||
header = self.qtablewidget.horizontalHeader()
|
state.tracker_list.update({"rutracker": self.rutrackerlist, "uztracker": self.uztrackerlist, "m0nkrus": self.monkruslist})
|
||||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
|
||||||
header.resizeSection(1, 500)
|
self.rutrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
self.rutrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
container = QWidget()
|
container = QWidget()
|
||||||
containerLayout = QVBoxLayout()
|
containerLayout = QVBoxLayout()
|
||||||
containerLayout.addWidget(self.searchbar)
|
containerLayout.addWidget(self.searchbar)
|
||||||
containerLayout.addWidget(self.qtablewidget)
|
containerLayout.addWidget(state.tracker_list[state.tracker])
|
||||||
|
|
||||||
class DownloadModel(QAbstractTableModel):
|
class DownloadModel(QAbstractTableModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -358,7 +357,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
delegate.clicked.connect(on_pause_resume_clicked)
|
delegate.clicked.connect(on_pause_resume_clicked)
|
||||||
|
|
||||||
# download button triggers
|
# download button triggers
|
||||||
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles))))
|
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles))))
|
||||||
|
|
||||||
container.setLayout(containerLayout)
|
container.setLayout(containerLayout)
|
||||||
self.setCentralWidget(container)
|
self.setCentralWidget(container)
|
||||||
@@ -378,15 +377,16 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
|
|
||||||
self.horizontal_layout = QHBoxLayout()
|
self.horizontal_layout = QHBoxLayout()
|
||||||
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
||||||
self.horizontal_layout.addWidget(self.qtablewidget)
|
self.tracker_widget = state.tracker_list[state.tracker]
|
||||||
|
self.horizontal_layout.addWidget(self.tracker_widget)
|
||||||
|
|
||||||
self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout)
|
self.tab1 = create_tab("Search", self.searchbar, state.tracker_list[state.tracker], self.tabs, self.dlbutton, self.horizontal_layout)
|
||||||
# self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None)
|
# self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None)
|
||||||
self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
|
self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
|
||||||
|
|
||||||
if state.image_path is not None and os.path.exists(state.image_path):
|
if state.image_path is not None and os.path.exists(state.image_path):
|
||||||
self.image = QImage(state.image_path)
|
self.image = QImage(state.image_path)
|
||||||
self.image = self.image.scaledToWidth(300, Qt.SmoothTransformation)
|
self.image = self.image.scaledToWidth(300, Qt.TransformationMode.SmoothTransformation)
|
||||||
self.pixmap = QPixmap.fromImage(self.image)
|
self.pixmap = QPixmap.fromImage(self.image)
|
||||||
self.overlay_label = QLabel(self)
|
self.overlay_label = QLabel(self)
|
||||||
self.overlay_label.setPixmap(self.pixmap)
|
self.overlay_label.setPixmap(self.pixmap)
|
||||||
@@ -417,10 +417,18 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
self.settings_btn = QtWidgets.QToolButton()
|
self.settings_btn = QtWidgets.QToolButton()
|
||||||
self.settings_btn.setIconSize(QSize(21, 21))
|
self.settings_btn.setIconSize(QSize(21, 21))
|
||||||
self.settings_btn.setStyleSheet("background: transparent;")
|
self.settings_btn.setStyleSheet("background: transparent;")
|
||||||
|
|
||||||
if darkdetect.isDark():
|
if darkdetect.isDark():
|
||||||
self.settings_btn.setIcon(QIcon(get_asset_path("settings_white.png")))
|
settings_white_pixmap = QPixmap()
|
||||||
|
settings_white = base64.b64decode(settings_white_base64)
|
||||||
|
settings_white_pixmap.loadFromData(settings_white)
|
||||||
|
self.settings_btn.setIcon(QIcon(settings_white_pixmap))
|
||||||
else:
|
else:
|
||||||
self.settings_btn.setIcon(QIcon(get_asset_path("settings_black.png")))
|
settings_black_pixmap = QPixmap()
|
||||||
|
settings_black = base64.b64decode(settings_black_base64)
|
||||||
|
settings_black_pixmap.loadFromData(settings_black)
|
||||||
|
self.settings_btn.setIcon(QIcon(settings_black_pixmap))
|
||||||
|
|
||||||
self.settings_btn.setToolTip("Settings")
|
self.settings_btn.setToolTip("Settings")
|
||||||
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
|
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
|
||||||
@@ -475,10 +483,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
MainWindow._instance.log_signal.emit(text)
|
MainWindow._instance.log_signal.emit(text)
|
||||||
|
|
||||||
def _on_log_signal(self, text):
|
def _on_log_signal(self, text):
|
||||||
self.consoleLog.append(text)
|
if hasattr(self, 'consoleLog'):
|
||||||
self.consoleLog.verticalScrollBar().setValue(
|
self.consoleLog.append(text)
|
||||||
self.consoleLog.verticalScrollBar().maximum()
|
self.consoleLog.verticalScrollBar().setValue(
|
||||||
)
|
self.consoleLog.verticalScrollBar().maximum()
|
||||||
|
)
|
||||||
|
|
||||||
def update_image_overlay(self, new_image_path):
|
def update_image_overlay(self, new_image_path):
|
||||||
self.image = QImage(new_image_path)
|
self.image = QImage(new_image_path)
|
||||||
@@ -504,12 +513,20 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
def set_tracker(self, _):
|
def set_tracker(self, _):
|
||||||
state.tracker = self.tracker_list.currentText()
|
state.tracker = self.tracker_list.currentText()
|
||||||
|
|
||||||
|
while self.horizontal_layout.count():
|
||||||
|
item = self.horizontal_layout.takeAt(0)
|
||||||
|
if item.widget():
|
||||||
|
item.widget().setParent(None)
|
||||||
|
|
||||||
|
tracker_widget = state.tracker_list[state.tracker]
|
||||||
|
self.horizontal_layout.addWidget(tracker_widget)
|
||||||
|
|
||||||
def show_empty_results(self, show: bool):
|
def show_empty_results(self, show: bool):
|
||||||
if show:
|
if show:
|
||||||
self.qtablewidget.hide()
|
state.tracker_list[state.tracker].hide()
|
||||||
self.emptyResults.show()
|
self.emptyResults.show()
|
||||||
else:
|
else:
|
||||||
self.qtablewidget.show()
|
state.tracker_list[state.tracker].show()
|
||||||
self.emptyResults.hide()
|
self.emptyResults.hide()
|
||||||
|
|
||||||
def show_empty_downloads(self):
|
def show_empty_downloads(self):
|
||||||
@@ -523,8 +540,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
|||||||
# thank you claude
|
# thank you claude
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
table_width = self.qtablewidget.viewport().width()
|
table_width = state.tracker_list[state.tracker].viewport().width()
|
||||||
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
|
state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3))
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QContextMenuEvent):
|
def contextMenuEvent(self, event: QContextMenuEvent):
|
||||||
if self.downloadList.underMouse():
|
if self.downloadList.underMouse():
|
||||||
@@ -17,22 +17,22 @@ def return_pressed(self):
|
|||||||
response = scrape_uztracker(search_text)
|
response = scrape_uztracker(search_text)
|
||||||
if response:
|
if response:
|
||||||
state.post_titles, state.post_urls = response
|
state.post_titles, state.post_urls = response
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
if state.post_titles and len(state.post_titles) > 0:
|
if state.post_titles and len(state.post_titles) > 0:
|
||||||
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
|
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"])
|
||||||
self.qtablewidget.setRowCount(len(state.post_titles))
|
state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
|
||||||
for i, author in enumerate(state.post_author):
|
for i, author in enumerate(state.post_author):
|
||||||
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
|
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
|
||||||
for i, title in enumerate(state.post_titles):
|
for i, title in enumerate(state.post_titles):
|
||||||
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
|
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
|
||||||
self.show_empty_results(False)
|
self.show_empty_results(False)
|
||||||
else:
|
else:
|
||||||
consoleLog(f"No Results for {search_text}")
|
consoleLog(f"No Results for {search_text}")
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.show_empty_results(True)
|
self.show_empty_results(True)
|
||||||
else:
|
else:
|
||||||
consoleLog(f"No response from uztracker")
|
consoleLog(f"No response from uztracker")
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.show_empty_results(True)
|
self.show_empty_results(True)
|
||||||
|
|
||||||
elif state.tracker == "rutracker":
|
elif state.tracker == "rutracker":
|
||||||
@@ -41,27 +41,27 @@ def return_pressed(self):
|
|||||||
_, state.posts, _, _, cached = split_data(response)
|
_, state.posts, _, _, cached = split_data(response)
|
||||||
if state.posts == []:
|
if state.posts == []:
|
||||||
consoleLog(f"No Results for {search_text}")
|
consoleLog(f"No Results for {search_text}")
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.show_empty_results(True)
|
self.show_empty_results(True)
|
||||||
else:
|
else:
|
||||||
state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts)
|
state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts)
|
||||||
self.show_empty_results(False)
|
self.show_empty_results(False)
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
|
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
|
||||||
self.qtablewidget.setRowCount(len(state.post_titles))
|
state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
|
||||||
for i, author in enumerate(state.post_author):
|
for i, author in enumerate(state.post_author):
|
||||||
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
|
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
|
||||||
for i, title in enumerate(state.post_titles):
|
for i, title in enumerate(state.post_titles):
|
||||||
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
|
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
|
||||||
for i, seeders in enumerate(state.post_seeders):
|
for i, seeders in enumerate(state.post_seeders):
|
||||||
self.qtablewidget.setItem(i, 2, QTableWidgetItem(seeders))
|
state.tracker_list[state.tracker].setItem(i, 2, QTableWidgetItem(seeders))
|
||||||
for i, leechers in enumerate(state.post_leechers):
|
for i, leechers in enumerate(state.post_leechers):
|
||||||
self.qtablewidget.setItem(i, 3, QTableWidgetItem(leechers))
|
state.tracker_list[state.tracker].setItem(i, 3, QTableWidgetItem(leechers))
|
||||||
if state.debug == True:
|
if state.debug == True:
|
||||||
consoleLog(f"Response Cached: {cached}")
|
consoleLog(f"Response Cached: {cached}")
|
||||||
else:
|
else:
|
||||||
consoleLog(f"No response from rutracker")
|
consoleLog(f"No response from rutracker")
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.show_empty_results(True)
|
self.show_empty_results(True)
|
||||||
|
|
||||||
elif state.tracker == "m0nkrus":
|
elif state.tracker == "m0nkrus":
|
||||||
@@ -69,15 +69,15 @@ def return_pressed(self):
|
|||||||
|
|
||||||
if state.posts == []:
|
if state.posts == []:
|
||||||
consoleLog(f"No Results for {search_text}")
|
consoleLog(f"No Results for {search_text}")
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.show_empty_results(True)
|
self.show_empty_results(True)
|
||||||
else:
|
else:
|
||||||
state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts)
|
state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts)
|
||||||
self.show_empty_results(False)
|
self.show_empty_results(False)
|
||||||
self.qtablewidget.clear()
|
state.tracker_list[state.tracker].clear()
|
||||||
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
|
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"])
|
||||||
self.qtablewidget.setRowCount(len(state.post_titles))
|
state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
|
||||||
for i, author in enumerate(state.post_author):
|
for i, author in enumerate(state.post_author):
|
||||||
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
|
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
|
||||||
for i, title in enumerate(state.post_titles):
|
for i, title in enumerate(state.post_titles):
|
||||||
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
|
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
|
||||||
@@ -36,7 +36,7 @@ def init_session():
|
|||||||
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
||||||
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
||||||
else:
|
else:
|
||||||
consoleLog(f"Error finding IP for Interface {state.bound_interface}")
|
consoleLog(f"Skipping Binding, no Interface set. ({state.bound_interface})")
|
||||||
|
|
||||||
state.dl_session.apply_settings(settings)
|
state.dl_session.apply_settings(settings)
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ def add_download(magnet_uri, dl_path=state.download_path):
|
|||||||
init_session()
|
init_session()
|
||||||
|
|
||||||
if magnet_uri in state.active_downloads:
|
if magnet_uri in state.active_downloads:
|
||||||
consoleLog("Skipping, download already running...")
|
consoleLog("Skipping Downloading, download already running...")
|
||||||
return
|
return
|
||||||
|
|
||||||
magnetdl = lt.parse_magnet_uri(magnet_uri)
|
magnetdl = lt.parse_magnet_uri(magnet_uri)
|
||||||
@@ -114,7 +114,7 @@ def update_settings():
|
|||||||
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
||||||
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
||||||
else:
|
else:
|
||||||
consoleLog(f"Error finding IP for Interface {state.bound_interface}")
|
consoleLog(f"Skipping Binding, no Interface set. ({state.bound_interface})")
|
||||||
|
|
||||||
state.dl_session.apply_settings(settings)
|
state.dl_session.apply_settings(settings)
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from PySide6.QtCore import QObject, Signal
|
from PySide6.QtCore import QObject, Signal
|
||||||
|
from PySide6.QtWidgets import QTableWidget
|
||||||
from typing import Optional, Any, List, Dict
|
from typing import Optional, Any, List, Dict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ class AppState(QObject):
|
|||||||
self.debug: bool = False
|
self.debug: bool = False
|
||||||
self.autoresume: bool = True
|
self.autoresume: bool = True
|
||||||
self.tracker: str = "rutracker"
|
self.tracker: str = "rutracker"
|
||||||
|
self.tracker_list: dict[str, QTableWidget] = {}
|
||||||
self.api_url: str = "https://api.michijackson.xyz"
|
self.api_url: str = "https://api.michijackson.xyz"
|
||||||
self.download_path: str = str(Path.home() / "Downloads")
|
self.download_path: str = str(Path.home() / "Downloads")
|
||||||
self.up_speed_limit: int = 0
|
self.up_speed_limit: int = 0
|
||||||
@@ -24,7 +24,12 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
|
|||||||
|
|
||||||
|
|
||||||
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
|
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
|
||||||
consoleLog("Skipped Logging, download already in file")
|
if magnet_uri in state.active_downloads:
|
||||||
|
consoleLog("Skipping Logging, download already running...")
|
||||||
|
return
|
||||||
|
consoleLog("File already in Log, updating Download State...")
|
||||||
|
hash = extract_hash_from_magnet(magnet_uri)
|
||||||
|
update_download_completed_by_hash(hash, False)
|
||||||
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
|
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user