feat(bestdebrid): add BestDebrid web-login provider

- New BestDebrid web-login option (BrowserWindow + session.fetch)
- Uses bestdebrid.com/api/v1/generateLink with browser session cookies
- Login via BestDebrid website in embedded browser window
- Toggle "BestDebrid per Web-Login statt API-Token verwenden"
- Provider label shows "BestDebrid (Web)" or "BestDebrid (API)"
- Session persistence respects "Token merken" setting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-06 11:26:48 +01:00
parent 54e502b9af
commit 438a9f209e
12 changed files with 368 additions and 7 deletions

View File

@ -23,6 +23,7 @@ import { fetchAllDebridHostInfo } from "./debrid";
import { parseCollectorInput } from "./link-parser";
import { configureLogger, getLogFilePath, logger } from "./logger";
import { AllDebridWebFallback } from "./all-debrid-web";
import { BestDebridWebFallback } from "./bestdebrid-web";
import { RealDebridWebFallback } from "./realdebrid-web";
import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log";
import { MegaWebFallback } from "./mega-web-fallback";
@ -50,6 +51,8 @@ export class AppController {
private allDebridWebFallback: AllDebridWebFallback;
private bestDebridWebFallback: BestDebridWebFallback;
private lastUpdateCheck: UpdateCheckResult | null = null;
private lastUpdateCheckAt = 0;
@ -71,10 +74,12 @@ export class AppController {
}));
this.realDebridWebFallback = new RealDebridWebFallback(() => this.settings.rememberToken);
this.allDebridWebFallback = new AllDebridWebFallback(() => this.settings.rememberToken);
this.bestDebridWebFallback = new BestDebridWebFallback(() => this.settings.rememberToken);
this.manager = new DownloadManager(this.settings, session, this.storagePaths, {
megaWebUnrestrict: (link: string, signal?: AbortSignal) => this.megaWebFallback.unrestrict(link, signal),
allDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.allDebridWebFallback.unrestrict(link, signal),
realDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.realDebridWebFallback.unrestrict(link, signal),
bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
onHistoryEntry: (entry: HistoryEntry) => {
addHistoryEntry(this.storagePaths, entry);
@ -117,6 +122,7 @@ export class AppController {
|| settings.realDebridUseWebLogin
|| (settings.megaLogin.trim() && settings.megaPassword.trim())
|| settings.bestToken.trim()
|| settings.bestDebridUseWebLogin
|| settings.allDebridUseWebLogin
|| settings.allDebridToken.trim()
|| (settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim())
@ -180,6 +186,9 @@ export class AppController {
void this.allDebridWebFallback.clearSessions().catch((error) => {
logger.warn(`AllDebrid Web-Session konnte nicht gelöscht werden: ${String(error)}`);
});
void this.bestDebridWebFallback.clearSessions().catch((error) => {
logger.warn(`BestDebrid Web-Session konnte nicht gelöscht werden: ${String(error)}`);
});
}
return this.settings;
}
@ -192,6 +201,10 @@ export class AppController {
await this.allDebridWebFallback.openLoginWindow();
}
public async openBestDebridLoginWindow(): Promise<void> {
await this.bestDebridWebFallback.openLoginWindow();
}
public async getAllDebridHostInfo(host = "rapidgator"): Promise<AllDebridHostInfo> {
if (this.settings.allDebridUseWebLogin) {
return this.allDebridWebFallback.getHostInfo(host);
@ -394,6 +407,7 @@ export class AppController {
this.megaWebFallback.dispose();
this.realDebridWebFallback.dispose();
this.allDebridWebFallback.dispose();
this.bestDebridWebFallback.dispose();
shutdownSessionLog();
logger.info("App beendet");
}

301
src/main/bestdebrid-web.ts Normal file
View File

@ -0,0 +1,301 @@
import { BrowserWindow, session } from "electron";
import { UnrestrictedLink } from "./realdebrid";
import { filenameFromUrl, sleep } from "./utils";
const BESTDEBRID_BASE_URL = "https://bestdebrid.com";
const BESTDEBRID_LOGIN_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");
}
function withTimeoutSignal(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal {
const timeoutSignal = AbortSignal.timeout(timeoutMs);
if (!signal) {
return timeoutSignal;
}
return AbortSignal.any([signal, timeoutSignal]);
}
function throwIfAborted(signal?: AbortSignal): void {
if (signal?.aborted) {
throw abortError();
}
}
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;
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return null;
}
return parsed as Record<string, unknown>;
} catch {
return null;
}
}
export class BestDebridWebFallback {
private queue: Promise<unknown> = Promise.resolve();
private loginWindow: BrowserWindow | null = null;
private loginWindowPartition = "";
private getRememberSession: () => boolean;
public constructor(getRememberSession: () => boolean) {
this.getRememberSession = getRememberSession;
}
public async unrestrict(link: string, signal?: AbortSignal): Promise<UnrestrictedLink | null> {
const overallSignal = withTimeoutSignal(signal, 10 * 60 * 1000);
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;
}
return this.waitForLoginAndGenerate(link, overallSignal);
}, overallSignal);
}
public async openLoginWindow(): Promise<void> {
const window = await this.ensureLoginWindow();
if (window.isMinimized()) {
window.restore();
}
window.show();
window.focus();
}
public async clearSessions(): Promise<void> {
this.disposeLoginWindow();
for (const partition of [BESTDEBRID_PERSISTENT_PARTITION, BESTDEBRID_TRANSIENT_PARTITION]) {
const currentSession = session.fromPartition(partition);
try {
await currentSession.clearStorageData({
storages: ["cookies", "indexdb", "localstorage", "serviceworkers", "cachestorage"]
});
} catch {
// ignore
}
try {
await currentSession.clearCache();
} catch {
// ignore
}
}
}
public dispose(): void {
this.disposeLoginWindow();
}
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;
const guardedJob = async (): Promise<T> => {
throwIfAborted(signal);
const waited = Date.now() - queuedAt;
if (waited > queueWaitTimeoutMs) {
throw new Error(`BestDebrid-Web Queue-Timeout (${Math.floor(waited / 1000)}s gewartet)`);
}
return job();
};
const run = this.queue.then(guardedJob, guardedJob);
this.queue = run.then(() => undefined, () => undefined);
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();
}
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.setMenuBarVisibility(false);
window.on("closed", () => {
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> {
throwIfAborted(signal);
const currentSession = session.fromPartition(this.getPartition());
const response = await currentSession.fetch(BESTDEBRID_GENERATE_URL, {
method: "POST",
headers: {
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,
"User-Agent": BESTDEBRID_USER_AGENT,
"X-Requested-With": "XMLHttpRequest"
},
body: new URLSearchParams({ link, pass: "", boxlinklist: "" }).toString(),
signal: withTimeoutSignal(signal, 30_000)
});
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" };
}
const payload = parseJson(text.trim());
if (!payload) {
return { kind: "login_required" };
}
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" };
}
throw new Error(`BestDebrid Web: ${message || "Unbekannter Fehler"}`);
}
const directUrl = String(payload.link || "").trim();
if (!directUrl) {
throw new Error("BestDebrid Web: Antwort ohne Download-Link");
}
const fileName = String(payload.filename || "").trim() || filenameFromUrl(directUrl) || filenameFromUrl(link);
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);
if (match) {
const value = parseFloat(match[1]);
const unit = match[2].toUpperCase();
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));
}
}
return {
kind: "success",
value: {
directUrl,
fileName,
fileSize,
retriesUsed: 0
}
};
}
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");
}
}

View File

@ -46,6 +46,7 @@ export function defaultSettings(): AppSettings {
megaPassword: "",
megaDebridPreferApi: true,
bestToken: "",
bestDebridUseWebLogin: false,
allDebridToken: "",
allDebridUseWebLogin: false,
ddownloadLogin: "",

View File

@ -34,11 +34,13 @@ interface ProviderUnrestrictedLink extends UnrestrictedLink {
export type MegaWebUnrestrictor = (link: string, signal?: AbortSignal) => Promise<UnrestrictedLink | null>;
export type AllDebridWebUnrestrictor = (link: string, signal?: AbortSignal) => Promise<UnrestrictedLink | null>;
export type RealDebridWebUnrestrictor = (link: string, signal?: AbortSignal) => Promise<UnrestrictedLink | null>;
export type BestDebridWebUnrestrictor = (link: string, signal?: AbortSignal) => Promise<UnrestrictedLink | null>;
interface DebridServiceOptions {
megaWebUnrestrict?: MegaWebUnrestrictor;
allDebridWebUnrestrict?: AllDebridWebUnrestrictor;
realDebridWebUnrestrict?: RealDebridWebUnrestrictor;
bestDebridWebUnrestrict?: BestDebridWebUnrestrictor;
}
function cloneSettings(settings: AppSettings): AppSettings {
@ -1585,6 +1587,10 @@ export class DebridService {
return Boolean(settings.allDebridUseWebLogin && this.options.allDebridWebUnrestrict);
}
private shouldUseBestDebridWeb(settings: AppSettings): boolean {
return Boolean(settings.bestDebridUseWebLogin && this.options.bestDebridWebUnrestrict);
}
public async unrestrictLink(link: string, signal?: AbortSignal, settingsSnapshot?: AppSettings): Promise<ProviderUnrestrictedLink> {
const settings = settingsSnapshot ? cloneSettings(settingsSnapshot) : cloneSettings(this.settings);
@ -1718,7 +1724,7 @@ export class DebridService {
if (provider === "onefichier") {
return Boolean(settings.oneFichierApiKey.trim());
}
return Boolean(settings.bestToken.trim());
return Boolean(this.shouldUseBestDebridWeb(settings) || settings.bestToken.trim());
}
private async unrestrictViaProvider(settings: AppSettings, provider: DebridProvider, link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
@ -1757,6 +1763,16 @@ export class DebridService {
if (provider === "onefichier") {
return new OneFichierClient(settings.oneFichierApiKey).unrestrictLink(link, signal);
}
return new BestDebridClient(settings.bestToken).unrestrictLink(link, signal);
if (this.shouldUseBestDebridWeb(settings) && this.options.bestDebridWebUnrestrict) {
const bdResult = await this.options.bestDebridWebUnrestrict(link, signal);
if (!bdResult) {
throw new Error("BestDebrid-Web-Fallback nicht verfügbar");
}
bdResult.sourceLabel = "Web";
return bdResult;
}
const bdResult = await new BestDebridClient(settings.bestToken).unrestrictLink(link, signal);
bdResult.sourceLabel = "API";
return bdResult;
}
}

View File

@ -38,7 +38,7 @@ function releaseTlsSkip(): void {
}
}
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
import { AllDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline } from "./debrid";
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline } from "./debrid";
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree } from "./extractor";
import { validateFileAgainstManifest } from "./integrity";
import { logger } from "./logger";
@ -159,6 +159,7 @@ type DownloadManagerOptions = {
megaWebUnrestrict?: MegaWebUnrestrictor;
allDebridWebUnrestrict?: AllDebridWebUnrestrictor;
realDebridWebUnrestrict?: RealDebridWebUnrestrictor;
bestDebridWebUnrestrict?: BestDebridWebUnrestrictor;
invalidateMegaSession?: () => void;
onHistoryEntry?: HistoryEntryCallback;
};
@ -953,7 +954,8 @@ export class DownloadManager extends EventEmitter {
this.debridService = new DebridService(settings, {
megaWebUnrestrict: options.megaWebUnrestrict,
allDebridWebUnrestrict: options.allDebridWebUnrestrict,
realDebridWebUnrestrict: options.realDebridWebUnrestrict
realDebridWebUnrestrict: options.realDebridWebUnrestrict,
bestDebridWebUnrestrict: options.bestDebridWebUnrestrict
});
this.invalidateMegaSessionFn = options.invalidateMegaSession;
this.onHistoryEntryCallback = options.onHistoryEntry;

View File

@ -454,6 +454,10 @@ function registerIpcHandlers(): void {
await controller.openAllDebridLoginWindow();
});
ipcMain.handle(IPC_CHANNELS.OPEN_BESTDEBRID_LOGIN, async () => {
await controller.openBestDebridLoginWindow();
});
ipcMain.handle(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO, async () => {
return controller.getAllDebridHostInfo();
});

View File

@ -112,6 +112,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
megaPassword: asText(settings.megaPassword),
megaDebridPreferApi: settings.megaDebridPreferApi !== undefined ? Boolean(settings.megaDebridPreferApi) : true,
bestToken: asText(settings.bestToken),
bestDebridUseWebLogin: Boolean(settings.bestDebridUseWebLogin),
allDebridToken: asText(settings.allDebridToken),
allDebridUseWebLogin: Boolean(settings.allDebridUseWebLogin),
ddownloadLogin: asText(settings.ddownloadLogin),
@ -207,6 +208,7 @@ function sanitizeCredentialPersistence(settings: AppSettings): AppSettings {
megaLogin: "",
megaPassword: "",
bestToken: "",
bestDebridUseWebLogin: settings.bestDebridUseWebLogin,
allDebridToken: "",
ddownloadLogin: "",
ddownloadPassword: "",

View File

@ -54,6 +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),
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),

View File

@ -63,7 +63,7 @@ const emptyStats = (): DownloadStats => ({
const emptySnapshot = (): UiSnapshot => ({
settings: {
token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridPreferApi: true, bestToken: "", allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "",
token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridPreferApi: true, bestToken: "", bestDebridUseWebLogin: false, allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "",
archivePasswordList: "",
rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid",
providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
@ -988,14 +988,14 @@ export function App(): ReactElement {
if (settingsDraft.megaLogin.trim() && settingsDraft.megaPassword.trim()) {
list.push("megadebrid");
}
if (settingsDraft.bestToken.trim()) {
if (settingsDraft.bestDebridUseWebLogin || settingsDraft.bestToken.trim()) {
list.push("bestdebrid");
}
if (settingsDraft.allDebridUseWebLogin || settingsDraft.allDebridToken.trim()) {
list.push("alldebrid");
}
return list;
}, [settingsDraft.token, settingsDraft.realDebridUseWebLogin, settingsDraft.megaLogin, settingsDraft.megaPassword, settingsDraft.bestToken, settingsDraft.allDebridToken, settingsDraft.allDebridUseWebLogin]);
}, [settingsDraft.token, settingsDraft.realDebridUseWebLogin, settingsDraft.megaLogin, settingsDraft.megaPassword, settingsDraft.bestToken, settingsDraft.bestDebridUseWebLogin, settingsDraft.allDebridToken, settingsDraft.allDebridUseWebLogin]);
// DDownload is a direct file hoster (not a debrid service) and is used automatically
// for ddownload.com/ddl.to URLs. It counts as a configured account but does not
@ -1156,6 +1156,16 @@ export function App(): ReactElement {
});
};
const onOpenBestDebridLogin = async (): Promise<void> => {
await performQuickAction(async () => {
await persistDraftSettings();
await window.rd.openBestDebridLogin();
showToast("BestDebrid Login-Fenster geöffnet", 2200);
}, (error) => {
showToast(`BestDebrid Login fehlgeschlagen: ${String(error)}`, 2800);
});
};
const onCheckUpdates = async (): Promise<void> => {
let updateResult: UpdateCheckResult | null = null;
await performQuickAction(async () => {
@ -2844,6 +2854,13 @@ 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>
{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>
</>
)}
<label>AllDebrid API Key</label>
<input type="password" value={settingsDraft.allDebridToken} onChange={(e) => setText("allDebridToken", e.target.value)} />
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.allDebridUseWebLogin} onChange={(e) => setBool("allDebridUseWebLogin", e.target.checked)} /> AllDebrid per Web-Login statt API-Key verwenden</label>

View File

@ -36,6 +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",
GET_ALLDEBRID_HOST_INFO: "app:get-alldebrid-host-info",
RETRY_EXTRACTION: "queue:retry-extraction",
EXTRACT_NOW: "queue:extract-now",

View File

@ -49,6 +49,7 @@ export interface ElectronApi {
openSessionLog: () => Promise<void>;
openRealDebridLogin: () => Promise<void>;
openAllDebridLogin: () => Promise<void>;
openBestDebridLogin: () => Promise<void>;
getAllDebridHostInfo: () => Promise<AllDebridHostInfo>;
retryExtraction: (packageId: string) => Promise<void>;
extractNow: (packageId: string) => Promise<void>;

View File

@ -43,6 +43,7 @@ export interface AppSettings {
megaPassword: string;
megaDebridPreferApi: boolean;
bestToken: string;
bestDebridUseWebLogin: boolean;
allDebridToken: string;
allDebridUseWebLogin: boolean;
ddownloadLogin: string;