Compare commits
2 Commits
661b1e8c21
...
4a883fb93f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a883fb93f | ||
|
|
66878174e6 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.7.169",
|
||||
"version": "1.7.170",
|
||||
"description": "Desktop downloader",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -115,6 +115,9 @@ interface AccountDialogState {
|
||||
megaAccounts: MegaDialogAccount[];
|
||||
megaNewLogin: string;
|
||||
megaNewPassword: string;
|
||||
// IDs der im Bearbeiten-Dialog (temporär) deaktivierten Mega-Debrid-Accounts.
|
||||
// Draft-State: wird erst beim Speichern in settings.megaDebridDisabledAccountIds übernommen.
|
||||
megaDisabledIds: string[];
|
||||
}
|
||||
|
||||
interface DebridLinkAccountKeyEntry {
|
||||
@ -584,7 +587,7 @@ function summarizeAccountLines(kind: AccountKind, settings: AppSettings): string
|
||||
}
|
||||
|
||||
function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | null, settings: AppSettings): AccountDialogState {
|
||||
const baseMega: Pick<AccountDialogState, "megaAccounts" | "megaNewLogin" | "megaNewPassword"> = { megaAccounts: [], megaNewLogin: "", megaNewPassword: "" };
|
||||
const baseMega: Pick<AccountDialogState, "megaAccounts" | "megaNewLogin" | "megaNewPassword" | "megaDisabledIds"> = { megaAccounts: [], megaNewLogin: "", megaNewPassword: "", megaDisabledIds: [] };
|
||||
if (!kind) {
|
||||
return {
|
||||
mode,
|
||||
@ -613,7 +616,9 @@ function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | n
|
||||
}
|
||||
const parsed = parseMegaDebridAccounts(megaToken);
|
||||
const megaAccounts = parsed.map((a) => ({ login: a.login, password: a.password }));
|
||||
return { mode, kind, token: megaToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, megaAccounts, megaNewLogin: "", megaNewPassword: "" };
|
||||
const loadedIds = new Set(parsed.map((a) => a.id));
|
||||
const megaDisabledIds = (settings.megaDebridDisabledAccountIds || []).filter((id) => loadedIds.has(id));
|
||||
return { mode, kind, token: megaToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, megaAccounts, megaNewLogin: "", megaNewPassword: "", megaDisabledIds };
|
||||
}
|
||||
case "bestdebrid-api":
|
||||
return { mode, kind, token: settings.bestToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||
@ -678,14 +683,18 @@ function applyAccountDialogToSettings(settings: AppSettings, dialog: AccountDial
|
||||
const megaParsed = parseMegaDebridAccounts(megaSerialized);
|
||||
const firstLogin = megaParsed.length > 0 ? megaParsed[0].login : "";
|
||||
const firstPassword = megaParsed.length > 0 ? megaParsed[0].password : "";
|
||||
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridApiEnabled: true, megaDebridPreferApi: true, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||
const validIds = new Set(megaParsed.map((a) => a.id));
|
||||
const megaDebridDisabledAccountIds = (dialog.megaDisabledIds || []).filter((id) => validIds.has(id));
|
||||
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridApiEnabled: true, megaDebridPreferApi: true, megaDebridDisabledAccountIds, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||
}
|
||||
case "megadebrid-web": {
|
||||
const megaSerialized = serializeMegaDebridAccounts(dialog.megaAccounts);
|
||||
const megaParsed = parseMegaDebridAccounts(megaSerialized);
|
||||
const firstLogin = megaParsed.length > 0 ? megaParsed[0].login : "";
|
||||
const firstPassword = megaParsed.length > 0 ? megaParsed[0].password : "";
|
||||
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridWebEnabled: true, megaDebridPreferApi: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||
const validIds = new Set(megaParsed.map((a) => a.id));
|
||||
const megaDebridDisabledAccountIds = (dialog.megaDisabledIds || []).filter((id) => validIds.has(id));
|
||||
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridWebEnabled: true, megaDebridPreferApi: false, megaDebridDisabledAccountIds, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||
}
|
||||
case "bestdebrid-api":
|
||||
return { ...settings, bestToken: token, bestDebridUseWebLogin: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||
@ -5682,13 +5691,17 @@ export function App(): ReactElement {
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<label>Konfigurierte Accounts ({accountDialog.megaAccounts.length})</label>
|
||||
<div className="account-dl-key-limit-list">
|
||||
{accountDialog.megaAccounts.map((account, index) => (
|
||||
<div key={index} className="account-dl-key-limit-row">
|
||||
{accountDialog.megaAccounts.map((account, index) => {
|
||||
const accId = getMegaDebridAccountId(account.login);
|
||||
const accDisabled = (accountDialog.megaDisabledIds || []).includes(accId);
|
||||
return (
|
||||
<div key={index} className={`account-dl-key-limit-row${accDisabled ? " disabled" : ""}`}>
|
||||
<div className="account-dl-key-meta">
|
||||
<strong>Account {index + 1}</strong>
|
||||
<span>{maskMegaDebridLogin(account.login)}</span>
|
||||
{accDisabled && <span className="account-validity-badge invalid" title="Dieser Account wird beim Download übersprungen, bleibt aber gespeichert.">Deaktiviert</span>}
|
||||
{(() => {
|
||||
const st = snapshot?.settings?.debridAccountStatuses?.[getMegaDebridAccountId(account.login)];
|
||||
const st = snapshot?.settings?.debridAccountStatuses?.[accId];
|
||||
if (!st) return <span className="account-validity-badge unknown" title="Noch nicht geprüft – auf „Alle prüfen“ klicken">Noch nicht geprüft</span>;
|
||||
const checkedAgo = formatCheckedAgo(st.checkedAt);
|
||||
const tip = `${st.message}${st.email ? ` · ${st.email}` : ""} · geprüft ${checkedAgo}`;
|
||||
@ -5697,18 +5710,35 @@ export function App(): ReactElement {
|
||||
return <span className="account-validity-badge ok" title={tip}>{st.message}</span>;
|
||||
})()}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
|
||||
<button
|
||||
className={accDisabled ? "btn success" : "btn"}
|
||||
title={accDisabled ? "Account wieder aktivieren" : "Account temporär deaktivieren — wird beim Download übersprungen, aber nicht gelöscht"}
|
||||
onClick={() => setAccountDialog((prev) => {
|
||||
if (!prev) return prev;
|
||||
const cur = prev.megaDisabledIds || [];
|
||||
const nextDisabled = cur.includes(accId)
|
||||
? cur.filter((id) => id !== accId)
|
||||
: [...cur, accId];
|
||||
return { ...prev, megaDisabledIds: nextDisabled };
|
||||
})}
|
||||
>
|
||||
{accDisabled ? "Aktivieren" : "Deaktivieren"}
|
||||
</button>
|
||||
<button
|
||||
className="btn danger"
|
||||
onClick={() => setAccountDialog((prev) => {
|
||||
if (!prev) return prev;
|
||||
const nextAccounts = prev.megaAccounts.filter((_, i) => i !== index);
|
||||
return { ...prev, megaAccounts: nextAccounts, token: serializeMegaDebridAccounts(nextAccounts) };
|
||||
return { ...prev, megaAccounts: nextAccounts, megaDisabledIds: (prev.megaDisabledIds || []).filter((id) => id !== accId), token: serializeMegaDebridAccounts(nextAccounts) };
|
||||
})}
|
||||
>
|
||||
Entfernen
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -90,3 +90,20 @@ App läuft headless auf Windows-Server → Nutzer sitzt nicht davor. Größte L
|
||||
- Gestashtes Crash-Debris (`stash@{0}`): enthält Revert von 08372f9/18eada9/98dc366 + log.old. Bei Bedarf inspizierbar/recoverbar; sonst irgendwann verwerfbar.
|
||||
- 08372f9 (Passwort-Daemon-Reset) bewusst nicht neu aufgerollt (außerhalb dieses Goals, kein Hinweis auf Defekt).
|
||||
- Untracked `*-postprocess/` + `fix-library-renames.mjs`: alte Experimente (Apr/Mai), unverändert gelassen.
|
||||
|
||||
---
|
||||
|
||||
## E. Mega-Debrid Account temporär deaktivieren (UI) — 2026-05-31
|
||||
|
||||
**Goal:** Einzelne Mega-Debrid-Accounts deaktivieren (statt löschen) → Rotation überspringt sie, nutzt die anderen.
|
||||
|
||||
**Befund:** Backend KOMPLETT vorhanden — `megaDebridDisabledAccountIds` (Typ/Defaults/Storage-Normalisierung) + Rotation-Skip (debrid.ts:1944) + Verfügbarkeits-Checks. Es fehlt NUR das UI-Toggle (Debrid-Link hat es bereits via keyStatsPopup). ID-Seam verifiziert: `getMegaDebridAccountId(login)` (trim+lowercase) ist auf beiden Seiten identisch → Test grün.
|
||||
|
||||
**Ansatz (B-kohärent):** Toggle in die bestehende Mega-Account-Liste im Bearbeiten-Dialog falten (draft-then-Save, KEIN Live-Persist → kohärent, minimal). Schritte:
|
||||
1. [x] TDD: Test „skips a manually disabled Mega-Debrid account" (acc1 disabled → acc2) — grün gegen Backend.
|
||||
2. [ ] `AccountDialogState`: Feld `megaDisabledIds: string[]`.
|
||||
3. [ ] Dialog-Init (createAccountDialogState): aus `settings.megaDebridDisabledAccountIds`.
|
||||
4. [ ] JSX Mega-Account-Liste: Aktivieren/Deaktivieren-Button + disabled-Styling pro Account.
|
||||
5. [ ] Entfernen-Handler: ID auch aus megaDisabledIds entfernen.
|
||||
6. [ ] `buildSettingsFromDialog` (megadebrid-api/web): `megaDebridDisabledAccountIds` aus Draft (gefiltert auf vorhandene Accounts) übernehmen.
|
||||
7. [ ] Verifizieren: tsc unverändert, volle Suite grün, Toggle-Test grün.
|
||||
|
||||
@ -1,6 +1,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 { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
||||
import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits";
|
||||
import { DebridService, extractRapidgatorFilenameFromHtml, fetchAllDebridHostInfo, fetchDebridLinkHostLimits, filenameFromRapidgatorUrlPath, getDebridLinkKeyRuntimeStateForTests, normalizeResolvedFilename, resetDebridLinkRuntimeStateForTests, resetMegaDebridRuntimeStateForTests } from "../src/main/debrid";
|
||||
|
||||
@ -1440,6 +1441,47 @@ describe("debrid service", () => {
|
||||
expect(webCalls).toBeGreaterThanOrEqual(4);
|
||||
}, 30000);
|
||||
|
||||
it("skips a manually disabled Mega-Debrid account and uses the next one", async () => {
|
||||
// User-Feature: einen Account temporaer deaktivieren (statt loeschen) -> die
|
||||
// Rotation ueberspringt ihn und nutzt die anderen. Beweist den ID-Seam: die ID in
|
||||
// megaDebridDisabledAccountIds MUSS exakt der ID entsprechen, die die Rotation via
|
||||
// getMegaDebridAccountId(login) liest (sonst greift das Deaktivieren nicht).
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
token: "",
|
||||
bestToken: "",
|
||||
allDebridToken: "",
|
||||
megaLogin: "user1",
|
||||
megaPassword: "pass1",
|
||||
megaCredentials: "user1:pass1\nuser2:pass2",
|
||||
megaDebridDisabledAccountIds: [getMegaDebridAccountId("user1")], // acc1 deaktiviert
|
||||
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: "from-acc2.rar",
|
||||
directUrl: "https://mega-web.example/from-acc2.rar",
|
||||
fileSize: null,
|
||||
retriesUsed: 0
|
||||
}));
|
||||
|
||||
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||
const result = await service.unrestrictLink("https://rapidgator.net/file/disabled-acc-test");
|
||||
|
||||
// Der deaktivierte acc1 wird uebersprungen -> acc2 loest den Link auf.
|
||||
expect((result as { sourceAccountId?: string }).sourceAccountId).toBe(getMegaDebridAccountId("user2"));
|
||||
expect(result.directUrl).toBe("https://mega-web.example/from-acc2.rar");
|
||||
// acc1 wurde gar nicht erst versucht -> megaWeb nur 1x (fuer acc2) aufgerufen.
|
||||
expect(megaWeb).toHaveBeenCalledTimes(1);
|
||||
}, 20000);
|
||||
|
||||
it("respects provider selection and does not append hidden providers", async () => {
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user