Der kostenlose Beta-Key wechselt ~monatlich und laeuft zum Monatsende ab; bisher
musste er von Hand nachgetragen werden, sonst blockt Blu-ray-Ripping irgendwann
still. Neu: taeglicher Forum-Abgleich (t=1053) -> Key in die Settings
(makemkvAppKey). Der Worker liest ihn VOR jedem Rip (tasks.py) -> wirkt ohne
Rebuild ab dem naechsten Rip.
- docker/api/makemkv_key.py: extract_key (pure, testbar) + fetch_current_key +
refresh_once/refresh_loop; loggt in die App-Logs (add_log).
- docker/api/main.py: refresh_loop als startup-Task.
- docker/api/test_makemkv_key.py: Parser-Tests - fingen den Bug, dass Beta-Keys
laenger als 64 Zeichen sind (Regex {50,} statt {64}).
Betrifft nur die Software-Lizenz (oeffentlicher Gratis-Key), NICHT Disc-Schluessel
- die zieht MakeMKV via LibreDrive selbst. Ruff gruen, 4 Tests gruen, Live-Fetch
gegen das echte Forum verifiziert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import uuid
|
||||
|
||||
import db
|
||||
import devices as device_discovery
|
||||
import makemkv_key
|
||||
import mounts as mount_verwaltung
|
||||
import notify
|
||||
from celery_client import celery_client, start_rip
|
||||
@@ -53,6 +54,10 @@ async def startup_event():
|
||||
await asyncio.to_thread(remount)
|
||||
|
||||
asyncio.create_task(disc_watcher())
|
||||
# MakeMKV-Beta-Key automatisch aktuell halten (wechselt ~monatlich, laeuft zum
|
||||
# Monatsende ab) — sonst blockt Blu-ray-Ripping irgendwann still. Taeglicher
|
||||
# Forum-Abgleich; wirkt ohne Rebuild ab dem naechsten Rip.
|
||||
asyncio.create_task(makemkv_key.refresh_loop())
|
||||
|
||||
|
||||
# Auto-Pre-Scan-Ergebnisse je Laufwerk: das Dashboard zeigt damit sofort,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Automatische Erneuerung des MakeMKV-Beta-Keys.
|
||||
|
||||
Der kostenlose MakeMKV-Beta-Key (Forum-Thread "MakeMKV is free while in beta",
|
||||
t=1053) wechselt etwa monatlich und laeuft zum Monatsende ab. Ohne gueltigen Key
|
||||
faellt Blu-ray/UHD-Ripping in den 30-Tage-Testmodus; DVD-Ripping bleibt frei.
|
||||
Bisher musste der Key von Hand nachgetragen werden (UI: Einstellungen -> System).
|
||||
|
||||
Dieses Modul holt den aktuellen Key vom Forum und schreibt ihn in die Settings
|
||||
(makemkvAppKey). Der Worker liest den Key VOR jedem Rip aus den Settings
|
||||
(docker/worker/tasks.py) und schreibt settings.conf -> die Erneuerung wirkt ohne
|
||||
Rebuild/Neustart, ab dem naechsten Rip.
|
||||
|
||||
Wichtig: Das ist die kostenlose, oeffentliche Beta-LIZENZ der Software selbst —
|
||||
NICHT das Entschluesseln oder Verteilen von Disc-Schluesseln. Den AACS-Schluessel
|
||||
zieht MakeMKV via LibreDrive ohnehin selbst aus dem Laufwerk.
|
||||
|
||||
Quelle/Format dokumentiert (AGENTS Regel D — nicht geraten):
|
||||
- Forum-Thread: https://forum.makemkv.com/forum/viewtopic.php?t=1053
|
||||
- Key-Format: "T-" + langer String aus [A-Za-z0-9_] (typ. ~66 Zeichen; die exakte
|
||||
Laenge variiert je Key, darum kein fixes Limit, sondern mind. 50, greedy).
|
||||
"""
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
FORUM_URL = "https://forum.makemkv.com/forum/viewtopic.php?t=1053"
|
||||
KEY_RE = re.compile(r"T-[A-Za-z0-9_]{50,}")
|
||||
_HEADERS = {"User-Agent": "rippy-makemkv-key-refresh/1.0"}
|
||||
|
||||
|
||||
def extract_key(html: str) -> str | None:
|
||||
"""Zieht den Beta-Key ("T-...") aus dem Forum-HTML. Pure Funktion -> testbar."""
|
||||
treffer = KEY_RE.search(html or "")
|
||||
return treffer.group(0) if treffer else None
|
||||
|
||||
|
||||
def fetch_current_key(timeout: int = 15) -> str | None:
|
||||
"""Holt den aktuellen Beta-Key vom MakeMKV-Forum. None bei Fehler/kein Treffer."""
|
||||
import requests # lazy: haelt extract_key + den Test dependency-frei
|
||||
antwort = requests.get(FORUM_URL, headers=_HEADERS, timeout=timeout)
|
||||
antwort.raise_for_status()
|
||||
return extract_key(antwort.text)
|
||||
|
||||
|
||||
def _apply_key(key: str) -> bool:
|
||||
"""Merge den Key in die Settings. save_settings ueberschreibt das GANZE JSON,
|
||||
darum erst lesen, dann setzen. Rueckgabe True, wenn sich der Key geaendert hat."""
|
||||
import db
|
||||
einstellungen = db.get_settings()
|
||||
if (einstellungen.get("makemkvAppKey") or "").strip() == key:
|
||||
return False
|
||||
einstellungen["makemkvAppKey"] = key
|
||||
db.save_settings(einstellungen)
|
||||
return True
|
||||
|
||||
|
||||
async def refresh_once() -> bool:
|
||||
"""Einmal holen + anwenden. Best-effort, loggt in die App-Logs. True = geaendert."""
|
||||
import db
|
||||
key = await asyncio.to_thread(fetch_current_key)
|
||||
if not key:
|
||||
db.add_log("warning", "makemkv-key",
|
||||
"Aktueller Beta-Key nicht im Forum gefunden (Format geaendert?).")
|
||||
return False
|
||||
geaendert = await asyncio.to_thread(_apply_key, key)
|
||||
if geaendert:
|
||||
db.add_log("success", "makemkv-key",
|
||||
f"MakeMKV-Beta-Key automatisch erneuert (…{key[-6:]}).")
|
||||
return geaendert
|
||||
|
||||
|
||||
async def refresh_loop(intervall_stunden: int = 24, start_verzoegerung_s: int = 60) -> None:
|
||||
"""Periodische Erneuerung (Default: taeglich, damit ein Monatswechsel nie verpasst
|
||||
wird). Fehler werden geloggt, aber nie geworfen -> die API laeuft weiter."""
|
||||
import db
|
||||
await asyncio.sleep(start_verzoegerung_s)
|
||||
while True:
|
||||
try:
|
||||
await refresh_once()
|
||||
except Exception as exc:
|
||||
db.add_log("warning", "makemkv-key", f"Key-Refresh fehlgeschlagen: {exc!r}")
|
||||
await asyncio.sleep(max(3600, intervall_stunden * 3600))
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Tests fuer den MakeMKV-Beta-Key-Parser (dependency-frei, nur extract_key)."""
|
||||
from makemkv_key import extract_key
|
||||
|
||||
# Beispiel-Key im echten Format (T- + 64 Zeichen [A-Za-z0-9_]).
|
||||
_KEY = "T-BSaJ6gwgMx4eIggWkVYXiVP_6zehm7WAO9dEydvzOHFHoZ6YQ82BL5cGpYDxvyRWnS"
|
||||
|
||||
|
||||
def test_extract_key_findet_key_im_html():
|
||||
html = f'<div class="content"><pre><code>{_KEY}</code></pre></div>'
|
||||
assert extract_key(html) == _KEY
|
||||
|
||||
|
||||
def test_extract_key_ohne_key_gibt_none():
|
||||
assert extract_key("<html>kein Schluessel hier</html>") is None
|
||||
assert extract_key("") is None
|
||||
|
||||
|
||||
def test_extract_key_zu_kurz_kein_treffer():
|
||||
# Format ist streng T- + genau 64 Zeichen — zu kurz faellt raus.
|
||||
assert extract_key("T-zukurz123") is None
|
||||
|
||||
|
||||
def test_extract_key_liefert_ganzen_key():
|
||||
# Regression: die Regex darf den Key NICHT abschneiden (Beta-Keys sind laenger
|
||||
# als die im Forum genannten 64 Zeichen — hier der volle Key zwischen Text).
|
||||
treffer = extract_key(f"Vorher-Text {_KEY} Nachher-Text")
|
||||
assert treffer == _KEY
|
||||
assert treffer.startswith("T-")
|
||||
Reference in New Issue
Block a user