From f1e35f5f4118156aa5a94ebe901178c339c08778 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 17 Jun 2026 10:16:16 +0200 Subject: [PATCH] Fix: Mega "bis Tagesreset gesperrt" parkt das Paket bis zum Reset statt es den ganzen Tag alle 2 min neu zu versuchen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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:: (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. --- src/main/debrid.ts | 3 ++- src/main/download-manager.ts | 30 ++++++++++++++++++++++++++++++ tasks/audit-loop.md | 28 ++++++++++++++++++++++++++-- tasks/entscheidungen-offen.md | 10 ++++++++++ tests/debrid.test.ts | 7 +++++-- tests/unrestrict-retry.test.ts | 31 ++++++++++++++++++++++++++++++- 6 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/main/debrid.ts b/src/main/debrid.ts index 83d78e0..680aed4 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -2165,7 +2165,8 @@ class MegaDebridClient { throw new Error(`mega_debrid_cooldown:${retryMs}:${cooldownFailures.join(" | ")}`); } if (parkedUntilRestartSeen) { - throw new Error(`Mega-Debrid: Alle Accounts am Tageslimit (bis zum Tagesreset gesperrt)${cooldownFailures.length > 0 ? ` | ${cooldownFailures.join(" | ")}` : ""}`); + const resetParkMs = Math.max(1000, megaDebridDailyParkExpiry(Date.now()) - Date.now() + 1000); + throw new Error(`mega_debrid_reset_park:${resetParkMs}:Mega-Debrid: Alle Accounts am Tageslimit (bis zum Tagesreset gesperrt)${cooldownFailures.length > 0 ? ` | ${cooldownFailures.join(" | ")}` : ""}`); } throw new Error("Mega-Debrid: Kein aktiver Account verfuegbar"); } diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index e6184b2..318c709 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -659,6 +659,19 @@ export function parseMegaDebridCooldownRetry(errorText: string): { delayMs: numb return { delayMs, detail: text.replace(/mega_debrid_cooldown:\d+:/i, "").trim() }; } +export function parseMegaDebridResetPark(errorText: string): { delayMs: number; detail: string } | null { + const match = String(errorText || "").match(/mega_debrid_reset_park:(\d+):(.*)$/is); + if (!match) { + return null; + } + const raw = Number(match[1]); + if (!Number.isFinite(raw) || raw <= 0) { + return null; + } + const delayMs = Math.max(1000, Math.min(26 * 60 * 60 * 1000, raw)); + return { delayMs, detail: String(match[2] || "").trim() }; +} + function parseDebridLinkTerminalFailure(errorText: string): { kind: "invalid_all" | "no_active_key"; detail: string } | null { const raw = String(errorText || ""); const match = raw.match(/debrid_link_(invalid_all|no_active_key):(.*)$/i); @@ -9395,6 +9408,23 @@ export class DownloadManager extends EventEmitter { return; } + const megaResetPark = parseMegaDebridResetPark(errorText); + if (megaResetPark && active.unrestrictRetries < maxUnrestrictRetries) { + active.unrestrictRetries += 1; + item.retries += 1; + logger.warn(`Mega-Debrid bis Tagesreset gesperrt: item=${item.fileName || item.id}, retry=${active.unrestrictRetries}/${retryDisplayLimit}, delay=${megaResetPark.delayMs}ms, link=${item.url.slice(0, 80)}`); + this.queueRetry( + item, + active, + megaResetPark.delayMs, + `Mega-Debrid bis Tagesreset gesperrt, Pause ${Math.ceil(megaResetPark.delayMs / 1000)}s` + ); + item.lastError = megaResetPark.detail || errorText; + this.persistSoon(); + this.emitState(); + return; + } + const megaCooldownRetry = parseMegaDebridCooldownRetry(errorText); if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) { active.unrestrictRetries += 1; diff --git a/tasks/audit-loop.md b/tasks/audit-loop.md index 088fc22..d006f28 100644 --- a/tasks/audit-loop.md +++ b/tasks/audit-loop.md @@ -125,11 +125,35 @@ Produkt-Tradeoff geht an den Nutzer (tasks/entscheidungen-offen.md). ### An den Nutzer vorgelegt (NICHT autonom) → tasks/entscheidungen-offen.md - 60s-Failover-Kappung (HIGH): A) pro-Provider-Timeout (Failover immer, aber bis 3×60s - Worst-Case) vs B) globales Budget mit Failover-Reserve (langsamer Provider1 frueher - abgeschnitten). Produkt-Tradeoff = Nutzerwahl. Post-214 weitgehend latent. + Worst-Case, Drehregler ueber Pro-Provider-Wert) vs B) globales Budget mit Failover-Reserve + (langsamer Provider1 frueher abgeschnitten). Produkt-Tradeoff = Nutzerwahl. Post-214 weitgehend latent. - Gespiegelter Mega API/Web-Schalter (HIGH, #R3-2): gemeinsamer Schalter vs unabhaengige pro-Modus-Flags (Migration + UI-Redesign noetig, Nutzer UI-sensibel). +### SYNTHESE-Pass (Cross-Layer: Manager-Cooldown-Keys vs Debrid Account/Daily-Park) — Workflow wrfxpjudj +2 Kandidaten, 1 confirmed (3/3), 1 refuted (0/3). Advisor-Hypothese (Key-Mismatch) WIDERLEGT. +- **REFUTED (0/3) KSM-1 Key-Mismatch:** normalizeProviderOrder (storage.ts:144/411) speichert IMMER + den aufgeloesten 'megadebrid-api'/'-web', NIE den virtuellen 'megadebrid'. Also fallen + Cooldown-WRITE (recordProviderFailure), CHECK (getProviderFailureKeyForItem), CLEAR und READ + (findFallbackProviderNotInCooldown) auf denselben aufgeloesten Key → KEINE Divergenz. Der + 986fbab-Routing-Fix ist fuer den Mega-Fall nachweislich sicher (No-Stranding haelt). +- **CONFIRMED (3/3) LOW MEGA-UNTILRESTART-MISCLASS (GEFIXT):** Der untilRestart-Park-Throw + (debrid.ts:2167) trug KEINEN Maschinen-Token → Manager-Catch klassifiziert ihn als generischen + Unrestrict-Fehler (isUnrestrictFailure matcht "mega_debrid") → Retry alle ~2min den ganzen Tag + + recordProviderFailure (Circuit-Breaker-Verschmutzung), statt einmal bis Tagesreset zu parken. + Default-Config (retryLimit=0=∞) self-heilt um Mitternacht, KEIN Stranding. Genau die + Round-3-untilRestart-Park-Absicht, die hier unterlaufen wurde. Predates 986fbab. + Fix: debrid.ts emittiert jetzt `mega_debrid_reset_park::` (megaDebridDailyParkExpiry); + neue reine parseMegaDebridResetPark (kein 15min-Clamp, 26h-Cap) + Catch-Branch VOR der + Cooldown-Klassifikation queued bis Tagesreset OHNE recordProviderFailure. Rot-bewiesen + (Parser-Tests + debrid-Token-Assertion). tsc=6. +- **End-to-end-Verdikt:** Retry/Cooldown/Rotation/Failover ist unter Default-Config kohaerent + self-heilend (jeder Park hat Zeitgrenze + Auto-Clear). Einzige Rest-Inkohaerenz war die + Layering-Naht oben (in-memory untilRestart-Park unsichtbar fuer getAvailableMegaDebridAccounts + + isProviderDailyLimited) — mit dem konservativen String-Klassifikations-Fix geschlossen. + Der breitere Fix (Selektierbarkeit cooldown-aware machen) bewusst NICHT gemacht (groesserer + Blast-Radius auf Live-Server). + ## Runde 2 (Download-Ausfuehrung: stream/resume/disk/integrity/extract/persist) — Workflow whspc8ddv 14 confirmed / 7 refuted (>=2/3 adversarisch). Alle HIGH/MED unten unabhaengig am echten Code verifiziert (Zeilen zitiert) bevor gefixt. Jeder Fix mit rot-bewiesenem Test, tsc bleibt 6. diff --git a/tasks/entscheidungen-offen.md b/tasks/entscheidungen-offen.md index 3982693..c5cce8e 100644 --- a/tasks/entscheidungen-offen.md +++ b/tasks/entscheidungen-offen.md @@ -63,6 +63,16 @@ ODER zwei unabhängige Pro-Modus-Schalter (mehr Kontrolle, aber UI + Migration n ## Erledigt in dieser Runde (zur Info, kein Handlungsbedarf) +- **Mega „bis Tagesreset gesperrt" parkt jetzt wirklich (statt alle 2 min neu zu versuchen):** + Wenn ALLE Mega-Accounts wegen wiederholt leerer Antworten bis zum Tagesreset geparkt waren, + hat das Tool den Fehler bisher als normalen Umwandlungsfehler behandelt und den ganzen Tag + alle ~2 Minuten neu probiert (und dabei den Provider-Circuit-Breaker mit Fehlern vollgemüllt). + Jetzt erkennt es den Park und legt das Paket EINMAL bis zum Tagesreset schlafen — genau das, + was der „bis Tagesreset"-Park eigentlich erreichen sollte. Bei Standard-Einstellungen heilte + sich das vorher schon um Mitternacht selbst (kein Datenverlust), war aber unnötige Log-Flut + und Churn. Rot-bewiesener Test. + + - **MED Failover-Routing:** Wenn ein Provider in den Manager-Cooldown läuft (≥20 Fehler in Folge) und auto-Fallback an ist, hat der Manager bisher zwar einen Ersatz-Provider berechnet, ihn aber WEGGEWORFEN — die Kette führte trotzdem wieder mit dem ausgebremsten Provider an. diff --git a/tests/debrid.test.ts b/tests/debrid.test.ts index 4c51765..edbf86e 100644 --- a/tests/debrid.test.ts +++ b/tests/debrid.test.ts @@ -2027,7 +2027,7 @@ describe("debrid service", () => { expect((result as { sourceAccountId?: string }).sourceAccountId).toBe(getMegaDebridAccountId("user2")); }, 20000); - it("fails terminally (no retry timer) when ALL Mega-Debrid accounts are parked until restart", async () => { + it("emits an until-Tagesreset park token (so the manager parks, not 2min-retries) when ALL Mega-Debrid accounts are parked until restart", async () => { const settings = { ...defaultSettings(), token: "", @@ -2054,7 +2054,10 @@ describe("debrid service", () => { const megaWeb = vi.fn(async () => ({ fileName: "x.rar", directUrl: "https://mega-web.example/x.rar", fileSize: null, retriesUsed: 0 })); const service = new DebridService(settings, { megaWebUnrestrict: megaWeb }); - await expect(service.unrestrictLink("https://rapidgator.net/file/all-parked-test")).rejects.toThrow(/bis zum Tagesreset gesperrt/i); + const err = await service.unrestrictLink("https://rapidgator.net/file/all-parked-test").then(() => null, (e: unknown) => e as Error); + expect(err).toBeInstanceOf(Error); + expect(err!.message).toMatch(/bis zum Tagesreset gesperrt/i); + expect(err!.message).toMatch(/mega_debrid_reset_park:\d+:/); expect(megaWeb).not.toHaveBeenCalled(); }, 20000); diff --git a/tests/unrestrict-retry.test.ts b/tests/unrestrict-retry.test.ts index dac77f0..a035a39 100644 --- a/tests/unrestrict-retry.test.ts +++ b/tests/unrestrict-retry.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { transientResolveRetryDelayMs, parseMegaDebridCooldownRetry } from "../src/main/download-manager"; +import { transientResolveRetryDelayMs, parseMegaDebridCooldownRetry, parseMegaDebridResetPark } from "../src/main/download-manager"; describe("transientResolveRetryDelayMs (fast, bounded retry for transient resolve failures)", () => { it("starts fast (<= 3s) instead of the 5s..120s exponential", () => { @@ -57,4 +57,33 @@ describe("parseMegaDebridCooldownRetry (honor the encoded account-cooldown delay expect(parseMegaDebridCooldownRetry("Datei beim Hoster gerade nicht abrufbar")).toBeNull(); expect(parseMegaDebridCooldownRetry("debrid_link_cooldown:5000:x")).toBeNull(); }); + + it("does NOT swallow the until-Tagesreset park token", () => { + expect(parseMegaDebridCooldownRetry("mega_debrid_reset_park:43200000:Alle Accounts bis zum Tagesreset gesperrt")).toBeNull(); + }); +}); + +describe("parseMegaDebridResetPark (park the item until the Tagesreset, not a ~2min generic retry)", () => { + it("parses the encoded until-reset delay from the park token", () => { + const r = parseMegaDebridResetPark("mega_debrid_reset_park:43200000:Mega-Debrid: Alle Accounts am Tageslimit (bis zum Tagesreset gesperrt)"); + expect(r).not.toBeNull(); + expect(r!.delayMs).toBe(43200000); + expect(r!.detail).toContain("bis zum Tagesreset gesperrt"); + }); + + it("parses it when embedded in the aggregated provider-chain error", () => { + const aggregated = "Unrestrict fehlgeschlagen: Mega-Debrid API: mega_debrid_reset_park:7200000:Alle Accounts bis zum Tagesreset gesperrt"; + expect(parseMegaDebridResetPark(aggregated)!.delayMs).toBe(7200000); + }); + + it("is NOT clamped to the 15min cooldown ceiling (can park multiple hours)", () => { + expect(parseMegaDebridResetPark("mega_debrid_reset_park:21600000:x")!.delayMs).toBe(21600000); + }); + + it("clamps to a sane [1s, 26h] window and rejects junk", () => { + expect(parseMegaDebridResetPark("mega_debrid_reset_park:1:x")!.delayMs).toBe(1000); + expect(parseMegaDebridResetPark("mega_debrid_reset_park:999999999999:x")!.delayMs).toBe(26 * 60 * 60 * 1000); + expect(parseMegaDebridResetPark("mega_debrid_cooldown:20330:x")).toBeNull(); + expect(parseMegaDebridResetPark("kein Token hier")).toBeNull(); + }); });