Compare commits
No commits in common. "471e40b87fab6083326feed9feb27b7a24dad948" and "d594c5082b6c5aed39baaa73d6ea70cde61060e4" have entirely different histories.
471e40b87f
...
d594c5082b
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.7.226",
|
||||
"version": "1.7.225",
|
||||
"description": "Desktop downloader",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -288,7 +288,6 @@ type MegaDebridCooldownCategory = "invalid" | "rate_limit" | "quota" | "temporar
|
||||
type MegaDebridCooldownDetail = { until: number; message: string; category: MegaDebridCooldownCategory; untilRestart?: boolean };
|
||||
const megaDebridAccountCooldowns = new Map<string, MegaDebridCooldownDetail>();
|
||||
const MEGA_DEBRID_ACCOUNT_COOLDOWN_MS = 120_000;
|
||||
const MEGA_DEBRID_SLOW_LINK_RETRY_MS = 120_000;
|
||||
const MEGA_DEBRID_INVALID_ACCOUNT_COOLDOWN_MS = 60 * 60 * 1000;
|
||||
|
||||
// A Mega-Web account abort (the shared unrestrict timeout firing while this
|
||||
@ -2069,33 +2068,15 @@ class MegaDebridClient {
|
||||
} catch (error) {
|
||||
const elapsedMs = Date.now() - testStartedAt;
|
||||
const abortText = compactErrorText(error).replace(/^Error:\s*/i, "");
|
||||
// Timeout/abort on THIS account (the shared unrestrict timeout fired). The
|
||||
// account-wide cooldown exists ONLY to make the retry rotate to another
|
||||
// account — so it is set only when another usable account actually exists.
|
||||
// With no rotation target (single account / all others busy), cooling the
|
||||
// sole account would freeze EVERY queued item while the account is healthy;
|
||||
// a >60s timeout is a slow-LINK signal, not an unhealthy-account signal, so
|
||||
// we park just this link (mega_debrid_slow_link) and leave the account free
|
||||
// for other items. A quick user-cancel (below the min run) parks nothing.
|
||||
// Timeout/abort on THIS account (the shared unrestrict signal fired). Cool
|
||||
// the account down — if it actually ran, not a quick user-cancel — so the
|
||||
// download-manager's retry rotates to the NEXT account instead of hammering
|
||||
// this one. The shared signal is now aborted, so we stop this pass; the
|
||||
// retry runs the rotation fresh with this account skipped. A genuine cancel
|
||||
// is not retried by the caller, so the cooldown is harmless there.
|
||||
if (/aborted/i.test(abortText) && !/timeout/i.test(abortText)) {
|
||||
const ranLongEnough = elapsedMs >= getMegaDebridAbortMinRunMs();
|
||||
const otherUsableAccounts = orderedEntries.reduce((count, candidate) => {
|
||||
if (candidate.account.id === account.id) {
|
||||
return count;
|
||||
}
|
||||
if (isMegaDebridAccountDisabled(settings, candidate.account.id)) {
|
||||
return count;
|
||||
}
|
||||
if (isMegaDebridAccountDailyLimitReached(settings, candidate.account.id)) {
|
||||
return count;
|
||||
}
|
||||
if (getMegaDebridAccountCooldownState(`${candidate.account.id}:${mode}`)) {
|
||||
return count;
|
||||
}
|
||||
return count + 1;
|
||||
}, 0);
|
||||
const rotateToAnotherAccount = ranLongEnough && otherUsableAccounts > 0;
|
||||
if (rotateToAnotherAccount) {
|
||||
if (ranLongEnough) {
|
||||
setMegaDebridAccountCooldownState(cooldownKey, MEGA_DEBRID_ACCOUNT_COOLDOWN_MS, `Abbruch/Timeout nach ${Math.ceil(elapsedMs / 1000)}s`, "temporary");
|
||||
}
|
||||
traceConversionPhase({
|
||||
@ -2104,18 +2085,15 @@ class MegaDebridClient {
|
||||
account: rotationLabel,
|
||||
workMs: elapsedMs,
|
||||
outcome: "aborted",
|
||||
detail: `${abortText}${rotateToAnotherAccount ? ` cd=${Math.ceil(MEGA_DEBRID_ACCOUNT_COOLDOWN_MS / 1000)}s` : ranLongEnough ? ` slowlink=${Math.ceil(MEGA_DEBRID_SLOW_LINK_RETRY_MS / 1000)}s` : ""}`
|
||||
detail: `${abortText}${ranLongEnough ? ` cd=${Math.ceil(MEGA_DEBRID_ACCOUNT_COOLDOWN_MS / 1000)}s` : ""}`
|
||||
});
|
||||
failures.push(`Mega-Debrid${accountLabel}: ${abortText}`);
|
||||
logAccountRotation("WARN", providerName, rotationLabel, "TIMEOUT_COOLDOWN", {
|
||||
elapsedMs,
|
||||
reason: abortText,
|
||||
cooldownSec: rotateToAnotherAccount ? Math.ceil(MEGA_DEBRID_ACCOUNT_COOLDOWN_MS / 1000) : 0,
|
||||
next: rotateToAnotherAccount ? "naechster Account beim Retry" : "Einzel-Retry (Account bleibt fuer andere Items frei)"
|
||||
cooldownSec: ranLongEnough ? Math.ceil(MEGA_DEBRID_ACCOUNT_COOLDOWN_MS / 1000) : 0,
|
||||
next: "naechster Account beim Retry"
|
||||
});
|
||||
if (ranLongEnough && !rotateToAnotherAccount) {
|
||||
throw new Error(`mega_debrid_slow_link:${MEGA_DEBRID_SLOW_LINK_RETRY_MS}:Mega-Debrid${accountLabel}: ${abortText}`);
|
||||
}
|
||||
throw new Error(`Mega-Debrid${accountLabel}: ${abortText}`);
|
||||
}
|
||||
const failure = MegaDebridClient.classifyAccountFailure(error);
|
||||
|
||||
@ -660,20 +660,6 @@ export function parseMegaDebridCooldownRetry(errorText: string): { delayMs: numb
|
||||
return { delayMs, detail: text.replace(/mega_debrid_cooldown:\d+:/i, "").trim() };
|
||||
}
|
||||
|
||||
export function parseMegaDebridSlowLinkRetry(errorText: string): { delayMs: number; detail: string } | null {
|
||||
const text = String(errorText || "");
|
||||
const match = text.match(/mega_debrid_slow_link:(\d+)/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const raw = Number(match[1]);
|
||||
if (!Number.isFinite(raw) || raw <= 0) {
|
||||
return null;
|
||||
}
|
||||
const delayMs = Math.max(1000, Math.min(15 * 60 * 1000, raw));
|
||||
return { delayMs, detail: text.replace(/mega_debrid_slow_link:\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) {
|
||||
@ -9481,24 +9467,6 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const megaRawError = error instanceof Error ? String(error.message || "") : String(error || "");
|
||||
const megaSlowLinkRetry = parseMegaDebridSlowLinkRetry(megaRawError);
|
||||
if (megaSlowLinkRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||
active.unrestrictRetries += 1;
|
||||
item.retries += 1;
|
||||
item.provider = null;
|
||||
logger.warn(`Mega-Debrid Link langsam (Timeout): item=${item.fileName || item.id}, retry=${active.unrestrictRetries}/${retryDisplayLimit}, delay=${megaSlowLinkRetry.delayMs}ms, link=${item.url.slice(0, 80)}`);
|
||||
this.queueRetry(
|
||||
item,
|
||||
active,
|
||||
megaSlowLinkRetry.delayMs,
|
||||
`Mega-Debrid: Link zu langsam, Einzel-Retry in ${Math.ceil(megaSlowLinkRetry.delayMs / 1000)}s`
|
||||
);
|
||||
item.lastError = megaSlowLinkRetry.detail || errorText;
|
||||
this.persistSoon();
|
||||
this.emitState();
|
||||
return;
|
||||
}
|
||||
|
||||
const megaCooldownRetry = parseMegaDebridCooldownRetry(megaRawError);
|
||||
if (megaCooldownRetry && active.unrestrictRetries < maxUnrestrictRetries) {
|
||||
active.unrestrictRetries += 1;
|
||||
|
||||
@ -2071,55 +2071,6 @@ describe("debrid service", () => {
|
||||
expect(calls).toBeGreaterThanOrEqual(1);
|
||||
}, 20000);
|
||||
|
||||
it("single Mega-Debrid account: a long Web abort parks only the slow link and does NOT freeze the sole account", async () => {
|
||||
process.env.RD_MEGA_ABORT_MIN_RUN_MS = "0";
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
token: "",
|
||||
bestToken: "",
|
||||
allDebridToken: "",
|
||||
megaLogin: "user",
|
||||
megaPassword: "pass",
|
||||
megaCredentials: "user:pass",
|
||||
megaDebridPreferApi: false,
|
||||
providerOrder: [] as const,
|
||||
providerPrimary: "megadebrid" as const,
|
||||
providerSecondary: "none" as const,
|
||||
providerTertiary: "none" as const,
|
||||
autoProviderFallback: false
|
||||
};
|
||||
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
||||
|
||||
const controller = new AbortController();
|
||||
let calls = 0;
|
||||
const megaWeb = vi.fn((): Promise<{ fileName: string; directUrl: string; fileSize: number | null; retriesUsed: number }> => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
controller.abort("simulated-60s-timeout");
|
||||
return Promise.reject(new Error("aborted"));
|
||||
}
|
||||
return Promise.resolve({
|
||||
fileName: "healthy.rar",
|
||||
directUrl: "https://www11.unrestrict.link/download/file/ok/healthy.rar",
|
||||
fileSize: null,
|
||||
retriesUsed: 0
|
||||
});
|
||||
});
|
||||
|
||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||
|
||||
const err = await service.unrestrictLink("https://rapidgator.net/file/slow-link.rar.html", controller.signal).then(() => null, (e: unknown) => e);
|
||||
expect(err).toBeTruthy();
|
||||
expect(String(err)).toMatch(/mega_debrid_slow_link:\d+:/i);
|
||||
|
||||
const key = `${getMegaDebridAccountId("user")}:web`;
|
||||
expect(getMegaDebridAccountCooldownState(key)).toBeNull();
|
||||
|
||||
const second = await service.unrestrictLink("https://rapidgator.net/file/healthy.rar.html");
|
||||
expect(second.provider).toBe("megadebrid");
|
||||
expect(calls).toBeGreaterThanOrEqual(2);
|
||||
}, 20000);
|
||||
|
||||
it("escalates a Mega-Debrid account to 'until restart' after the empty-response streak threshold", () => {
|
||||
const key = `${getMegaDebridAccountId("user1")}:web`;
|
||||
expect(MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART).toBe(3);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { transientResolveRetryDelayMs, parseMegaDebridCooldownRetry, parseMegaDebridResetPark, parseMegaDebridSlowLinkRetry } 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", () => {
|
||||
@ -63,31 +63,6 @@ describe("parseMegaDebridCooldownRetry (honor the encoded account-cooldown delay
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseMegaDebridSlowLinkRetry (park only the slow link, never the account)", () => {
|
||||
it("parses the encoded delay from a slow-link error", () => {
|
||||
const r = parseMegaDebridSlowLinkRetry("mega_debrid_slow_link:120000:Mega-Debrid (Account 1/1, Su******e3): aborted");
|
||||
expect(r).not.toBeNull();
|
||||
expect(r!.delayMs).toBe(120000);
|
||||
expect(r!.detail).toContain("Mega-Debrid");
|
||||
});
|
||||
|
||||
it("parses it when embedded in the aggregated provider-chain error", () => {
|
||||
const aggregated = "Provider-Kette: Mega-Debrid Web fehlgeschlagen (Error: mega_debrid_slow_link:90000:Mega-Debrid (Account 1/1): aborted)";
|
||||
expect(parseMegaDebridSlowLinkRetry(aggregated)!.delayMs).toBe(90000);
|
||||
});
|
||||
|
||||
it("clamps to [1s, 15min]", () => {
|
||||
expect(parseMegaDebridSlowLinkRetry("mega_debrid_slow_link:1:x")!.delayMs).toBe(1000);
|
||||
expect(parseMegaDebridSlowLinkRetry("mega_debrid_slow_link:99999999:x")!.delayMs).toBe(15 * 60 * 1000);
|
||||
});
|
||||
|
||||
it("does not collide with the account-cooldown or reset-park tokens", () => {
|
||||
expect(parseMegaDebridSlowLinkRetry("mega_debrid_cooldown:20330:x")).toBeNull();
|
||||
expect(parseMegaDebridSlowLinkRetry("mega_debrid_reset_park:43200000:x")).toBeNull();
|
||||
expect(parseMegaDebridCooldownRetry("mega_debrid_slow_link:120000:x")).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)");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user