From 314eb20e6b4c5a115898f51a4119df6da0261bc6 Mon Sep 17 00:00:00 2001 From: vlado Date: Mon, 15 Dec 2025 06:28:20 +0100 Subject: [PATCH] reporter --- 30 OpenTextLIsting v5.py | 2 +- 50 TorrentManipulation.py | 295 ++++++++++++++++++++++++++++++++++++++ 60 Testcountoftorrents.py | 72 ++++++++++ 3 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 50 TorrentManipulation.py create mode 100644 60 Testcountoftorrents.py diff --git a/30 OpenTextLIsting v5.py b/30 OpenTextLIsting v5.py index ac2d9e5..35ca7b9 100644 --- a/30 OpenTextLIsting v5.py +++ b/30 OpenTextLIsting v5.py @@ -39,7 +39,7 @@ COOKIE_FILE = Path("sktorrent_cookies.json") # Start URL pro kategorii 24, seřazeno podle data DESC START_URL = ( "https://sktorrent.eu/torrent/torrents.php" - "?active=0&category=24&order=data&by=DESC&zaner=&jazyk=&page=90" + "?search=&category=24&zaner=&jazyk=&active=0" ) chrome_options = Options() diff --git a/50 TorrentManipulation.py b/50 TorrentManipulation.py new file mode 100644 index 0000000..0d7d898 --- /dev/null +++ b/50 TorrentManipulation.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import time +from datetime import datetime, timedelta + +import pymysql +import qbittorrentapi +import bencodepy + + +# ============================== +# ⚙ CONFIGURATION +# ============================== + +DB_CONFIG = { + "host": "192.168.1.76", + "port": 3307, + "user": "root", + "password": "Vlado9674+", + "database": "torrents", + "charset": "utf8mb4", + "autocommit": True, +} + +QBT_CONFIG = { + "host": "192.168.1.76", + "port": 8080, + "username": "admin", + "password": "adminadmin", +} + +MAX_ACTIVE_DOWNLOADS = 10 +LOOP_SLEEP_SECONDS = 60 + +# Torrent označíme jako "dead" pokud nebyl nikdy "seen_complete" +# více než X minut od přidání +DEAD_TORRENT_MINUTES = 5 + +DEFAULT_SAVE_PATH = None + + + +# ============================== +# 🔧 CONNECT +# ============================== + +db = pymysql.connect(**DB_CONFIG) +cursor = db.cursor(pymysql.cursors.DictCursor) + +qb = qbittorrentapi.Client( + host=QBT_CONFIG["host"], + port=QBT_CONFIG["port"], + username=QBT_CONFIG["username"], + password=QBT_CONFIG["password"], +) + +try: + qb.auth_log_in() + print("✅ Connected to qBittorrent.") +except Exception as e: + print("❌ Could not connect:", e) + raise SystemExit(1) + + + +# ============================== +# 🧪 TORRENT VALIDATION +# ============================== + +def is_valid_torrent(blob: bytes) -> bool: + """ + Returns True only if BLOB is a valid .torrent file. + """ + try: + data = bencodepy.decode(blob) + return isinstance(data, dict) and b"info" in data + except Exception: + return False + + + +# ============================== +# 🔄 SYNC FROM QB → DB +# ============================== + +def sync_qb_to_db(): + torrents = qb.torrents_info() + + for t in torrents: + completion_dt = None + if getattr(t, "completion_on", 0): + try: + completion_dt = datetime.fromtimestamp(t.completion_on) + except: + pass + + sql = """ + UPDATE torrents + SET qb_added = 1, + qb_hash = COALESCE(qb_hash, %s), + qb_state = %s, + qb_progress = %s, + qb_savepath = %s, + qb_completed_datetime = + IF(%s IS NOT NULL AND qb_completed_datetime IS NULL, %s, qb_completed_datetime), + qb_last_update = NOW() + WHERE qb_hash = %s OR torrent_hash = %s + """ + + cursor.execute(sql, ( + t.hash, + t.state, + float(t.progress) * 100.0, + getattr(t, "save_path", None), + completion_dt, + completion_dt, + t.hash, + t.hash + )) + + + +# ============================== +# 🧹 HANDLE COMPLETED + DEAD TORRENTS +# ============================== + +def handle_completed_and_dead(): + torrents = qb.torrents_info() + + for t in torrents: + t_hash = t.hash + state = t.state + progress = float(t.progress) + + # ========================== + # ✔ COMPLETED + # ========================== + if progress >= 1.0 or state in {"completed", "uploading", "stalledUP", "queuedUP"}: + print(f"✅ Completed torrent → remove (keep data): {t.name}") + try: + qb.torrents_delete(torrent_hashes=t_hash, delete_files=False) + except Exception as e: + print("⚠️ delete failed:", e) + + cursor.execute(""" + UPDATE torrents + SET qb_state='completed', + qb_progress=100, + qb_completed_datetime=NOW(), + qb_last_update=NOW() + WHERE qb_hash=%s OR torrent_hash=%s + """, (t_hash, t_hash)) + + continue + + # ========================== + # ❌ DEAD TORRENT (never seen_complete) + # ========================== + + props = qb.torrents_properties(t_hash) + seen = getattr(props, "last_seen", 0) + + if seen == -1: # never seen complete + added_dt = getattr(t, "added_on", 0) + if added_dt: + added_time = datetime.fromtimestamp(added_dt) + if datetime.now() - added_time > timedelta(minutes=DEAD_TORRENT_MINUTES): + print(f"💀 Dead torrent (> {DEAD_TORRENT_MINUTES} min unseen): {t.name}") + try: + qb.torrents_delete(torrent_hashes=t_hash, delete_files=True) + except: + pass + + cursor.execute(""" + UPDATE torrents + SET qb_state='dead', + qb_last_update=NOW() + WHERE qb_hash=%s OR torrent_hash=%s + """, (t_hash, t_hash)) + + + +# ============================== +# 📊 COUNT ACTIVE DOWNLOADS +# ============================== + +def count_active_downloads(): + torrents = qb.torrents_info(filter="all") + return sum(1 for t in torrents if float(t.progress) < 1.0) + + + +# ============================== +# ➕ ENQUEUE NEW TORRENTS +# ============================== + +def enqueue_new_torrents(): + active = count_active_downloads() + + print("DEBUG active =", active) + + if active >= MAX_ACTIVE_DOWNLOADS: + print(f"📦 {active}/{MAX_ACTIVE_DOWNLOADS} active → no enqueue") + return + + slots = MAX_ACTIVE_DOWNLOADS - active + + sql = """ + SELECT id, torrent_hash, torrent_content, torrent_filename, added_datetime + FROM torrents + WHERE (qb_added IS NULL OR qb_added = 0) + AND torrent_content IS NOT NULL + ORDER BY added_datetime DESC -- <── take NEWEST FIRST + LIMIT %s + """ + cursor.execute(sql, (slots,)) + rows = cursor.fetchall() + + if not rows: + print("ℹ️ No new torrents") + return + + for row in rows: + t_id = row["id"] + t_hash = row["torrent_hash"] + blob = row["torrent_content"] + filename = row.get("torrent_filename", "unknown.torrent") + + if not blob: + print("⚠️ empty blob, skip") + continue + + # ========================== + # 🧪 VALIDATION OF .TORRENT + # ========================== + + if not is_valid_torrent(blob): + print(f"❌ INVALID TORRENT id={t_id}, size={len(blob)} → deleting content") + + cursor.execute(""" + UPDATE torrents + SET qb_state='invalid', + torrent_content=NULL, + qb_last_update=NOW() + WHERE id=%s + """, (t_id,)) + continue + + # ========================== + # ➕ ADD TORRENT + # ========================== + + print(f"➕ Adding torrent: {filename} ({t_hash})") + + try: + qb.torrents_add(torrent_files=blob, savepath=DEFAULT_SAVE_PATH) + except Exception as e: + print(f"❌ Failed to add {t_hash}: {e}") + continue + + cursor.execute(""" + UPDATE torrents + SET qb_added=1, + qb_hash=COALESCE(qb_hash, %s), + qb_state='added', + qb_last_update=NOW() + WHERE id=%s + """, (t_hash, t_id)) + + + +# ============================== +# 🏁 MAIN LOOP +# ============================== + +print("🚀 Worker started") + +try: + while True: + print(f"\n⏱ Loop {datetime.now():%Y-%m-%d %H:%M:%S}") + + sync_qb_to_db() + handle_completed_and_dead() + enqueue_new_torrents() + + print(f"🛌 Sleep {LOOP_SLEEP_SECONDS}s\n") + time.sleep(LOOP_SLEEP_SECONDS) + +except KeyboardInterrupt: + print("🛑 Stopping worker...") + +finally: + db.close() + print("👋 Bye.") diff --git a/60 Testcountoftorrents.py b/60 Testcountoftorrents.py new file mode 100644 index 0000000..8f6b8b3 --- /dev/null +++ b/60 Testcountoftorrents.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from datetime import datetime +import qbittorrentapi + +# ============================== +# CONFIG – přizpůsob si podle sebe +# ============================== +QBT_CONFIG = { + "host": "192.168.1.76", + "port": 8080, + "username": "admin", + "password": "adminadmin", +} + +def fmt_ts(ts: int) -> str: + """ + Převod unix timestampu na čitelný string. + qBittorrent vrací -1 pokud hodnota není známá. + """ + if ts is None or ts <= 0: + return "—" + try: + return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") + except Exception: + return f"invalid({ts})" + + +def main(): + # Připojení + qb = qbittorrentapi.Client( + host=QBT_CONFIG["host"], + port=QBT_CONFIG["port"], + username=QBT_CONFIG["username"], + password=QBT_CONFIG["password"], + ) + + try: + qb.auth_log_in() + print("✅ Connected to qBittorrent\n") + except Exception as e: + print("❌ Could not connect to qBittorrent:", e) + return + + # Všechno, žádný filter na downloading + torrents = qb.torrents_info(filter='all') + print(f"Found {len(torrents)} torrents (filter='all')\n") + + for t in torrents: + # properties – obsahují last_seen + try: + props = qb.torrents_properties(t.hash) + except Exception as e: + print(f"⚠️ Cannot get properties for {t.hash[:8]} {t.name}: {e}") + continue + + seen_complete = getattr(t, "seen_complete", None) # z /torrents/info + last_seen = getattr(props, "last_seen", None) # z /torrents/properties + + print("=" * 80) + print(f"Name : {t.name}") + print(f"Hash : {t.hash}") + print(f"State : {t.state}") + print(f"Progress : {float(t.progress) * 100:.2f}%") + print(f"Seen complete: {fmt_ts(seen_complete)} (t.seen_complete)") + print(f"Last seen : {fmt_ts(last_seen)} (props.last_seen)") + + print("\n✅ Done.") + +if __name__ == "__main__": + main()