Compare commits
2 Commits
d776ad589d
...
67854feeb4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67854feeb4 | ||
|
|
153318274d |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.6.78",
|
||||
"version": "1.6.79",
|
||||
"description": "Desktop downloader",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -201,8 +201,8 @@ export class AppController {
|
||||
await this.allDebridWebFallback.openLoginWindow();
|
||||
}
|
||||
|
||||
public async openBestDebridLoginWindow(): Promise<void> {
|
||||
await this.bestDebridWebFallback.openLoginWindow();
|
||||
public async importBestDebridCookies(filePath: string): Promise<number> {
|
||||
return this.bestDebridWebFallback.importCookiesFromFile(filePath);
|
||||
}
|
||||
|
||||
public async getAllDebridHostInfo(host = "rapidgator"): Promise<AllDebridHostInfo> {
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { BrowserWindow, session } from "electron";
|
||||
import fs from "node:fs";
|
||||
import { session } from "electron";
|
||||
import { UnrestrictedLink } from "./realdebrid";
|
||||
import { filenameFromUrl, sleep } from "./utils";
|
||||
import { logger } from "./logger";
|
||||
|
||||
const BESTDEBRID_BASE_URL = "https://bestdebrid.com";
|
||||
const BESTDEBRID_LOGIN_URL = `${BESTDEBRID_BASE_URL}/en/downloader/`;
|
||||
const BESTDEBRID_DOWNLOADER_URL = `${BESTDEBRID_BASE_URL}/en/downloader/`;
|
||||
const BESTDEBRID_GENERATE_URL = `${BESTDEBRID_BASE_URL}/api/v1/generateLink`;
|
||||
const BESTDEBRID_PERSISTENT_PARTITION = "persist:bestdebrid-web";
|
||||
const BESTDEBRID_TRANSIENT_PARTITION = "bestdebrid-web";
|
||||
const BESTDEBRID_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36";
|
||||
|
||||
type GenerateOutcome =
|
||||
| { kind: "success"; value: UnrestrictedLink }
|
||||
| { kind: "login_required" };
|
||||
|
||||
function abortError(): Error {
|
||||
return new Error("aborted:bestdebrid-web");
|
||||
}
|
||||
@ -31,35 +29,6 @@ function throwIfAborted(signal?: AbortSignal): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void> {
|
||||
if (!signal) {
|
||||
await sleep(ms);
|
||||
return;
|
||||
}
|
||||
if (signal.aborted) {
|
||||
throw abortError();
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let timer: NodeJS.Timeout | null = setTimeout(() => {
|
||||
timer = null;
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
resolve();
|
||||
}, Math.max(0, ms));
|
||||
|
||||
const onAbort = (): void => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
reject(abortError());
|
||||
};
|
||||
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
function parseJson(text: string): Record<string, unknown> | null {
|
||||
try {
|
||||
const parsed = JSON.parse(text) as unknown;
|
||||
@ -72,12 +41,44 @@ function parseJson(text: string): Record<string, unknown> | null {
|
||||
}
|
||||
}
|
||||
|
||||
interface NetscapeCookie {
|
||||
domain: string;
|
||||
httpOnly: boolean;
|
||||
path: string;
|
||||
secure: boolean;
|
||||
expirationDate: number;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function parseNetscapeCookieFile(text: string): NetscapeCookie[] {
|
||||
const cookies: NetscapeCookie[] = [];
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
const parts = trimmed.split("\t");
|
||||
if (parts.length < 7) {
|
||||
continue;
|
||||
}
|
||||
cookies.push({
|
||||
domain: parts[0],
|
||||
httpOnly: parts[1].toUpperCase() === "TRUE",
|
||||
path: parts[2],
|
||||
secure: parts[3].toUpperCase() === "TRUE",
|
||||
expirationDate: Number(parts[4]) || 0,
|
||||
name: parts[5],
|
||||
value: parts[6]
|
||||
});
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
export class BestDebridWebFallback {
|
||||
private queue: Promise<unknown> = Promise.resolve();
|
||||
|
||||
private loginWindow: BrowserWindow | null = null;
|
||||
|
||||
private loginWindowPartition = "";
|
||||
private cookiesImported = false;
|
||||
|
||||
private getRememberSession: () => boolean;
|
||||
|
||||
@ -86,32 +87,60 @@ export class BestDebridWebFallback {
|
||||
}
|
||||
|
||||
public async unrestrict(link: string, signal?: AbortSignal): Promise<UnrestrictedLink | null> {
|
||||
const overallSignal = withTimeoutSignal(signal, 10 * 60 * 1000);
|
||||
const overallSignal = withTimeoutSignal(signal, 60_000);
|
||||
return this.runExclusive(async () => {
|
||||
throwIfAborted(overallSignal);
|
||||
if (!String(link || "").trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initial = await this.generate(link, overallSignal);
|
||||
if (initial.kind === "success") {
|
||||
return initial.value;
|
||||
if (!this.cookiesImported) {
|
||||
throw new Error("BestDebrid: Keine Cookies importiert. Bitte zuerst über Einstellungen eine Cookie-Datei importieren.");
|
||||
}
|
||||
return this.waitForLoginAndGenerate(link, overallSignal);
|
||||
|
||||
const result = await this.generate(link, overallSignal);
|
||||
if (result.kind === "success") {
|
||||
return result.value;
|
||||
}
|
||||
throw new Error("BestDebrid: Nicht eingeloggt. Bitte neue Cookie-Datei importieren.");
|
||||
}, overallSignal);
|
||||
}
|
||||
|
||||
public async openLoginWindow(): Promise<void> {
|
||||
const window = await this.ensureLoginWindow();
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
public async importCookiesFromFile(filePath: string): Promise<number> {
|
||||
const text = fs.readFileSync(filePath, "utf-8");
|
||||
const cookies = parseNetscapeCookieFile(text);
|
||||
const bestDebridCookies = cookies.filter((c) =>
|
||||
c.domain.includes("bestdebrid.com")
|
||||
);
|
||||
|
||||
if (bestDebridCookies.length === 0) {
|
||||
throw new Error("Keine BestDebrid-Cookies in der Datei gefunden");
|
||||
}
|
||||
window.show();
|
||||
window.focus();
|
||||
|
||||
const currentSession = session.fromPartition(this.getPartition());
|
||||
currentSession.setUserAgent(BESTDEBRID_USER_AGENT);
|
||||
|
||||
for (const cookie of bestDebridCookies) {
|
||||
const url = `https://${cookie.domain.replace(/^\./, "")}${cookie.path}`;
|
||||
await currentSession.cookies.set({
|
||||
url,
|
||||
name: cookie.name,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain,
|
||||
path: cookie.path,
|
||||
secure: cookie.secure,
|
||||
httpOnly: cookie.httpOnly,
|
||||
expirationDate: cookie.expirationDate > 0 ? cookie.expirationDate : undefined
|
||||
});
|
||||
}
|
||||
|
||||
this.cookiesImported = true;
|
||||
logger.info(`BestDebrid: ${bestDebridCookies.length} Cookies importiert aus ${filePath}`);
|
||||
return bestDebridCookies.length;
|
||||
}
|
||||
|
||||
public async clearSessions(): Promise<void> {
|
||||
this.disposeLoginWindow();
|
||||
this.cookiesImported = false;
|
||||
for (const partition of [BESTDEBRID_PERSISTENT_PARTITION, BESTDEBRID_TRANSIENT_PARTITION]) {
|
||||
const currentSession = session.fromPartition(partition);
|
||||
try {
|
||||
@ -130,22 +159,13 @@ export class BestDebridWebFallback {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposeLoginWindow();
|
||||
// nothing to clean up
|
||||
}
|
||||
|
||||
private getPartition(): string {
|
||||
return this.getRememberSession() ? BESTDEBRID_PERSISTENT_PARTITION : BESTDEBRID_TRANSIENT_PARTITION;
|
||||
}
|
||||
|
||||
private disposeLoginWindow(): void {
|
||||
const current = this.loginWindow;
|
||||
this.loginWindow = null;
|
||||
this.loginWindowPartition = "";
|
||||
if (current && !current.isDestroyed()) {
|
||||
current.close();
|
||||
}
|
||||
}
|
||||
|
||||
private async runExclusive<T>(job: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
||||
const queuedAt = Date.now();
|
||||
const queueWaitTimeoutMs = 90_000;
|
||||
@ -162,72 +182,7 @@ export class BestDebridWebFallback {
|
||||
return run;
|
||||
}
|
||||
|
||||
private async ensureLoginWindow(): Promise<BrowserWindow> {
|
||||
const partition = this.getPartition();
|
||||
const existing = this.loginWindow;
|
||||
if (existing && !existing.isDestroyed() && this.loginWindowPartition === partition) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing && !existing.isDestroyed()) {
|
||||
existing.close();
|
||||
}
|
||||
|
||||
// Set user agent on session level so Cloudflare Turnstile sees a real Chrome
|
||||
const currentSession = session.fromPartition(partition);
|
||||
currentSession.setUserAgent(BESTDEBRID_USER_AGENT);
|
||||
|
||||
const window = new BrowserWindow({
|
||||
width: 1120,
|
||||
height: 900,
|
||||
minWidth: 980,
|
||||
minHeight: 760,
|
||||
autoHideMenuBar: true,
|
||||
title: "BestDebrid Web-Login",
|
||||
webPreferences: {
|
||||
partition,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
}
|
||||
});
|
||||
window.webContents.setUserAgent(BESTDEBRID_USER_AGENT);
|
||||
window.setMenuBarVisibility(false);
|
||||
|
||||
// Inject anti-fingerprint patches via CDP before any page JS runs.
|
||||
// The debugger must stay attached until after page load so the
|
||||
// registered scripts actually execute on every new document.
|
||||
let debuggerAttached = false;
|
||||
try {
|
||||
window.webContents.debugger.attach("1.3");
|
||||
debuggerAttached = true;
|
||||
await window.webContents.debugger.sendCommand("Page.addScriptToEvaluateOnNewDocument", {
|
||||
source: [
|
||||
"Object.defineProperty(navigator, 'webdriver', { get: () => false });",
|
||||
"Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });",
|
||||
"Object.defineProperty(navigator, 'languages', { get: () => ['de-DE', 'de', 'en-US', 'en'] });",
|
||||
"window.chrome = { runtime: {}, loadTimes: function() {}, csi: function() {} };"
|
||||
].join("\n")
|
||||
});
|
||||
} catch {
|
||||
// CDP not available — continue without patches
|
||||
}
|
||||
|
||||
window.on("closed", () => {
|
||||
if (debuggerAttached) {
|
||||
try { window.webContents.debugger.detach(); } catch { /* already detached */ }
|
||||
}
|
||||
if (this.loginWindow === window) {
|
||||
this.loginWindow = null;
|
||||
this.loginWindowPartition = "";
|
||||
}
|
||||
});
|
||||
this.loginWindow = window;
|
||||
this.loginWindowPartition = partition;
|
||||
await window.loadURL(BESTDEBRID_LOGIN_URL);
|
||||
return window;
|
||||
}
|
||||
|
||||
private async generate(link: string, signal?: AbortSignal): Promise<GenerateOutcome> {
|
||||
private async generate(link: string, signal?: AbortSignal): Promise<{ kind: "success"; value: UnrestrictedLink } | { kind: "login_required" }> {
|
||||
throwIfAborted(signal);
|
||||
const currentSession = session.fromPartition(this.getPartition());
|
||||
const response = await currentSession.fetch(BESTDEBRID_GENERATE_URL, {
|
||||
@ -236,7 +191,7 @@ export class BestDebridWebFallback {
|
||||
Accept: "application/json, text/javascript, */*; q=0.01",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
Origin: BESTDEBRID_BASE_URL,
|
||||
Referer: BESTDEBRID_LOGIN_URL,
|
||||
Referer: BESTDEBRID_DOWNLOADER_URL,
|
||||
"User-Agent": BESTDEBRID_USER_AGENT,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
},
|
||||
@ -246,7 +201,6 @@ export class BestDebridWebFallback {
|
||||
|
||||
const text = await response.text();
|
||||
|
||||
// Not logged in — BestDebrid redirects or returns HTML login page
|
||||
if (!response.ok || text.trim().startsWith("<!") || text.trim().startsWith("<html")) {
|
||||
return { kind: "login_required" };
|
||||
}
|
||||
@ -259,9 +213,7 @@ export class BestDebridWebFallback {
|
||||
const error = Number(payload.error ?? -1);
|
||||
const message = String(payload.message || "").trim();
|
||||
|
||||
// error != 0 means failure
|
||||
if (error !== 0) {
|
||||
// Check if it's a login/auth issue
|
||||
if (/login|log in|sign in|not logged|session|auth/i.test(message)) {
|
||||
return { kind: "login_required" };
|
||||
}
|
||||
@ -277,11 +229,10 @@ export class BestDebridWebFallback {
|
||||
const fileSizeRaw = String(payload.size || "").trim();
|
||||
let fileSize: number | null = null;
|
||||
if (fileSizeRaw) {
|
||||
// Size might be like "96.63 MB" — parse it
|
||||
const match = fileSizeRaw.match(/([\d.]+)\s*(KB|MB|GB|TB|B)/i);
|
||||
const match = fileSizeRaw.match(/([\d.]+)\s*(KB|KiB|MB|MiB|GB|GiB|TB|TiB|B)/i);
|
||||
if (match) {
|
||||
const value = parseFloat(match[1]);
|
||||
const unit = match[2].toUpperCase();
|
||||
const unit = match[2].toUpperCase().replace("IB", "B");
|
||||
const multipliers: Record<string, number> = { B: 1, KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024, TB: 1024 * 1024 * 1024 * 1024 };
|
||||
fileSize = Math.floor(value * (multipliers[unit] || 1));
|
||||
}
|
||||
@ -297,33 +248,4 @@ export class BestDebridWebFallback {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async waitForLoginAndGenerate(link: string, signal?: AbortSignal): Promise<UnrestrictedLink | null> {
|
||||
const window = await this.ensureLoginWindow();
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.show();
|
||||
window.focus();
|
||||
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < 10 * 60 * 1000) {
|
||||
throwIfAborted(signal);
|
||||
if (window.isDestroyed()) {
|
||||
throw new Error("BestDebrid Web-Login abgebrochen");
|
||||
}
|
||||
|
||||
const outcome = await this.generate(link, signal);
|
||||
if (outcome.kind === "success") {
|
||||
if (!window.isDestroyed()) {
|
||||
window.close();
|
||||
}
|
||||
return outcome.value;
|
||||
}
|
||||
|
||||
await sleepWithSignal(1_500, signal);
|
||||
}
|
||||
|
||||
throw new Error("BestDebrid Web-Login Timeout");
|
||||
}
|
||||
}
|
||||
|
||||
@ -4789,7 +4789,7 @@ export class DownloadManager extends EventEmitter {
|
||||
item.updatedAt = nowMs();
|
||||
this.emitState();
|
||||
}
|
||||
const result = await this.downloadToFile(active, unrestricted.directUrl, item.targetPath, item.totalBytes, unrestricted.skipTlsVerify);
|
||||
const result = await this.downloadToFile(active, unrestricted.directUrl, item.targetPath, item.totalBytes, unrestricted.skipTlsVerify, pLabel);
|
||||
active.resumable = result.resumable;
|
||||
if (!active.resumable && !active.nonResumableCounted) {
|
||||
active.nonResumableCounted = true;
|
||||
@ -5176,8 +5176,10 @@ export class DownloadManager extends EventEmitter {
|
||||
directUrl: string,
|
||||
targetPath: string,
|
||||
knownTotal: number | null,
|
||||
skipTlsVerify?: boolean
|
||||
skipTlsVerify?: boolean,
|
||||
pLabel?: string
|
||||
): Promise<{ resumable: boolean }> {
|
||||
const label = pLabel || providerLabel(this.session.items[active.itemId]?.provider);
|
||||
const item = this.session.items[active.itemId];
|
||||
if (!item) {
|
||||
throw new Error("Download-Item fehlt");
|
||||
@ -5426,7 +5428,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (nowTick - lastDiskBusyEmitAt >= 1200) {
|
||||
item.status = "downloading";
|
||||
item.speedBps = 0;
|
||||
item.fullStatus = `Warte auf Festplatte (${pLabel})`;
|
||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||
item.updatedAt = nowTick;
|
||||
this.emitState();
|
||||
lastDiskBusyEmitAt = nowTick;
|
||||
@ -5537,7 +5539,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (nowTick - lastIdleEmitAt >= idlePulseMs) {
|
||||
item.status = "downloading";
|
||||
item.speedBps = 0;
|
||||
item.fullStatus = `Warte auf Festplatte (${pLabel})`;
|
||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||
item.updatedAt = nowTick;
|
||||
this.emitState();
|
||||
lastIdleEmitAt = nowTick;
|
||||
@ -5553,7 +5555,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
item.status = "downloading";
|
||||
item.speedBps = 0;
|
||||
item.fullStatus = `Warte auf Daten (${pLabel})`;
|
||||
item.fullStatus = `Warte auf Daten (${label})`;
|
||||
if (nowTick - lastIdleEmitAt >= idlePulseMs) {
|
||||
item.updatedAt = nowTick;
|
||||
this.emitState();
|
||||
@ -5662,7 +5664,7 @@ export class DownloadManager extends EventEmitter {
|
||||
if (nowTick - lastDiskBusyEmitAt >= 1200) {
|
||||
item.status = "downloading";
|
||||
item.speedBps = 0;
|
||||
item.fullStatus = `Warte auf Festplatte (${pLabel})`;
|
||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||
item.updatedAt = nowTick;
|
||||
this.emitState();
|
||||
lastDiskBusyEmitAt = nowTick;
|
||||
@ -5706,10 +5708,10 @@ export class DownloadManager extends EventEmitter {
|
||||
const diskBusy = diskBusySince > 0 && nowMs() - diskBusySince >= DISK_BUSY_THRESHOLD_MS;
|
||||
if (diskBusy) {
|
||||
item.speedBps = 0;
|
||||
item.fullStatus = `Warte auf Festplatte (${pLabel})`;
|
||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||
} else {
|
||||
item.speedBps = Math.max(0, Math.floor(speed));
|
||||
item.fullStatus = `Download läuft (${pLabel})`;
|
||||
item.fullStatus = `Download läuft (${label})`;
|
||||
}
|
||||
const nowTick = nowMs();
|
||||
if (nowTick - lastUiEmitAt >= uiUpdateIntervalMs) {
|
||||
|
||||
@ -454,8 +454,19 @@ function registerIpcHandlers(): void {
|
||||
await controller.openAllDebridLoginWindow();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.OPEN_BESTDEBRID_LOGIN, async () => {
|
||||
await controller.openBestDebridLoginWindow();
|
||||
ipcMain.handle(IPC_CHANNELS.IMPORT_BESTDEBRID_COOKIES, async () => {
|
||||
const options = {
|
||||
properties: ["openFile"] as Array<"openFile">,
|
||||
filters: [
|
||||
{ name: "Cookie-Datei", extensions: ["txt"] },
|
||||
{ name: "Alle Dateien", extensions: ["*"] }
|
||||
]
|
||||
};
|
||||
const result = mainWindow ? await dialog.showOpenDialog(mainWindow, options) : await dialog.showOpenDialog(options);
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return controller.importBestDebridCookies(result.filePaths[0]);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO, async () => {
|
||||
|
||||
@ -54,7 +54,7 @@ const api: ElectronApi = {
|
||||
openSessionLog: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_SESSION_LOG),
|
||||
openRealDebridLogin: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_REALDEBRID_LOGIN),
|
||||
openAllDebridLogin: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_ALLDEBRID_LOGIN),
|
||||
openBestDebridLogin: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.OPEN_BESTDEBRID_LOGIN),
|
||||
importBestDebridCookies: (): Promise<number> => ipcRenderer.invoke(IPC_CHANNELS.IMPORT_BESTDEBRID_COOKIES),
|
||||
getAllDebridHostInfo: (): Promise<AllDebridHostInfo> => ipcRenderer.invoke(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO),
|
||||
retryExtraction: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId),
|
||||
extractNow: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId),
|
||||
|
||||
@ -1156,13 +1156,17 @@ export function App(): ReactElement {
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenBestDebridLogin = async (): Promise<void> => {
|
||||
const onImportBestDebridCookies = async (): Promise<void> => {
|
||||
await performQuickAction(async () => {
|
||||
await persistDraftSettings();
|
||||
await window.rd.openBestDebridLogin();
|
||||
showToast("BestDebrid Login-Fenster geöffnet", 2200);
|
||||
const count = await window.rd.importBestDebridCookies();
|
||||
if (count > 0) {
|
||||
showToast(`${count} BestDebrid-Cookies importiert`, 2200);
|
||||
} else {
|
||||
showToast("Keine Cookie-Datei ausgewählt", 2200);
|
||||
}
|
||||
}, (error) => {
|
||||
showToast(`BestDebrid Login fehlgeschlagen: ${String(error)}`, 2800);
|
||||
showToast(`BestDebrid Cookie-Import fehlgeschlagen: ${String(error)}`, 2800);
|
||||
});
|
||||
};
|
||||
|
||||
@ -2854,11 +2858,11 @@ export function App(): ReactElement {
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.megaDebridPreferApi} onChange={(e) => setBool("megaDebridPreferApi", e.target.checked)} /> Mega-Debrid bevorzugt über API (schneller, Fallback auf Web)</label>
|
||||
<label>BestDebrid API Token</label>
|
||||
<input type="password" value={settingsDraft.bestToken} onChange={(e) => setText("bestToken", e.target.value)} />
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.bestDebridUseWebLogin} onChange={(e) => setBool("bestDebridUseWebLogin", e.target.checked)} /> BestDebrid per Web-Login statt API-Token verwenden</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.bestDebridUseWebLogin} onChange={(e) => setBool("bestDebridUseWebLogin", e.target.checked)} /> BestDebrid per Cookie-Import statt API-Token verwenden</label>
|
||||
{settingsDraft.bestDebridUseWebLogin && (
|
||||
<>
|
||||
<div className="hint">Beim ersten Link oder über den Button unten öffnet sich ein BestDebrid-Browserfenster. Der Login läuft dort manuell über die Website.</div>
|
||||
<button className="btn" disabled={actionBusy} onClick={() => { void onOpenBestDebridLogin(); }}>BestDebrid Web-Login öffnen</button>
|
||||
<div className="hint">Exportiere deine BestDebrid-Cookies als Netscape-Textdatei (z.B. mit der Browser-Extension "Get cookies.txt LOCALLY") und importiere sie hier.</div>
|
||||
<button className="btn" disabled={actionBusy} onClick={() => { void onImportBestDebridCookies(); }}>BestDebrid Cookies importieren</button>
|
||||
</>
|
||||
)}
|
||||
<label>AllDebrid API Key</label>
|
||||
|
||||
@ -36,7 +36,7 @@ export const IPC_CHANNELS = {
|
||||
OPEN_SESSION_LOG: "app:open-session-log",
|
||||
OPEN_REALDEBRID_LOGIN: "app:open-realdebrid-login",
|
||||
OPEN_ALLDEBRID_LOGIN: "app:open-alldebrid-login",
|
||||
OPEN_BESTDEBRID_LOGIN: "app:open-bestdebrid-login",
|
||||
IMPORT_BESTDEBRID_COOKIES: "app:import-bestdebrid-cookies",
|
||||
GET_ALLDEBRID_HOST_INFO: "app:get-alldebrid-host-info",
|
||||
RETRY_EXTRACTION: "queue:retry-extraction",
|
||||
EXTRACT_NOW: "queue:extract-now",
|
||||
|
||||
@ -49,7 +49,7 @@ export interface ElectronApi {
|
||||
openSessionLog: () => Promise<void>;
|
||||
openRealDebridLogin: () => Promise<void>;
|
||||
openAllDebridLogin: () => Promise<void>;
|
||||
openBestDebridLogin: () => Promise<void>;
|
||||
importBestDebridCookies: () => Promise<number>;
|
||||
getAllDebridHostInfo: () => Promise<AllDebridHostInfo>;
|
||||
retryExtraction: (packageId: string) => Promise<void>;
|
||||
extractNow: (packageId: string) => Promise<void>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user