reporter
This commit is contained in:
@@ -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()
|
||||
|
||||
295
50 TorrentManipulation.py
Normal file
295
50 TorrentManipulation.py
Normal file
@@ -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.")
|
||||
72
60 Testcountoftorrents.py
Normal file
72
60 Testcountoftorrents.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user