Feature: Mega-Debrid-Account beim Hinzufuegen sofort pruefen (Gueltigkeit + Premium)

Beim "Hinzufuegen" eines Mega-Debrid-Accounts im Bearbeiten-Dialog wird der Account
jetzt sofort einzeln geprueft (connectUser) — Login-Gueltigkeit + Premium-Restlaufzeit
erscheinen direkt als Badge, ohne den Tab schliessen und "Alle pruefen" klicken zu
muessen. Waehrend der Pruefung zeigt das Badge "Pruefe…".

Neue Einzel-Check-IPC (Spiegel von checkDebridAccounts): CHECK_MEGA_DEBRID_ACCOUNT
-> app-controller.checkSingleMegaDebridAccount(login, password) baut den Account-Entry
(id via getMegaDebridAccountId), ruft die bestehende checkMegaDebridAccount() und merged
das Ergebnis via applyDebridAccountStatuses -> Snapshot -> Badge (gleicher Pfad wie
"Alle pruefen"). Funktioniert fuer den noch nicht gespeicherten Draft-Account, weil
applyDebridAccountStatuses merged (kein Pruning) + emitState.

Kern-Check-Logik unveraendert + weiterhin durch account-check.test.ts gedeckt.
643 Tests gruen, tsc 9 (unveraendert), Build sauber. GUI compile-/build-verifiziert,
im laufenden Electron noch nicht click-getestet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-05-31 14:20:21 +02:00
parent 4a883fb93f
commit efb5696c13
6 changed files with 63 additions and 9 deletions

View File

@ -24,7 +24,8 @@ import { importDlcContainers } from "./container";
import { APP_VERSION } from "./constants"; import { APP_VERSION } from "./constants";
import { DownloadManager } from "./download-manager"; import { DownloadManager } from "./download-manager";
import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid"; import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid";
import { checkAllDebridAccounts } from "./account-check"; import { checkAllDebridAccounts, checkMegaDebridAccount } from "./account-check";
import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
import { parseCollectorInput } from "./link-parser"; import { parseCollectorInput } from "./link-parser";
import { configureLogger, getLogFilePath, logger } from "./logger"; import { configureLogger, getLogFilePath, logger } from "./logger";
import { AllDebridWebFallback } from "./all-debrid-web"; import { AllDebridWebFallback } from "./all-debrid-web";
@ -389,6 +390,20 @@ public async checkDebridAccounts(): Promise<DebridAccountStatus[]> {
}); });
return statuses; return statuses;
} }
/** Check a SINGLE Mega-Debrid account by raw credentials (used when an account
* is added in the dialog, before it is saved) and merge the result so the
* validity/premium badge updates immediately without a full "Alle pruefen". */
public async checkSingleMegaDebridAccount(login: string, password: string): Promise<DebridAccountStatus | null> {
const entry = parseMegaDebridAccounts(`${login.trim()}:${password.trim()}`)[0];
if (!entry) {
return null;
}
const status = await checkMegaDebridAccount(entry);
this.manager.applyDebridAccountStatuses([status]);
this.audit("INFO", "Mega-Debrid-Account einzeln geprueft", { valid: status.valid, premium: status.isPremium });
return status;
}
public async checkUpdates(): Promise<UpdateCheckResult> { public async checkUpdates(): Promise<UpdateCheckResult> {
const result = await checkGitHubUpdate(this.settings.updateRepo); const result = await checkGitHubUpdate(this.settings.updateRepo);
if (!result.error) { if (!result.error) {

View File

@ -648,6 +648,10 @@ function registerIpcHandlers(): void {
return controller.checkDebridAccounts(); return controller.checkDebridAccounts();
}); });
ipcMain.handle(IPC_CHANNELS.CHECK_MEGA_DEBRID_ACCOUNT, async (_event, login: string, password: string) => {
return controller.checkSingleMegaDebridAccount(String(login || ""), String(password || ""));
});
ipcMain.handle(IPC_CHANNELS.IMPORT_BACKUP, async () => { ipcMain.handle(IPC_CHANNELS.IMPORT_BACKUP, async () => {
const options = { const options = {
properties: ["openFile"] as Array<"openFile">, properties: ["openFile"] as Array<"openFile">,

View File

@ -77,6 +77,7 @@ const api: ElectronApi = {
getAllDebridHostInfo: (): Promise<AllDebridHostInfo> => ipcRenderer.invoke(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO), getAllDebridHostInfo: (): Promise<AllDebridHostInfo> => ipcRenderer.invoke(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO),
getDebridLinkHostLimits: (): Promise<DebridLinkHostLimitInfo[]> => ipcRenderer.invoke(IPC_CHANNELS.GET_DEBRIDLINK_HOST_LIMITS), getDebridLinkHostLimits: (): Promise<DebridLinkHostLimitInfo[]> => ipcRenderer.invoke(IPC_CHANNELS.GET_DEBRIDLINK_HOST_LIMITS),
checkDebridAccounts: (): Promise<DebridAccountStatus[]> => ipcRenderer.invoke(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS), checkDebridAccounts: (): Promise<DebridAccountStatus[]> => ipcRenderer.invoke(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS),
checkMegaDebridAccount: (login: string, password: string): Promise<DebridAccountStatus | null> => ipcRenderer.invoke(IPC_CHANNELS.CHECK_MEGA_DEBRID_ACCOUNT, login, password),
retryExtraction: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId), retryExtraction: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId),
extractNow: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId), extractNow: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId),
resetPackage: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RESET_PACKAGE, packageId), resetPackage: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RESET_PACKAGE, packageId),

View File

@ -1611,6 +1611,8 @@ export function App(): ReactElement {
const [showAllPackages, setShowAllPackages] = useState(false); const [showAllPackages, setShowAllPackages] = useState(false);
const [actionBusy, setActionBusy] = useState(false); const [actionBusy, setActionBusy] = useState(false);
const [accountCheckBusy, setAccountCheckBusy] = useState(false); const [accountCheckBusy, setAccountCheckBusy] = useState(false);
// Account-IDs, die gerade beim Hinzufügen einzeln geprüft werden (Mega-Debrid).
const [megaCheckingIds, setMegaCheckingIds] = useState<Set<string>>(() => new Set());
const actionBusyRef = useRef(false); const actionBusyRef = useRef(false);
const actionUnlockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const actionUnlockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const mountedRef = useRef(true); const mountedRef = useRef(true);
@ -2663,6 +2665,24 @@ export function App(): ReactElement {
} }
}, [showToast]); }, [showToast]);
const runMegaAccountCheck = useCallback(async (login: string, password: string): Promise<void> => {
const trimmedLogin = login.trim();
const trimmedPassword = password.trim();
if (!trimmedLogin || !trimmedPassword) return;
const accId = getMegaDebridAccountId(trimmedLogin);
setMegaCheckingIds((prev) => { const next = new Set(prev); next.add(accId); return next; });
try {
const status = await window.rd.checkMegaDebridAccount(trimmedLogin, trimmedPassword);
if (status) {
showToast(status.valid ? `Account geprüft — ${status.message}` : `Account ungültig — ${status.message}`, 3200);
}
} catch (error) {
showToast(`Account-Check fehlgeschlagen: ${String(error)}`, 3200);
} finally {
setMegaCheckingIds((prev) => { const next = new Set(prev); next.delete(accId); return next; });
}
}, [showToast]);
const openCreateAccountDialog = (): void => { const openCreateAccountDialog = (): void => {
setAccountDialogSearch(""); setAccountDialogSearch("");
setAccountDialog(createAccountDialogState("create", null, settingsDraft)); setAccountDialog(createAccountDialogState("create", null, settingsDraft));
@ -5677,13 +5697,23 @@ export function App(): ReactElement {
<button <button
className="btn" className="btn"
disabled={!accountDialog.megaNewLogin.trim() || !accountDialog.megaNewPassword.trim()} disabled={!accountDialog.megaNewLogin.trim() || !accountDialog.megaNewPassword.trim()}
onClick={() => setAccountDialog((prev) => { onClick={() => {
const login = accountDialog.megaNewLogin.trim();
const password = accountDialog.megaNewPassword.trim();
if (!login || !password) return;
const exists = accountDialog.megaAccounts.some((a) => a.login.trim().toLowerCase() === login.toLowerCase());
setAccountDialog((prev) => {
if (!prev || !prev.megaNewLogin.trim() || !prev.megaNewPassword.trim()) return prev; if (!prev || !prev.megaNewLogin.trim() || !prev.megaNewPassword.trim()) return prev;
const exists = prev.megaAccounts.some((a) => a.login.trim().toLowerCase() === prev.megaNewLogin.trim().toLowerCase()); if (prev.megaAccounts.some((a) => a.login.trim().toLowerCase() === login.toLowerCase())) return prev;
if (exists) return prev; const nextAccounts = [...prev.megaAccounts, { login, password }];
const nextAccounts = [...prev.megaAccounts, { login: prev.megaNewLogin.trim(), password: prev.megaNewPassword.trim() }];
return { ...prev, megaAccounts: nextAccounts, megaNewLogin: "", megaNewPassword: "", token: serializeMegaDebridAccounts(nextAccounts) }; return { ...prev, megaAccounts: nextAccounts, megaNewLogin: "", megaNewPassword: "", token: serializeMegaDebridAccounts(nextAccounts) };
})} });
// Sofort beim Anlegen pruefen (Gueltigkeit + Premium-Restlaufzeit) —
// Badge aktualisiert sich via Snapshot, ohne Tab schliessen / "Alle pruefen".
if (!exists) {
void runMegaAccountCheck(login, password);
}
}}
> >
Hinzufügen Hinzufügen
</button> </button>
@ -5700,7 +5730,9 @@ export function App(): ReactElement {
<strong>Account {index + 1}</strong> <strong>Account {index + 1}</strong>
<span>{maskMegaDebridLogin(account.login)}</span> <span>{maskMegaDebridLogin(account.login)}</span>
{accDisabled && <span className="account-validity-badge invalid" title="Dieser Account wird beim Download übersprungen, bleibt aber gespeichert.">Deaktiviert</span>} {accDisabled && <span className="account-validity-badge invalid" title="Dieser Account wird beim Download übersprungen, bleibt aber gespeichert.">Deaktiviert</span>}
{(() => { {megaCheckingIds.has(accId)
? <span className="account-validity-badge unknown" title="Account wird gerade geprüft…">Prüfe</span>
: (() => {
const st = snapshot?.settings?.debridAccountStatuses?.[accId]; 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>; 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 checkedAgo = formatCheckedAgo(st.checkedAt);

View File

@ -56,6 +56,7 @@ export const IPC_CHANNELS = {
GET_ALLDEBRID_HOST_INFO: "app:get-alldebrid-host-info", GET_ALLDEBRID_HOST_INFO: "app:get-alldebrid-host-info",
GET_DEBRIDLINK_HOST_LIMITS: "app:get-debridlink-host-limits", GET_DEBRIDLINK_HOST_LIMITS: "app:get-debridlink-host-limits",
CHECK_DEBRID_ACCOUNTS: "app:check-debrid-accounts", CHECK_DEBRID_ACCOUNTS: "app:check-debrid-accounts",
CHECK_MEGA_DEBRID_ACCOUNT: "app:check-mega-debrid-account",
RETRY_EXTRACTION: "queue:retry-extraction", RETRY_EXTRACTION: "queue:retry-extraction",
EXTRACT_NOW: "queue:extract-now", EXTRACT_NOW: "queue:extract-now",
RESET_PACKAGE: "queue:reset-package", RESET_PACKAGE: "queue:reset-package",

View File

@ -74,6 +74,7 @@ export interface ElectronApi {
getAllDebridHostInfo: () => Promise<AllDebridHostInfo>; getAllDebridHostInfo: () => Promise<AllDebridHostInfo>;
getDebridLinkHostLimits: () => Promise<DebridLinkHostLimitInfo[]>; getDebridLinkHostLimits: () => Promise<DebridLinkHostLimitInfo[]>;
checkDebridAccounts: () => Promise<DebridAccountStatus[]>; checkDebridAccounts: () => Promise<DebridAccountStatus[]>;
checkMegaDebridAccount: (login: string, password: string) => Promise<DebridAccountStatus | null>;
retryExtraction: (packageId: string) => Promise<void>; retryExtraction: (packageId: string) => Promise<void>;
extractNow: (packageId: string) => Promise<void>; extractNow: (packageId: string) => Promise<void>;
resetPackage: (packageId: string) => Promise<void>; resetPackage: (packageId: string) => Promise<void>;