From 76b3f99476b1fb193f4201a5c592cabdc3d52b30 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 17 Jun 2026 05:47:52 +0200 Subject: [PATCH] Fix: Mega-Debrid-Account sperrt sich nicht mehr dauerhaft selbst (Self-Cooldown bis Neustart) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/main/debrid.ts | 29 ++++++++++++++++++++--------- src/renderer/App.tsx | 6 +++--- tests/debrid.test.ts | 25 ++++++++++++++++++------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/main/debrid.ts b/src/main/debrid.ts index 0662c0c..a1813b5 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -349,14 +349,26 @@ export function primeMegaDebridRuntimeCooldownForTests(accountId: string, cooldo setMegaDebridAccountCooldownState(accountId, cooldownMs, message, "temporary"); } -export function primeMegaDebridUntilRestartForTests(accountId: string, message = "Tageslimit (Test) — bis Neustart gesperrt"): void { +export function primeMegaDebridUntilRestartForTests(accountId: string, message = "Tageslimit (Test) — bis zum Tagesreset gesperrt"): void { setMegaDebridAccountCooldownState(accountId, 0, message, "quota", true); } +export function classifyMegaDebridAccountFailureForTests( + error: unknown +): { fatal: boolean; cooldownMs: number; message: string; category: MegaDebridCooldownCategory; limitSignal?: boolean } { + return MegaDebridClient.classifyAccountFailure(error); +} + function clearMegaDebridAccountCooldownState(accountId: string): void { megaDebridAccountCooldowns.delete(accountId); } +function megaDebridDailyParkExpiry(now: number): number { + const midnight = new Date(now); + midnight.setHours(24, 0, 0, 0); + return Math.max(midnight.getTime(), now + MEGA_DEBRID_ACCOUNT_COOLDOWN_MS); +} + function setMegaDebridAccountCooldownState( accountId: string, cooldownMs: number, @@ -366,7 +378,7 @@ function setMegaDebridAccountCooldownState( ): void { if (untilRestart) { megaDebridAccountCooldowns.set(accountId, { - until: Number.MAX_SAFE_INTEGER, + until: megaDebridDailyParkExpiry(Date.now()), message, category, untilRestart: true @@ -1987,7 +1999,7 @@ class MegaDebridClient { ? "Neustart" : new Date(accountCooldownState.until).toLocaleTimeString(); const reasonText = accountCooldownState.untilRestart - ? "Tageslimit erreicht — bis Neustart gesperrt" + ? "Tageslimit erreicht — bis zum Tagesreset gesperrt" : `Cooldown bis ${untilStr}`; logger.info(`Mega-Debrid${accountLabel}: uebersprungen (${reasonText}), pruefe naechsten Account`); logAccountRotation("INFO", providerName, rotationLabel, "SKIP_COOLDOWN", { @@ -2086,7 +2098,7 @@ class MegaDebridClient { const streak = recordMegaDebridEmptyResponseStreak(cooldownKey); if (streak >= MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART) { parkUntilRestart = true; - parkMessage = `Tageslimit erreicht (${streak}x kein Server/leere Antwort) — bis Neustart gesperrt`; + parkMessage = `Tageslimit erreicht (${streak}x kein Server/leere Antwort) — bis zum Tagesreset gesperrt`; } } else { clearMegaDebridEmptyResponseStreak(cooldownKey); @@ -2109,7 +2121,7 @@ class MegaDebridClient { throw new Error(`Mega-Debrid${accountLabel}: ${failure.message}`); } const cooldownInfo = parkUntilRestart - ? ", bis Neustart gesperrt" + ? ", bis zum Tagesreset gesperrt" : failure.cooldownMs > 0 ? `, Cooldown ${Math.ceil(failure.cooldownMs / 1000)}s` : ""; @@ -2146,14 +2158,14 @@ class MegaDebridClient { throw new Error(`mega_debrid_cooldown:${retryMs}:${cooldownFailures.join(" | ")}`); } if (parkedUntilRestartSeen) { - throw new Error(`Mega-Debrid: Alle Accounts am Tageslimit (bis Neustart gesperrt)${cooldownFailures.length > 0 ? ` | ${cooldownFailures.join(" | ")}` : ""}`); + throw new Error(`Mega-Debrid: Alle Accounts am Tageslimit (bis zum Tagesreset gesperrt)${cooldownFailures.length > 0 ? ` | ${cooldownFailures.join(" | ")}` : ""}`); } throw new Error("Mega-Debrid: Kein aktiver Account verfuegbar"); } throw new Error(failures.join(" | ") || "Mega-Debrid: Kein aktiver Account verfuegbar"); } - private static classifyAccountFailure( + static classifyAccountFailure( error: unknown ): { fatal: boolean; cooldownMs: number; message: string; category: MegaDebridCooldownCategory; limitSignal?: boolean } { const errorText = compactErrorText(error).replace(/^Error:\s*/i, ""); @@ -2206,8 +2218,7 @@ class MegaDebridClient { fatal: false, cooldownMs: MEGA_DEBRID_ACCOUNT_COOLDOWN_MS, message: "Kein Server fuer diesen Hoster (Tageslimit/Hoster nicht verfuegbar)", - category: "quota", - limitSignal: true + category: "quota" }; } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 068bb99..804c636 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1159,13 +1159,13 @@ function formatCheckedAgo(checkedAt: number): string { } function rotationEventText(ev: { event: string; cooldownSec?: number; next?: string; reason?: string }): string { - const untilRestart = /bis Neustart gesperrt/i.test(ev.reason || ""); + const untilRestart = /bis zum Tagesreset gesperrt/i.test(ev.reason || ""); switch (ev.event) { case "OK": return "erfolgreich"; case "FAILED": { if (untilRestart) { const nx = ev.next && ev.next !== "ENDE" ? ` → ${ev.next}` : ""; - return `Tageslimit erreicht, bis Neustart gesperrt${nx}`; + return `Tageslimit erreicht, bis zum Tagesreset gesperrt${nx}`; } const cd = ev.cooldownSec ? `, Cooldown ${ev.cooldownSec}s` : ""; const nx = ev.next && ev.next !== "ENDE" ? ` → ${ev.next}` : ""; @@ -1176,7 +1176,7 @@ function rotationEventText(ev: { event: string; cooldownSec?: number; next?: str const cd = ev.cooldownSec ? `, Cooldown ${ev.cooldownSec}s` : ""; return `Timeout/Abbruch${cd} → nächster Account beim Retry`; } - case "SKIP_COOLDOWN": return untilRestart ? "übersprungen (bis Neustart gesperrt)" : "übersprungen (Cooldown aktiv)"; + case "SKIP_COOLDOWN": return untilRestart ? "übersprungen (bis zum Tagesreset gesperrt)" : "übersprungen (Cooldown aktiv)"; case "SKIP_DISABLED": return "übersprungen (deaktiviert)"; case "SKIP_DAILY_LIMIT": return "übersprungen (Tageslimit erreicht)"; case "SKIP_HOST_COOLDOWN": return "übersprungen (Host-Cooldown)"; diff --git a/tests/debrid.test.ts b/tests/debrid.test.ts index 42f8023..7d70c02 100644 --- a/tests/debrid.test.ts +++ b/tests/debrid.test.ts @@ -3,7 +3,7 @@ import { defaultSettings, REQUEST_RETRIES } from "../src/main/constants"; import { parseDebridLinkApiKeys } from "../src/shared/debrid-link-keys"; import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts"; import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits"; -import { clearMegaDebridEmptyResponseStreak, DebridService, extractRapidgatorFilenameFromHtml, fetchAllDebridHostInfo, fetchDebridLinkHostLimits, filenameFromRapidgatorUrlPath, getDebridLinkKeyRuntimeStateForTests, getMegaDebridAccountCooldownState, MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART, MEGA_DEBRID_STICKY_LINKS, normalizeResolvedFilename, primeMegaDebridUntilRestartForTests, recordMegaDebridEmptyResponseStreak, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid"; +import { classifyMegaDebridAccountFailureForTests, clearMegaDebridEmptyResponseStreak, DebridService, extractRapidgatorFilenameFromHtml, fetchAllDebridHostInfo, fetchDebridLinkHostLimits, filenameFromRapidgatorUrlPath, getDebridLinkKeyRuntimeStateForTests, getMegaDebridAccountCooldownState, MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART, MEGA_DEBRID_STICKY_LINKS, normalizeResolvedFilename, primeMegaDebridUntilRestartForTests, recordMegaDebridEmptyResponseStreak, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid"; const originalFetch = globalThis.fetch; @@ -1918,13 +1918,24 @@ describe("debrid service", () => { expect(recordMegaDebridEmptyResponseStreak(key)).toBe(1); }); - it("keeps an 'until restart' park active forever (never expires until process restart)", () => { + it("self-heals a daily-limit park after the daily reset instead of staying locked until process restart", () => { const key = `${getMegaDebridAccountId("user1")}:api`; primeMegaDebridUntilRestartForTests(key); - const now = getMegaDebridAccountCooldownState(key); - expect(now?.untilRestart).toBe(true); - const farFuture = Date.now() + 100 * 24 * 60 * 60 * 1000; - expect(getMegaDebridAccountCooldownState(key, farFuture)?.untilRestart).toBe(true); + const active = getMegaDebridAccountCooldownState(key); + expect(active?.untilRestart).toBe(true); + expect(getMegaDebridAccountCooldownState(key, Date.now() + 60_000)?.untilRestart).toBe(true); + const afterReset = Date.now() + 25 * 60 * 60 * 1000; + expect(getMegaDebridAccountCooldownState(key, afterReset)).toBeNull(); + }); + + it("does NOT treat a per-hoster 'no server' failure as an account daily-limit signal (no until-restart park)", () => { + const noServer = classifyMegaDebridAccountFailureForTests(new Error("no server available for this host")); + expect(noServer.limitSignal).toBeFalsy(); + expect(noServer.category).toBe("quota"); + expect(noServer.cooldownMs).toBeGreaterThan(0); + + const genuineEmpty = classifyMegaDebridAccountFailureForTests(new Error("Antwort leer")); + expect(genuineEmpty.limitSignal).toBe(true); }); it("skips a Mega-Debrid account parked until restart and rotates to the next, without re-testing it", async () => { @@ -1990,7 +2001,7 @@ 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 Neustart gesperrt/i); + await expect(service.unrestrictLink("https://rapidgator.net/file/all-parked-test")).rejects.toThrow(/bis zum Tagesreset gesperrt/i); expect(megaWeb).not.toHaveBeenCalled(); }, 20000);