Fix: Mega "bis Tagesreset gesperrt" parkt das Paket bis zum Reset statt es den ganzen Tag alle 2 min neu zu versuchen
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.
This commit is contained in:
parent
03c908bd30
commit
f1e35f5f41
@ -2165,7 +2165,8 @@ class MegaDebridClient {
|
|||||||
throw new Error(`mega_debrid_cooldown:${retryMs}:${cooldownFailures.join(" | ")}`);
|
throw new Error(`mega_debrid_cooldown:${retryMs}:${cooldownFailures.join(" | ")}`);
|
||||||
}
|
}
|
||||||
if (parkedUntilRestartSeen) {
|
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");
|
throw new Error("Mega-Debrid: Kein aktiver Account verfuegbar");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -659,6 +659,19 @@ export function parseMegaDebridCooldownRetry(errorText: string): { delayMs: numb
|
|||||||
return { delayMs, detail: text.replace(/mega_debrid_cooldown:\d+:/i, "").trim() };
|
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 {
|
function parseDebridLinkTerminalFailure(errorText: string): { kind: "invalid_all" | "no_active_key"; detail: string } | null {
|
||||||
const raw = String(errorText || "");
|
const raw = String(errorText || "");
|
||||||
const match = raw.match(/debrid_link_(invalid_all|no_active_key):(.*)$/i);
|
const match = raw.match(/debrid_link_(invalid_all|no_active_key):(.*)$/i);
|
||||||
@ -9395,6 +9408,23 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
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);
|
const megaCooldownRetry = parseMegaDebridCooldownRetry(errorText);
|
||||||
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||||
active.unrestrictRetries += 1;
|
active.unrestrictRetries += 1;
|
||||||
|
|||||||
@ -125,11 +125,35 @@ Produkt-Tradeoff geht an den Nutzer (tasks/entscheidungen-offen.md).
|
|||||||
|
|
||||||
### An den Nutzer vorgelegt (NICHT autonom) → 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
|
- 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
|
Worst-Case, Drehregler ueber Pro-Provider-Wert) vs B) globales Budget mit Failover-Reserve
|
||||||
abgeschnitten). Produkt-Tradeoff = Nutzerwahl. Post-214 weitgehend latent.
|
(langsamer Provider1 frueher abgeschnitten). Produkt-Tradeoff = Nutzerwahl. Post-214 weitgehend latent.
|
||||||
- Gespiegelter Mega API/Web-Schalter (HIGH, #R3-2): gemeinsamer Schalter vs unabhaengige
|
- Gespiegelter Mega API/Web-Schalter (HIGH, #R3-2): gemeinsamer Schalter vs unabhaengige
|
||||||
pro-Modus-Flags (Migration + UI-Redesign noetig, Nutzer UI-sensibel).
|
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:<msBisReset>:` (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
|
## 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
|
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.
|
verifiziert (Zeilen zitiert) bevor gefixt. Jeder Fix mit rot-bewiesenem Test, tsc bleibt 6.
|
||||||
|
|||||||
@ -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)
|
## 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
|
- **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,
|
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.
|
ihn aber WEGGEWORFEN — die Kette führte trotzdem wieder mit dem ausgebremsten Provider an.
|
||||||
|
|||||||
@ -2027,7 +2027,7 @@ describe("debrid service", () => {
|
|||||||
expect((result as { sourceAccountId?: string }).sourceAccountId).toBe(getMegaDebridAccountId("user2"));
|
expect((result as { sourceAccountId?: string }).sourceAccountId).toBe(getMegaDebridAccountId("user2"));
|
||||||
}, 20000);
|
}, 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 = {
|
const settings = {
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
token: "",
|
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 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 });
|
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();
|
expect(megaWeb).not.toHaveBeenCalled();
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
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)", () => {
|
describe("transientResolveRetryDelayMs (fast, bounded retry for transient resolve failures)", () => {
|
||||||
it("starts fast (<= 3s) instead of the 5s..120s exponential", () => {
|
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("Datei beim Hoster gerade nicht abrufbar")).toBeNull();
|
||||||
expect(parseMegaDebridCooldownRetry("debrid_link_cooldown:5000:x")).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user