Compare commits

..

No commits in common. "6d6453dc4be4d1ece35128b07ac728fae2390d0f" and "0003d786d85987428b68f75a5ab5c57ba198c786" have entirely different histories.

8 changed files with 97 additions and 167 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.6.91", "version": "1.6.90",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -59,7 +59,6 @@ export function defaultSettings(): AppSettings {
linkSnappyPassword: "", linkSnappyPassword: "",
archivePasswordList: "", archivePasswordList: "",
rememberToken: true, rememberToken: true,
providerOrder: ["realdebrid", "megadebrid-api", "bestdebrid"],
providerPrimary: "realdebrid", providerPrimary: "realdebrid",
providerSecondary: "megadebrid-api", providerSecondary: "megadebrid-api",
providerTertiary: "bestdebrid", providerTertiary: "bestdebrid",

View File

@ -1993,10 +1993,11 @@ export class DebridService {
} }
} }
// Dynamische Reihenfolge: providerOrder hat Vorrang, Fallback auf altes primary/secondary/tertiary const order = toProviderOrder(
const order: DebridProvider[] = (settings.providerOrder && settings.providerOrder.length > 0) settings.providerPrimary,
? uniqueProviderOrder(settings.providerOrder) settings.providerSecondary,
: toProviderOrder(settings.providerPrimary, settings.providerSecondary, settings.providerTertiary); settings.providerTertiary
);
const primary = order[0]; const primary = order[0];
if (!settings.autoProviderFallback) { if (!settings.autoProviderFallback) {

View File

@ -156,41 +156,6 @@ function normalizeHosterRouting(raw: unknown, megaDebridPreferApi: boolean, mega
return result; return result;
} }
function normalizeProviderOrder(
raw: unknown,
megaDebridPreferApi: boolean,
megaDebridApiEnabled: boolean,
megaDebridWebEnabled: boolean,
legacyPrimary: unknown,
legacySecondary: unknown,
legacyTertiary: unknown
): DebridProvider[] {
let list: unknown[] = [];
if (Array.isArray(raw) && raw.length > 0) {
list = raw;
} else {
// Migrate from old primary/secondary/tertiary
const candidates = [legacyPrimary, legacySecondary, legacyTertiary].filter(
(v) => v && String(v).trim() && String(v).trim() !== "none"
);
if (candidates.length > 0) {
list = candidates;
}
}
const seen = new Set<DebridProvider>();
const result: DebridProvider[] = [];
for (const entry of list) {
const provider = normalizeConfiguredProvider(entry, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled);
if (provider && !seen.has(provider)) {
seen.add(provider);
result.push(provider);
}
}
return result;
}
const DEPRECATED_UPDATE_REPOS = new Set([ const DEPRECATED_UPDATE_REPOS = new Set([
"sucukdeluxe/real-debrid-downloader" "sucukdeluxe/real-debrid-downloader"
]); ]);
@ -235,11 +200,6 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
linkSnappyPassword: asText(settings.linkSnappyPassword), linkSnappyPassword: asText(settings.linkSnappyPassword),
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n|\r/g, "\n"), archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n|\r/g, "\n"),
rememberToken: Boolean(settings.rememberToken), rememberToken: Boolean(settings.rememberToken),
providerOrder: normalizeProviderOrder(
settings.providerOrder,
megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled,
settings.providerPrimary, settings.providerSecondary, settings.providerTertiary
),
providerPrimary: normalizeConfiguredProvider(settings.providerPrimary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled) || defaults.providerPrimary, providerPrimary: normalizeConfiguredProvider(settings.providerPrimary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled) || defaults.providerPrimary,
providerSecondary: normalizeFallbackProvider(settings.providerSecondary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled), providerSecondary: normalizeFallbackProvider(settings.providerSecondary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),
providerTertiary: normalizeFallbackProvider(settings.providerTertiary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled), providerTertiary: normalizeFallbackProvider(settings.providerTertiary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),

View File

@ -312,21 +312,24 @@ function getActiveProvidersFromSettings(settings: AppSettings): DebridProvider[]
return getConfiguredProvidersFromSettings(settings).filter((p) => !disabled.has(p)); return getConfiguredProvidersFromSettings(settings).filter((p) => !disabled.has(p));
} }
// Leitet die aktive Provider-Reihenfolge aus providerOrder ab, function normalizeProviderSelectionForSettings(settings: AppSettings): Pick<AppSettings, "providerPrimary" | "providerSecondary" | "providerTertiary"> {
// gefiltert auf tatsächlich konfigurierte und nicht deaktivierte Provider. const configuredProviders = getActiveProvidersFromSettings(settings);
// Direkt-Hoster (onefichier, ddownload) werden ausgeschlossen. const primaryProvider = configuredProviders.includes(settings.providerPrimary)
const DIRECT_HOSTERS: ReadonlySet<DebridProvider> = new Set(["onefichier", "ddownload"]); ? settings.providerPrimary
: (configuredProviders[0] ?? "realdebrid");
function normalizeProviderOrderForSettings(settings: AppSettings): DebridProvider[] { const secondaryChoices = configuredProviders.filter((provider) => provider !== primaryProvider);
const active = new Set(getActiveProvidersFromSettings(settings).filter((p) => !DIRECT_HOSTERS.has(p))); const secondaryProvider = secondaryChoices.includes(settings.providerSecondary as DebridProvider)
// Behalte bestehende Reihenfolge aus providerOrder, filtere nicht-konfigurierte heraus ? settings.providerSecondary
const ordered = (settings.providerOrder || []).filter((p) => active.has(p)); : "none";
const inOrder = new Set(ordered); const tertiaryChoices = configuredProviders.filter((provider) => provider !== primaryProvider && provider !== secondaryProvider);
// Füge neue Provider hinten an, die noch nicht in der Reihenfolge sind const tertiaryProvider = tertiaryChoices.includes(settings.providerTertiary as DebridProvider)
for (const p of active) { ? settings.providerTertiary
if (!inOrder.has(p)) ordered.push(p); : "none";
} return {
return ordered; providerPrimary: primaryProvider,
providerSecondary: configuredProviders.length >= 2 ? secondaryProvider : "none",
providerTertiary: configuredProviders.length >= 3 ? tertiaryProvider : "none"
};
} }
function getConfiguredAccountKind(settings: AppSettings, service: AccountService): AccountKind | null { function getConfiguredAccountKind(settings: AppSettings, service: AccountService): AccountKind | null {
@ -533,10 +536,10 @@ const emptyStats = (): DownloadStats => ({
const emptySnapshot = (): UiSnapshot => ({ const emptySnapshot = (): UiSnapshot => ({
settings: { settings: {
token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridApiEnabled: false, megaDebridWebEnabled: false, megaDebridPreferApi: true, bestToken: "", bestDebridUseWebLogin: false, allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "", debridLinkApiKeys: "", linkSnappyLogin: "", linkSnappyPassword: "", token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridApiEnabled: false, megaDebridWebEnabled: false, megaDebridPreferApi: true, bestToken: "", bestDebridUseWebLogin: false, allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "",
archivePasswordList: "", archivePasswordList: "",
rememberToken: true, providerOrder: [], providerPrimary: "realdebrid", providerSecondary: "none", rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid-api",
providerTertiary: "none", autoProviderFallback: true, outputDir: "", packageName: "", providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
autoExtract: true, autoRename4sf4sj: false, extractDir: "", createExtractSubfolder: true, hybridExtract: true, autoExtract: true, autoRename4sf4sj: false, extractDir: "", createExtractSubfolder: true, hybridExtract: true,
collectMkvToLibrary: false, mkvLibraryDir: "", collectMkvToLibrary: false, mkvLibraryDir: "",
cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false, cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false,
@ -1558,28 +1561,32 @@ export function App(): ReactElement {
[settingsDraft.oneFichierApiKey]); [settingsDraft.oneFichierApiKey]);
const totalConfiguredAccounts = configuredProviders.length + (hasDdownloadAccount ? 1 : 0) + (hasOneFichierAccount ? 1 : 0); const totalConfiguredAccounts = configuredProviders.length + (hasDdownloadAccount ? 1 : 0) + (hasOneFichierAccount ? 1 : 0);
const providerSelection = useMemo(() => normalizeProviderSelectionForSettings(settingsDraft), [settingsDraft]);
const primaryProviderValue: DebridProvider = providerSelection.providerPrimary;
// Dynamische Provider-Reihenfolge (ersetzt altes primary/secondary/tertiary) const secondaryProviderChoices = useMemo(() => (
const activeProviderOrder = useMemo(() => normalizeProviderOrderForSettings(settingsDraft), [settingsDraft]); configuredProviders.filter((provider) => provider !== primaryProviderValue)
), [configuredProviders, primaryProviderValue]);
// Setzt providerOrder + backwards-kompatible Felder synchron const secondaryProviderValue: DebridFallbackProvider = providerSelection.providerSecondary;
const setProviderOrder = useCallback((newOrder: DebridProvider[]) => {
setSettingsDraft((prev) => ({ const tertiaryProviderChoices = useMemo(() => {
...prev, const blocked = new Set<string>([primaryProviderValue]);
providerOrder: newOrder, if (secondaryProviderValue !== "none") {
providerPrimary: newOrder[0] ?? prev.providerPrimary, blocked.add(secondaryProviderValue);
providerSecondary: newOrder[1] ?? "none", }
providerTertiary: newOrder[2] ?? "none" return configuredProviders.filter((provider) => !blocked.has(provider));
})); }, [configuredProviders, primaryProviderValue, secondaryProviderValue]);
}, []);
const tertiaryProviderValue: DebridFallbackProvider = providerSelection.providerTertiary;
const normalizedSettingsDraft: AppSettings = useMemo(() => ({ const normalizedSettingsDraft: AppSettings = useMemo(() => ({
...settingsDraft, ...settingsDraft,
providerOrder: activeProviderOrder, ...providerSelection
providerPrimary: activeProviderOrder[0] ?? settingsDraft.providerPrimary, }), [
providerSecondary: (activeProviderOrder[1] ?? "none") as DebridFallbackProvider, settingsDraft,
providerTertiary: (activeProviderOrder[2] ?? "none") as DebridFallbackProvider providerSelection
}), [settingsDraft, activeProviderOrder]); ]);
const configuredAccounts = useMemo(() => { const configuredAccounts = useMemo(() => {
const entries: ConfiguredAccountEntry[] = []; const entries: ConfiguredAccountEntry[] = [];
@ -3748,46 +3755,37 @@ export function App(): ReactElement {
<div className="settings-section card"> <div className="settings-section card">
<h3>Hoster-Reihenfolge</h3> <h3>Hoster-Reihenfolge</h3>
<div className="hint"> <div className="hint">Debrid-Accounts können hier priorisiert werden. Direkte Host-Accounts wie DDownload und 1Fichier laufen separat.</div>
Lege fest, in welcher Reihenfolge die Debrid-Accounts für Links genutzt werden. {configuredProviders.length === 0 && (
Der erste Eintrag ist der Hauptaccount. Direkt-Hoster (1Fichier, DDownload) laufen separat und erscheinen nicht hier.
</div>
{activeProviderOrder.length === 0 && (
<div className="account-empty-state compact"> <div className="account-empty-state compact">
<strong>Keine Debrid-Reihenfolge verfügbar</strong> <strong>Keine Debrid-Reihenfolge verfuegbar</strong>
<span>Füge mindestens einen Debrid-Account hinzu, dann kannst Du die Reihenfolge festlegen.</span> <span>Füge mindestens einen Debrid-Account hinzu, dann kannst Du Hauptaccount und Alternativen festlegen.</span>
</div> </div>
)} )}
{activeProviderOrder.length > 0 && ( {configuredProviders.length >= 1 && (
<div className="provider-order-list"> <div>
{activeProviderOrder.map((provider, idx) => ( <label>Hauptaccount</label>
<div key={provider} className="provider-order-row"> <select value={primaryProviderValue} onChange={(e) => setText("providerPrimary", e.target.value)}>
<span className="provider-order-num">{idx + 1}.</span> {configuredProviders.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
<span className="provider-order-label">{providerLabelWithMode(provider, settingsDraft)}</span> </select>
<div className="provider-order-actions"> </div>
<button )}
className="btn btn-sm" {configuredProviders.length >= 2 && (
disabled={idx === 0} <div>
onClick={() => { <label>1. Hoster-Alternative</label>
const next = [...activeProviderOrder]; <select value={secondaryProviderValue} onChange={(e) => setText("providerSecondary", e.target.value)}>
[next[idx - 1], next[idx]] = [next[idx], next[idx - 1]]; <option value="none">Keine Alternative</option>
setProviderOrder(next); {secondaryProviderChoices.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
}} </select>
title="Nach oben" </div>
></button> )}
<button {configuredProviders.length >= 3 && (
className="btn btn-sm" <div>
disabled={idx === activeProviderOrder.length - 1} <label>2. Hoster-Alternative</label>
onClick={() => { <select value={tertiaryProviderValue} onChange={(e) => setText("providerTertiary", e.target.value)}>
const next = [...activeProviderOrder]; <option value="none">Keine Alternative</option>
[next[idx + 1], next[idx]] = [next[idx], next[idx + 1]]; {tertiaryProviderChoices.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
setProviderOrder(next); </select>
}}
title="Nach unten"
></button>
</div>
</div>
))}
</div> </div>
)} )}
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoProviderFallback} onChange={(e) => setBool("autoProviderFallback", e.target.checked)} /> Bei Fehlern oder Fair-Use automatisch zum nächsten Provider wechseln</label> <label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoProviderFallback} onChange={(e) => setBool("autoProviderFallback", e.target.checked)} /> Bei Fehlern oder Fair-Use automatisch zum nächsten Provider wechseln</label>
@ -3970,6 +3968,27 @@ export function App(): ReactElement {
<input type="password" value={settingsDraft.ddownloadPassword || ""} onChange={(e) => setText("ddownloadPassword", e.target.value)} /> <input type="password" value={settingsDraft.ddownloadPassword || ""} onChange={(e) => setText("ddownloadPassword", e.target.value)} />
<label>1Fichier API Key</label> <label>1Fichier API Key</label>
<input type="password" value={settingsDraft.oneFichierApiKey || ""} onChange={(e) => setText("oneFichierApiKey", e.target.value)} /> <input type="password" value={settingsDraft.oneFichierApiKey || ""} onChange={(e) => setText("oneFichierApiKey", e.target.value)} />
{configuredProviders.length === 0 && (
<div className="hint">Füge mindestens einen Account hinzu, dann erscheint die Hoster-Auswahl.</div>
)}
{configuredProviders.length >= 1 && (
<div><label>Hauptaccount</label><select value={primaryProviderValue} onChange={(e) => setText("providerPrimary", e.target.value)}>
{configuredProviders.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
</select></div>
)}
{configuredProviders.length >= 2 && (
<div><label>1. Hoster-Alternative</label><select value={secondaryProviderValue} onChange={(e) => setText("providerSecondary", e.target.value)}>
<option value="none">Keine Alternative</option>
{secondaryProviderChoices.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
</select></div>
)}
{configuredProviders.length >= 3 && (
<div><label>2. Hoster-Alternative</label><select value={tertiaryProviderValue} onChange={(e) => setText("providerTertiary", e.target.value)}>
<option value="none">Keine Alternative</option>
{tertiaryProviderChoices.map((provider) => (<option key={provider} value={provider}>{providerLabelWithMode(provider, settingsDraft)}</option>))}
</select></div>
)}
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoProviderFallback} onChange={(e) => setBool("autoProviderFallback", e.target.checked)} /> Bei Fehler/Fair-Use automatisch zum nächsten Provider wechseln</label>
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.rememberToken} onChange={(e) => setBool("rememberToken", e.target.checked)} /> Zugangsdaten lokal speichern</label> <label className="toggle-line"><input type="checkbox" checked={settingsDraft.rememberToken} onChange={(e) => setBool("rememberToken", e.target.checked)} /> Zugangsdaten lokal speichern</label>
</div> </div>
</div> </div>

View File

@ -1368,47 +1368,6 @@ body,
max-width: 260px; max-width: 260px;
} }
.provider-order-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
}
.provider-order-row {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--border);
}
.provider-order-num {
font-size: 0.8rem;
color: var(--muted);
min-width: 18px;
text-align: right;
}
.provider-order-label {
flex: 1;
font-size: 0.9rem;
font-weight: 500;
}
.provider-order-actions {
display: flex;
gap: 4px;
}
.provider-order-actions .btn-sm {
padding: 2px 8px;
font-size: 0.9rem;
line-height: 1.4;
}
.account-modal { .account-modal {
width: min(960px, calc(100vw - 36px)); width: min(960px, calc(100vw - 36px));
max-height: calc(100vh - 36px); max-height: calc(100vh - 36px);

View File

@ -66,7 +66,6 @@ export interface AppSettings {
linkSnappyPassword: string; linkSnappyPassword: string;
archivePasswordList: string; archivePasswordList: string;
rememberToken: boolean; rememberToken: boolean;
providerOrder: DebridProvider[];
providerPrimary: DebridProvider; providerPrimary: DebridProvider;
providerSecondary: DebridFallbackProvider; providerSecondary: DebridFallbackProvider;
providerTertiary: DebridFallbackProvider; providerTertiary: DebridFallbackProvider;

View File

@ -17,7 +17,6 @@ describe("debrid service", () => {
megaLogin: "user", megaLogin: "user",
megaPassword: "pass", megaPassword: "pass",
bestToken: "", bestToken: "",
providerOrder: [] as const,
providerPrimary: "realdebrid" as const, providerPrimary: "realdebrid" as const,
providerSecondary: "megadebrid" as const, providerSecondary: "megadebrid" as const,
providerTertiary: "bestdebrid" as const, providerTertiary: "bestdebrid" as const,
@ -183,7 +182,6 @@ describe("debrid service", () => {
const settings = { const settings = {
...defaultSettings(), ...defaultSettings(),
allDebridToken: "ad-token", allDebridToken: "ad-token",
providerOrder: [] as const,
providerPrimary: "alldebrid" as const, providerPrimary: "alldebrid" as const,
providerSecondary: "none" as const, providerSecondary: "none" as const,
providerTertiary: "none" as const, providerTertiary: "none" as const,
@ -214,7 +212,6 @@ describe("debrid service", () => {
token: "", token: "",
bestToken: "", bestToken: "",
allDebridToken: "ad-token", allDebridToken: "ad-token",
providerOrder: [] as const,
providerPrimary: "alldebrid" as const, providerPrimary: "alldebrid" as const,
providerSecondary: "realdebrid" as const, providerSecondary: "realdebrid" as const,
providerTertiary: "megadebrid" as const, providerTertiary: "megadebrid" as const,
@ -288,7 +285,6 @@ describe("debrid service", () => {
...defaultSettings(), ...defaultSettings(),
allDebridToken: "ad-token", allDebridToken: "ad-token",
allDebridUseWebLogin: true, allDebridUseWebLogin: true,
providerOrder: [] as const,
providerPrimary: "alldebrid" as const, providerPrimary: "alldebrid" as const,
providerSecondary: "none" as const, providerSecondary: "none" as const,
providerTertiary: "none" as const, providerTertiary: "none" as const,
@ -424,7 +420,6 @@ describe("debrid service", () => {
allDebridToken: "", allDebridToken: "",
megaLogin: "user", megaLogin: "user",
megaPassword: "pass", megaPassword: "pass",
providerOrder: [] as const,
providerPrimary: "megadebrid" as const, providerPrimary: "megadebrid" as const,
providerSecondary: "megadebrid" as const, providerSecondary: "megadebrid" as const,
providerTertiary: "megadebrid" as const, providerTertiary: "megadebrid" as const,
@ -489,7 +484,6 @@ describe("debrid service", () => {
megaPassword: "pass", megaPassword: "pass",
megaDebridApiEnabled: true, megaDebridApiEnabled: true,
megaDebridWebEnabled: true, megaDebridWebEnabled: true,
providerOrder: [] as const,
providerPrimary: "megadebrid-api" as const, providerPrimary: "megadebrid-api" as const,
providerSecondary: "megadebrid-web" as const, providerSecondary: "megadebrid-web" as const,
providerTertiary: "none" as const, providerTertiary: "none" as const,
@ -520,7 +514,6 @@ describe("debrid service", () => {
allDebridToken: "", allDebridToken: "",
megaLogin: "user", megaLogin: "user",
megaPassword: "pass", megaPassword: "pass",
providerOrder: [] as const,
providerPrimary: "megadebrid" as const, providerPrimary: "megadebrid" as const,
providerSecondary: "none" as const, providerSecondary: "none" as const,
providerTertiary: "none" as const, providerTertiary: "none" as const,