Compare commits
No commits in common. "284c5e7aa69f4ff9612cde42ab8e455fdfdca166" and "479c7a3f3f2461a6b3a26b77213db487acbc58db" have entirely different histories.
284c5e7aa6
...
479c7a3f3f
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.35",
|
"version": "1.6.34",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -104,7 +104,6 @@ export class AppController {
|
|||||||
|| (settings.megaLogin.trim() && settings.megaPassword.trim())
|
|| (settings.megaLogin.trim() && settings.megaPassword.trim())
|
||||||
|| settings.bestToken.trim()
|
|| settings.bestToken.trim()
|
||||||
|| settings.allDebridToken.trim()
|
|| settings.allDebridToken.trim()
|
||||||
|| (settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +284,7 @@ export class AppController {
|
|||||||
|
|
||||||
public exportBackup(): string {
|
public exportBackup(): string {
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
const SENSITIVE_KEYS: (keyof AppSettings)[] = ["token", "megaPassword", "bestToken", "allDebridToken", "ddownloadPassword"];
|
const SENSITIVE_KEYS: (keyof AppSettings)[] = ["token", "megaPassword", "bestToken", "allDebridToken"];
|
||||||
for (const key of SENSITIVE_KEYS) {
|
for (const key of SENSITIVE_KEYS) {
|
||||||
const val = settings[key];
|
const val = settings[key];
|
||||||
if (typeof val === "string" && val.length > 0) {
|
if (typeof val === "string" && val.length > 0) {
|
||||||
@ -307,7 +306,7 @@ export class AppController {
|
|||||||
return { restored: false, message: "Kein gültiges Backup (settings/session fehlen)" };
|
return { restored: false, message: "Kein gültiges Backup (settings/session fehlen)" };
|
||||||
}
|
}
|
||||||
const importedSettings = parsed.settings as AppSettings;
|
const importedSettings = parsed.settings as AppSettings;
|
||||||
const SENSITIVE_KEYS: (keyof AppSettings)[] = ["token", "megaPassword", "bestToken", "allDebridToken", "ddownloadPassword"];
|
const SENSITIVE_KEYS: (keyof AppSettings)[] = ["token", "megaPassword", "bestToken", "allDebridToken"];
|
||||||
for (const key of SENSITIVE_KEYS) {
|
for (const key of SENSITIVE_KEYS) {
|
||||||
const val = (importedSettings as Record<string, unknown>)[key];
|
const val = (importedSettings as Record<string, unknown>)[key];
|
||||||
if (typeof val === "string" && val.startsWith("***")) {
|
if (typeof val === "string" && val.startsWith("***")) {
|
||||||
|
|||||||
@ -45,8 +45,6 @@ export function defaultSettings(): AppSettings {
|
|||||||
megaPassword: "",
|
megaPassword: "",
|
||||||
bestToken: "",
|
bestToken: "",
|
||||||
allDebridToken: "",
|
allDebridToken: "",
|
||||||
ddownloadLogin: "",
|
|
||||||
ddownloadPassword: "",
|
|
||||||
archivePasswordList: "",
|
archivePasswordList: "",
|
||||||
rememberToken: true,
|
rememberToken: true,
|
||||||
providerPrimary: "realdebrid",
|
providerPrimary: "realdebrid",
|
||||||
|
|||||||
@ -15,8 +15,7 @@ const PROVIDER_LABELS: Record<DebridProvider, string> = {
|
|||||||
realdebrid: "Real-Debrid",
|
realdebrid: "Real-Debrid",
|
||||||
megadebrid: "Mega-Debrid",
|
megadebrid: "Mega-Debrid",
|
||||||
bestdebrid: "BestDebrid",
|
bestdebrid: "BestDebrid",
|
||||||
alldebrid: "AllDebrid",
|
alldebrid: "AllDebrid"
|
||||||
ddownload: "DDownload"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ProviderUnrestrictedLink extends UnrestrictedLink {
|
interface ProviderUnrestrictedLink extends UnrestrictedLink {
|
||||||
@ -959,193 +958,6 @@ class AllDebridClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DDOWNLOAD_URL_RE = /^https?:\/\/(?:www\.)?(?:ddownload\.com|ddl\.to)\/([a-z0-9]+)/i;
|
|
||||||
const DDOWNLOAD_WEB_BASE = "https://ddownload.com";
|
|
||||||
const DDOWNLOAD_WEB_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36";
|
|
||||||
|
|
||||||
class DdownloadClient {
|
|
||||||
private login: string;
|
|
||||||
private password: string;
|
|
||||||
private cookies: string = "";
|
|
||||||
|
|
||||||
public constructor(login: string, password: string) {
|
|
||||||
this.login = login;
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async webLogin(signal?: AbortSignal): Promise<void> {
|
|
||||||
// Step 1: GET login page to extract form token
|
|
||||||
const loginPageRes = await fetch(`${DDOWNLOAD_WEB_BASE}/login.html`, {
|
|
||||||
headers: { "User-Agent": DDOWNLOAD_WEB_UA },
|
|
||||||
redirect: "manual",
|
|
||||||
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
|
||||||
});
|
|
||||||
const loginPageHtml = await loginPageRes.text();
|
|
||||||
const tokenMatch = loginPageHtml.match(/name="token" value="([^"]+)"/);
|
|
||||||
const pageCookies = (loginPageRes.headers.getSetCookie?.() || []).map((c: string) => c.split(";")[0]).join("; ");
|
|
||||||
|
|
||||||
// Step 2: POST login
|
|
||||||
const body = new URLSearchParams({
|
|
||||||
op: "login",
|
|
||||||
token: tokenMatch?.[1] || "",
|
|
||||||
rand: "",
|
|
||||||
redirect: "",
|
|
||||||
login: this.login,
|
|
||||||
password: this.password
|
|
||||||
});
|
|
||||||
const loginRes = await fetch(`${DDOWNLOAD_WEB_BASE}/`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"User-Agent": DDOWNLOAD_WEB_UA,
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
...(pageCookies ? { Cookie: pageCookies } : {})
|
|
||||||
},
|
|
||||||
body: body.toString(),
|
|
||||||
redirect: "manual",
|
|
||||||
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drain body
|
|
||||||
try { await loginRes.text(); } catch { /* ignore */ }
|
|
||||||
|
|
||||||
const setCookies = loginRes.headers.getSetCookie?.() || [];
|
|
||||||
const xfss = setCookies.find((c: string) => c.startsWith("xfss="));
|
|
||||||
const loginCookie = setCookies.find((c: string) => c.startsWith("login="));
|
|
||||||
if (!xfss) {
|
|
||||||
throw new Error("DDownload Login fehlgeschlagen (kein Session-Cookie)");
|
|
||||||
}
|
|
||||||
this.cookies = [loginCookie, xfss].filter(Boolean).map((c: string) => c.split(";")[0]).join("; ");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async unrestrictLink(link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
|
|
||||||
const match = link.match(DDOWNLOAD_URL_RE);
|
|
||||||
if (!match) {
|
|
||||||
throw new Error("Kein DDownload-Link");
|
|
||||||
}
|
|
||||||
const fileCode = match[1];
|
|
||||||
let lastError = "";
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= REQUEST_RETRIES; attempt += 1) {
|
|
||||||
try {
|
|
||||||
if (signal?.aborted) throw new Error("aborted:debrid");
|
|
||||||
|
|
||||||
// Login if no session yet
|
|
||||||
if (!this.cookies) {
|
|
||||||
await this.webLogin(signal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: GET file page to extract form fields
|
|
||||||
const filePageRes = await fetch(`${DDOWNLOAD_WEB_BASE}/${fileCode}`, {
|
|
||||||
headers: {
|
|
||||||
"User-Agent": DDOWNLOAD_WEB_UA,
|
|
||||||
Cookie: this.cookies
|
|
||||||
},
|
|
||||||
redirect: "manual",
|
|
||||||
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Premium with direct downloads enabled → redirect immediately
|
|
||||||
if (filePageRes.status >= 300 && filePageRes.status < 400) {
|
|
||||||
const directUrl = filePageRes.headers.get("location") || "";
|
|
||||||
try { await filePageRes.text(); } catch { /* drain */ }
|
|
||||||
if (directUrl) {
|
|
||||||
return {
|
|
||||||
fileName: filenameFromUrl(directUrl) || filenameFromUrl(link),
|
|
||||||
directUrl,
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: attempt - 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = await filePageRes.text();
|
|
||||||
|
|
||||||
// Check for file not found
|
|
||||||
if (/File Not Found|file was removed|file was banned/i.test(html)) {
|
|
||||||
throw new Error("DDownload: Datei nicht gefunden");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract form fields
|
|
||||||
const idVal = html.match(/name="id" value="([^"]+)"/)?.[1] || fileCode;
|
|
||||||
const randVal = html.match(/name="rand" value="([^"]+)"/)?.[1] || "";
|
|
||||||
const fileNameMatch = html.match(/class="file-info-name"[^>]*>([^<]+)</);
|
|
||||||
const fileName = fileNameMatch?.[1]?.trim() || filenameFromUrl(link);
|
|
||||||
|
|
||||||
// Step 2: POST download2 for premium download
|
|
||||||
const dlBody = new URLSearchParams({
|
|
||||||
op: "download2",
|
|
||||||
id: idVal,
|
|
||||||
rand: randVal,
|
|
||||||
referer: "",
|
|
||||||
method_premium: "1",
|
|
||||||
adblock_detected: "0"
|
|
||||||
});
|
|
||||||
|
|
||||||
const dlRes = await fetch(`${DDOWNLOAD_WEB_BASE}/${fileCode}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"User-Agent": DDOWNLOAD_WEB_UA,
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
Cookie: this.cookies,
|
|
||||||
Referer: `${DDOWNLOAD_WEB_BASE}/${fileCode}`
|
|
||||||
},
|
|
||||||
body: dlBody.toString(),
|
|
||||||
redirect: "manual",
|
|
||||||
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dlRes.status >= 300 && dlRes.status < 400) {
|
|
||||||
const directUrl = dlRes.headers.get("location") || "";
|
|
||||||
try { await dlRes.text(); } catch { /* drain */ }
|
|
||||||
if (directUrl) {
|
|
||||||
return {
|
|
||||||
fileName: fileName || filenameFromUrl(directUrl),
|
|
||||||
directUrl,
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: attempt - 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dlHtml = await dlRes.text();
|
|
||||||
// Try to find direct URL in response HTML
|
|
||||||
const directMatch = dlHtml.match(/https?:\/\/[a-z0-9]+\.(?:dstorage\.org|ddownload\.com|ddl\.to|ucdn\.to)[^\s"'<>]+/i);
|
|
||||||
if (directMatch) {
|
|
||||||
return {
|
|
||||||
fileName,
|
|
||||||
directUrl: directMatch[0],
|
|
||||||
fileSize: null,
|
|
||||||
retriesUsed: attempt - 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for error messages
|
|
||||||
const errMatch = dlHtml.match(/class="err"[^>]*>([^<]+)</i);
|
|
||||||
if (errMatch) {
|
|
||||||
throw new Error(`DDownload: ${errMatch[1].trim()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("DDownload: Kein Download-Link erhalten");
|
|
||||||
} catch (error) {
|
|
||||||
lastError = compactErrorText(error);
|
|
||||||
if (signal?.aborted || (/aborted/i.test(lastError) && !/timeout/i.test(lastError))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Re-login on auth errors
|
|
||||||
if (/login|session|cookie/i.test(lastError)) {
|
|
||||||
this.cookies = "";
|
|
||||||
}
|
|
||||||
if (attempt >= REQUEST_RETRIES || !isRetryableErrorText(lastError)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await sleepWithSignal(retryDelay(attempt), signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(String(lastError || "DDownload Unrestrict fehlgeschlagen").replace(/^Error:\s*/i, ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DebridService {
|
export class DebridService {
|
||||||
private settings: AppSettings;
|
private settings: AppSettings;
|
||||||
|
|
||||||
@ -1297,9 +1109,6 @@ export class DebridService {
|
|||||||
if (provider === "alldebrid") {
|
if (provider === "alldebrid") {
|
||||||
return Boolean(settings.allDebridToken.trim());
|
return Boolean(settings.allDebridToken.trim());
|
||||||
}
|
}
|
||||||
if (provider === "ddownload") {
|
|
||||||
return Boolean(settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim());
|
|
||||||
}
|
|
||||||
return Boolean(settings.bestToken.trim());
|
return Boolean(settings.bestToken.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,9 +1122,6 @@ export class DebridService {
|
|||||||
if (provider === "alldebrid") {
|
if (provider === "alldebrid") {
|
||||||
return new AllDebridClient(settings.allDebridToken).unrestrictLink(link, signal);
|
return new AllDebridClient(settings.allDebridToken).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
if (provider === "ddownload") {
|
|
||||||
return new DdownloadClient(settings.ddownloadLogin, settings.ddownloadPassword).unrestrictLink(link, signal);
|
|
||||||
}
|
|
||||||
return new BestDebridClient(settings.bestToken).unrestrictLink(link, signal);
|
return new BestDebridClient(settings.bestToken).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6439,8 +6439,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`);
|
logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`);
|
||||||
if (result.extracted > 0) {
|
if (result.extracted > 0) {
|
||||||
pkg.postProcessLabel = "Renaming...";
|
|
||||||
this.emitState();
|
|
||||||
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
||||||
}
|
}
|
||||||
if (result.failed > 0) {
|
if (result.failed > 0) {
|
||||||
@ -6553,10 +6551,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const allDone = success + failed + cancelled >= items.length;
|
const allDone = success + failed + cancelled >= items.length;
|
||||||
|
|
||||||
if (!allDone && this.settings.hybridExtract && this.settings.autoExtract && failed === 0 && success > 0) {
|
if (!allDone && this.settings.hybridExtract && this.settings.autoExtract && failed === 0 && success > 0) {
|
||||||
pkg.postProcessLabel = "Entpacken...";
|
|
||||||
await this.runHybridExtraction(packageId, pkg, items, signal);
|
await this.runHybridExtraction(packageId, pkg, items, signal);
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
pkg.postProcessLabel = undefined;
|
|
||||||
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "queued" : "paused";
|
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "queued" : "paused";
|
||||||
pkg.updatedAt = nowMs();
|
pkg.updatedAt = nowMs();
|
||||||
return;
|
return;
|
||||||
@ -6570,7 +6566,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (!this.session.packages[packageId]) {
|
if (!this.session.packages[packageId]) {
|
||||||
return; // Package was fully cleaned up
|
return; // Package was fully cleaned up
|
||||||
}
|
}
|
||||||
pkg.postProcessLabel = undefined;
|
|
||||||
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "downloading" : "queued";
|
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "downloading" : "queued";
|
||||||
pkg.updatedAt = nowMs();
|
pkg.updatedAt = nowMs();
|
||||||
this.emitState();
|
this.emitState();
|
||||||
@ -6578,7 +6573,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!allDone) {
|
if (!allDone) {
|
||||||
pkg.postProcessLabel = undefined;
|
|
||||||
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "downloading" : "queued";
|
pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "downloading" : "queued";
|
||||||
logger.info(`Post-Processing verschoben: pkg=${pkg.name}, noch offene items`);
|
logger.info(`Post-Processing verschoben: pkg=${pkg.name}, noch offene items`);
|
||||||
return;
|
return;
|
||||||
@ -6588,7 +6582,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const alreadyMarkedExtracted = completedItems.length > 0 && completedItems.every((item) => isExtractedLabel(item.fullStatus));
|
const alreadyMarkedExtracted = completedItems.length > 0 && completedItems.every((item) => isExtractedLabel(item.fullStatus));
|
||||||
|
|
||||||
if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) {
|
if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) {
|
||||||
pkg.postProcessLabel = "Entpacken...";
|
|
||||||
pkg.status = "extracting";
|
pkg.status = "extracting";
|
||||||
this.emitState();
|
this.emitState();
|
||||||
const extractionStartMs = nowMs();
|
const extractionStartMs = nowMs();
|
||||||
@ -6739,8 +6732,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
// Auto-rename even when some archives failed — successfully extracted files still need renaming
|
// Auto-rename even when some archives failed — successfully extracted files still need renaming
|
||||||
if (result.extracted > 0) {
|
if (result.extracted > 0) {
|
||||||
pkg.postProcessLabel = "Renaming...";
|
|
||||||
this.emitState();
|
|
||||||
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6844,8 +6835,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.autoExtract && alreadyMarkedExtracted && failed === 0 && success > 0 && this.settings.cleanupMode !== "none") {
|
if (this.settings.autoExtract && alreadyMarkedExtracted && failed === 0 && success > 0 && this.settings.cleanupMode !== "none") {
|
||||||
pkg.postProcessLabel = "Aufräumen...";
|
|
||||||
this.emitState();
|
|
||||||
const removedArchives = await this.cleanupRemainingArchiveArtifacts(pkg.outputDir);
|
const removedArchives = await this.cleanupRemainingArchiveArtifacts(pkg.outputDir);
|
||||||
if (removedArchives > 0) {
|
if (removedArchives > 0) {
|
||||||
logger.info(`Hybrid-Post-Cleanup entfernte Archive: pkg=${pkg.name}, entfernt=${removedArchives}`);
|
logger.info(`Hybrid-Post-Cleanup entfernte Archive: pkg=${pkg.name}, entfernt=${removedArchives}`);
|
||||||
@ -6853,8 +6842,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success > 0 && (pkg.status === "completed" || pkg.status === "failed")) {
|
if (success > 0 && (pkg.status === "completed" || pkg.status === "failed")) {
|
||||||
pkg.postProcessLabel = "Verschiebe MKVs...";
|
|
||||||
this.emitState();
|
|
||||||
await this.collectMkvFilesToLibrary(packageId, pkg);
|
await this.collectMkvFilesToLibrary(packageId, pkg);
|
||||||
}
|
}
|
||||||
if (this.runPackageIds.has(packageId)) {
|
if (this.runPackageIds.has(packageId)) {
|
||||||
@ -6864,7 +6851,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.runCompletedPackages.delete(packageId);
|
this.runCompletedPackages.delete(packageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pkg.postProcessLabel = undefined;
|
|
||||||
pkg.updatedAt = nowMs();
|
pkg.updatedAt = nowMs();
|
||||||
logger.info(`Post-Processing Ende: pkg=${pkg.name}, status=${pkg.status}`);
|
logger.info(`Post-Processing Ende: pkg=${pkg.name}, status=${pkg.status}`);
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { AppSettings, BandwidthScheduleEntry, DebridProvider, DownloadItem, Down
|
|||||||
import { defaultSettings } from "./constants";
|
import { defaultSettings } from "./constants";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
|
|
||||||
const VALID_PRIMARY_PROVIDERS = new Set(["realdebrid", "megadebrid", "bestdebrid", "alldebrid", "ddownload"]);
|
const VALID_PRIMARY_PROVIDERS = new Set(["realdebrid", "megadebrid", "bestdebrid", "alldebrid"]);
|
||||||
const VALID_FALLBACK_PROVIDERS = new Set(["none", "realdebrid", "megadebrid", "bestdebrid", "alldebrid", "ddownload"]);
|
const VALID_FALLBACK_PROVIDERS = new Set(["none", "realdebrid", "megadebrid", "bestdebrid", "alldebrid"]);
|
||||||
const VALID_CLEANUP_MODES = new Set(["none", "trash", "delete"]);
|
const VALID_CLEANUP_MODES = new Set(["none", "trash", "delete"]);
|
||||||
const VALID_CONFLICT_MODES = new Set(["overwrite", "skip", "rename", "ask"]);
|
const VALID_CONFLICT_MODES = new Set(["overwrite", "skip", "rename", "ask"]);
|
||||||
const VALID_FINISHED_POLICIES = new Set(["never", "immediate", "on_start", "package_done"]);
|
const VALID_FINISHED_POLICIES = new Set(["never", "immediate", "on_start", "package_done"]);
|
||||||
@ -17,7 +17,7 @@ const VALID_PACKAGE_PRIORITIES = new Set<string>(["high", "normal", "low"]);
|
|||||||
const VALID_DOWNLOAD_STATUSES = new Set<DownloadStatus>([
|
const VALID_DOWNLOAD_STATUSES = new Set<DownloadStatus>([
|
||||||
"queued", "validating", "downloading", "paused", "reconnect_wait", "extracting", "integrity_check", "completed", "failed", "cancelled"
|
"queued", "validating", "downloading", "paused", "reconnect_wait", "extracting", "integrity_check", "completed", "failed", "cancelled"
|
||||||
]);
|
]);
|
||||||
const VALID_ITEM_PROVIDERS = new Set<DebridProvider>(["realdebrid", "megadebrid", "bestdebrid", "alldebrid", "ddownload"]);
|
const VALID_ITEM_PROVIDERS = new Set<DebridProvider>(["realdebrid", "megadebrid", "bestdebrid", "alldebrid"]);
|
||||||
const VALID_ONLINE_STATUSES = new Set(["online", "offline", "checking"]);
|
const VALID_ONLINE_STATUSES = new Set(["online", "offline", "checking"]);
|
||||||
|
|
||||||
function asText(value: unknown): string {
|
function asText(value: unknown): string {
|
||||||
@ -111,8 +111,6 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
|||||||
megaPassword: asText(settings.megaPassword),
|
megaPassword: asText(settings.megaPassword),
|
||||||
bestToken: asText(settings.bestToken),
|
bestToken: asText(settings.bestToken),
|
||||||
allDebridToken: asText(settings.allDebridToken),
|
allDebridToken: asText(settings.allDebridToken),
|
||||||
ddownloadLogin: asText(settings.ddownloadLogin),
|
|
||||||
ddownloadPassword: asText(settings.ddownloadPassword),
|
|
||||||
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n/g, "\n"),
|
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n/g, "\n"),
|
||||||
rememberToken: Boolean(settings.rememberToken),
|
rememberToken: Boolean(settings.rememberToken),
|
||||||
providerPrimary: settings.providerPrimary,
|
providerPrimary: settings.providerPrimary,
|
||||||
@ -202,9 +200,7 @@ function sanitizeCredentialPersistence(settings: AppSettings): AppSettings {
|
|||||||
megaLogin: "",
|
megaLogin: "",
|
||||||
megaPassword: "",
|
megaPassword: "",
|
||||||
bestToken: "",
|
bestToken: "",
|
||||||
allDebridToken: "",
|
allDebridToken: ""
|
||||||
ddownloadLogin: "",
|
|
||||||
ddownloadPassword: ""
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +442,6 @@ export function normalizeLoadedSessionTransientFields(session: SessionState): Se
|
|||||||
if (ACTIVE_PKG_STATUSES.has(pkg.status)) {
|
if (ACTIVE_PKG_STATUSES.has(pkg.status)) {
|
||||||
pkg.status = "queued";
|
pkg.status = "queued";
|
||||||
}
|
}
|
||||||
pkg.postProcessLabel = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear stale session-level running/paused flags
|
// Clear stale session-level running/paused flags
|
||||||
|
|||||||
@ -336,8 +336,6 @@ function parseReleasePayload(payload: Record<string, unknown>, fallback: UpdateC
|
|||||||
const releaseUrl = String(payload.html_url || fallback.releaseUrl);
|
const releaseUrl = String(payload.html_url || fallback.releaseUrl);
|
||||||
const setup = pickSetupAsset(readReleaseAssets(payload));
|
const setup = pickSetupAsset(readReleaseAssets(payload));
|
||||||
|
|
||||||
const body = typeof payload.body === "string" ? payload.body.trim() : "";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateAvailable: isRemoteNewer(APP_VERSION, latestVersion),
|
updateAvailable: isRemoteNewer(APP_VERSION, latestVersion),
|
||||||
currentVersion: APP_VERSION,
|
currentVersion: APP_VERSION,
|
||||||
@ -346,8 +344,7 @@ function parseReleasePayload(payload: Record<string, unknown>, fallback: UpdateC
|
|||||||
releaseUrl,
|
releaseUrl,
|
||||||
setupAssetUrl: setup?.browser_download_url || "",
|
setupAssetUrl: setup?.browser_download_url || "",
|
||||||
setupAssetName: setup?.name || "",
|
setupAssetName: setup?.name || "",
|
||||||
setupAssetDigest: setup?.digest || "",
|
setupAssetDigest: setup?.digest || ""
|
||||||
releaseNotes: body || undefined
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const cleanupLabels: Record<string, string> = {
|
|||||||
const AUTO_RENDER_PACKAGE_LIMIT = 260;
|
const AUTO_RENDER_PACKAGE_LIMIT = 260;
|
||||||
|
|
||||||
const providerLabels: Record<DebridProvider, string> = {
|
const providerLabels: Record<DebridProvider, string> = {
|
||||||
realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid", ddownload: "DDownload"
|
realdebrid: "Real-Debrid", megadebrid: "Mega-Debrid", bestdebrid: "BestDebrid", alldebrid: "AllDebrid"
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDateTime(ts: number): string {
|
function formatDateTime(ts: number): string {
|
||||||
@ -928,11 +928,8 @@ export function App(): ReactElement {
|
|||||||
if (settingsDraft.allDebridToken.trim()) {
|
if (settingsDraft.allDebridToken.trim()) {
|
||||||
list.push("alldebrid");
|
list.push("alldebrid");
|
||||||
}
|
}
|
||||||
if (settingsDraft.ddownloadLogin.trim() && settingsDraft.ddownloadPassword.trim()) {
|
|
||||||
list.push("ddownload");
|
|
||||||
}
|
|
||||||
return list;
|
return list;
|
||||||
}, [settingsDraft.token, settingsDraft.megaLogin, settingsDraft.megaPassword, settingsDraft.bestToken, settingsDraft.allDebridToken, settingsDraft.ddownloadLogin, settingsDraft.ddownloadPassword]);
|
}, [settingsDraft.token, settingsDraft.megaLogin, settingsDraft.megaPassword, settingsDraft.bestToken, settingsDraft.allDebridToken]);
|
||||||
|
|
||||||
const primaryProviderValue: DebridProvider = useMemo(() => {
|
const primaryProviderValue: DebridProvider = useMemo(() => {
|
||||||
if (configuredProviders.includes(settingsDraft.providerPrimary)) {
|
if (configuredProviders.includes(settingsDraft.providerPrimary)) {
|
||||||
@ -993,14 +990,9 @@ export function App(): ReactElement {
|
|||||||
if (source === "manual") { showToast(`Kein Update verfügbar (v${result.currentVersion})`, 2000); }
|
if (source === "manual") { showToast(`Kein Update verfügbar (v${result.currentVersion})`, 2000); }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let changelogBlock = "";
|
|
||||||
if (result.releaseNotes) {
|
|
||||||
const notes = result.releaseNotes.length > 500 ? `${result.releaseNotes.slice(0, 500)}…` : result.releaseNotes;
|
|
||||||
changelogBlock = `\n\n--- Changelog ---\n${notes}`;
|
|
||||||
}
|
|
||||||
const approved = await askConfirmPrompt({
|
const approved = await askConfirmPrompt({
|
||||||
title: "Update verfügbar",
|
title: "Update verfügbar",
|
||||||
message: `${result.latestTag} (aktuell v${result.currentVersion})${changelogBlock}\n\nJetzt automatisch herunterladen und installieren?`,
|
message: `${result.latestTag} (aktuell v${result.currentVersion})\n\nJetzt automatisch herunterladen und installieren?`,
|
||||||
confirmLabel: "Jetzt installieren"
|
confirmLabel: "Jetzt installieren"
|
||||||
});
|
});
|
||||||
if (!mountedRef.current) {
|
if (!mountedRef.current) {
|
||||||
@ -2719,10 +2711,6 @@ export function App(): ReactElement {
|
|||||||
<input type="password" value={settingsDraft.bestToken} onChange={(e) => setText("bestToken", e.target.value)} />
|
<input type="password" value={settingsDraft.bestToken} onChange={(e) => setText("bestToken", e.target.value)} />
|
||||||
<label>AllDebrid API Key</label>
|
<label>AllDebrid API Key</label>
|
||||||
<input type="password" value={settingsDraft.allDebridToken} onChange={(e) => setText("allDebridToken", e.target.value)} />
|
<input type="password" value={settingsDraft.allDebridToken} onChange={(e) => setText("allDebridToken", e.target.value)} />
|
||||||
<label>DDownload Login</label>
|
|
||||||
<input value={settingsDraft.ddownloadLogin} onChange={(e) => setText("ddownloadLogin", e.target.value)} />
|
|
||||||
<label>DDownload Passwort</label>
|
|
||||||
<input type="password" value={settingsDraft.ddownloadPassword} onChange={(e) => setText("ddownloadPassword", e.target.value)} />
|
|
||||||
{configuredProviders.length === 0 && (
|
{configuredProviders.length === 0 && (
|
||||||
<div className="hint">Füge mindestens einen Account hinzu, dann erscheint die Hoster-Auswahl.</div>
|
<div className="hint">Füge mindestens einen Account hinzu, dann erscheint die Hoster-Auswahl.</div>
|
||||||
)}
|
)}
|
||||||
@ -3356,7 +3344,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
|
|||||||
<span key={col} className={`pkg-col pkg-col-prio${pkg.priority === "high" ? " prio-high" : pkg.priority === "low" ? " prio-low" : ""}`}>{pkg.priority === "high" ? "Hoch" : pkg.priority === "low" ? "Niedrig" : ""}</span>
|
<span key={col} className={`pkg-col pkg-col-prio${pkg.priority === "high" ? " prio-high" : pkg.priority === "low" ? " prio-low" : ""}`}>{pkg.priority === "high" ? "Hoch" : pkg.priority === "low" ? "Niedrig" : ""}</span>
|
||||||
);
|
);
|
||||||
case "status": return (
|
case "status": return (
|
||||||
<span key={col} className="pkg-col pkg-col-status">[{done}/{total}{done === total && total > 0 ? " - Done" : ""}{failed > 0 ? ` · ${failed} Fehler` : ""}{cancelled > 0 ? ` · ${cancelled} abgebr.` : ""}]{pkg.postProcessLabel ? ` ${pkg.postProcessLabel}` : ""}</span>
|
<span key={col} className="pkg-col pkg-col-status">[{done}/{total}{done === total && total > 0 ? " - Done" : ""}{failed > 0 ? ` · ${failed} Fehler` : ""}{cancelled > 0 ? ` · ${cancelled} abgebr.` : ""}]</span>
|
||||||
);
|
);
|
||||||
case "speed": return (
|
case "speed": return (
|
||||||
<span key={col} className="pkg-col pkg-col-speed">{packageSpeed > 0 ? formatSpeedMbps(packageSpeed) : ""}</span>
|
<span key={col} className="pkg-col pkg-col-speed">{packageSpeed > 0 ? formatSpeedMbps(packageSpeed) : ""}</span>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export type CleanupMode = "none" | "trash" | "delete";
|
|||||||
export type ConflictMode = "overwrite" | "skip" | "rename" | "ask";
|
export type ConflictMode = "overwrite" | "skip" | "rename" | "ask";
|
||||||
export type SpeedMode = "global" | "per_download";
|
export type SpeedMode = "global" | "per_download";
|
||||||
export type FinishedCleanupPolicy = "never" | "immediate" | "on_start" | "package_done";
|
export type FinishedCleanupPolicy = "never" | "immediate" | "on_start" | "package_done";
|
||||||
export type DebridProvider = "realdebrid" | "megadebrid" | "bestdebrid" | "alldebrid" | "ddownload";
|
export type DebridProvider = "realdebrid" | "megadebrid" | "bestdebrid" | "alldebrid";
|
||||||
export type DebridFallbackProvider = DebridProvider | "none";
|
export type DebridFallbackProvider = DebridProvider | "none";
|
||||||
export type AppTheme = "dark" | "light";
|
export type AppTheme = "dark" | "light";
|
||||||
export type PackagePriority = "high" | "normal" | "low";
|
export type PackagePriority = "high" | "normal" | "low";
|
||||||
@ -42,8 +42,6 @@ export interface AppSettings {
|
|||||||
megaPassword: string;
|
megaPassword: string;
|
||||||
bestToken: string;
|
bestToken: string;
|
||||||
allDebridToken: string;
|
allDebridToken: string;
|
||||||
ddownloadLogin: string;
|
|
||||||
ddownloadPassword: string;
|
|
||||||
archivePasswordList: string;
|
archivePasswordList: string;
|
||||||
rememberToken: boolean;
|
rememberToken: boolean;
|
||||||
providerPrimary: DebridProvider;
|
providerPrimary: DebridProvider;
|
||||||
@ -121,7 +119,6 @@ export interface PackageEntry {
|
|||||||
cancelled: boolean;
|
cancelled: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
priority: PackagePriority;
|
priority: PackagePriority;
|
||||||
postProcessLabel?: string;
|
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}
|
}
|
||||||
@ -222,7 +219,6 @@ export interface UpdateCheckResult {
|
|||||||
setupAssetUrl?: string;
|
setupAssetUrl?: string;
|
||||||
setupAssetName?: string;
|
setupAssetName?: string;
|
||||||
setupAssetDigest?: string;
|
setupAssetDigest?: string;
|
||||||
releaseNotes?: string;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user