From 5ecb636d953d22dcc63f16ae0d08075e1bdf3811 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Mon, 20 Apr 2026 16:52:18 +0200 Subject: [PATCH] Debrid-Link skip-Errors: Key bleibt "ready" statt "error" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fileNotAvailable, disabledServerHost, notFreeHost, serverNotAllowed, freeServerOverload, maintenanceHost, noServerHost sind LINK- oder HOST-level Fehler, nicht Key-level. Der Key antwortet ganz normal und sagt nur "diesen Link kann ich aktuell nicht verarbeiten". Vorher wurde trotzdem der Runtime-Status auf "error" gesetzt — sah in der UI aus als waere der Key kaputt und hat die Rotations-Heuristiken irritiert. Fix: bei failure.category === "skip" den Runtime-Status in Ruhe lassen. Der Key bleibt "ready" (bzw. was er vorher war). Invalid bleibt "invalid", alle anderen fehlerhaften Antworten bleiben "error". Test: Key 1 gibt fileNotAvailable zurueck → Key 2 erfolgreich. Key 1 darf danach NICHT "error" sein (per neuem Test-Helper getDebridLinkKeyRuntimeStateForTests). --- src/main/debrid.ts | 15 ++++++++++- tests/debrid.test.ts | 59 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/main/debrid.ts b/src/main/debrid.ts index a88dd45..9d49cbb 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -131,6 +131,11 @@ export function primeDebridLinkRuntimeCooldownForTests(keyId: string, cooldownMs setDebridLinkKeyCooldownState(keyId, cooldownMs, message, "temporary"); } +export function getDebridLinkKeyRuntimeStateForTests(keyId: string): DebridLinkRuntimeState | null { + const status = debridLinkKeyRuntimeStatuses.get(keyId); + return status ? status.state : null; +} + function clearDebridLinkKeyCooldownState(keyId: string): void { debridLinkKeyCooldowns.delete(keyId); debridLinkKeyCooldownDetails.delete(keyId); @@ -2678,7 +2683,15 @@ class DebridLinkClient { } } else { clearDebridLinkKeyCooldownState(apiKey.id); - setDebridLinkKeyRuntimeStatus(apiKey.id, failure.category === "invalid" ? "invalid" : "error", failure.message); + if (failure.category === "invalid") { + setDebridLinkKeyRuntimeStatus(apiKey.id, "invalid", failure.message); + } else if (failure.category !== "skip") { + // "skip" means the LINK or HOST is unavailable (fileNotAvailable, + // disabledServerHost, notFreeHost, freeServerOverload, ...), NOT + // that the key is broken. The key responded normally — leave its + // runtime status alone so the UI doesn't flag it as errored. + setDebridLinkKeyRuntimeStatus(apiKey.id, "error", failure.message); + } } if (failure.fatal) { logAccountRotation("ERROR", providerName, rotationLabel, "FATAL", { diff --git a/tests/debrid.test.ts b/tests/debrid.test.ts index 7f936d5..3a4b359 100644 --- a/tests/debrid.test.ts +++ b/tests/debrid.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { defaultSettings, REQUEST_RETRIES } from "../src/main/constants"; import { parseDebridLinkApiKeys } from "../src/shared/debrid-link-keys"; import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits"; -import { DebridService, extractRapidgatorFilenameFromHtml, fetchAllDebridHostInfo, fetchDebridLinkHostLimits, filenameFromRapidgatorUrlPath, normalizeResolvedFilename, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid"; +import { DebridService, extractRapidgatorFilenameFromHtml, fetchAllDebridHostInfo, fetchDebridLinkHostLimits, filenameFromRapidgatorUrlPath, getDebridLinkKeyRuntimeStateForTests, normalizeResolvedFilename, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid"; const originalFetch = globalThis.fetch; @@ -472,6 +472,63 @@ describe("debrid service", () => { expect(r3.providerLabel).toContain("Key 1"); }); + it("does not mark Debrid-Link key as errored when the API returns fileNotAvailable (link-level, not key-level)", async () => { + const settings = { + ...defaultSettings(), + debridLinkApiKeys: "dl-key-one\ndl-key-two", + providerOrder: ["debridlink"] as const, + providerPrimary: "debridlink" as const, + providerSecondary: "none" as const, + providerTertiary: "none" as const, + autoProviderFallback: true + }; + + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise => { + const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const headers = init?.headers; + let authHeader = ""; + if (headers instanceof Headers) { + authHeader = headers.get("Authorization") || ""; + } else if (Array.isArray(headers)) { + authHeader = headers.find(([key]) => key.toLowerCase() === "authorization")?.[1] || ""; + } else { + authHeader = String((headers as Record | undefined)?.Authorization || ""); + } + + if (!url.includes("/downloader/add")) { + return new Response("not-found", { status: 404 }); + } + if (authHeader === "Bearer dl-key-one") { + return new Response(JSON.stringify({ + success: false, + error: "fileNotAvailable", + error_description: "link is currently not available" + }), { status: 403, headers: { "Content-Type": "application/json" } }); + } + return new Response(JSON.stringify({ + success: true, + value: { + downloadUrl: "https://debrid-link.example/ok.bin", + name: "ok.bin", + size: 1024 + } + }), { status: 200, headers: { "Content-Type": "application/json" } }); + }) as typeof fetch; + + const key1Id = parseDebridLinkApiKeys("dl-key-one")[0].id; + const key2Id = parseDebridLinkApiKeys("dl-key-two")[0].id; + + const service = new DebridService(settings); + const result = await service.unrestrictLink("https://rapidgator.net/file/example"); + expect(result.providerLabel).toContain("Key 2"); + + // Key-one responded normally — just that the link was unavailable on the + // hoster side. Key-one is NOT broken and must not be flagged as "error". + expect(getDebridLinkKeyRuntimeStateForTests(key1Id)).not.toBe("error"); + // Key-two served the link successfully, so it's "ready". + expect(getDebridLinkKeyRuntimeStateForTests(key2Id)).toBe("ready"); + }); + it("treats bad Debrid-Link file passwords as fatal and does not rotate keys", async () => { const settings = { ...defaultSettings(),