Fix: Mega-Debrid-Account sperrt sich nicht mehr dauerhaft selbst (Self-Cooldown bis Neustart)
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.
This commit is contained in:
parent
68f50eaa5e
commit
76b3f99476
@ -349,14 +349,26 @@ export function primeMegaDebridRuntimeCooldownForTests(accountId: string, cooldo
|
|||||||
setMegaDebridAccountCooldownState(accountId, cooldownMs, message, "temporary");
|
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);
|
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 {
|
function clearMegaDebridAccountCooldownState(accountId: string): void {
|
||||||
megaDebridAccountCooldowns.delete(accountId);
|
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(
|
function setMegaDebridAccountCooldownState(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
cooldownMs: number,
|
cooldownMs: number,
|
||||||
@ -366,7 +378,7 @@ function setMegaDebridAccountCooldownState(
|
|||||||
): void {
|
): void {
|
||||||
if (untilRestart) {
|
if (untilRestart) {
|
||||||
megaDebridAccountCooldowns.set(accountId, {
|
megaDebridAccountCooldowns.set(accountId, {
|
||||||
until: Number.MAX_SAFE_INTEGER,
|
until: megaDebridDailyParkExpiry(Date.now()),
|
||||||
message,
|
message,
|
||||||
category,
|
category,
|
||||||
untilRestart: true
|
untilRestart: true
|
||||||
@ -1987,7 +1999,7 @@ class MegaDebridClient {
|
|||||||
? "Neustart"
|
? "Neustart"
|
||||||
: new Date(accountCooldownState.until).toLocaleTimeString();
|
: new Date(accountCooldownState.until).toLocaleTimeString();
|
||||||
const reasonText = accountCooldownState.untilRestart
|
const reasonText = accountCooldownState.untilRestart
|
||||||
? "Tageslimit erreicht — bis Neustart gesperrt"
|
? "Tageslimit erreicht — bis zum Tagesreset gesperrt"
|
||||||
: `Cooldown bis ${untilStr}`;
|
: `Cooldown bis ${untilStr}`;
|
||||||
logger.info(`Mega-Debrid${accountLabel}: uebersprungen (${reasonText}), pruefe naechsten Account`);
|
logger.info(`Mega-Debrid${accountLabel}: uebersprungen (${reasonText}), pruefe naechsten Account`);
|
||||||
logAccountRotation("INFO", providerName, rotationLabel, "SKIP_COOLDOWN", {
|
logAccountRotation("INFO", providerName, rotationLabel, "SKIP_COOLDOWN", {
|
||||||
@ -2086,7 +2098,7 @@ class MegaDebridClient {
|
|||||||
const streak = recordMegaDebridEmptyResponseStreak(cooldownKey);
|
const streak = recordMegaDebridEmptyResponseStreak(cooldownKey);
|
||||||
if (streak >= MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART) {
|
if (streak >= MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART) {
|
||||||
parkUntilRestart = true;
|
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 {
|
} else {
|
||||||
clearMegaDebridEmptyResponseStreak(cooldownKey);
|
clearMegaDebridEmptyResponseStreak(cooldownKey);
|
||||||
@ -2109,7 +2121,7 @@ class MegaDebridClient {
|
|||||||
throw new Error(`Mega-Debrid${accountLabel}: ${failure.message}`);
|
throw new Error(`Mega-Debrid${accountLabel}: ${failure.message}`);
|
||||||
}
|
}
|
||||||
const cooldownInfo = parkUntilRestart
|
const cooldownInfo = parkUntilRestart
|
||||||
? ", bis Neustart gesperrt"
|
? ", bis zum Tagesreset gesperrt"
|
||||||
: failure.cooldownMs > 0
|
: failure.cooldownMs > 0
|
||||||
? `, Cooldown ${Math.ceil(failure.cooldownMs / 1000)}s`
|
? `, Cooldown ${Math.ceil(failure.cooldownMs / 1000)}s`
|
||||||
: "";
|
: "";
|
||||||
@ -2146,14 +2158,14 @@ 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 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("Mega-Debrid: Kein aktiver Account verfuegbar");
|
||||||
}
|
}
|
||||||
throw new Error(failures.join(" | ") || "Mega-Debrid: Kein aktiver Account verfuegbar");
|
throw new Error(failures.join(" | ") || "Mega-Debrid: Kein aktiver Account verfuegbar");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static classifyAccountFailure(
|
static classifyAccountFailure(
|
||||||
error: unknown
|
error: unknown
|
||||||
): { fatal: boolean; cooldownMs: number; message: string; category: MegaDebridCooldownCategory; limitSignal?: boolean } {
|
): { fatal: boolean; cooldownMs: number; message: string; category: MegaDebridCooldownCategory; limitSignal?: boolean } {
|
||||||
const errorText = compactErrorText(error).replace(/^Error:\s*/i, "");
|
const errorText = compactErrorText(error).replace(/^Error:\s*/i, "");
|
||||||
@ -2206,8 +2218,7 @@ class MegaDebridClient {
|
|||||||
fatal: false,
|
fatal: false,
|
||||||
cooldownMs: MEGA_DEBRID_ACCOUNT_COOLDOWN_MS,
|
cooldownMs: MEGA_DEBRID_ACCOUNT_COOLDOWN_MS,
|
||||||
message: "Kein Server fuer diesen Hoster (Tageslimit/Hoster nicht verfuegbar)",
|
message: "Kein Server fuer diesen Hoster (Tageslimit/Hoster nicht verfuegbar)",
|
||||||
category: "quota",
|
category: "quota"
|
||||||
limitSignal: true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1159,13 +1159,13 @@ function formatCheckedAgo(checkedAt: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rotationEventText(ev: { event: string; cooldownSec?: number; next?: string; reason?: string }): 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) {
|
switch (ev.event) {
|
||||||
case "OK": return "erfolgreich";
|
case "OK": return "erfolgreich";
|
||||||
case "FAILED": {
|
case "FAILED": {
|
||||||
if (untilRestart) {
|
if (untilRestart) {
|
||||||
const nx = ev.next && ev.next !== "ENDE" ? ` → ${ev.next}` : "";
|
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 cd = ev.cooldownSec ? `, Cooldown ${ev.cooldownSec}s` : "";
|
||||||
const nx = ev.next && ev.next !== "ENDE" ? ` → ${ev.next}` : "";
|
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` : "";
|
const cd = ev.cooldownSec ? `, Cooldown ${ev.cooldownSec}s` : "";
|
||||||
return `Timeout/Abbruch${cd} → nächster Account beim Retry`;
|
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_DISABLED": return "übersprungen (deaktiviert)";
|
||||||
case "SKIP_DAILY_LIMIT": return "übersprungen (Tageslimit erreicht)";
|
case "SKIP_DAILY_LIMIT": return "übersprungen (Tageslimit erreicht)";
|
||||||
case "SKIP_HOST_COOLDOWN": return "übersprungen (Host-Cooldown)";
|
case "SKIP_HOST_COOLDOWN": return "übersprungen (Host-Cooldown)";
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { defaultSettings, REQUEST_RETRIES } from "../src/main/constants";
|
|||||||
import { parseDebridLinkApiKeys } from "../src/shared/debrid-link-keys";
|
import { parseDebridLinkApiKeys } from "../src/shared/debrid-link-keys";
|
||||||
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
||||||
import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits";
|
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;
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
@ -1918,13 +1918,24 @@ describe("debrid service", () => {
|
|||||||
expect(recordMegaDebridEmptyResponseStreak(key)).toBe(1);
|
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`;
|
const key = `${getMegaDebridAccountId("user1")}:api`;
|
||||||
primeMegaDebridUntilRestartForTests(key);
|
primeMegaDebridUntilRestartForTests(key);
|
||||||
const now = getMegaDebridAccountCooldownState(key);
|
const active = getMegaDebridAccountCooldownState(key);
|
||||||
expect(now?.untilRestart).toBe(true);
|
expect(active?.untilRestart).toBe(true);
|
||||||
const farFuture = Date.now() + 100 * 24 * 60 * 60 * 1000;
|
expect(getMegaDebridAccountCooldownState(key, Date.now() + 60_000)?.untilRestart).toBe(true);
|
||||||
expect(getMegaDebridAccountCooldownState(key, farFuture)?.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 () => {
|
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 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 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();
|
expect(megaWeb).not.toHaveBeenCalled();
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user