Fix: Mega-Debrid Link-Umwandlung wieder schnell (klebrige Rotation statt Pro-Link-Wechsel)
Regress aus v1.7.197/198: Das Round-Robin wechselte bei JEDEM Link den Account, und die Latenz-Demotion (v1.7.198) stufte einen Account direkt nach seinem zwangslaeufig langsamen KALTEN Login als "langsam" ein und rotierte weg — Mega-Web cacht Sessions aber pro Account (~20 Min). Ergebnis: jeder Link zahlte einen kalten Login in die serielle Single-Flight-Queue → minutenlanger Vorlauf, bevor die 8 parallelen Downloads anliefen. Vorher (First-Wins) lief alles ueber EINEN warmen Account → schnell. Fix: - Latenz-Demotion (EMA-Sortierung) komplett entfernt — sie war die Ursache des Kalt-Login-Teufelskreises (jeder Account galt nach seinem ersten Login als langsam und wurde weggedraengt). - Rotation jetzt KLEBRIG: gestartet wird beim Cursor (zuletzt erfolgreich genutzter, warmer Account); der Cursor wird im Erfolgszweig nur weitergesetzt, wenn der Schwung MEGA_DEBRID_STICKY_LINKS (25) erreicht ist — sonst bleibt er auf dem Account. Aufeinanderfolgende Links laufen so auf demselben warmen Account (schnell). Limit/Cooldown/Fehler ueberspringen den Account weiterhin und die Rotation klebt dann am naechsten. Ueber die Zeit (alle 25 Links bzw. bei Limits) kommen weiterhin alle Accounts dran — Account 4 inklusive. Tests: 4 alte Round-Robin-/Demotions-Tests durch 3 klebrige ersetzt (bleibt auf 1 Account ueber 5 Links; wechselt erst nach 25 Links; ueberspringt gesperrten und klebt am naechsten). 809 Tests, tsc=6 Baseline, self-check + build ok.
This commit is contained in:
parent
1e7a3b15aa
commit
6ec080489e
@ -297,22 +297,14 @@ const megaDebridEmptyResponseStreaks = new Map<string, number>();
|
|||||||
export const MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART = 3;
|
export const MEGA_DEBRID_EMPTY_STREAK_UNTIL_RESTART = 3;
|
||||||
|
|
||||||
let megaDebridRotationCursor = 0;
|
let megaDebridRotationCursor = 0;
|
||||||
|
let megaDebridStickyCount = 0;
|
||||||
const MEGA_DEBRID_SLOW_ACCOUNT_FLOOR_MS = 6000;
|
// Mega-Web cacht Sessions pro Account (~20 Min). Wuerde jede Link-Aufloesung den
|
||||||
const MEGA_DEBRID_SLOW_ACCOUNT_FACTOR = 3;
|
// Account wechseln (reines Round-Robin), zahlte JEDER Link einen kalten Login in
|
||||||
const megaDebridAccountLatencyEma = new Map<string, number>();
|
// die serielle Single-Flight-Queue → minutenlanger Vorlauf. Stattdessen bleibt die
|
||||||
|
// Rotation "klebrig": ein funktionierender Account wird fuer einen Schwung Links
|
||||||
function recordMegaDebridUnrestrictLatency(cooldownKey: string, elapsedMs: number): void {
|
// behalten (warm/schnell), erst danach (oder bei Limit/Cooldown/Fehler) auf den
|
||||||
if (!Number.isFinite(elapsedMs) || elapsedMs < 0) {
|
// naechsten gewechselt. So bleibt es schnell UND ueber die Zeit kommen alle dran.
|
||||||
return;
|
export const MEGA_DEBRID_STICKY_LINKS = 25;
|
||||||
}
|
|
||||||
const prev = megaDebridAccountLatencyEma.get(cooldownKey);
|
|
||||||
megaDebridAccountLatencyEma.set(cooldownKey, prev === undefined ? elapsedMs : prev * 0.7 + elapsedMs * 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function primeMegaDebridLatencyForTests(cooldownKey: string, emaMs: number): void {
|
|
||||||
megaDebridAccountLatencyEma.set(cooldownKey, emaMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function recordMegaDebridEmptyResponseStreak(accountId: string): number {
|
export function recordMegaDebridEmptyResponseStreak(accountId: string): number {
|
||||||
const streak = (megaDebridEmptyResponseStreaks.get(accountId) || 0) + 1;
|
const streak = (megaDebridEmptyResponseStreaks.get(accountId) || 0) + 1;
|
||||||
@ -328,7 +320,7 @@ export function resetMegaDebridRuntimeStateForTests(): void {
|
|||||||
megaDebridAccountCooldowns.clear();
|
megaDebridAccountCooldowns.clear();
|
||||||
megaDebridEmptyResponseStreaks.clear();
|
megaDebridEmptyResponseStreaks.clear();
|
||||||
megaDebridRotationCursor = 0;
|
megaDebridRotationCursor = 0;
|
||||||
megaDebridAccountLatencyEma.clear();
|
megaDebridStickyCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pruneExpiredMegaDebridRuntimeState(now = Date.now()): number {
|
export function pruneExpiredMegaDebridRuntimeState(now = Date.now()): number {
|
||||||
@ -1925,51 +1917,18 @@ class MegaDebridClient {
|
|||||||
const providerName = `Mega-Debrid ${mode === "api" ? "API" : "Web"}`;
|
const providerName = `Mega-Debrid ${mode === "api" ? "API" : "Web"}`;
|
||||||
const linkShort = String(link || "").slice(0, 80);
|
const linkShort = String(link || "").slice(0, 80);
|
||||||
|
|
||||||
// Round-Robin statt First-Wins: ohne den Cursor startete jede Link-Aufloesung
|
// Klebrige Rotation: gestartet wird beim Cursor (zuletzt erfolgreich genutzter,
|
||||||
// bei Account 1 und endete beim ersten brauchbaren — ein spaeterer Account kam
|
// also warmer Account), danach der Reihe nach als Failover. Alle Skip-/Cooldown-
|
||||||
// nur dran, wenn ALLE davor am Limit/Cooldown waren. Live-Folge: Account 1-2
|
// Checks bleiben. Der Cursor wird erst im Erfolgszweig weitergesetzt — nach einem
|
||||||
// liefen staendig ins Tageslimit, Account 3 trug den Rest allein und Account 4
|
// Schwung erfolgreicher Umwandlungen (MEGA_DEBRID_STICKY_LINKS) — sodass aufeinander
|
||||||
// wurde nie auch nur angeschaut. Der Cursor laesst jede Aufloesung beim Account
|
// folgende Links auf demselben warmen Account laufen statt jeweils neu einzuloggen.
|
||||||
// NACH dem zuletzt getesteten starten, alle Skip-/Cooldown-Checks bleiben.
|
|
||||||
const startOffset = ((megaDebridRotationCursor % accounts.length) + accounts.length) % accounts.length;
|
const startOffset = ((megaDebridRotationCursor % accounts.length) + accounts.length) % accounts.length;
|
||||||
const rotationOrder: { account: MegaDebridAccountEntry; idx: number }[] = [];
|
const orderedEntries: { account: MegaDebridAccountEntry; idx: number }[] = [];
|
||||||
for (let step = 0; step < accounts.length; step += 1) {
|
for (let step = 0; step < accounts.length; step += 1) {
|
||||||
const idx = (startOffset + step) % accounts.length;
|
const idx = (startOffset + step) % accounts.length;
|
||||||
rotationOrder.push({ account: accounts[idx], idx });
|
orderedEntries.push({ account: accounts[idx], idx });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latenz-Schutz fuer die Mega-Web-Single-Flight-Queue: dort laufen ALLE
|
|
||||||
// Umwandlungen seriell, ein nachweislich langsamer Account bremst also
|
|
||||||
// jeden nachfolgenden Link aus. Accounts mit deutlich erhoehtem Erfolgs-EMA
|
|
||||||
// wandern ans Ende der Reihenfolge (= Failover-Reserve) statt in der
|
|
||||||
// gleichmaessigen Rotation mitzulaufen; erholt sich ihr EMA, rotieren sie
|
|
||||||
// automatisch wieder mit. Ungemessene Accounts gelten als gesund.
|
|
||||||
let bestEma = Infinity;
|
|
||||||
let anyUnmeasuredUsable = false;
|
|
||||||
for (const entry of rotationOrder) {
|
|
||||||
if (isMegaDebridAccountDisabled(settings, entry.account.id)) continue;
|
|
||||||
if (isMegaDebridAccountDailyLimitReached(settings, entry.account.id)) continue;
|
|
||||||
if (getMegaDebridAccountCooldownState(`${entry.account.id}:${mode}`)) continue;
|
|
||||||
const ema = megaDebridAccountLatencyEma.get(`${entry.account.id}:${mode}`);
|
|
||||||
if (ema === undefined) {
|
|
||||||
anyUnmeasuredUsable = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ema < bestEma) bestEma = ema;
|
|
||||||
}
|
|
||||||
const slowThreshold = Math.max(
|
|
||||||
MEGA_DEBRID_SLOW_ACCOUNT_FLOOR_MS,
|
|
||||||
anyUnmeasuredUsable ? 0 : (Number.isFinite(bestEma) ? MEGA_DEBRID_SLOW_ACCOUNT_FACTOR * bestEma : Infinity)
|
|
||||||
);
|
|
||||||
const isSlowEntry = (entry: { account: MegaDebridAccountEntry }): boolean => {
|
|
||||||
const ema = megaDebridAccountLatencyEma.get(`${entry.account.id}:${mode}`);
|
|
||||||
return ema !== undefined && ema > slowThreshold;
|
|
||||||
};
|
|
||||||
const orderedEntries = [
|
|
||||||
...rotationOrder.filter((entry) => !isSlowEntry(entry)),
|
|
||||||
...rotationOrder.filter((entry) => isSlowEntry(entry))
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let orderPos = 0; orderPos < orderedEntries.length; orderPos += 1) {
|
for (let orderPos = 0; orderPos < orderedEntries.length; orderPos += 1) {
|
||||||
const entry = orderedEntries[orderPos];
|
const entry = orderedEntries[orderPos];
|
||||||
const account = entry.account;
|
const account = entry.account;
|
||||||
@ -2011,24 +1970,26 @@ class MegaDebridClient {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emaForLog = megaDebridAccountLatencyEma.get(cooldownKey);
|
|
||||||
logger.info(`Mega-Debrid${accountLabel}: TESTE Account fuer Link-Generierung...`);
|
logger.info(`Mega-Debrid${accountLabel}: TESTE Account fuer Link-Generierung...`);
|
||||||
logAccountRotation("INFO", providerName, rotationLabel, "TEST", {
|
logAccountRotation("INFO", providerName, rotationLabel, "TEST", {
|
||||||
link: linkShort,
|
link: linkShort
|
||||||
emaMs: emaForLog !== undefined ? Math.round(emaForLog) : undefined,
|
|
||||||
slow: isSlowEntry(entry) ? true : undefined
|
|
||||||
});
|
});
|
||||||
const testStartedAt = Date.now();
|
const testStartedAt = Date.now();
|
||||||
|
|
||||||
usableAccountSeen = true;
|
usableAccountSeen = true;
|
||||||
megaDebridRotationCursor = idx + 1;
|
|
||||||
try {
|
try {
|
||||||
const client = new MegaDebridClient(account.login, account.password, mode, allowApiFallback, megaWebUnrestrict);
|
const client = new MegaDebridClient(account.login, account.password, mode, allowApiFallback, megaWebUnrestrict);
|
||||||
const result = await client.unrestrictLink(link, signal);
|
const result = await client.unrestrictLink(link, signal);
|
||||||
clearMegaDebridAccountCooldownState(cooldownKey);
|
clearMegaDebridAccountCooldownState(cooldownKey);
|
||||||
clearMegaDebridEmptyResponseStreak(cooldownKey);
|
clearMegaDebridEmptyResponseStreak(cooldownKey);
|
||||||
const elapsedMs = Date.now() - testStartedAt;
|
const elapsedMs = Date.now() - testStartedAt;
|
||||||
recordMegaDebridUnrestrictLatency(cooldownKey, elapsedMs);
|
megaDebridStickyCount += 1;
|
||||||
|
if (megaDebridStickyCount >= MEGA_DEBRID_STICKY_LINKS) {
|
||||||
|
megaDebridRotationCursor = idx + 1;
|
||||||
|
megaDebridStickyCount = 0;
|
||||||
|
} else {
|
||||||
|
megaDebridRotationCursor = idx;
|
||||||
|
}
|
||||||
logger.info(`Mega-Debrid${accountLabel}: Unrestrict OK nach ${elapsedMs}ms -> ${result.fileName || "?"}`);
|
logger.info(`Mega-Debrid${accountLabel}: Unrestrict OK nach ${elapsedMs}ms -> ${result.fileName || "?"}`);
|
||||||
logAccountRotation("INFO", providerName, rotationLabel, "OK", {
|
logAccountRotation("INFO", providerName, rotationLabel, "OK", {
|
||||||
elapsedMs,
|
elapsedMs,
|
||||||
|
|||||||
@ -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, normalizeResolvedFilename, primeMegaDebridLatencyForTests, primeMegaDebridUntilRestartForTests, recordMegaDebridEmptyResponseStreak, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid";
|
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";
|
||||||
|
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
@ -1379,166 +1379,85 @@ describe("debrid service", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("verteilt Links per Round-Robin ueber ALLE Mega-Debrid-Accounts statt immer beim ersten zu starten", async () => {
|
it("bleibt klebrig bei einem funktionierenden Account (kein Account-Wechsel pro Link)", async () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
token: "",
|
token: "", bestToken: "", allDebridToken: "",
|
||||||
bestToken: "",
|
megaLogin: "user1", megaPassword: "pass1",
|
||||||
allDebridToken: "",
|
|
||||||
megaLogin: "user1",
|
|
||||||
megaPassword: "pass1",
|
|
||||||
megaCredentials: "user1:pass1\nuser2:pass2\nuser3:pass3\nuser4:pass4",
|
megaCredentials: "user1:pass1\nuser2:pass2\nuser3:pass3\nuser4:pass4",
|
||||||
megaDebridPreferApi: false,
|
megaDebridPreferApi: false,
|
||||||
providerOrder: [] as const,
|
providerOrder: [] as const, providerPrimary: "megadebrid" as const,
|
||||||
providerPrimary: "megadebrid" as const,
|
providerSecondary: "none" as const, providerTertiary: "none" as const,
|
||||||
providerSecondary: "none" as const,
|
|
||||||
providerTertiary: "none" as const,
|
|
||||||
autoProviderFallback: false
|
autoProviderFallback: false
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
||||||
|
|
||||||
const megaWeb = vi.fn(async () => ({
|
const megaWeb = vi.fn(async () => ({ fileName: "ok.rar", directUrl: "https://mega-web.example/ok.rar", fileSize: null, retriesUsed: 0 }));
|
||||||
fileName: "ok.rar",
|
|
||||||
directUrl: "https://mega-web.example/ok.rar",
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||||
const usedIds: (string | undefined)[] = [];
|
const usedIds: (string | undefined)[] = [];
|
||||||
for (let i = 0; i < 5; i += 1) {
|
for (let i = 0; i < 5; i += 1) {
|
||||||
const result = await service.unrestrictLink(`https://rapidgator.net/file/rr-${i}`);
|
const result = await service.unrestrictLink(`https://rapidgator.net/file/sticky-${i}`);
|
||||||
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(usedIds).toEqual([
|
expect(usedIds).toEqual(new Array(5).fill(getMegaDebridAccountId("user1")));
|
||||||
getMegaDebridAccountId("user1"),
|
|
||||||
getMegaDebridAccountId("user2"),
|
|
||||||
getMegaDebridAccountId("user3"),
|
|
||||||
getMegaDebridAccountId("user4"),
|
|
||||||
getMegaDebridAccountId("user1")
|
|
||||||
]);
|
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
it("Round-Robin respektiert Cooldowns: gesperrter Account wird in der Reihe uebersprungen", async () => {
|
it("wechselt erst nach einem Schwung Links auf den naechsten Account", async () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
token: "",
|
token: "", bestToken: "", allDebridToken: "",
|
||||||
bestToken: "",
|
megaLogin: "user1", megaPassword: "pass1",
|
||||||
allDebridToken: "",
|
megaCredentials: "user1:pass1\nuser2:pass2",
|
||||||
megaLogin: "user1",
|
|
||||||
megaPassword: "pass1",
|
|
||||||
megaCredentials: "user1:pass1\nuser2:pass2\nuser3:pass3",
|
|
||||||
megaDebridPreferApi: false,
|
megaDebridPreferApi: false,
|
||||||
providerOrder: [] as const,
|
providerOrder: [] as const, providerPrimary: "megadebrid" as const,
|
||||||
providerPrimary: "megadebrid" as const,
|
providerSecondary: "none" as const, providerTertiary: "none" as const,
|
||||||
providerSecondary: "none" as const,
|
|
||||||
providerTertiary: "none" as const,
|
|
||||||
autoProviderFallback: false
|
autoProviderFallback: false
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
globalThis.fetch = (async () => new Response("error", { status: 500 })) as typeof fetch;
|
||||||
|
|
||||||
const megaWeb = vi.fn(async () => ({
|
const megaWeb = vi.fn(async () => ({ fileName: "ok.rar", directUrl: "https://mega-web.example/ok.rar", fileSize: null, retriesUsed: 0 }));
|
||||||
fileName: "ok.rar",
|
|
||||||
directUrl: "https://mega-web.example/ok.rar",
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
primeMegaDebridUntilRestartForTests(`${getMegaDebridAccountId("user2")}:web`);
|
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||||
|
const usedIds: (string | undefined)[] = [];
|
||||||
|
for (let i = 0; i < MEGA_DEBRID_STICKY_LINKS + 1; i += 1) {
|
||||||
|
const result = await service.unrestrictLink(`https://rapidgator.net/file/chunk-${i}`);
|
||||||
|
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(usedIds.slice(0, MEGA_DEBRID_STICKY_LINKS)).toEqual(new Array(MEGA_DEBRID_STICKY_LINKS).fill(getMegaDebridAccountId("user1")));
|
||||||
|
expect(usedIds[MEGA_DEBRID_STICKY_LINKS]).toBe(getMegaDebridAccountId("user2"));
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
it("ueberspringt einen gesperrten Account und bleibt dann klebrig beim naechsten", async () => {
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "", bestToken: "", allDebridToken: "",
|
||||||
|
megaLogin: "user1", megaPassword: "pass1",
|
||||||
|
megaCredentials: "user1:pass1\nuser2:pass2\nuser3:pass3",
|
||||||
|
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 megaWeb = vi.fn(async () => ({ fileName: "ok.rar", directUrl: "https://mega-web.example/ok.rar", fileSize: null, retriesUsed: 0 }));
|
||||||
|
|
||||||
|
primeMegaDebridUntilRestartForTests(`${getMegaDebridAccountId("user1")}:web`);
|
||||||
|
|
||||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||||
const usedIds: (string | undefined)[] = [];
|
const usedIds: (string | undefined)[] = [];
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
const result = await service.unrestrictLink(`https://rapidgator.net/file/rr-skip-${i}`);
|
const result = await service.unrestrictLink(`https://rapidgator.net/file/skip-${i}`);
|
||||||
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(usedIds).toEqual([
|
expect(usedIds).toEqual(new Array(3).fill(getMegaDebridAccountId("user2")));
|
||||||
getMegaDebridAccountId("user1"),
|
|
||||||
getMegaDebridAccountId("user3"),
|
|
||||||
getMegaDebridAccountId("user1")
|
|
||||||
]);
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
it("depriorisiert einen nachweislich langsamen Account in der Rotation (Single-Flight-Schutz)", async () => {
|
|
||||||
const settings = {
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "",
|
|
||||||
bestToken: "",
|
|
||||||
allDebridToken: "",
|
|
||||||
megaLogin: "user1",
|
|
||||||
megaPassword: "pass1",
|
|
||||||
megaCredentials: "user1:pass1\nuser2:pass2",
|
|
||||||
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 megaWeb = vi.fn(async () => ({
|
|
||||||
fileName: "ok.rar",
|
|
||||||
directUrl: "https://mega-web.example/ok.rar",
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
primeMegaDebridLatencyForTests(`${getMegaDebridAccountId("user1")}:web`, 15000);
|
|
||||||
primeMegaDebridLatencyForTests(`${getMegaDebridAccountId("user2")}:web`, 800);
|
|
||||||
|
|
||||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
|
||||||
const usedIds: (string | undefined)[] = [];
|
|
||||||
for (let i = 0; i < 2; i += 1) {
|
|
||||||
const result = await service.unrestrictLink(`https://rapidgator.net/file/slow-${i}`);
|
|
||||||
usedIds.push((result as { sourceAccountId?: string }).sourceAccountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(usedIds).toEqual([
|
|
||||||
getMegaDebridAccountId("user2"),
|
|
||||||
getMegaDebridAccountId("user2")
|
|
||||||
]);
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
it("nutzt den langsamen Account weiterhin, wenn die schnellen gesperrt sind (Failover-Reserve)", async () => {
|
|
||||||
const settings = {
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "",
|
|
||||||
bestToken: "",
|
|
||||||
allDebridToken: "",
|
|
||||||
megaLogin: "user1",
|
|
||||||
megaPassword: "pass1",
|
|
||||||
megaCredentials: "user1:pass1\nuser2:pass2",
|
|
||||||
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 megaWeb = vi.fn(async () => ({
|
|
||||||
fileName: "ok.rar",
|
|
||||||
directUrl: "https://mega-web.example/ok.rar",
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
primeMegaDebridLatencyForTests(`${getMegaDebridAccountId("user1")}:web`, 15000);
|
|
||||||
primeMegaDebridLatencyForTests(`${getMegaDebridAccountId("user2")}:web`, 800);
|
|
||||||
primeMegaDebridUntilRestartForTests(`${getMegaDebridAccountId("user2")}:web`);
|
|
||||||
|
|
||||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
|
||||||
const result = await service.unrestrictLink("https://rapidgator.net/file/slow-fallback");
|
|
||||||
|
|
||||||
expect((result as { sourceAccountId?: string }).sourceAccountId).toBe(getMegaDebridAccountId("user1"));
|
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
it("rotates to the next Mega-Debrid account when one hits its daily limit (error-based)", async () => {
|
it("rotates to the next Mega-Debrid account when one hits its daily limit (error-based)", async () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user