Symptom (per Ferndiagnose live verifiziert): Bei nur EINEM Mega-Debrid-Account lief
der Download eine Weile sauber, dann standen schlagartig ALLE Items ~120s im
"Mega-Debrid Cooldown" — obwohl der Account voellig gesund war (andere Links loesten
zeitgleich in 13-18s auf). Jede Cooldown-Zeile zeigte exakt dieselbe Deadline
(20:52:55.901) -> ein einziger account-weiter Cooldown, einmal gesetzt.
Ursache: Laeuft eine Web-Aufloesung laenger als das 60s-Gesamt-Timeout, feuert der
Abbruch-Pfad (debrid.ts) einen 120s-Account-Cooldown. Dessen einziger Zweck (laut
Code-Kommentar) ist, den Retry auf den NAECHSTEN Account rotieren zu lassen. Bei nur
einem Account gibt es keinen naechsten -> stattdessen findet jedes folgende Item den
einzigen Account im Cooldown und wird bis zu 120s geparkt -> die ganze Liste steht.
Ein 60s-Timeout ist ein Signal fuer einen LANGSAMEN LINK, nicht fuer einen
ungesunden Account.
Fix: Der Account-Cooldown wird nur noch gesetzt, wenn es tatsaechlich einen anderen
nutzbaren Account zum Rotieren gibt. Ohne Rotationsziel (Einzel-Account / alle
anderen belegt) wird der Account NICHT mehr eingefroren; stattdessen wird nur der
langsame Link selbst geparkt (mega_debrid_slow_link -> Item-Retry), waehrend alle
anderen Items weiter ueber den gesunden Account laufen. Das Mehr-Account-Verhalten
(Rotation per Account-Cooldown) bleibt unveraendert.
Der baugleiche Debrid-Link-Pfad (Einzel-Key, debrid.ts) ist derselbe Muster-Typ,
aber ein separater, hier nicht genutzter Provider mit eigener Nachbehandlung -
bewusst nicht mitgebuendelt.
Tests: debrid.test.ts (Einzel-Account-Abbruch parkt nur den Link, KEIN
Account-Cooldown, zweites Item loest weiter auf) + unrestrict-retry.test.ts
(parseMegaDebridSlowLinkRetry, keine Token-Kollision). Suite 934 gruen, tsc 6.
Eine leere Mega-Debrid-API-Antwort (success, aber kein Download-Link ->
debrid.ts:1927 'Linkgenerierung lieferte kein Ergebnis') fiel in
classifyAccountFailure in den Default-Zweig -> 30s Account-Cooldown
('Mega-Debrid Cooldown, neuer Versuch in 31s'). Das ist KEIN Limit-/Quota-Signal,
sondern ein transienter API-Blip, der praktisch sofort wieder funktioniert (vom
Nutzer live beobachtet: danach ging es). Fix: eigener Zweig klassifiziert die leere
Antwort wie die anderen transienten Resolve-Fehler (cooldownMs 0, kein
Account-Cooldown) -> der Download-Manager nimmt den schnellen Transient-Retry-Pfad
(3s, eskaliert auf max 10s) statt 31s zu warten. Kein limitSignal (kein
until-restart-Park). Rot-bewiesener Test (ohne Fix cooldownMs 30000; mit Fix 0 +
Meldung trifft isMegaDebridTransientResolveFailure). Volle Suite 894 gruen, tsc=6.
BYTE-DROP-RETRY-1 (MED): Integritaets-/Zu-klein-/Tiny-Neuversuche zaehlten die
volle Dateigroesse pro Versuch erneut in die Byte-Statistik. Ursache: die 3
rm-dann-frisch-Sites (Integrity-Fail 8979, too-small 9020, tiny 10486) riefen
dropItemContribution() auf, das den itemContributedBytes-Eintrag loescht, den die
einzige Reconciliation (9991, writeMode 'w') zum Subtrahieren braucht -> Subtraktion
tot -> Re-Download addiert erneut. Fix (Mechanismus a): dropItemContribution an
diesen 3 Sites entfernt, der Eintrag ueberlebt, 9991 subtrahiert ihn korrekt
(selbst-korrigierend nach writeMode). Zusaetzlich am selben Punkt
totalDownloadedAllTime subtrahiert (wurde nie subtrahiert -> doppelte bei JEDEM
frischen Re-Download). BEWUSST ausgeklammert: recordProviderDownloadedBytes/
providerDailyUsageBytes, da diese isProviderDailyLimited (= Verhalten) steuern und
nicht provider-keyed sind. Reine Telemetrie-Korrektur, kein Slot/Admission betroffen.
DL-1 (LOW): Ein abort-ohne-timeout (User-Cancel) setzte am DebridLink-Rotations-
Catch via classifyKeyFailure einen 15s-Key-Cooldown (und konnte ueber Keys zu einer
providerweiten Kaskade fuehren). Fix: Mega-Gate (2072) am Catch (2789) gespiegelt -
abort-ohne-timeout + elapsedMs < getMegaDebridAbortMinRunMs() -> kein Cooldown;
ran-long-enough -> 120s (Retry rotiert); throw bailt die Rotation.
DL-CONCURRENCY-PILEUP (MED): untersucht -> bereits strukturell geloest
(getSerializedValidatingLimit = nutzbare Accounts + shouldDelayStartForItem 8526;
MW-1 haelt das Limit hoch). Kein Eingriff (Nutzer-Entscheidung).
Je rot-bewiesener Test (per Temp-Revert, nicht-vakuum; BYTE-DROP beide Beine
einzeln). Volle Suite 893 gruen, tsc unveraendert (6 vorbestehende Fehler).
MW-1 (HIGH): Ein gesunder Mega-Web-Account wurde 120s gesperrt, wenn der
60s-Gesamttimeout ablief, waehrend die Umwandlung noch SERIELL in der
per-Account-Single-Flight-Queue auf ihren Vorgaenger wartete - also bevor echte
Arbeit begann. Die Queue-Wartezeit zaehlte zu elapsedMs (>= 8s), sodass der
Abbruch faelschlich wie ein In-Arbeit-Abbruch eines langsamen Accounts gewertet
wurde. Genau die gemeldete 'Tool sperrt sich selbst'-Klasse (Web-Variante).
Zweiteilig: (1) MegaWebFallback.runExclusive trackt workStarted und meldet einen
Abbruch-vor-Arbeitsbeginn als Queue-Timeout statt aborted:mega-web;
(2) unrestrictViaWeb bewahrt diese Klassifikation statt sie zu aborted:debrid zu
plaetten -> die Rotation trifft die Queue-Timeout-Ausnahme (cooldownMs 0) statt
den Abbruch-Cooldown. Ein echter In-Arbeit-Abbruch sperrt weiterhin (langsame
Accounts korrekt ueberspringen). Nach dem No-Cooldown-Pfad rotiert der Retry per
In-Flight-Tiefe auf einen freien Account.
SET-MIG-01 (MED): Eine Config von vor v1.6.90 kannte die getrennten
Mega-API/Web-Enable-Flags nicht. readSettingsFile merged {...defaultSettings(),
...parsed}, also fuellten die false-Defaults die fehlenden Flags BEVOR
normalizeSettings lief -> der creds-basierte Migrationszweig war tot, Mega blieb
auf aus trotz vorhandener Creds und wurde beim ersten Settings-Save still aus der
providerOrder demotet. Fix: reine migrateLegacyMegaEnableFlags seedet die Flags
nur, wenn BEIDE im RAW-parsed fehlen und Creds vorhanden sind (absent-both als
einziger sicherer Trigger; present-false bleibt unberuehrt = bewusst-deaktiviert
nicht re-aktivieren). Greift nur bei Legacy-Configs, die seit dem Upgrade noch
nicht neu gespeichert wurden.
Je rot-bewiesener Test (per Temp-Revert verifiziert, nicht-vakuum). Volle Suite
890 gruen, tsc unveraendert (6 vorbestehende Fehler). Runde-9/10-Doku + die zwei
offenen Nutzer-Entscheidungen aktualisiert.
Wenn ALLE Mega-Debrid-Accounts wegen wiederholt leerer/Server-loser Antworten
bis zum Tagesreset geparkt waren (in-memory untilRestart-Park aus Runde 3),
warf unrestrictWithAccounts einen reinen Klartext-Fehler ohne Maschinen-Token.
Im Manager-Catch fiel dieser durch parseMegaDebridCooldownRetry (kein
mega_debrid_cooldown:-Praefix) und isMegaDebridTransientResolveFailure und
landete im generischen isUnrestrictFailure-Zweig — nur weil der Text
"mega-debrid" enthaelt. Folge: das Item wurde den ganzen Tag etwa alle zwei
Minuten neu versucht (120s-Cap) und fuetterte dabei recordProviderFailure den
Provider-Circuit-Breaker, statt einmal bis zum Tagesreset zu parken. Bei
Standard-Einstellungen (retryLimit=0=unendlich) heilte es sich zwar um
Mitternacht selbst (kein Stranding), war aber unnoetige Log-Flut und Churn und
unterlief genau die untilRestart-Park-Absicht aus Runde 3.
Cross-Layer-Synthese-Pass (adversarisch verifiziert) hat parallel die
Advisor-Hypothese eines Key-Mismatch (Manager cool't aufgeloesten
"megadebrid-api", Fallback-Suche liest rohen "megadebrid") WIDERLEGT:
normalizeProviderOrder speichert immer den aufgeloesten Key, also faellt
Write/Check/Clear/Read auf denselben Key — der Routing-Fix (986fbab) ist fuer
den Mega-Fall nachweislich sicher.
Fix (konservativ, spiegelt den bereits korrekten mega_debrid_cooldown-Zweig):
- debrid.ts emittiert beim untilRestart-Park jetzt den Token
mega_debrid_reset_park:<msBisReset>: (Delay aus megaDebridDailyParkExpiry,
deckt sich exakt mit dem in-memory Park-Ablauf).
- download-manager.ts: neue reine parseMegaDebridResetPark (kein 15min-Clamp,
26h-Cap) + Catch-Branch VOR der Cooldown-Klassifikation, der bis zum
Tagesreset queued OHNE recordProviderFailure (ein geplanter Park ist kein
Provider-Fehler).
- Token enthaelt weiter "mega_debrid" → jeder Fall-through landet schlimmstenfalls
im heutigen Verhalten (sichere Untergrenze).
Tests: 5 neue (parseMegaDebridResetPark: parst/embedded/kein-15min-Clamp/Cap+Junk,
plus cooldown-Parser ignoriert den neuen Token) + die all-parked debrid-Assertion
prueft jetzt den Token. Volle Suite 881 gruen, tsc unveraendert.
Wenn ein Provider in den Manager-Cooldown laeuft (>=20 Fehler in Folge) und
auto-Fallback aktiv ist, berechnete der Manager zwar einen Ersatz-Provider
(findFallbackProviderNotInCooldown), warf ihn dann aber weg: der unrestrictLink-
Aufruf bekam keinen Hint, also baute debrid.ts die Provider-Kette frisch aus
providerOrder und fuehrte erneut mit dem ausgebremsten Provider 1 an — ein meist
in den 60s-Timeout laufender, verschwendeter Versuch, bevor ueberhaupt der
gesunde Provider drankam.
Jetzt wird der Ersatz-Provider als bevorzugter Lead durchgereicht. Die Kette
wird UMSORTIERT, nicht beschnitten: der ausgebremste Provider bleibt als letzter
Notnagel in der Reihenfolge, sodass kein Link gestrandet werden kann. Sind ALLE
Provider im Cooldown, erreicht der Aufruf unrestrictLink ohnehin nicht (der
else-Zweig queued einen Retry). Greift damit nur, wenn ein Provider bereits
nachweislich degradiert ist — strikt besser, wenn aktiv, und ohne Timeout-,
Cancel- oder Budget-Vertrag anzufassen.
- debrid.ts: reine leadProviderChainWith(order, preferred) (export) + optionaler
4. Param preferredLeadProvider an unrestrictLink; Reorder direkt nach Aufbau
der Reihenfolge.
- download-manager.ts: Ersatz-Provider in preferredLeadProvider festhalten und
an den unrestrictLink-Aufruf durchreichen.
- Tests: Integration (ohne Hint fuehrt realdebrid, mit Hint fuehrt debridlink) +
drei reine Helper-Tests fuer die No-Stranding-Invariante.
Zwei verbleibende HIGH-Punkte sind bewusst NICHT autonom geaendert, sondern als
Produkt-/UI-Entscheidung in tasks/entscheidungen-offen.md vorgelegt (globaler
60s-Failover-Timeout; gespiegelter Mega API/Web-Schalter).
Ein gesunder Mega-Debrid-Account konnte sich dauerhaft "bis Neustart" sperren,
ohne sich von selbst zu erholen — genau das vom Nutzer beobachtete Verhalten
("das Tool setzt sich selbst Cooldowns"). Zwei Ursachen, beide behoben:
1) Falsche Einstufung eines HOSTER-Problems als ACCOUNT-Limit:
classifyAccountFailure hat den hoster-spezifischen Fehler "Kein Server fuer
diesen Hoster" (MEGA_DEBRID_NO_SERVER_RE — Mega-Debrid hat fuer EINEN Hoster,
z.B. Rapidgator, gerade keinen Server) mit limitSignal=true markiert. Dieses
Signal speist den "bis Neustart"-Streak (3x -> Dauer-Park). Bei einer
Warteschlange voller Links desselben (kurzzeitig nicht bedienbaren) Hosters
kletterte der Streak hoch und parkte einen kerngesunden Account — der sich
dann ueber die Rotation auf ALLE Accounts ausbreitete. Fix: limitSignal aus
dem No-Server-Zweig entfernt (der 120s-Cooldown bleibt). Hoster-Verfuegbarkeit
ist kein Account-Gesundheitssignal. Der echte Tageslimit-Pfad (leere Antwort)
bleibt unveraendert.
2) "Bis Neustart"-Park hatte keine Ablaufzeit (until = MAX_SAFE_INTEGER) und
konnte daher NIE von selbst aufgehen — auch nicht, nachdem Mega-Debrid wieder
Server hatte oder das Tageslimit um Mitternacht zuruecksetzt. Nur ein Neustart
half. Fix: Der Park laeuft jetzt zum naechsten Tagesreset (lokale Mitternacht,
mind. 120s) ab und heilt sich damit garantiert innerhalb von <=24h selbst —
egal welcher Zweig ihn ausgeloest hat. Texte entsprechend von "bis Neustart
gesperrt" auf "bis zum Tagesreset gesperrt" angepasst (Main + Renderer
konsistent).
Tests: (a) ein geparkter Account ist nach dem Tagesreset wieder frei statt
dauerhaft gesperrt (rot ohne Fix: MAX_SAFE_INTEGER laeuft nie ab); (b) ein
"no server"-Fehler liefert kein limitSignal mehr (rot ohne Fix), waehrend die
echte leere Antwort weiterhin als Limit zaehlt. 86 Debrid-Tests gruen.
Wenn ein 1fichier- oder DDownload-Link beim Entsperren scheiterte, fiel das
Tool bisher trotzdem auf die Provider-Kette (z.B. Mega-Debrid) durch — auch
wenn der Nutzer den automatischen Anbieter-Fallback in den Einstellungen
ausgeschaltet hatte. Damit wurde ein anderer Anbieter (und dessen Tageslimit/
Account) ungewollt verbraucht, obwohl der Nutzer genau das unterbinden wollte.
Jetzt prüfen die catch-Blöcke beider Hoster zusätzlich autoProviderFallback:
ist er aus, wird der Fehler direkt weitergereicht statt still auf den nächsten
Anbieter umzuschalten. Die Abbruch-Behandlung (User-Cancel/echtes aborted)
bleibt unverändert davor.
Wichtig: Die Dateinamen-Auflösung (getLinkInfos) bleibt bewusst NICHT betroffen
— deren Fehlschlag muss weiterhin nicht-fatal sein.
Test: 1fichier liefert KO + Fallback aus → Linkgenerierung wird abgelehnt und
die Mega-Debrid-getLink-Route nachweislich nicht aufgerufen.
Aus einem adversarisch verifizierten Multi-Agent-Audit (14 confirmed/11 refuted):
#1 HIGH Scheduler-Freeze: findNextQueuedItem hatte keinen activeTasks-Guard.
Wird ein Item zurueckgesetzt/ueberschrieben, waehrend sein alter Task noch in
einem nicht-abbrechbaren await parkt (z.B. Integritaets-Check), liefert
findNextQueuedItem dasselbe Item, startItem lehnt es ab ohne activeTasks zu
verkleinern → der SYNCHRONE Admission-Loop dreht endlos → Event-Loop friert
permanent ein. Fix: `if (this.activeTasks.has(itemId)) continue;`. Repro-Test
(ohne Fix haengt sogar der vitest-Timeout — Freeze bewiesen).
#2/#3 MED mega_debrid_cooldown:<ms> wurde verworfen: kein Parser (nur das
debrid_link-Analogon) → Item lief in den generischen 5s-Exponential-Backoff
und fragte cooled Accounts im Sekundentakt erneut an. Neuer
parseMegaDebridCooldownRetry (nimmt das frueheste Cooldown-Ende ueber alle
Accounts) + Handler VOR transient/generic → Item wartet die echte Cooldown-Zeit.
#7/#8 MED Mega per-Account Tages-Usage wurde am Tageswechsel nie zurueckgesetzt
(ensureProviderDailyUsageFresh ruecksetzte nur provider/debrid-link), und weil
es den Tagesschluessel zuerst weiterstellte, lief auch der Reset in
addMegaDebridAccountDailyUsageBytes ins Leere → Accounts blieben den ganzen Tag
faelschlich "am Limit" und schrumpften das (neue) Pro-Account-Umwandlungslimit.
Fix: megaDebridAccountDailyUsageBytes im Tageswechsel mit zuruecksetzen.
#14 LOW classifyAccountFailure: rate_limit-Branch vor quota (quota matchte
"limit" in "rate limit" → Fehl-Kategorisierung).
852/852 gruen, tsc unveraendert (6). #4 (empty→until-restart) bewusst deferred.
Live aus conversion.log belegt: bei API-first + maxParallel hammerten viele
parallele Umwandlungen DENSELBEN Account → Mega-Debrids 1-Token-pro-Account-
Regel → "Token error, please log-in" (Token gegenseitig invalidiert). Ein
einzelner generischer Null-getLink wurde dann als "Login oder Unrestrict
fehlgeschlagen" geworfen → classifyAccountFailure matchte das Wort "Login" →
category=invalid → 60-MINUTEN-Cooldown auf einen NACHWEISLICH funktionierenden
Account (39x OK davor, klappt auch auf der Webseite). Folge: alle Accounts
cooled, alle Slots haengen an Fehl-Umwandlungen (conv12/dl0), nichts laedt.
Fix A (download-manager): getSerializedValidatingLimit gilt jetzt auch fuer
megadebrid-api (nicht nur -web) = Anzahl nutzbarer Accounts ohne :api-Cooldown.
Damit laeuft hoechstens EINE API-Umwandlung pro Account gleichzeitig (Rotation
verteilt 1/Account) — respektiert die 1-Token-Regel, verhindert die Token-
Kollision und gibt Download-Slots frei.
Fix B (debrid): (1) generische Wurf-Meldung "Login oder Unrestrict
fehlgeschlagen" -> "Linkgenerierung lieferte kein Ergebnis" (kein "Login"-
Trigger mehr). (2) classifyAccountFailure: "token error"/"please log-in" ->
kurzer temporary-Cooldown (15s); invalid-Branch nur noch bei ECHTEN
Credential-Fehlern (bad login/incorrect password/invalid credentials/
unauthorized/forbidden/connectUser) statt losem login|auth. Ein transienter
Fehler sperrt einen guten Account nicht mehr 60 min, sondern hoechstens Sekunden.
Tests: API-Serialisierung 2-Acct->2-parallel + 1-Acct->1-at-a-time; kein
60-min-invalid bei Null-Ergebnis; token-error<60s. 844/844 gruen, tsc=6.
Bewusst NUR Diagnose, KEINE Verhaltensaenderung — damit das naechste
Support-Bundle das echte aktuelle Verhalten zeigt und der naechste Fix
die Ursache trifft statt zu raten (live belegt: "Unrestrict Timeout nach
60s" R53/R64, API-"Token error, please log-in" kuehlt beide Accounts ab).
Neues Modul conversion-trace.ts: AsyncLocalStorage-Trace, der einen
unrestrict-Versuch ueber alle Schichten begleitet und EINEN strukturierten
Block pro Versuch nach conversion.log schreibt (Datei-Infra wie
account-rotation-log: Rotation bei 5 MB, 14 Tage Retention; im Support-
Bundle). tracePhase ist no-op ohne aktiven Trace — additiv, null Risiko.
Instrumentiert wird die Kern-Blindstelle der bisherigen Logs:
- download-manager Boundary: runWithConversionTrace + Slot-Belegung
(conv/dl/active/max) + Caller-Timeout-Attribution (was lief, als die 60s
feuerten).
- Provider-Kette (debrid): chain-try/ok/failed/aborted — zeigt, ob der
Web->API-Failover ueberhaupt feuert oder vom globalen Abbruch gekappt wird.
- Mega-Rotation: mega-account mit workMs + outcome (ok/failed/fatal/aborted)
+ Cooldown je Account.
- API-Token-Lifecycle: token cached/pending-join/fresh-login + connectMs,
getLink response_code/text — klaert die Herkunft von "Token error".
- Mega-Web runExclusive: web-queue mit queueWaitMs UND workMs getrennt —
klaert, ob die 60s Warten in der Queue oder echte langsame Arbeit sind.
Test conversion-trace.test.ts (Formatter + ALS-Kontext-Propagation).
838/838 gruen, tsc unveraendert (6 Baseline), Build ok.
v1.7.210 hat den franzoesischen Mega-Debrid-Fehler "Fichier supprime chez
l'hebergeur" als permanent toten Link behandelt (Item sofort gescheitert, kein
Web-Fallback). Die Support-Bundle-Logs widerlegen das: von 18 Links mit diesem
Fehler haben 4 Sekunden spaeter ein OK geliefert -- 1x ueber Web, 3x ueber
API-Retry (7-66s). Der Fehler ist TRANSIENT; v1.7.210 haette diese erholbaren
Links faelschlich dauerhaft gekillt und den rettenden Web-Fallback abgeklemmt.
Korrektur:
- classifyAccountFailure: "supprime"/"introuvable" -> temporaer, cooldownMs 0,
NICHT fatal. Der eigentliche Stall-Verursacher (30s Account-Cooldown, der bei
nur einem Account auch alle gesunden Links via SKIP_COOLDOWN blockierte) ist
damit weg, OHNE die erholbaren Links zu verlieren.
- isPermanentLinkError: franzoesische Phrasen wieder entfernt (nur echte
permanente engl. Signale fuer RD/AllDebrid bleiben).
- Provider-Kette + interner Web-Fallback: Kurzschluss entfernt -> Web-Fallback
wird wieder versucht (hat 1/4 der Links gerettet).
- Pacing kommt vom bestehenden 5s-Exponential-Backoff (unrestrictDelayMs) pro
Item, nicht vom Account-Cooldown -> weit unter Mega-Debrid 50 req/s, kein
IP-Ban-Risiko.
- Anzeige: deutsche, transient formulierte Meldung "Datei beim Hoster gerade
nicht abrufbar" statt franzoesisch und statt "tot".
- shared/dead-link.ts -> shared/mega-debrid-errors.ts, Semantik korrekt benannt.
Tests: tests/mega-debrid-errors.test.ts + behavioraler Test in debrid.test.ts
(supprime -> kein Account-Cooldown, deutsche Meldung, retrybar). Volle Suite
822/822 gruen.
Behebt die Queue-Quervergiftung; macht echte tote Links bei retryLimit 0 NICHT
aufgeben (vorbestehend, unveraendert). Parallel-Umwandlung folgt separat.
Mega-Debrid liefert bei geloeschten Hoster-Dateien franzoesische Fehlertexte
("Fichier supprime chez l'hebergeur"). Diese wurden weder in classifyAccountFailure
(debrid.ts) noch in isPermanentLinkError (download-manager.ts) erkannt und landeten
im generischen "temporaer"-Zweig: 30s Account-Cooldown + (bei retryLimit 0) endlose
Wiederholung. Mit nur einem Account (als API UND Web) blockierte ein einziger toter
Link ueber den Cooldown auch alle gesunden Links (SKIP_COOLDOWN) -- die Queue stand.
Beleg aus dem Support-Bundle: 479 von 983 Zeilen im Rotations-Log waren dieser Fehler.
- shared/dead-link.ts: zentrale Tot-Link-Erkennung (frz./dt./engl.), eine Quelle fuer
beide Schichten, damit die Muster nicht auseinanderdriften.
- classifyAccountFailure: toter Link -> fatal/skip, KEIN Account-Cooldown (Account ist
gesund, nur die Datei ist weg).
- isPermanentLinkError: toter Link -> Item sofort als gescheitert markiert, kein
Endlos-Retry. Greift auch im aggregierten Provider-Ketten-Fehlerstring.
- Provider-Kette + interner Web-Fallback: toter Link -> kein Fallback auf weitere
Provider/Modi (spart den 60s-Web-Timeout; saubere Meldung erreicht isPermanentLinkError
unveraendert).
- Anzeige: deutsche Klartext-Meldung "Link tot - Datei beim Hoster geloescht" statt frz.
- Account-Liste: Hinweis, dass Mega-Debrid API + Web derselbe Account in zwei Modi sind.
Tests: tests/dead-link.test.ts (inkl. aggregierter Provider-Ketten-String + akzentfreie
Variante). Volle Suite 823/823 gruen.
User-Wunsch: die Links eines Pakets parallel umwandeln statt seriell (single-flight),
damit sich die Download-Slots schneller fuellen. Gewaehlt: parallel ueber mehrere
Accounts (ein Login je Account laeuft parallel), NIE zwei gleichzeitig auf demselben
Account (Mega-Debrid-Sperr-Risiko).
- MegaWebFallback: globale Einzel-Warteschlange (this.queue) -> Warteschlange PRO Account
(this.queues: Map<login, Promise>). Gleicher Account serialisiert (kein Doppel-Login,
kein Hammern), verschiedene Accounts parallel. Key vor runExclusive berechnet.
- debrid.ts unrestrictWithAccounts: megaDebridInFlight zaehlt die LAUFENDE Tiefe pro
Account (Map<`${id}:${mode}`, number>). Kandidaten werden nach aufsteigender Tiefe
stabil sortiert (cursorOrder als Gleichstand-Tiebreak): gleichzeitige Aufloesungen
greifen den am wenigsten belegten Account -> auch bei mehr Links als Accounts
gleichmaessige Verteilung (4 Accounts, 8 parallel -> 2 je Account), statt sich hinter
dem Cursor-Account zu stauen. Sequenziell (alles Tiefe 0) bleibt es klebrig beim warmen
Account. add/inc vor dem try, dec/cleanup im finally (kein Leak).
- classifyAccountFailure: "Queue-Timeout" (lokaler Eigen-Stau) gibt jetzt cooldownMs:0 —
der warme Account wird nicht mehr faelschlich fuer Eigen-Stau mit Cooldown bestraft.
Vor Release adversarial per Multi-Agent-Workflow geprueft (4 Lenses + Verify). Der Review
fand genau die Ueberzahl-Stau-Schwaeche (binaeres belegt/frei staute >Accounts-Links hinter
einem Account) — daraufhin auf Tiefen-Zaehlung umgestellt. Sperr-Risiko strukturell
ausgeschlossen (per-Account-Queue serialisiert unabhaengig vom Set). 4 neue Tests
(gleicher Account 1 Login, verschiedene Accounts parallel, 4 gleichzeitig->4 Accounts,
8/2-Ueberzahl->4/4 ausgewogen). 813 Tests, tsc=6, self-check + build ok.
Regress aus v1.7.197/198: Das Round-Robin wechselte bei JEDEM Link den Account,
und die Latenz-Demotion (v1.7.198) stufte einen Account direkt nach seinem
zwangslaeufig langsamen KALTEN Login als "langsam" ein und rotierte weg — Mega-Web
cacht Sessions aber pro Account (~20 Min). Ergebnis: jeder Link zahlte einen kalten
Login in die serielle Single-Flight-Queue → minutenlanger Vorlauf, bevor die 8
parallelen Downloads anliefen. Vorher (First-Wins) lief alles ueber EINEN warmen
Account → schnell.
Fix:
- Latenz-Demotion (EMA-Sortierung) komplett entfernt — sie war die Ursache des
Kalt-Login-Teufelskreises (jeder Account galt nach seinem ersten Login als
langsam und wurde weggedraengt).
- Rotation jetzt KLEBRIG: gestartet wird beim Cursor (zuletzt erfolgreich genutzter,
warmer Account); der Cursor wird im Erfolgszweig nur weitergesetzt, wenn der
Schwung MEGA_DEBRID_STICKY_LINKS (25) erreicht ist — sonst bleibt er auf dem
Account. Aufeinanderfolgende Links laufen so auf demselben warmen Account
(schnell). Limit/Cooldown/Fehler ueberspringen den Account weiterhin und die
Rotation klebt dann am naechsten. Ueber die Zeit (alle 25 Links bzw. bei
Limits) kommen weiterhin alle Accounts dran — Account 4 inklusive.
Tests: 4 alte Round-Robin-/Demotions-Tests durch 3 klebrige ersetzt (bleibt auf 1
Account ueber 5 Links; wechselt erst nach 25 Links; ueberspringt gesperrten und
klebt am naechsten). 809 Tests, tsc=6 Baseline, self-check + build ok.
Folge-Fix zu v1.7.197 (Round-Robin): User meldete direkt nach dem Update
deutlich langsamere Link-Umwandlung. Ursache: MegaWebFallback.runExclusive
ist eine GLOBALE Single-Flight-Queue — alle Umwandlungen laufen seriell.
Vor v1.7.197 liefen praktisch alle Links ueber denselben warmen, schnellen
Account (~800ms); das Round-Robin mischte nun auch die zuvor nie genutzten
Accounts in die Reihe. Ist einer davon langsam (kalte Session, traegere
Server, abgelaufenes Premium), blockiert seine Umwandlung in der seriellen
Queue ALLE nachfolgenden Links — gefuehlt wird alles langsam.
Fix: Pro Account+Modus wird ein EMA (0.7/0.3) der erfolgreichen
Unrestrict-Dauer gefuehrt. Bei jeder Aufloesung wird die Round-Robin-
Reihenfolge partitioniert: Accounts, deren EMA ueber
max(6s, 3x bestes verfuegbares EMA) liegt, wandern ans Ende der Reihe —
sie bleiben Failover-Reserve (werden weiter genutzt, wenn die schnellen
am Limit/Cooldown sind), laufen aber nicht mehr in der gleichmaessigen
Verteilung mit. Ungemessene Accounts gelten als gesund (bekommen ihre
Chance und damit ein EMA). Erholt sich ein Account (relativer Threshold),
rotiert er automatisch wieder mit. Schon nach EINEM langsamen Erfolg ist
ein Bremser-Account aus der Verteilung draussen. Fehlschlaege werden wie
bisher ueber die bestehenden Cooldowns behandelt.
Diagnose-Sichtbarkeit: das TEST-Event im account-rotation.log traegt jetzt
emaMs und slow=true, damit ein gebremster Account sofort erkennbar ist.
2 neue Regressionstests (langsamer Account wird depriorisiert; bleibt
Failover-Reserve wenn die schnellen gesperrt sind). Suite 808 gruen,
tsc=6 Baseline, self-check + build ok.
Symptom (User): 4 Accounts hinterlegt, aber die Rotation nutzte nur die
Accounts 1-3 — der 4. wurde nie angefasst, ausser man deaktivierte die
anderen manuell.
Ursache (im Live-Log des Servers belegt, rd_downloader.log): Die
Account-Schleife in unrestrictWithAccounts startete bei JEDER
Link-Aufloesung bei Account 1 und nahm den ersten brauchbaren
(First-Usable-Wins-Failover). Ein spaeterer Account kam nur dran, wenn
ALLE davor am Tageslimit/Cooldown/deaktiviert waren. Live-Verteilung:
Account 1 = 749 OK + 1314x "Tageslimit — bis Neustart gesperrt",
Account 2 = 1733 OK + 448x Tageslimit-Sperre, Account 3 = 1603 OK ohne
ein einziges Limit — die Kette endete deshalb IMMER spaetestens bei
Account 3, und Account 4 tauchte im gesamten Log mit keinem einzigen
Event auf (nicht getestet, nicht geskippt). Accounts 1-2 liefen also
staendig ins Tageslimit, waehrend die Kapazitaet von Account 4 jeden
Tag verfiel.
Fix: Round-Robin-Cursor in unrestrictWithAccounts. Jede Aufloesung
startet beim Account NACH dem zuletzt getesteten (Modulo ueber die
Liste), alle bestehenden Checks (deaktiviert, lokales Tageslimit,
Cooldown, Park-bis-Neustart) bleiben unveraendert und werden in der
neuen Reihenfolge durchlaufen. Damit verteilen sich Links gleichmaessig
ueber alle Accounts, kein Account wird mehr stumpf bis ans Limit
gehaemmert, und Account 4 nimmt automatisch teil. Der Cursor ist
Modul-State (bei App-Neustart wieder Account 1), die "naechster
Account"-Vorschau im Fehlerpfad folgt der Modulo-Reihenfolge, und
resetMegaDebridRuntimeStateForTests setzt ihn fuer deterministische
Tests zurueck. Die Debrid-Link-Key-Rotation (separate Schleife) bleibt
unveraendert First-Wins — dort ist kein Account konfiguriert; bei
Bedarf gleiches Muster nachziehen.
Nebenfix: Das Support-Bundle exportiert jetzt auch
logs/account-rotation.log (+ .old) — genau dieses Log fehlte im Bundle
und haette die Diagnose sofort geliefert (die Datei kollidiert
namentlich mit dem gleichnamigen Log des Multi-Hoster-Uploaders, daher
war zunaechst das falsche Log in der Analyse).
Tests: 2 neue Regressionstests (5 Links auf 4 Accounts -> 1,2,3,4,1;
Cooldown-Account wird in der Reihe uebersprungen), Suite 806 gruen,
tsc=6 Baseline, self-check + build ok.
When a Mega-Web account's unrestrict aborts because the shared unrestrict
timeout fired while it was running, give that account a 2-min cooldown
(only if it actually ran >=8s, so a quick user-cancel does not cool it
down). The download-manager retry then skips the cooled-down account and
rotates to the next one, instead of hammering the same account every 60s.
- debrid.ts: handle the abort in the rotation catch before classifyAccountFailure
- rotation log event TIMEOUT_COOLDOWN (+ renderer label) replaces the misleading
red "fataler Fehler" for this case
- RD_MEGA_ABORT_MIN_RUN_MS env override for the run-length threshold
- 2 regression tests (cooldown set -> next call rotates; quick abort -> no cooldown)
Strip every comment from the source (parsed with the TypeScript compiler so
strings, template literals, regex literals and JSX are never touched), and drop
internal/working artifacts that do not belong in the public repository
(design mockups, internal analysis docs, a stray backup file and an old log).
No functional change: build is green, the full test suite passes.
User-Entscheidung: ein Mega-Debrid-Account am Tageslimit soll bis zum Programm-Neustart
uebersprungen werden, nicht alle 20s/2min neu getestet.
Ground Truth (Support-Bundle gegrept): der limitierte Account liefert im Web-Pfad NIE eine
unterscheidbare Meldung — "Kein Server" = 0 Treffer, "Antwort leer" = 20.861. Tageslimit und
transienter Blip sind auf Message-Ebene nicht trennbar (generate() findet ohne processDebrid-
Code keinen Code -> return null -> "Antwort leer"). Ein Trigger auf "Kein Server" waere toter Code.
Loesung (Verhaltens-Signal statt Wortlaut):
- megaDebridEmptyResponseStreaks zaehlt aufeinanderfolgende "Antwort leer"/"Kein Server"-
Treffer je Account; ab 3 wird der Account bis Neustart geparkt (until=MAX_SAFE_INTEGER,
nur In-Memory -> Neustart loescht). Erfolg/anderer Fehler setzt zurueck.
- classifyAccountFailure markiert beide Signale als limitSignal (Symmetrie: ein einzelner
evtl. transienter Treffer parkt NICHT, behaelt kurzen Cooldown).
- Skip-Branch: "uebersprungen (bis Neustart gesperrt)", traegt nicht zu earliestCooldownUntil
bei (kein absurder Retry-Timer); Post-Loop wirft klare Endmeldung wenn alle geparkt.
- generate() surfacet "Kein Server" zusaetzlich als Page-Error (falls es doch im HTML steht).
- UI: Rotations-Verlauf zeigt "bis Neustart gesperrt".
Verifiziert: tsc 9 (Baseline), 655 Tests + 5 neue (inkl. Wiring-E2E der eine echte leere
Antwort durch unrestrictWithAccounts->classify->catch->Park treibt), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-Cause (verifiziert via Support-Bundle): Der Web-Unrestrict lief fuer JEDEN rotierten
Account mit den Creds des ersten/Legacy-Accounts (settings.megaLogin), weil MegaWebFallback
EINE geteilte Cookie-Session + festes getCredentials() nutzte UND megaWebUnrestrict ohne
Account-Bezug aufgerufen wurde. Item-Log-Beweis: "Account 2/2 (FabelDavid): Mega-Web Antwort
leer", obwohl FabelDavid real funktioniert — die Rotation nutzte FabelDavid nie wirklich,
sondern immer Account 1 (am Limit). Alle bisherigen Fixes (v1.7.169-172) lagen downstream
dieses Punkts und konnten den Bug nicht beheben.
Fix:
- MegaWebUnrestrictor bekommt optionalen `account`-Parameter; MegaDebridClient.unrestrictViaWeb
reicht this.login/this.password (den rotierten Account) durch; app-controller leitet ihn weiter.
- MegaWebFallback: Per-Login Session-Cache (Map<login,{cookie,setAt}>) statt einem geteilten
Cookie; login() gibt das Cookie zurueck, generate() bekommt es als Param. Jeder Account nutzt
seine eigene Session — kein Re-Login-Thrash unter Parallel-Last (maxParallel=8).
Tests: mega-web-fallback (Login-POST traegt den uebergebenen Account-Login, nicht den Default)
+ debrid-Rotation (jeder Account erhaelt SEINE Creds; Account 2 loest auf). 647 Tests gruen,
tsc 9, Build sauber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report: Account 1 am Tageslimit liefert "Kein Server fuer diesen Hoster verfuegbar".
Bisher lief das durch die volle Web-Retry-Maschine (generate->null -> re-Login -> 3x
REQUEST_RETRIES) und fraß ~40s des GETEILTEN 60s-Unrestrict-Budgets -> der funktionierende
naechste Account (FabelDavid) lief in den Timeout (aborted:debrid -> als fatal klassifiziert,
"abgebrochen (fataler Fehler)" im Rotations-Verlauf), obwohl er gehen wuerde.
Fix (3 Teile, gemeinsame MEGA_DEBRID_NO_SERVER_RE):
1. mega-web-fallback generate(): die "Kein Server"-Meldung wird surfacet (throw) statt
null zurueckzugeben -> kein re-Login + erneutes Pollen.
2. unrestrictViaWeb: bricht bei der Meldung ab (kein 3x-REQUEST_RETRIES) -> sofortige
Retries sind zwecklos (Limit bleibt) und verbrennen das geteilte Rotations-Budget.
3. classifyAccountFailure: erkennt die Meldung -> quota-Cooldown (2 min) -> naechster
Account, mit echter Meldung im Log statt generischem "Antwort leer".
So scheitert der limitierte Account schnell (1 Versuch) und der naechste Account bekommt
das volle Budget zum Aufloesen.
Tests: mega-web-fallback (throw + ajaxCalls=1) + debrid-Rotation (acc1 Limit -> acc2,
calls=2). 645 Tests gruen, tsc 9, Build sauber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v1.7.168 fuehrte einen 25s-Per-Account-Timeout in die Rotation ein, Annahme: ein
"haengender" Account solle uebersprungen werden. Falsch: der Mega-Debrid-WEB-Unrestrict
ist eine Polling-Schleife (mega-web-fallback.ts: bis 60 Durchlaeufe, intern 180s-Ceiling)
— Mega-Debrid laedt die Datei erst auf den eigenen Server, das dauert legitim 30-180s.
Der 25s-Cap schnitt JEDEN Account mitten im Polling ab ("Account-Timeout nach 25s" in
Dauerschleife), die Datei wurde nie aufgeloest. Ein Timeout ist bei einem langsam-
pollenden Provider KEIN Account-Fehler und darf keine Rotation ausloesen.
Revert auf den Stand vor c4a49d9: Rotation nur noch bei echten Account-Fehlern
(Quota/Ban/ungueltig -> Cooldown -> naechster). debrid.ts + debrid.test.ts (inkl. des
dedizierten Per-Account-Timeout-Tests) zurueckgesetzt. 641 Tests gruen, tsc 9 (unveraendert).
WICHTIG: Behebt nur die von mir verursachte Regression — macht den Download NICHT von
selbst funktionsfaehig. Offene Frage (Mega-Web langsam-aber-funktioniert vs. Server-IP
geblockt) ist erst zu klaeren, bevor am eigentlichen Unrestrict-Timeout gedreht wird.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kernbug (User-Log, v1.7.168): "Unrestrict Timeout nach 60s" — Account 1 hing die
volle Zeit, acc2/acc3 wurden NIE versucht. Ursache: die gesamte Account-Rotation
lief unter EINEM geteilten ~60s-Signal (download-manager wickelt den ganzen
unrestrictLink in getUnrestrictTimeoutMs()); haengt acc1 bis es feuert, bricht die
ganze Rotation ab.
Fix (debrid.ts): jeder Account/Key bekommt im Rotations-Loop sein EIGENES Timeout
(PER_ACCOUNT_ATTEMPT_TIMEOUT_MS=25s, env RD_PER_ACCOUNT_TIMEOUT_MS, clamp 8-45s) via
AbortController + AbortSignal.any([global, attempt]). Catch: globaler signal.aborted
-> throw (Rotation stoppen); nur attemptController.signal.aborted -> 30s-Cooldown +
naechster Account. Ueber die Retry-Zyklen werden mit den Cooldowns alle Accounts erreicht.
Test: "aborts Mega web unrestrict when caller signal is cancelled" pruefte vorher
Objekt-Identitaet (.toBe(controller.signal)); der Per-Account-Timeout wrappt das Signal
aber zwingend (AbortSignal.any), die gereichte Instanz ist daher absichtlich nicht mehr
identisch. Umgestellt auf VERHALTEN: gereichtes Signal ist eine AbortSignal-Instanz und
propagiert das Caller-Cancel (aborted=true).
Recovered aus dem reset-weggesetzten Commit ae3ee1f (der andere Chat committete den Fix,
der Test brach, er resettete + hing). 641 Tests gruen, tsc unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UX: Beim Hinzufuegen von mega.nz Links wurden bisher nur die opaken
URL-Fragmente angezeigt ("pZl1wBRQ" etc.). Echte Filenames kamen erst
beim Mega-Debrid Unrestrict-Call, d.h. unmittelbar vor Download-Start.
Fix: Neuer src/main/mega-public-api.ts holt Filename + Groesse direkt
von Mega's Public API (g.api.mega.co.nz/cs) ohne Mega-Debrid-Quota
anzufassen. AES-128-CBC Decryption der Attribute mit dem Key aus
dem URL-Fragment.
resolveFilenames (debrid.ts) ruft den neuen Resolver fuer alle
erkannten mega.nz Links auf (concurrency 4). Auf Fehler/Rate-Limit
fallback auf den bestehenden Unrestrict-Pfad.
19 neue Tests fuer URL-Parser, AES-Decryption, Mocked-Fetch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fileNotAvailable, disabledServerHost, notFreeHost, serverNotAllowed,
freeServerOverload, maintenanceHost, noServerHost sind LINK- oder
HOST-level Fehler, nicht Key-level. Der Key antwortet ganz normal und
sagt nur "diesen Link kann ich aktuell nicht verarbeiten".
Vorher wurde trotzdem der Runtime-Status auf "error" gesetzt — sah in
der UI aus als waere der Key kaputt und hat die Rotations-Heuristiken
irritiert.
Fix: bei failure.category === "skip" den Runtime-Status in Ruhe lassen.
Der Key bleibt "ready" (bzw. was er vorher war). Invalid bleibt
"invalid", alle anderen fehlerhaften Antworten bleiben "error".
Test: Key 1 gibt fileNotAvailable zurueck → Key 2 erfolgreich. Key 1
darf danach NICHT "error" sein (per neuem Test-Helper
getDebridLinkKeyRuntimeStateForTests).
Previously, when a Debrid-Link key returned maxDataHost or maxLinkHost
("you've used up YOUR per-host quota for this hoster on this key"), the
WHOLE key got a 2-min key-wide cooldown — blocking it for all hosters
even though it was only exhausted for that one host.
Now those errors apply a per-(key, host) cooldown instead:
- Key 1 hits maxDataHost on rapidgator → only (Key 1, rapidgator) is
blocked. Key 1 stays usable for uploaded.net etc. in the same rotation
- Same key + same host on subsequent attempts: skipped with explicit
"Host-Cooldown rapidgator bis HH:MM:SS" log line
- Key-wide quotas (maxLink, maxData) still apply key-wide as before
Implementation:
- DEBRID_LINK_QUOTA_ERRORS split into key-wide vs host-only sets
- New debridLinkKeyHostCooldowns map keyed by `${keyId}|${hoster}`
- setDebridLinkKeyHostCooldownState mirrors max-wins / strong-category
semantics of the per-key version, falls back to key-wide cooldown when
the hoster can't be parsed (safer than thrashing)
- Key runtime status stays "ready" on host-only failures — only this
(key, host) is blocked, the key is still healthy for other hosters
- Reset/prune helpers (resetDebridLinkRuntimeStateForTests,
pruneDebridLinkRuntimeStateForKeys, pruneExpiredDebridLinkRuntimeState)
clear the new map too
- New rotation log event SKIP_HOST_COOLDOWN
Test: 2 keys, key1 hits maxDataHost on rapidgator → key2 succeeds.
Second rapidgator request: key1 SKIPPED via host-cooldown.
Third request to uploaded.net: key1 tried again and succeeds.
- New dedicated account-rotation.log (audit-style) so multi-account/key
rotation flow is visible without rd_downloader.log noise
- Mega-Debrid: always show "Account X/Y (masked@login)" label even with
one account, and log a clear "TESTE Account fuer Link-Generierung..."
line BEFORE every network call so the user sees which account is in
play even if the call hangs
- Mega-Debrid: on FAILED, log which account will be tried next
(skipping disabled/limited/cooldown ones), so the rotation is auditable
- Mega-Web "Antwort leer" / empty body now uses 20s cooldown instead of
120s — empty responses are typically transient server hiccups, not
real failures (caused healthy accounts to be unfairly blocked)
- Generic transport/unknown errors: 30s cooldown instead of 120s
- Global stall watchdog no longer counts items in "validating" status —
per-item validating watchdog already handles them and gives the
multi-account rotation enough time. Without this fix the global
watchdog could abort the unrestrict mid-rotation (e.g. account 3 of 3
still being tested) just because no download bytes had arrived
- Same logging treatment applied to Debrid-Link key rotation
User reported Debrid-Link "often jumps into provider-cooldown" and feels
unstable. Root cause was four cooperating bugs that turned isolated key-level
failures into provider-wide multi-minute outages.
Fix 1: Skip provider-wide circuit breaker for ALL Debrid-Link errors
(download-manager.ts ~8689)
Previously only the explicit `debrid_link_cooldown:` sentinel was bypassed;
every other Debrid-Link error (terminal failures, timeouts, parse errors)
still went through recordProviderFailure() + applyProviderBusyBackoff(),
applying a provider-wide cooldown ON TOP of the per-key cooldown debrid.ts
already managed. Now any error message containing "debrid-link" or where
the failure key is "debridlink" skips the provider-level circuit breaker
entirely. Per-key cooldowns alone are the right granularity.
Fix 2: Transport errors get a short 15s cooldown, not 2 min
(debrid.ts ~2684)
A single network timeout / ECONNRESET was parking the key for 2 full
minutes. With 9 keys all of which might experience the same transient
issue at different moments, this could cascade into all keys cooling
down for 2 min each. Now isolated transport hiccups get 15s while real
API/server problems still get the full 2 min.
Fix 3: HTTP 200 with success:false (no error code) is now temporary, not fatal
(debrid.ts ~2691)
Previously these went through to the fallthrough "fatal: true" which
permanently failed the item. Now they get a 30s temporary cooldown and
the item retries on the next key.
Fix 4: setDebridLinkKeyCooldownState is max-wins under concurrent calls
(debrid.ts ~135)
When 8 parallel items all hit the same key with floodDetected, each
computes its own cooldown duration and calls setDebrid LinkKeyCooldown
State. Without max-wins, the LAST setter could shorten the cooldown
(e.g. one item read a 1h Retry-After header, another defaulted to 2 min;
the 2 min would then overwrite the 1h). Now the longer cooldown wins,
with rate_limit/quota/invalid categories also winning over plain
temporary regardless of duration.
Tests: 201/201 (debrid + download-manager) green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reported user bug: "When I add an account, remove it, add a new one, the
new account is only really used after restart." Multiple cache layers were
not invalidated when settings changed, causing the system to keep using
stale state until the app was restarted.
Three layers of caching needed invalidation:
1. DebridService cached client instances (debrid.ts ~3067)
The cached DebridLinkClient / LinkSnappyClient / DdownloadClient hold
internal state (session cookies, auth tokens, parsed key lists). Without
explicit invalidation when credentials change, the OLD client instance
keeps serving requests until apiKeysRaw / login+password no longer match
the cache key - which doesn't always trigger because the cache key may
incidentally match (e.g. user removes and re-adds the same key).
Fix: setSettings() now compares previous vs next credentials per provider
and explicitly clears the cache when they differ.
2. MegaDebridClient.cachedApiTokens (debrid.ts ~1533)
Static module-level token cache keyed by login (lowercase). When a user
changes the password for an existing login (same login, new password),
the cached token was kept for up to 20 minutes and would only get cleared
after the API returned 401/403.
Fix: Two new static methods - pruneCachedTokensNotIn() removes entries
whose login is no longer in the active list, and clearCachedApiToken()
force-clears a specific login. Both called from setSettings() based on
diff between previous and next account list.
3. Module-level Debrid-Link cooldown maps (debrid.ts ~41-51)
debridLinkKeyCooldowns / debridLinkKeyCooldownDetails / runtime statuses
are keyed by API key ID (FNV-1a hash of the key). When a key was put
into cooldown then removed from settings then re-added later, the old
cooldown entry would still block it.
Fix: New pruneDebridLinkRuntimeStateForKeys() function called from
setSettings() removes cooldown entries for keys no longer in the active
set.
4. providerFailures circuit-breaker map (download-manager.ts ~1990)
Per-provider failure tracking with cooldownUntil. When a user removes a
failing account and adds a new one of the same provider, the cooldown
would carry over. Now setSettings() compares per-provider credentials
and clears matching providerFailures entries when they change.
Reproduction (now fixed):
1. Add Debrid-Link key A
2. Trigger a failure (e.g. invalid link) so A goes into cooldown
3. Remove A, add B in settings
4. Try a download immediately - previously the cooldown for A or the
cached client with A's state could still prevent B from being used.
After this fix B is used immediately.
Tests: 201/201 (debrid + download-manager) green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three low-risk optimizations that reduce CPU/memory footprint without
changing user-visible behavior:
1. Periodic cleanup of unbounded module-level Maps (24/7 stability):
- debridLinkKeyCooldowns, debridLinkKeyCooldownDetails,
debridLinkKeyRuntimeStatuses (debrid.ts)
- megaDebridAccountCooldowns (debrid.ts)
- allDebridHostInfoCache (download-manager.ts)
- All pruned every 10 min via existing resetStaleRetryState() with a
1h grace window for debugging
- Without this, modules accumulated entries indefinitely over days
of continuous server operation
2. Hoist regex literals in resolveArchiveItemsFromList() to module scope.
Avoids 5 RegExp constructions per call. The function is called per
archive set during extraction discovery — adds up on packages with
many archives.
3. BandwidthChart: skip the 250ms redraw interval while the session is
stopped or paused. The chart renders once on state change to show the
latest history, then sleeps until downloads resume. Saves 4 canvas
redraws per second when idle (renderer CPU).
No functional changes. All 565 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add safeJsonReplacer to all JSON.stringify calls in storage.ts to prevent
NaN/Infinity values from corrupting state files and causing queue loss
- Fix LinkSnappy and 1Fichier retry loops: use sleepWithSignal() instead of
sleep() so abort signals are respected during retry delays
- Fix Debrid-Link polling: replace raw setTimeout with sleepWithSignal() so
URL generation polling can be cancelled
- Fix Mega-Debrid doConnectApi: clear token cache on 401/403 responses
instead of caching invalid credentials for 20 minutes
- Add logging when normalizeLoadedSession removes orphaned items so data
loss during startup is visible in logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- notDebrid (host-level) no longer burns all keys: stops rotation immediately
with 5min cooldown instead of cycling through all 9 keys pointlessly
- Remove double provider-blockade: debrid_link_cooldown no longer stacks
recordProviderFailure + applyProviderBusyBackoff on top of key cooldowns
- Detect timeout cascades: 2+ consecutive transport failures trigger 3min
cooldown instead of burning remaining keys
- Case-sensitive rename: files with different casing (e.g. lowercase scene
names) now get properly renamed instead of being skipped as "already matching"
- Extended sample filter: detect -s.mkv suffix and \Sample\ subdirectories
in auto-rename (already worked in MKV-move)
- Add key status display with state pills in Debrid-Link key stats popup
- Add parseDebridLinkTerminalFailure for fast-fail on exhausted keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add polling loop (5x 2s) in resolveDownloaderEntry when /add returns
no downloadUrl — Debrid-Link sometimes needs seconds to generate links
- Classify missing/expired downloadUrl as temporary instead of fatal so
key rotation kicks in before giving up
- Change notDebrid from fatal to temporary — "host may be down" is
transient, all keys should be tried before failing
- Raise parseRetryAfterMs cap from 2min to 1h — floodDetected mandates
"retry after 1 hour" per API docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple Mega-Debrid accounts can now be configured as login:password
pairs (one per line). When an account hits Fair-Use limits or errors,
the next account is tried automatically.
- New parser module mega-debrid-accounts.ts (parse, ID generation,
masking, serialization)
- Per-account daily limits, usage tracking, enable/disable
- Account rotation with per-mode cooldowns (API failures don't
block Web attempts)
- Backward compatible: existing single megaLogin/megaPassword
is auto-migrated to the new format
- UI: textarea for credentials, account list with masked logins
Follows the existing Debrid-Link multi-key pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace shared currentKeyIndex round-robin with sequential scan from
first key. All parallel items now consistently use the same key (e.g.
Key 3) until it hits a quota/error, then all move to Key 4, etc.
Previously, currentKeyIndex was shared across parallel unrestrict calls,
causing items to scatter across keys (3, 5, 7) even when Key 3 still
had capacity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "notDebrid", "disabledServerHost", "notFree" as immediate-skip errors
(no 3x retry per key, break to next key instantly like quota errors)
- Add per-key cooldown cache (2 min) so parallel items skip recently-failed keys
instead of all starting at the same broken key
- Set cooldown on quota errors too, preventing repeated checks on exhausted keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Daily traffic limits:
- Per-provider daily download limit (configurable in GB per provider)
- Per Debrid-Link API key daily limit (individual limits per key)
- Usage tracking with automatic daily reset at midnight
- Provider is skipped when daily limit reached, falls back to next provider
- Reset button per provider and per Debrid-Link key in account settings
- Hoster routing skips daily-limited providers gracefully
Debrid-Link multi-key improvements:
- Keys now display with labels (#1, #2...) and masked tokens in account list
- Option to show detailed per-key view with individual usage stats
- Keys that hit their daily limit are automatically skipped
- providerAccountId/providerAccountLabel stored per download item
Auto-sort packages by progress:
- Active packages automatically sorted to top during downloads
- Sorted by completion ratio, then downloaded bytes
- Toggle in settings (autoSortPackagesByProgress)
UI polish:
- Package column headers: flatter, more transparent design
- LinkSnappy mode label: "Login" renamed to "Web"
- Account list: new toggle for detailed Debrid-Link key display
- Account usage stats section with warning styling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace fixed primary/secondary/tertiary slots with unlimited ordered
providerOrder: DebridProvider[] list; supports as many accounts as needed
- Provider list reorderable via up/down buttons in Accounts settings tab
- Migration: derives order from legacy primary/secondary/tertiary if empty
- Mega-Debrid split into megadebrid-api and megadebrid-web as separate providers
- Add per-hoster routing (hosterRouting) to assign specific debrid provider per hoster
- Fix duplicate MKV files in library: filter out sample files from Sample subfolders
- Remove legacy provider-selection dropdowns from hidden settings section
- Add CSS for provider-order-list/row/num/label/actions classes
- Update debrid tests: add providerOrder: [] to use legacy fallback path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New settings field hosterRouting maps file hosters to specific debrid providers
- 27 known hosters predefined (Rapidgator, Uploaded, Turbobit, Nitroflare, etc.)
- Custom hoster support via prompt dialog
- Routing takes priority over default provider chain
- Falls back to normal chain on error when autoProviderFallback is enabled
- Logs routing decisions: "Hoster-Zuordnung: rapidgator → Debrid-Link"
- Full UI section in settings with add/remove/change provider per hoster
- Storage validation and normalization for hosterRouting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add LinkSnappy provider with cookie-based session auth and /api/linkgen
- Upgrade LinkSnappy download URLs from http to https (fix 425 errors)
- Add account deactivation toggle (disabledProviders in settings)
- Show account type (API/Web/Login) in provider dropdowns
- Show API key count for Debrid-Link in status label
- Fix all missing German umlauts throughout the UI
- Wider modal for textarea, compact action buttons in one row
- Debrid-Link: log which API key (#1/#2) is used for unrestrict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>