import { DragEvent, ReactElement, useEffect, useMemo, useState } from "react"; import type { AppSettings, DebridProvider, DownloadItem, PackageEntry, UiSnapshot, UpdateCheckResult } from "../shared/types"; type Tab = "collector" | "downloads" | "settings"; const emptySnapshot = (): UiSnapshot => ({ settings: { token: "", megaToken: "", megaLogin: "", megaPassword: "", bestToken: "", allDebridToken: "", rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid", providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "", autoExtract: true, extractDir: "", createExtractSubfolder: true, hybridExtract: true, cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false, removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true, autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never", maxParallel: 4, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global", updateRepo: "", autoUpdateCheck: true }, session: { version: 2, packageOrder: [], packages: {}, items: {}, runStartedAt: 0, totalDownloadedBytes: 0, summaryText: "", reconnectUntil: 0, reconnectReason: "", paused: false, running: false, updatedAt: Date.now() }, summary: null, speedText: "Geschwindigkeit: 0 B/s", etaText: "ETA: --", canStart: true, canStop: false, canPause: false }); const cleanupLabels: Record = { never: "Nie", immediate: "Sofort", on_start: "Beim App-Start", package_done: "Sobald Paket fertig ist" }; const providerLabels: Record = { realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid" }; export function App(): ReactElement { const [snapshot, setSnapshot] = useState(emptySnapshot); const [tab, setTab] = useState("collector"); const [linksRaw, setLinksRaw] = useState(""); const [statusToast, setStatusToast] = useState(""); const [settingsDraft, setSettingsDraft] = useState(emptySnapshot().settings); useEffect(() => { let unsubscribe: (() => void) | null = null; void window.rd.getSnapshot().then((state) => { setSnapshot(state); setSettingsDraft(state.settings); if (state.settings.autoUpdateCheck) { void window.rd.checkUpdates().then((result) => { void handleUpdateResult(result, "startup"); }); } }); unsubscribe = window.rd.onStateUpdate((state) => { setSnapshot(state); }); return () => { if (unsubscribe) { unsubscribe(); } }; }, []); const packages = useMemo(() => snapshot.session.packageOrder .map((id: string) => snapshot.session.packages[id]) .filter(Boolean), [snapshot]); const handleUpdateResult = async (result: UpdateCheckResult, source: "manual" | "startup"): Promise => { if (result.error) { if (source === "manual") { setStatusToast(`Update-Check fehlgeschlagen: ${result.error}`); setTimeout(() => setStatusToast(""), 2800); } return; } if (!result.updateAvailable) { if (source === "manual") { setStatusToast(`Kein Update verfügbar (v${result.currentVersion})`); setTimeout(() => setStatusToast(""), 2000); } return; } const approved = window.confirm( `Update verfügbar: ${result.latestTag} (aktuell v${result.currentVersion})\n\nJetzt automatisch herunterladen und installieren?` ); if (!approved) { setStatusToast(`Update verfügbar: ${result.latestTag}`); setTimeout(() => setStatusToast(""), 2600); return; } const install = await window.rd.installUpdate(); if (install.started) { setStatusToast("Updater gestartet - App wird geschlossen"); setTimeout(() => setStatusToast(""), 2600); return; } setStatusToast(`Auto-Update fehlgeschlagen: ${install.message}`); setTimeout(() => setStatusToast(""), 3200); }; const onSaveSettings = async (): Promise => { await window.rd.updateSettings(settingsDraft); setStatusToast("Settings gespeichert"); setTimeout(() => setStatusToast(""), 1800); }; const onCheckUpdates = async (): Promise => { const result = await window.rd.checkUpdates(); await handleUpdateResult(result, "manual"); }; const onAddLinks = async (): Promise => { await window.rd.updateSettings(settingsDraft); const result = await window.rd.addLinks({ rawText: linksRaw, packageName: settingsDraft.packageName }); if (result.addedLinks > 0) { setStatusToast(`${result.addedPackages} Paket(e), ${result.addedLinks} Link(s) hinzugefügt`); setLinksRaw(""); } else { setStatusToast("Keine gültigen Links gefunden"); } setTimeout(() => setStatusToast(""), 2200); }; const onImportDlc = async (): Promise => { const files = await window.rd.pickContainers(); if (files.length === 0) { return; } const result = await window.rd.addContainers(files); setStatusToast(`DLC importiert: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`); setTimeout(() => setStatusToast(""), 2200); }; const onDrop = async (event: DragEvent): Promise => { event.preventDefault(); const files = Array.from(event.dataTransfer.files ?? []) as File[]; const dlc = files .filter((file) => file.name.toLowerCase().endsWith(".dlc")) .map((file) => (file as unknown as { path?: string }).path) .filter((value): value is string => !!value); if (dlc.length === 0) { return; } const result = await window.rd.addContainers(dlc); setStatusToast(`Drag-and-Drop: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`); setTimeout(() => setStatusToast(""), 2200); }; const setBool = (key: keyof AppSettings, value: boolean): void => { setSettingsDraft((prev: AppSettings) => ({ ...prev, [key]: value })); }; const setText = (key: keyof AppSettings, value: string): void => { setSettingsDraft((prev: AppSettings) => ({ ...prev, [key]: value })); }; const setNum = (key: keyof AppSettings, value: number): void => { setSettingsDraft((prev: AppSettings) => ({ ...prev, [key]: value })); }; return (

Debrid Download Manager

Multi-Provider Workflow
{snapshot.speedText}
{snapshot.etaText}
setNum("speedLimitKbps", Number(event.target.value) || 0)} /> KB/s
{tab === "collector" && (

Linksammler