Compare commits
2 Commits
9f94404435
...
977a5c4175
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
977a5c4175 | ||
|
|
c811649b9d |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.85",
|
"version": "1.6.86",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -98,6 +98,7 @@ export function defaultSettings(): AppSettings {
|
|||||||
columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"],
|
columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"],
|
||||||
extractCpuPriority: "high",
|
extractCpuPriority: "high",
|
||||||
autoExtractWhenStopped: true,
|
autoExtractWhenStopped: true,
|
||||||
disabledProviders: []
|
disabledProviders: [],
|
||||||
|
hosterRouting: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,16 @@ const PROVIDER_LABELS: Record<DebridProvider, string> = {
|
|||||||
linksnappy: "LinkSnappy"
|
linksnappy: "LinkSnappy"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function extractHosterFromUrl(url: string): string {
|
||||||
|
try {
|
||||||
|
const host = new URL(url).hostname.replace(/^www\./, "").toLowerCase();
|
||||||
|
const parts = host.split(".");
|
||||||
|
return parts.length >= 2 ? parts[parts.length - 2] : host;
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface ProviderUnrestrictedLink extends UnrestrictedLink {
|
interface ProviderUnrestrictedLink extends UnrestrictedLink {
|
||||||
provider: DebridProvider;
|
provider: DebridProvider;
|
||||||
providerLabel: string;
|
providerLabel: string;
|
||||||
@ -1875,6 +1885,42 @@ export class DebridService {
|
|||||||
public async unrestrictLink(link: string, signal?: AbortSignal, settingsSnapshot?: AppSettings): Promise<ProviderUnrestrictedLink> {
|
public async unrestrictLink(link: string, signal?: AbortSignal, settingsSnapshot?: AppSettings): Promise<ProviderUnrestrictedLink> {
|
||||||
const settings = settingsSnapshot ? cloneSettings(settingsSnapshot) : cloneSettings(this.settings);
|
const settings = settingsSnapshot ? cloneSettings(settingsSnapshot) : cloneSettings(this.settings);
|
||||||
|
|
||||||
|
// Hoster-Zuordnung: prüfe ob für diesen Hoster ein bestimmter Provider konfiguriert ist
|
||||||
|
const routing = settings.hosterRouting || {};
|
||||||
|
const hosterKey = extractHosterFromUrl(link);
|
||||||
|
if (hosterKey && routing[hosterKey]) {
|
||||||
|
const routedProvider = routing[hosterKey];
|
||||||
|
if (this.isProviderConfiguredFor(settings, routedProvider)) {
|
||||||
|
logger.info(`Hoster-Zuordnung: ${hosterKey} → ${PROVIDER_LABELS[routedProvider]}`);
|
||||||
|
try {
|
||||||
|
const result = await this.unrestrictViaProvider(settings, routedProvider, link, signal);
|
||||||
|
let fileName = result.fileName;
|
||||||
|
if (isRapidgatorLink(link) && looksLikeOpaqueFilename(fileName || filenameFromUrl(link))) {
|
||||||
|
const fromPage = await resolveRapidgatorFilename(link, signal);
|
||||||
|
if (fromPage) fileName = fromPage;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
fileName,
|
||||||
|
provider: routedProvider,
|
||||||
|
providerLabel: PROVIDER_LABELS[routedProvider] + (result.sourceLabel ? ` (${result.sourceLabel})` : "")
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const errorText = compactErrorText(error);
|
||||||
|
if (signal?.aborted || (/aborted/i.test(errorText) && !/timeout/i.test(errorText))) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (!settings.autoProviderFallback) {
|
||||||
|
throw new Error(`Hoster-Zuordnung fehlgeschlagen (${hosterKey} → ${PROVIDER_LABELS[routedProvider]}): ${errorText}`);
|
||||||
|
}
|
||||||
|
logger.warn(`Hoster-Zuordnung ${hosterKey} → ${PROVIDER_LABELS[routedProvider]} fehlgeschlagen, Fallback auf Provider-Kette: ${errorText}`);
|
||||||
|
// Fall through to normal provider chain
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(`Hoster-Zuordnung ${hosterKey} → ${PROVIDER_LABELS[routedProvider]} übersprungen (Provider nicht konfiguriert/deaktiviert)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1Fichier is a direct file hoster. If the link is a 1fichier.com URL
|
// 1Fichier is a direct file hoster. If the link is a 1fichier.com URL
|
||||||
// and the API key is configured, use 1Fichier directly before debrid providers.
|
// and the API key is configured, use 1Fichier directly before debrid providers.
|
||||||
if (ONEFICHIER_URL_RE.test(link) && this.isProviderConfiguredFor(settings, "onefichier")) {
|
if (ONEFICHIER_URL_RE.test(link) && this.isProviderConfiguredFor(settings, "onefichier")) {
|
||||||
|
|||||||
@ -91,6 +91,19 @@ function normalizeColumnOrder(raw: unknown): string[] {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHosterRouting(raw: unknown): Record<string, DebridProvider> {
|
||||||
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
||||||
|
const result: Record<string, DebridProvider> = {};
|
||||||
|
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
||||||
|
const hoster = String(key).trim().toLowerCase();
|
||||||
|
const provider = String(value ?? "").trim();
|
||||||
|
if (hoster && VALID_PRIMARY_PROVIDERS.has(provider)) {
|
||||||
|
result[hoster] = provider as DebridProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const DEPRECATED_UPDATE_REPOS = new Set([
|
const DEPRECATED_UPDATE_REPOS = new Set([
|
||||||
"sucukdeluxe/real-debrid-downloader"
|
"sucukdeluxe/real-debrid-downloader"
|
||||||
]);
|
]);
|
||||||
@ -164,7 +177,8 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
|||||||
columnOrder: normalizeColumnOrder(settings.columnOrder),
|
columnOrder: normalizeColumnOrder(settings.columnOrder),
|
||||||
extractCpuPriority: settings.extractCpuPriority,
|
extractCpuPriority: settings.extractCpuPriority,
|
||||||
autoExtractWhenStopped: settings.autoExtractWhenStopped !== undefined ? Boolean(settings.autoExtractWhenStopped) : defaults.autoExtractWhenStopped,
|
autoExtractWhenStopped: settings.autoExtractWhenStopped !== undefined ? Boolean(settings.autoExtractWhenStopped) : defaults.autoExtractWhenStopped,
|
||||||
disabledProviders: Array.isArray(settings.disabledProviders) ? settings.disabledProviders.filter((p: unknown) => VALID_PRIMARY_PROVIDERS.has(p as string)) as DebridProvider[] : []
|
disabledProviders: Array.isArray(settings.disabledProviders) ? settings.disabledProviders.filter((p: unknown) => VALID_PRIMARY_PROVIDERS.has(p as string)) as DebridProvider[] : [],
|
||||||
|
hosterRouting: normalizeHosterRouting(settings.hosterRouting)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
||||||
|
|||||||
@ -536,7 +536,9 @@ const emptySnapshot = (): UiSnapshot => ({
|
|||||||
theme: "dark", collapseNewPackages: true, autoSkipExtracted: false, confirmDeleteSelection: true,
|
theme: "dark", collapseNewPackages: true, autoSkipExtracted: false, confirmDeleteSelection: true,
|
||||||
bandwidthSchedules: [], totalDownloadedAllTime: 0,
|
bandwidthSchedules: [], totalDownloadedAllTime: 0,
|
||||||
columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"],
|
columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"],
|
||||||
autoExtractWhenStopped: true
|
autoExtractWhenStopped: true,
|
||||||
|
disabledProviders: [],
|
||||||
|
hosterRouting: {}
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
version: 2, packageOrder: [], packages: {}, items: {}, runStartedAt: 0,
|
version: 2, packageOrder: [], packages: {}, items: {}, runStartedAt: 0,
|
||||||
@ -557,6 +559,36 @@ const providerLabels: Record<DebridProvider, string> = {
|
|||||||
realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid", ddownload: "DDownload", onefichier: "1Fichier", debridlink: "Debrid-Link", linksnappy: "LinkSnappy"
|
realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid", ddownload: "DDownload", onefichier: "1Fichier", debridlink: "Debrid-Link", linksnappy: "LinkSnappy"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const KNOWN_HOSTERS: { id: string; label: string }[] = [
|
||||||
|
{ id: "rapidgator", label: "Rapidgator" },
|
||||||
|
{ id: "uploaded", label: "Uploaded" },
|
||||||
|
{ id: "1fichier", label: "1Fichier" },
|
||||||
|
{ id: "ddownload", label: "DDownload" },
|
||||||
|
{ id: "ddl", label: "DDL.to" },
|
||||||
|
{ id: "turbobit", label: "Turbobit" },
|
||||||
|
{ id: "nitroflare", label: "Nitroflare" },
|
||||||
|
{ id: "filefactory", label: "FileFactory" },
|
||||||
|
{ id: "katfile", label: "Katfile" },
|
||||||
|
{ id: "hitfile", label: "Hitfile" },
|
||||||
|
{ id: "alfafile", label: "Alfafile" },
|
||||||
|
{ id: "k2s", label: "Keep2Share" },
|
||||||
|
{ id: "keep2share", label: "Keep2Share (alt)" },
|
||||||
|
{ id: "tezfiles", label: "Tezfiles" },
|
||||||
|
{ id: "fileboom", label: "Fileboom" },
|
||||||
|
{ id: "mexashare", label: "Mexashare" },
|
||||||
|
{ id: "wdupload", label: "WDUpload" },
|
||||||
|
{ id: "rosefile", label: "Rosefile" },
|
||||||
|
{ id: "filejoker", label: "FileJoker" },
|
||||||
|
{ id: "worldbytez", label: "Worldbytez" },
|
||||||
|
{ id: "fileland", label: "Fileland" },
|
||||||
|
{ id: "depositfiles", label: "DepositFiles" },
|
||||||
|
{ id: "mediafire", label: "MediaFire" },
|
||||||
|
{ id: "mega", label: "Mega.nz" },
|
||||||
|
{ id: "frdl", label: "FreeDownload" },
|
||||||
|
{ id: "hexupload", label: "HexUpload" },
|
||||||
|
{ id: "isra", label: "Isra.cloud" }
|
||||||
|
];
|
||||||
|
|
||||||
function providerLabelWithMode(provider: DebridProvider, settings: AppSettings): string {
|
function providerLabelWithMode(provider: DebridProvider, settings: AppSettings): string {
|
||||||
const base = providerLabels[provider];
|
const base = providerLabels[provider];
|
||||||
const kind = getConfiguredAccountKind(settings, provider);
|
const kind = getConfiguredAccountKind(settings, provider);
|
||||||
@ -3716,6 +3748,92 @@ export function App(): ReactElement {
|
|||||||
<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>
|
||||||
|
|
||||||
|
{configuredProviders.length >= 1 && (
|
||||||
|
<div className="settings-section card">
|
||||||
|
<h3>Hoster-Zuordnung</h3>
|
||||||
|
<div className="hint">Lege fest, welcher Debrid-Provider sich um welchen Filehoster kümmert. Nicht zugeordnete Hoster nutzen die Standard-Reihenfolge oben.</div>
|
||||||
|
{(() => {
|
||||||
|
const routing: Record<string, DebridProvider> = settingsDraft.hosterRouting || {};
|
||||||
|
const routingEntries = Object.entries(routing).sort(([a], [b]) => a.localeCompare(b));
|
||||||
|
const usedHosters = new Set(routingEntries.map(([h]) => h));
|
||||||
|
const availableHosters = KNOWN_HOSTERS.filter((h) => !usedHosters.has(h.id));
|
||||||
|
|
||||||
|
const setRouting = (newRouting: Record<string, DebridProvider>) => {
|
||||||
|
setSettingsDraft((prev) => ({ ...prev, hosterRouting: newRouting }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addEntry = (hosterId: string) => {
|
||||||
|
if (!hosterId || routing[hosterId]) return;
|
||||||
|
setRouting({ ...routing, [hosterId]: configuredProviders[0] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeEntry = (hosterId: string) => {
|
||||||
|
const copy = { ...routing };
|
||||||
|
delete copy[hosterId];
|
||||||
|
setRouting(copy);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeProvider = (hosterId: string, provider: DebridProvider) => {
|
||||||
|
setRouting({ ...routing, [hosterId]: provider });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{routingEntries.length > 0 && (
|
||||||
|
<div className="hoster-routing-table">
|
||||||
|
<div className="hoster-routing-header">
|
||||||
|
<span>Filehoster</span>
|
||||||
|
<span>Zuständiger Provider</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
{routingEntries.map(([hosterId, provider]) => {
|
||||||
|
const hosterLabel = KNOWN_HOSTERS.find((h) => h.id === hosterId)?.label || hosterId;
|
||||||
|
return (
|
||||||
|
<div key={hosterId} className="hoster-routing-row">
|
||||||
|
<span className="hoster-routing-label">{hosterLabel}</span>
|
||||||
|
<select value={provider} onChange={(e) => changeProvider(hosterId, e.target.value as DebridProvider)}>
|
||||||
|
{configuredProviders.map((p) => (
|
||||||
|
<option key={p} value={p}>{providerLabelWithMode(p, settingsDraft)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<button className="btn btn-sm btn-danger" onClick={() => removeEntry(hosterId)} title="Zuordnung entfernen">×</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{routingEntries.length === 0 && (
|
||||||
|
<div className="hint" style={{ fontStyle: "italic", opacity: 0.7 }}>Noch keine Zuordnungen. Alle Hoster nutzen die Standard-Reihenfolge.</div>
|
||||||
|
)}
|
||||||
|
<div className="hoster-routing-add">
|
||||||
|
<select
|
||||||
|
value=""
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
if (val === "__custom") {
|
||||||
|
const name = window.prompt("Hoster-Domain eingeben (z.B. rapidgator, turbobit):");
|
||||||
|
const clean = (name || "").trim().toLowerCase().replace(/^www\./, "").split(".")[0];
|
||||||
|
if (clean) addEntry(clean);
|
||||||
|
} else {
|
||||||
|
addEntry(val);
|
||||||
|
}
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="" disabled>Hoster hinzufügen...</option>
|
||||||
|
{availableHosters.map((h) => (
|
||||||
|
<option key={h.id} value={h.id}>{h.label}</option>
|
||||||
|
))}
|
||||||
|
<option value="" disabled>───────────</option>
|
||||||
|
<option value="__custom">Eigener Hoster...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div hidden>
|
<div hidden>
|
||||||
<div className="settings-section card">
|
<div className="settings-section card">
|
||||||
<h3>Accounts</h3>
|
<h3>Accounts</h3>
|
||||||
|
|||||||
@ -1305,6 +1305,69 @@ body,
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hoster-routing-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 40px;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: color-mix(in srgb, var(--card) 80%, var(--surface));
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--muted);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 40px;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-label {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-row select {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-row .btn-sm {
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
min-width: 28px;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-add {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoster-routing-add select {
|
||||||
|
max-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
.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);
|
||||||
|
|||||||
@ -96,6 +96,7 @@ export interface AppSettings {
|
|||||||
extractCpuPriority: ExtractCpuPriority;
|
extractCpuPriority: ExtractCpuPriority;
|
||||||
autoExtractWhenStopped: boolean;
|
autoExtractWhenStopped: boolean;
|
||||||
disabledProviders: DebridProvider[];
|
disabledProviders: DebridProvider[];
|
||||||
|
hosterRouting: Record<string, DebridProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadItem {
|
export interface DownloadItem {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user