Fix Debrid-Link key rotation cascade failure, case-sensitive rename, and sample filter

- notDebrid (host-level) no longer burns all keys: stops rotation immediately
  with 5min cooldown instead of cycling through all 9 keys pointlessly
- Remove double provider-blockade: debrid_link_cooldown no longer stacks
  recordProviderFailure + applyProviderBusyBackoff on top of key cooldowns
- Detect timeout cascades: 2+ consecutive transport failures trigger 3min
  cooldown instead of burning remaining keys
- Case-sensitive rename: files with different casing (e.g. lowercase scene
  names) now get properly renamed instead of being skipped as "already matching"
- Extended sample filter: detect -s.mkv suffix and \Sample\ subdirectories
  in auto-rename (already worked in MKV-move)
- Add key status display with state pills in Debrid-Link key stats popup
- Add parseDebridLinkTerminalFailure for fast-fail on exhausted keys

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe
2026-03-26 13:04:42 +01:00
co-authored by Claude Opus 4.6
parent 1d0b2ee8e3
commit 38179881f5
7 changed files with 288 additions and 31 deletions
+4 -4
View File
@@ -525,7 +525,7 @@ describe("debrid service", () => {
await expect(service.unrestrictLink("https://hoster.example/no-key-left.bin")).rejects.toThrow(/debrid-link nicht verfuegbar|kein aktiver api-key/i);
});
it("rotates through all keys on Debrid-Link notDebrid errors before failing", async () => {
it("stops rotation immediately on Debrid-Link notDebrid (provider-wide) — does NOT burn remaining keys", async () => {
const settings = {
...defaultSettings(),
debridLinkApiKeys: "dl-key-one\ndl-key-two",
@@ -551,9 +551,9 @@ describe("debrid service", () => {
}) as typeof fetch;
const service = new DebridService(settings);
await expect(service.unrestrictLink("https://hoster.example/not-debrid.bin")).rejects.toThrow(/notDebrid/);
// notDebrid is transient (host may be down) — both keys should be tried
expect(authHeaders).toEqual(["Bearer dl-key-one", "Bearer dl-key-two"]);
await expect(service.unrestrictLink("https://hoster.example/not-debrid.bin")).rejects.toThrow(/debrid_link_cooldown.*notDebrid/);
// notDebrid is a host-level issue — only Key 1 should be tried, Key 2 must NOT be burned
expect(authHeaders).toEqual(["Bearer dl-key-one"]);
});
it("continues to the next Debrid-Link key for non-provider-wide skip errors without caching a cooldown", async () => {