Release v1.6.90
This commit is contained in:
parent
0eb3403e40
commit
0003d786d8
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.89",
|
"version": "1.6.90",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.89",
|
"version": "1.6.90",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.89",
|
"version": "1.6.90",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -44,6 +44,8 @@ export function defaultSettings(): AppSettings {
|
|||||||
realDebridUseWebLogin: false,
|
realDebridUseWebLogin: false,
|
||||||
megaLogin: "",
|
megaLogin: "",
|
||||||
megaPassword: "",
|
megaPassword: "",
|
||||||
|
megaDebridApiEnabled: false,
|
||||||
|
megaDebridWebEnabled: false,
|
||||||
megaDebridPreferApi: true,
|
megaDebridPreferApi: true,
|
||||||
bestToken: "",
|
bestToken: "",
|
||||||
bestDebridUseWebLogin: false,
|
bestDebridUseWebLogin: false,
|
||||||
@ -58,7 +60,7 @@ export function defaultSettings(): AppSettings {
|
|||||||
archivePasswordList: "",
|
archivePasswordList: "",
|
||||||
rememberToken: true,
|
rememberToken: true,
|
||||||
providerPrimary: "realdebrid",
|
providerPrimary: "realdebrid",
|
||||||
providerSecondary: "megadebrid",
|
providerSecondary: "megadebrid-api",
|
||||||
providerTertiary: "bestdebrid",
|
providerTertiary: "bestdebrid",
|
||||||
autoProviderFallback: true,
|
autoProviderFallback: true,
|
||||||
outputDir: baseDir,
|
outputDir: baseDir,
|
||||||
|
|||||||
@ -25,6 +25,8 @@ const LINKSNAPPY_API_BASE = "https://linksnappy.com/api";
|
|||||||
const PROVIDER_LABELS: Record<DebridProvider, string> = {
|
const PROVIDER_LABELS: Record<DebridProvider, string> = {
|
||||||
realdebrid: "Real-Debrid",
|
realdebrid: "Real-Debrid",
|
||||||
megadebrid: "Mega-Debrid",
|
megadebrid: "Mega-Debrid",
|
||||||
|
"megadebrid-api": "Mega-Debrid API",
|
||||||
|
"megadebrid-web": "Mega-Debrid Web",
|
||||||
bestdebrid: "BestDebrid",
|
bestdebrid: "BestDebrid",
|
||||||
alldebrid: "AllDebrid",
|
alldebrid: "AllDebrid",
|
||||||
ddownload: "DDownload",
|
ddownload: "DDownload",
|
||||||
@ -67,6 +69,32 @@ function cloneSettings(settings: AppSettings): AppSettings {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasMegaDebridCredentials(settings: AppSettings): boolean {
|
||||||
|
return Boolean(settings.megaLogin.trim() && settings.megaPassword.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMegaDebridModeEnabled(settings: AppSettings, mode: "api" | "web"): boolean {
|
||||||
|
if (mode === "api") {
|
||||||
|
return settings.megaDebridApiEnabled
|
||||||
|
|| (hasMegaDebridCredentials(settings) && !settings.megaDebridApiEnabled && !settings.megaDebridWebEnabled && settings.megaDebridPreferApi);
|
||||||
|
}
|
||||||
|
return settings.megaDebridWebEnabled
|
||||||
|
|| (hasMegaDebridCredentials(settings) && !settings.megaDebridApiEnabled && !settings.megaDebridWebEnabled && !settings.megaDebridPreferApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMegaDebridProvider(settings: AppSettings, provider: DebridProvider): DebridProvider {
|
||||||
|
if (provider !== "megadebrid") {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
if (isMegaDebridModeEnabled(settings, "api") && !isMegaDebridModeEnabled(settings, "web")) {
|
||||||
|
return "megadebrid-api";
|
||||||
|
}
|
||||||
|
if (isMegaDebridModeEnabled(settings, "web") && !isMegaDebridModeEnabled(settings, "api")) {
|
||||||
|
return "megadebrid-web";
|
||||||
|
}
|
||||||
|
return settings.megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web";
|
||||||
|
}
|
||||||
|
|
||||||
type BestDebridRequest = {
|
type BestDebridRequest = {
|
||||||
url: string;
|
url: string;
|
||||||
useAuthHeader: boolean;
|
useAuthHeader: boolean;
|
||||||
@ -692,7 +720,9 @@ class MegaDebridClient {
|
|||||||
|
|
||||||
private password: string;
|
private password: string;
|
||||||
|
|
||||||
private preferApi: boolean;
|
private mode: "api" | "web";
|
||||||
|
|
||||||
|
private allowApiFallback: boolean;
|
||||||
|
|
||||||
private static cachedApiToken = "";
|
private static cachedApiToken = "";
|
||||||
|
|
||||||
@ -700,10 +730,11 @@ class MegaDebridClient {
|
|||||||
|
|
||||||
private static pendingConnect: Promise<string | null> | null = null;
|
private static pendingConnect: Promise<string | null> | null = null;
|
||||||
|
|
||||||
public constructor(login: string, password: string, preferApi: boolean, megaWebUnrestrict?: MegaWebUnrestrictor) {
|
public constructor(login: string, password: string, mode: "api" | "web", allowApiFallback: boolean, megaWebUnrestrict?: MegaWebUnrestrictor) {
|
||||||
this.login = login;
|
this.login = login;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.preferApi = preferApi;
|
this.mode = mode;
|
||||||
|
this.allowApiFallback = allowApiFallback;
|
||||||
this.megaWebUnrestrict = megaWebUnrestrict;
|
this.megaWebUnrestrict = megaWebUnrestrict;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -839,25 +870,27 @@ class MegaDebridClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async unrestrictLink(link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
|
public async unrestrictLink(link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
|
||||||
if (this.preferApi && this.login.trim() && this.password.trim()) {
|
if (this.mode === "api" && this.login.trim() && this.password.trim()) {
|
||||||
// API mode: try API first, fall back to web on failure
|
|
||||||
try {
|
try {
|
||||||
const apiResult = await this.unrestrictViaApi(link, signal);
|
const apiResult = await this.unrestrictViaApi(link, signal);
|
||||||
if (apiResult) {
|
if (apiResult) {
|
||||||
logger.info(`Mega-Debrid (API) unrestrict OK: ${apiResult.fileName}`);
|
logger.info(`Mega-Debrid (API) unrestrict OK: ${apiResult.fileName}`);
|
||||||
return apiResult;
|
return apiResult;
|
||||||
}
|
}
|
||||||
|
throw new Error("Mega-Debrid API: Login oder Unrestrict fehlgeschlagen");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorText = compactErrorText(error);
|
const errorText = compactErrorText(error);
|
||||||
if (signal?.aborted || (/aborted/i.test(errorText) && !/timeout/i.test(errorText))) {
|
if (signal?.aborted || (/aborted/i.test(errorText) && !/timeout/i.test(errorText))) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
if (!this.allowApiFallback) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
logger.warn(`Mega-Debrid API fehlgeschlagen, versuche Web-Fallback: ${errorText}`);
|
logger.warn(`Mega-Debrid API fehlgeschlagen, versuche Web-Fallback: ${errorText}`);
|
||||||
}
|
}
|
||||||
return this.unrestrictViaWeb(link, signal);
|
return this.unrestrictViaWeb(link, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web mode only
|
|
||||||
return this.unrestrictViaWeb(link, signal);
|
return this.unrestrictViaWeb(link, signal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2036,33 +2069,38 @@ export class DebridService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isProviderConfiguredFor(settings: AppSettings, provider: DebridProvider): boolean {
|
private isProviderConfiguredFor(settings: AppSettings, provider: DebridProvider): boolean {
|
||||||
if ((settings.disabledProviders || []).includes(provider)) return false;
|
const effectiveProvider = resolveMegaDebridProvider(settings, provider);
|
||||||
if (provider === "realdebrid") {
|
if ((settings.disabledProviders || []).includes(provider) || (settings.disabledProviders || []).includes(effectiveProvider)) return false;
|
||||||
|
if (effectiveProvider === "realdebrid") {
|
||||||
return Boolean(this.shouldUseRealDebridWeb(settings) || settings.token.trim());
|
return Boolean(this.shouldUseRealDebridWeb(settings) || settings.token.trim());
|
||||||
}
|
}
|
||||||
if (provider === "megadebrid") {
|
if (effectiveProvider === "megadebrid-api") {
|
||||||
return Boolean(settings.megaLogin.trim() && settings.megaPassword.trim());
|
return Boolean(hasMegaDebridCredentials(settings) && isMegaDebridModeEnabled(settings, "api"));
|
||||||
}
|
}
|
||||||
if (provider === "alldebrid") {
|
if (effectiveProvider === "megadebrid-web") {
|
||||||
|
return Boolean(hasMegaDebridCredentials(settings) && isMegaDebridModeEnabled(settings, "web") && this.options.megaWebUnrestrict);
|
||||||
|
}
|
||||||
|
if (effectiveProvider === "alldebrid") {
|
||||||
return Boolean(this.shouldUseAllDebridWeb(settings) || settings.allDebridToken.trim());
|
return Boolean(this.shouldUseAllDebridWeb(settings) || settings.allDebridToken.trim());
|
||||||
}
|
}
|
||||||
if (provider === "ddownload") {
|
if (effectiveProvider === "ddownload") {
|
||||||
return Boolean(settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim());
|
return Boolean(settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim());
|
||||||
}
|
}
|
||||||
if (provider === "onefichier") {
|
if (effectiveProvider === "onefichier") {
|
||||||
return Boolean(settings.oneFichierApiKey.trim());
|
return Boolean(settings.oneFichierApiKey.trim());
|
||||||
}
|
}
|
||||||
if (provider === "debridlink") {
|
if (effectiveProvider === "debridlink") {
|
||||||
return Boolean(settings.debridLinkApiKeys.trim());
|
return Boolean(settings.debridLinkApiKeys.trim());
|
||||||
}
|
}
|
||||||
if (provider === "linksnappy") {
|
if (effectiveProvider === "linksnappy") {
|
||||||
return Boolean(settings.linkSnappyLogin.trim() && settings.linkSnappyPassword.trim());
|
return Boolean(settings.linkSnappyLogin.trim() && settings.linkSnappyPassword.trim());
|
||||||
}
|
}
|
||||||
return Boolean(this.shouldUseBestDebridWeb(settings) || settings.bestToken.trim());
|
return Boolean(this.shouldUseBestDebridWeb(settings) || settings.bestToken.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async unrestrictViaProvider(settings: AppSettings, provider: DebridProvider, link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
|
private async unrestrictViaProvider(settings: AppSettings, provider: DebridProvider, link: string, signal?: AbortSignal): Promise<UnrestrictedLink> {
|
||||||
if (provider === "realdebrid") {
|
const effectiveProvider = resolveMegaDebridProvider(settings, provider);
|
||||||
|
if (effectiveProvider === "realdebrid") {
|
||||||
if (this.shouldUseRealDebridWeb(settings) && this.options.realDebridWebUnrestrict) {
|
if (this.shouldUseRealDebridWeb(settings) && this.options.realDebridWebUnrestrict) {
|
||||||
const result = await this.options.realDebridWebUnrestrict(link, signal);
|
const result = await this.options.realDebridWebUnrestrict(link, signal);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -2075,10 +2113,13 @@ export class DebridService {
|
|||||||
result.sourceLabel = "API";
|
result.sourceLabel = "API";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (provider === "megadebrid") {
|
if (effectiveProvider === "megadebrid-api") {
|
||||||
return new MegaDebridClient(settings.megaLogin, settings.megaPassword, settings.megaDebridPreferApi, this.options.megaWebUnrestrict).unrestrictLink(link, signal);
|
return new MegaDebridClient(settings.megaLogin, settings.megaPassword, "api", provider === "megadebrid" && settings.megaDebridPreferApi, this.options.megaWebUnrestrict).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
if (provider === "alldebrid") {
|
if (effectiveProvider === "megadebrid-web") {
|
||||||
|
return new MegaDebridClient(settings.megaLogin, settings.megaPassword, "web", false, this.options.megaWebUnrestrict).unrestrictLink(link, signal);
|
||||||
|
}
|
||||||
|
if (effectiveProvider === "alldebrid") {
|
||||||
if (this.shouldUseAllDebridWeb(settings) && this.options.allDebridWebUnrestrict) {
|
if (this.shouldUseAllDebridWeb(settings) && this.options.allDebridWebUnrestrict) {
|
||||||
const result = await this.options.allDebridWebUnrestrict(link, signal);
|
const result = await this.options.allDebridWebUnrestrict(link, signal);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -2091,18 +2132,18 @@ export class DebridService {
|
|||||||
adResult.sourceLabel = "API";
|
adResult.sourceLabel = "API";
|
||||||
return adResult;
|
return adResult;
|
||||||
}
|
}
|
||||||
if (provider === "ddownload") {
|
if (effectiveProvider === "ddownload") {
|
||||||
return this.getDdownloadClient(settings.ddownloadLogin, settings.ddownloadPassword).unrestrictLink(link, signal);
|
return this.getDdownloadClient(settings.ddownloadLogin, settings.ddownloadPassword).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
if (provider === "onefichier") {
|
if (effectiveProvider === "onefichier") {
|
||||||
return new OneFichierClient(settings.oneFichierApiKey).unrestrictLink(link, signal);
|
return new OneFichierClient(settings.oneFichierApiKey).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
if (provider === "debridlink") {
|
if (effectiveProvider === "debridlink") {
|
||||||
const dlResult = await this.getDebridLinkClient(settings.debridLinkApiKeys).unrestrictLink(link, signal);
|
const dlResult = await this.getDebridLinkClient(settings.debridLinkApiKeys).unrestrictLink(link, signal);
|
||||||
dlResult.sourceLabel = dlResult.sourceLabel || "API";
|
dlResult.sourceLabel = dlResult.sourceLabel || "API";
|
||||||
return dlResult;
|
return dlResult;
|
||||||
}
|
}
|
||||||
if (provider === "linksnappy") {
|
if (effectiveProvider === "linksnappy") {
|
||||||
return this.getLinkSnappyClient(settings.linkSnappyLogin, settings.linkSnappyPassword).unrestrictLink(link, signal);
|
return this.getLinkSnappyClient(settings.linkSnappyLogin, settings.linkSnappyPassword).unrestrictLink(link, signal);
|
||||||
}
|
}
|
||||||
if (this.shouldUseBestDebridWeb(settings) && this.options.bestDebridWebUnrestrict) {
|
if (this.shouldUseBestDebridWeb(settings) && this.options.bestDebridWebUnrestrict) {
|
||||||
|
|||||||
@ -371,6 +371,12 @@ function providerLabel(provider: DownloadItem["provider"]): string {
|
|||||||
if (provider === "megadebrid") {
|
if (provider === "megadebrid") {
|
||||||
return "Mega-Debrid";
|
return "Mega-Debrid";
|
||||||
}
|
}
|
||||||
|
if (provider === "megadebrid-api") {
|
||||||
|
return "Mega-Debrid API";
|
||||||
|
}
|
||||||
|
if (provider === "megadebrid-web") {
|
||||||
|
return "Mega-Debrid Web";
|
||||||
|
}
|
||||||
if (provider === "bestdebrid") {
|
if (provider === "bestdebrid") {
|
||||||
return "BestDebrid";
|
return "BestDebrid";
|
||||||
}
|
}
|
||||||
@ -383,6 +389,23 @@ function providerLabel(provider: DownloadItem["provider"]): string {
|
|||||||
return "Debrid";
|
return "Debrid";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveMegaDebridProvider(settings: AppSettings, provider: DebridProvider | null): DebridProvider | null {
|
||||||
|
if (provider !== "megadebrid") {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
const apiEnabled = settings.megaDebridApiEnabled
|
||||||
|
|| (settings.megaLogin.trim() && settings.megaPassword.trim() && !settings.megaDebridApiEnabled && !settings.megaDebridWebEnabled && settings.megaDebridPreferApi);
|
||||||
|
const webEnabled = settings.megaDebridWebEnabled
|
||||||
|
|| (settings.megaLogin.trim() && settings.megaPassword.trim() && !settings.megaDebridApiEnabled && !settings.megaDebridWebEnabled && !settings.megaDebridPreferApi);
|
||||||
|
if (apiEnabled && !webEnabled) {
|
||||||
|
return "megadebrid-api";
|
||||||
|
}
|
||||||
|
if (webEnabled && !apiEnabled) {
|
||||||
|
return "megadebrid-web";
|
||||||
|
}
|
||||||
|
return settings.megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web";
|
||||||
|
}
|
||||||
|
|
||||||
function pathKey(filePath: string): string {
|
function pathKey(filePath: string): string {
|
||||||
const resolved = path.resolve(filePath);
|
const resolved = path.resolve(filePath);
|
||||||
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
||||||
@ -3462,7 +3485,16 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.session.reconnectReason = "";
|
this.session.reconnectReason = "";
|
||||||
|
|
||||||
for (const item of Object.values(this.session.items)) {
|
for (const item of Object.values(this.session.items)) {
|
||||||
if (item.provider !== "realdebrid" && item.provider !== "megadebrid" && item.provider !== "bestdebrid" && item.provider !== "alldebrid" && item.provider !== "ddownload") {
|
if (item.provider === "megadebrid") {
|
||||||
|
item.provider = resolveMegaDebridProvider(this.settings, item.provider);
|
||||||
|
}
|
||||||
|
if (item.provider !== "realdebrid"
|
||||||
|
&& item.provider !== "megadebrid"
|
||||||
|
&& item.provider !== "megadebrid-api"
|
||||||
|
&& item.provider !== "megadebrid-web"
|
||||||
|
&& item.provider !== "bestdebrid"
|
||||||
|
&& item.provider !== "alldebrid"
|
||||||
|
&& item.provider !== "ddownload") {
|
||||||
item.provider = null;
|
item.provider = null;
|
||||||
}
|
}
|
||||||
if (item.status === "cancelled" && item.fullStatus === "Gestoppt") {
|
if (item.status === "cancelled" && item.fullStatus === "Gestoppt") {
|
||||||
@ -4302,7 +4334,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
entry.cooldownUntil = now + cooldownMs;
|
entry.cooldownUntil = now + cooldownMs;
|
||||||
logger.warn(`Provider Circuit-Breaker: ${key} ${entry.count} konsekutive Fehler, Cooldown ${cooldownMs / 1000}s`);
|
logger.warn(`Provider Circuit-Breaker: ${key} ${entry.count} konsekutive Fehler, Cooldown ${cooldownMs / 1000}s`);
|
||||||
// Invalidate mega-debrid session on cooldown to force fresh login
|
// Invalidate mega-debrid session on cooldown to force fresh login
|
||||||
if (key === "megadebrid" && this.invalidateMegaSessionFn) {
|
if ((key === "megadebrid" || key === "megadebrid-api" || key === "megadebrid-web") && this.invalidateMegaSessionFn) {
|
||||||
try {
|
try {
|
||||||
this.invalidateMegaSessionFn();
|
this.invalidateMegaSessionFn();
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
@ -4344,28 +4376,34 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isProviderConfigured(provider: DebridProvider): boolean {
|
private isProviderConfigured(provider: DebridProvider): boolean {
|
||||||
if ((this.settings.disabledProviders || []).includes(provider)) {
|
const effectiveProvider = resolveMegaDebridProvider(this.settings, provider) || provider;
|
||||||
|
if ((this.settings.disabledProviders || []).includes(provider) || (this.settings.disabledProviders || []).includes(effectiveProvider)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (provider === "realdebrid") {
|
if (effectiveProvider === "realdebrid") {
|
||||||
return Boolean(this.settings.realDebridUseWebLogin || this.settings.token.trim());
|
return Boolean(this.settings.realDebridUseWebLogin || this.settings.token.trim());
|
||||||
}
|
}
|
||||||
if (provider === "megadebrid") {
|
if (effectiveProvider === "megadebrid-api") {
|
||||||
return Boolean(this.settings.megaLogin.trim() && this.settings.megaPassword.trim());
|
return Boolean(resolveMegaDebridProvider(this.settings, "megadebrid") === "megadebrid-api" && this.settings.megaLogin.trim() && this.settings.megaPassword.trim()
|
||||||
|
|| this.settings.megaDebridApiEnabled && this.settings.megaLogin.trim() && this.settings.megaPassword.trim());
|
||||||
}
|
}
|
||||||
if (provider === "bestdebrid") {
|
if (effectiveProvider === "megadebrid-web") {
|
||||||
|
return Boolean(resolveMegaDebridProvider(this.settings, "megadebrid") === "megadebrid-web" && this.settings.megaLogin.trim() && this.settings.megaPassword.trim()
|
||||||
|
|| this.settings.megaDebridWebEnabled && this.settings.megaLogin.trim() && this.settings.megaPassword.trim());
|
||||||
|
}
|
||||||
|
if (effectiveProvider === "bestdebrid") {
|
||||||
return Boolean(this.settings.bestDebridUseWebLogin || this.settings.bestToken.trim());
|
return Boolean(this.settings.bestDebridUseWebLogin || this.settings.bestToken.trim());
|
||||||
}
|
}
|
||||||
if (provider === "alldebrid") {
|
if (effectiveProvider === "alldebrid") {
|
||||||
return Boolean(this.settings.allDebridUseWebLogin || this.settings.allDebridToken.trim());
|
return Boolean(this.settings.allDebridUseWebLogin || this.settings.allDebridToken.trim());
|
||||||
}
|
}
|
||||||
if (provider === "ddownload") {
|
if (effectiveProvider === "ddownload") {
|
||||||
return Boolean(this.settings.ddownloadLogin.trim() && this.settings.ddownloadPassword.trim());
|
return Boolean(this.settings.ddownloadLogin.trim() && this.settings.ddownloadPassword.trim());
|
||||||
}
|
}
|
||||||
if (provider === "onefichier") {
|
if (effectiveProvider === "onefichier") {
|
||||||
return Boolean(this.settings.oneFichierApiKey.trim());
|
return Boolean(this.settings.oneFichierApiKey.trim());
|
||||||
}
|
}
|
||||||
if (provider === "debridlink") {
|
if (effectiveProvider === "debridlink") {
|
||||||
return Boolean(this.settings.debridLinkApiKeys.trim());
|
return Boolean(this.settings.debridLinkApiKeys.trim());
|
||||||
}
|
}
|
||||||
if (provider === "linksnappy") {
|
if (provider === "linksnappy") {
|
||||||
@ -4376,7 +4414,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
private getExpectedProviderForItem(item: DownloadItem): DebridProvider | null {
|
private getExpectedProviderForItem(item: DownloadItem): DebridProvider | null {
|
||||||
if (item.provider) {
|
if (item.provider) {
|
||||||
return item.provider;
|
return resolveMegaDebridProvider(this.settings, item.provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hosterKey = extractHosterKey(item.url);
|
const hosterKey = extractHosterKey(item.url);
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import fsp from "node:fs/promises";
|
import fsp from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { AppSettings, BandwidthScheduleEntry, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, PackageEntry, PackagePriority, SessionState } from "../shared/types";
|
import { AppSettings, BandwidthScheduleEntry, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, PackageEntry, PackagePriority, SessionState } from "../shared/types";
|
||||||
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", "onefichier", "debridlink", "linksnappy"]);
|
const VALID_PRIMARY_PROVIDERS = new Set(["realdebrid", "megadebrid-api", "megadebrid-web", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink", "linksnappy"]);
|
||||||
const VALID_FALLBACK_PROVIDERS = new Set(["none", "realdebrid", "megadebrid", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink", "linksnappy"]);
|
const VALID_FALLBACK_PROVIDERS = new Set(["none", "realdebrid", "megadebrid-api", "megadebrid-web", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink", "linksnappy"]);
|
||||||
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", "onefichier", "debridlink"]);
|
const VALID_ITEM_PROVIDERS = new Set<DebridProvider>(["realdebrid", "megadebrid", "megadebrid-api", "megadebrid-web", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink"]);
|
||||||
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 {
|
||||||
@ -91,14 +91,66 @@ function normalizeColumnOrder(raw: unknown): string[] {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHosterRouting(raw: unknown): Record<string, DebridProvider> {
|
function getPreferredMegaDebridProvider(megaDebridPreferApi: boolean, megaDebridApiEnabled: boolean, megaDebridWebEnabled: boolean): DebridProvider {
|
||||||
|
if (megaDebridApiEnabled && !megaDebridWebEnabled) {
|
||||||
|
return "megadebrid-api";
|
||||||
|
}
|
||||||
|
if (megaDebridWebEnabled && !megaDebridApiEnabled) {
|
||||||
|
return "megadebrid-web";
|
||||||
|
}
|
||||||
|
return megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeConfiguredProvider(raw: unknown, megaDebridPreferApi: boolean, megaDebridApiEnabled: boolean, megaDebridWebEnabled: boolean): DebridProvider | null {
|
||||||
|
const provider = String(raw ?? "").trim();
|
||||||
|
if (!provider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (provider === "megadebrid") {
|
||||||
|
return getPreferredMegaDebridProvider(megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled);
|
||||||
|
}
|
||||||
|
return VALID_PRIMARY_PROVIDERS.has(provider) ? provider as DebridProvider : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFallbackProvider(raw: unknown, megaDebridPreferApi: boolean, megaDebridApiEnabled: boolean, megaDebridWebEnabled: boolean): DebridFallbackProvider {
|
||||||
|
const provider = String(raw ?? "").trim();
|
||||||
|
if (!provider || provider === "none") {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
const normalized = normalizeConfiguredProvider(provider, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled);
|
||||||
|
return normalized || "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDisabledProviders(raw: unknown): DebridProvider[] {
|
||||||
|
if (!Array.isArray(raw)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const seen = new Set<DebridProvider>();
|
||||||
|
const result: DebridProvider[] = [];
|
||||||
|
for (const entry of raw) {
|
||||||
|
const provider = String(entry ?? "").trim();
|
||||||
|
const candidates: DebridProvider[] = provider === "megadebrid"
|
||||||
|
? ["megadebrid-api", "megadebrid-web"]
|
||||||
|
: (VALID_PRIMARY_PROVIDERS.has(provider) ? [provider as DebridProvider] : []);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (seen.has(candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(candidate);
|
||||||
|
result.push(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHosterRouting(raw: unknown, megaDebridPreferApi: boolean, megaDebridApiEnabled: boolean, megaDebridWebEnabled: boolean): Record<string, DebridProvider> {
|
||||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
||||||
const result: Record<string, DebridProvider> = {};
|
const result: Record<string, DebridProvider> = {};
|
||||||
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
||||||
const hoster = String(key).trim().toLowerCase();
|
const hoster = String(key).trim().toLowerCase();
|
||||||
const provider = String(value ?? "").trim();
|
const provider = normalizeConfiguredProvider(value, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled);
|
||||||
if (hoster && VALID_PRIMARY_PROVIDERS.has(provider)) {
|
if (hoster && provider) {
|
||||||
result[hoster] = provider as DebridProvider;
|
result[hoster] = provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -118,12 +170,24 @@ function migrateUpdateRepo(raw: string, fallback: string): string {
|
|||||||
|
|
||||||
export function normalizeSettings(settings: AppSettings): AppSettings {
|
export function normalizeSettings(settings: AppSettings): AppSettings {
|
||||||
const defaults = defaultSettings();
|
const defaults = defaultSettings();
|
||||||
|
const megaLogin = asText(settings.megaLogin);
|
||||||
|
const megaPassword = asText(settings.megaPassword);
|
||||||
|
const megaDebridPreferApi = settings.megaDebridPreferApi !== undefined ? Boolean(settings.megaDebridPreferApi) : true;
|
||||||
|
const hasMegaCreds = Boolean(megaLogin && megaPassword);
|
||||||
|
const megaDebridApiEnabled = settings.megaDebridApiEnabled !== undefined
|
||||||
|
? Boolean(settings.megaDebridApiEnabled)
|
||||||
|
: (hasMegaCreds ? megaDebridPreferApi : defaults.megaDebridApiEnabled);
|
||||||
|
const megaDebridWebEnabled = settings.megaDebridWebEnabled !== undefined
|
||||||
|
? Boolean(settings.megaDebridWebEnabled)
|
||||||
|
: (hasMegaCreds ? !megaDebridPreferApi : defaults.megaDebridWebEnabled);
|
||||||
const normalized: AppSettings = {
|
const normalized: AppSettings = {
|
||||||
token: asText(settings.token),
|
token: asText(settings.token),
|
||||||
realDebridUseWebLogin: Boolean(settings.realDebridUseWebLogin),
|
realDebridUseWebLogin: Boolean(settings.realDebridUseWebLogin),
|
||||||
megaLogin: asText(settings.megaLogin),
|
megaLogin,
|
||||||
megaPassword: asText(settings.megaPassword),
|
megaPassword,
|
||||||
megaDebridPreferApi: settings.megaDebridPreferApi !== undefined ? Boolean(settings.megaDebridPreferApi) : true,
|
megaDebridApiEnabled,
|
||||||
|
megaDebridWebEnabled,
|
||||||
|
megaDebridPreferApi,
|
||||||
bestToken: asText(settings.bestToken),
|
bestToken: asText(settings.bestToken),
|
||||||
bestDebridUseWebLogin: Boolean(settings.bestDebridUseWebLogin),
|
bestDebridUseWebLogin: Boolean(settings.bestDebridUseWebLogin),
|
||||||
allDebridToken: asText(settings.allDebridToken),
|
allDebridToken: asText(settings.allDebridToken),
|
||||||
@ -136,9 +200,9 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
|||||||
linkSnappyPassword: asText(settings.linkSnappyPassword),
|
linkSnappyPassword: asText(settings.linkSnappyPassword),
|
||||||
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n|\r/g, "\n"),
|
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n|\r/g, "\n"),
|
||||||
rememberToken: Boolean(settings.rememberToken),
|
rememberToken: Boolean(settings.rememberToken),
|
||||||
providerPrimary: settings.providerPrimary,
|
providerPrimary: normalizeConfiguredProvider(settings.providerPrimary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled) || defaults.providerPrimary,
|
||||||
providerSecondary: settings.providerSecondary,
|
providerSecondary: normalizeFallbackProvider(settings.providerSecondary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),
|
||||||
providerTertiary: settings.providerTertiary,
|
providerTertiary: normalizeFallbackProvider(settings.providerTertiary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),
|
||||||
autoProviderFallback: Boolean(settings.autoProviderFallback),
|
autoProviderFallback: Boolean(settings.autoProviderFallback),
|
||||||
outputDir: normalizeAbsoluteDir(settings.outputDir, defaults.outputDir),
|
outputDir: normalizeAbsoluteDir(settings.outputDir, defaults.outputDir),
|
||||||
packageName: asText(settings.packageName),
|
packageName: asText(settings.packageName),
|
||||||
@ -177,8 +241,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: normalizeDisabledProviders(settings.disabledProviders),
|
||||||
hosterRouting: normalizeHosterRouting(settings.hosterRouting)
|
hosterRouting: normalizeHosterRouting(settings.hosterRouting, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) {
|
||||||
|
|||||||
@ -53,7 +53,7 @@ interface LinkPopupState {
|
|||||||
isPackage: boolean;
|
isPackage: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountService = "realdebrid" | "megadebrid" | "bestdebrid" | "alldebrid" | "ddownload" | "onefichier" | "debridlink" | "linksnappy";
|
type AccountService = "realdebrid" | "megadebrid-api" | "megadebrid-web" | "bestdebrid" | "alldebrid" | "ddownload" | "onefichier" | "debridlink" | "linksnappy";
|
||||||
type AccountKind =
|
type AccountKind =
|
||||||
| "realdebrid-api"
|
| "realdebrid-api"
|
||||||
| "realdebrid-web"
|
| "realdebrid-web"
|
||||||
@ -121,20 +121,20 @@ const ACCOUNT_OPTIONS: AccountOption[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "megadebrid-api",
|
kind: "megadebrid-api",
|
||||||
service: "megadebrid",
|
service: "megadebrid-api",
|
||||||
serviceLabel: "Mega-Debrid",
|
serviceLabel: "Mega-Debrid",
|
||||||
title: "Mega-Debrid API",
|
title: "Mega-Debrid API",
|
||||||
modeLabel: "API",
|
modeLabel: "API",
|
||||||
pickerDescription: "Login mit API-Präferenz und Web-Fallback.",
|
pickerDescription: "Login nur über die API, ohne Web-Fallback.",
|
||||||
needsCredentials: true
|
needsCredentials: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "megadebrid-web",
|
kind: "megadebrid-web",
|
||||||
service: "megadebrid",
|
service: "megadebrid-web",
|
||||||
serviceLabel: "Mega-Debrid",
|
serviceLabel: "Mega-Debrid",
|
||||||
title: "Mega-Debrid Web",
|
title: "Mega-Debrid Web",
|
||||||
modeLabel: "Web",
|
modeLabel: "Web",
|
||||||
pickerDescription: "Login mit Web-Präferenz über Nutzername und Passwort.",
|
pickerDescription: "Login nur über Web, ohne API-Fallback.",
|
||||||
needsCredentials: true
|
needsCredentials: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -209,7 +209,7 @@ const ACCOUNT_OPTIONS: AccountOption[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const ACCOUNT_SERVICES: AccountService[] = ["realdebrid", "megadebrid", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink", "linksnappy"];
|
const ACCOUNT_SERVICES: AccountService[] = ["realdebrid", "megadebrid-api", "megadebrid-web", "bestdebrid", "alldebrid", "ddownload", "onefichier", "debridlink", "linksnappy"];
|
||||||
const ACCOUNT_COLUMN_STORAGE_KEY = "rd-account-column-widths";
|
const ACCOUNT_COLUMN_STORAGE_KEY = "rd-account-column-widths";
|
||||||
const ACCOUNT_COLUMN_DEFAULT_WIDTHS: Record<AccountColumnKey, number> = {
|
const ACCOUNT_COLUMN_DEFAULT_WIDTHS: Record<AccountColumnKey, number> = {
|
||||||
service: 220,
|
service: 220,
|
||||||
@ -277,13 +277,20 @@ function getAccountPickerFunctionLabel(option: AccountOption): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasMegaDebridCredentials(settings: AppSettings): boolean {
|
||||||
|
return Boolean(settings.megaLogin.trim() && settings.megaPassword.trim());
|
||||||
|
}
|
||||||
|
|
||||||
function getConfiguredProvidersFromSettings(settings: AppSettings): DebridProvider[] {
|
function getConfiguredProvidersFromSettings(settings: AppSettings): DebridProvider[] {
|
||||||
const list: DebridProvider[] = [];
|
const list: DebridProvider[] = [];
|
||||||
if (settings.token.trim() || settings.realDebridUseWebLogin) {
|
if (settings.token.trim() || settings.realDebridUseWebLogin) {
|
||||||
list.push("realdebrid");
|
list.push("realdebrid");
|
||||||
}
|
}
|
||||||
if (settings.megaLogin.trim() && settings.megaPassword.trim()) {
|
if (hasMegaDebridCredentials(settings) && settings.megaDebridApiEnabled) {
|
||||||
list.push("megadebrid");
|
list.push("megadebrid-api");
|
||||||
|
}
|
||||||
|
if (hasMegaDebridCredentials(settings) && settings.megaDebridWebEnabled) {
|
||||||
|
list.push("megadebrid-web");
|
||||||
}
|
}
|
||||||
if (settings.bestDebridUseWebLogin || settings.bestToken.trim()) {
|
if (settings.bestDebridUseWebLogin || settings.bestToken.trim()) {
|
||||||
list.push("bestdebrid");
|
list.push("bestdebrid");
|
||||||
@ -330,9 +337,10 @@ function getConfiguredAccountKind(settings: AppSettings, service: AccountService
|
|||||||
case "realdebrid":
|
case "realdebrid":
|
||||||
if (settings.realDebridUseWebLogin) return "realdebrid-web";
|
if (settings.realDebridUseWebLogin) return "realdebrid-web";
|
||||||
return settings.token.trim() ? "realdebrid-api" : null;
|
return settings.token.trim() ? "realdebrid-api" : null;
|
||||||
case "megadebrid":
|
case "megadebrid-api":
|
||||||
if (!settings.megaLogin.trim() || !settings.megaPassword.trim()) return null;
|
return hasMegaDebridCredentials(settings) && settings.megaDebridApiEnabled ? "megadebrid-api" : null;
|
||||||
return settings.megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web";
|
case "megadebrid-web":
|
||||||
|
return hasMegaDebridCredentials(settings) && settings.megaDebridWebEnabled ? "megadebrid-web" : null;
|
||||||
case "bestdebrid":
|
case "bestdebrid":
|
||||||
if (settings.bestDebridUseWebLogin) return "bestdebrid-web";
|
if (settings.bestDebridUseWebLogin) return "bestdebrid-web";
|
||||||
return settings.bestToken.trim() ? "bestdebrid-api" : null;
|
return settings.bestToken.trim() ? "bestdebrid-api" : null;
|
||||||
@ -446,9 +454,9 @@ function applyAccountDialogToSettings(settings: AppSettings, dialog: AccountDial
|
|||||||
case "realdebrid-web":
|
case "realdebrid-web":
|
||||||
return { ...settings, token: "", realDebridUseWebLogin: true };
|
return { ...settings, token: "", realDebridUseWebLogin: true };
|
||||||
case "megadebrid-api":
|
case "megadebrid-api":
|
||||||
return { ...settings, megaLogin: login, megaPassword: password, megaDebridPreferApi: true };
|
return { ...settings, megaLogin: login, megaPassword: password, megaDebridApiEnabled: true, megaDebridPreferApi: true };
|
||||||
case "megadebrid-web":
|
case "megadebrid-web":
|
||||||
return { ...settings, megaLogin: login, megaPassword: password, megaDebridPreferApi: false };
|
return { ...settings, megaLogin: login, megaPassword: password, megaDebridWebEnabled: true, megaDebridPreferApi: false };
|
||||||
case "bestdebrid-api":
|
case "bestdebrid-api":
|
||||||
return { ...settings, bestToken: token, bestDebridUseWebLogin: false };
|
return { ...settings, bestToken: token, bestDebridUseWebLogin: false };
|
||||||
case "bestdebrid-web":
|
case "bestdebrid-web":
|
||||||
@ -474,8 +482,14 @@ function clearAccountServiceFromSettings(settings: AppSettings, service: Account
|
|||||||
switch (service) {
|
switch (service) {
|
||||||
case "realdebrid":
|
case "realdebrid":
|
||||||
return { ...settings, token: "", realDebridUseWebLogin: false };
|
return { ...settings, token: "", realDebridUseWebLogin: false };
|
||||||
case "megadebrid":
|
case "megadebrid-api":
|
||||||
return { ...settings, megaLogin: "", megaPassword: "" };
|
return settings.megaDebridWebEnabled
|
||||||
|
? { ...settings, megaDebridApiEnabled: false }
|
||||||
|
: { ...settings, megaLogin: "", megaPassword: "", megaDebridApiEnabled: false };
|
||||||
|
case "megadebrid-web":
|
||||||
|
return settings.megaDebridApiEnabled
|
||||||
|
? { ...settings, megaDebridWebEnabled: false }
|
||||||
|
: { ...settings, megaLogin: "", megaPassword: "", megaDebridWebEnabled: false };
|
||||||
case "bestdebrid":
|
case "bestdebrid":
|
||||||
return { ...settings, bestToken: "", bestDebridUseWebLogin: false };
|
return { ...settings, bestToken: "", bestDebridUseWebLogin: false };
|
||||||
case "alldebrid":
|
case "alldebrid":
|
||||||
@ -522,9 +536,9 @@ const emptyStats = (): DownloadStats => ({
|
|||||||
|
|
||||||
const emptySnapshot = (): UiSnapshot => ({
|
const emptySnapshot = (): UiSnapshot => ({
|
||||||
settings: {
|
settings: {
|
||||||
token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridPreferApi: true, bestToken: "", bestDebridUseWebLogin: false, allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "",
|
token: "", realDebridUseWebLogin: false, megaLogin: "", megaPassword: "", megaDebridApiEnabled: false, megaDebridWebEnabled: false, megaDebridPreferApi: true, bestToken: "", bestDebridUseWebLogin: false, allDebridToken: "", allDebridUseWebLogin: false, ddownloadLogin: "", ddownloadPassword: "", oneFichierApiKey: "",
|
||||||
archivePasswordList: "",
|
archivePasswordList: "",
|
||||||
rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid",
|
rememberToken: true, providerPrimary: "realdebrid", providerSecondary: "megadebrid-api",
|
||||||
providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
|
providerTertiary: "bestdebrid", autoProviderFallback: true, outputDir: "", packageName: "",
|
||||||
autoExtract: true, autoRename4sf4sj: false, extractDir: "", createExtractSubfolder: true, hybridExtract: true,
|
autoExtract: true, autoRename4sf4sj: false, extractDir: "", createExtractSubfolder: true, hybridExtract: true,
|
||||||
collectMkvToLibrary: false, mkvLibraryDir: "",
|
collectMkvToLibrary: false, mkvLibraryDir: "",
|
||||||
@ -556,7 +570,16 @@ 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", onefichier: "1Fichier", debridlink: "Debrid-Link", linksnappy: "LinkSnappy"
|
realdebrid: "Real-Debrid",
|
||||||
|
megadebrid: "Mega-Debrid",
|
||||||
|
"megadebrid-api": "Mega-Debrid API",
|
||||||
|
"megadebrid-web": "Mega-Debrid Web",
|
||||||
|
bestdebrid: "BestDebrid",
|
||||||
|
alldebrid: "AllDebrid",
|
||||||
|
ddownload: "DDownload",
|
||||||
|
onefichier: "1Fichier",
|
||||||
|
debridlink: "Debrid-Link",
|
||||||
|
linksnappy: "LinkSnappy"
|
||||||
};
|
};
|
||||||
|
|
||||||
const KNOWN_HOSTERS: { id: string; label: string }[] = [
|
const KNOWN_HOSTERS: { id: string; label: string }[] = [
|
||||||
@ -591,7 +614,10 @@ const KNOWN_HOSTERS: { id: string; label: string }[] = [
|
|||||||
|
|
||||||
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);
|
if (provider === "megadebrid" || provider === "megadebrid-api" || provider === "megadebrid-web") {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
const kind = getConfiguredAccountKind(settings, provider as AccountService);
|
||||||
if (!kind) return base;
|
if (!kind) return base;
|
||||||
const opt = ACCOUNT_OPTIONS.find((o) => o.kind === kind);
|
const opt = ACCOUNT_OPTIONS.find((o) => o.kind === kind);
|
||||||
return opt?.modeLabel ? `${base} (${opt.modeLabel})` : base;
|
return opt?.modeLabel ? `${base} (${opt.modeLabel})` : base;
|
||||||
@ -1573,9 +1599,9 @@ export function App(): ReactElement {
|
|||||||
let statusLabel = "Konfiguriert";
|
let statusLabel = "Konfiguriert";
|
||||||
let note = "";
|
let note = "";
|
||||||
if (kind === "megadebrid-api") {
|
if (kind === "megadebrid-api") {
|
||||||
note = "API wird bevorzugt, Web bleibt als Fallback aktiv.";
|
note = "Nur API aktiv. Kein Web-Fallback.";
|
||||||
} else if (kind === "megadebrid-web") {
|
} else if (kind === "megadebrid-web") {
|
||||||
note = "Web wird bevorzugt, API bleibt als Fallback aktiv.";
|
note = "Nur Web aktiv. Kein API-Fallback.";
|
||||||
} else if (kind === "realdebrid-web") {
|
} else if (kind === "realdebrid-web") {
|
||||||
note = "Login kann bei Bedarf direkt aus der Liste geöffnet werden.";
|
note = "Login kann bei Bedarf direkt aus der Liste geöffnet werden.";
|
||||||
} else if (kind === "bestdebrid-web") {
|
} else if (kind === "bestdebrid-web") {
|
||||||
@ -4285,10 +4311,10 @@ export function App(): ReactElement {
|
|||||||
<div className="account-modal-note">Der Web-Login nutzt ein echtes Browserfenster, damit reCAPTCHA sauber laeuft.</div>
|
<div className="account-modal-note">Der Web-Login nutzt ein echtes Browserfenster, damit reCAPTCHA sauber laeuft.</div>
|
||||||
)}
|
)}
|
||||||
{accountDialog.kind === "megadebrid-api" && (
|
{accountDialog.kind === "megadebrid-api" && (
|
||||||
<div className="account-modal-note">Mega-Debrid versucht zuerst die API und faellt bei Bedarf auf Web zurueck.</div>
|
<div className="account-modal-note">Dieser Account nutzt nur die Mega-Debrid API. Kein Web-Fallback.</div>
|
||||||
)}
|
)}
|
||||||
{accountDialog.kind === "megadebrid-web" && (
|
{accountDialog.kind === "megadebrid-web" && (
|
||||||
<div className="account-modal-note">Mega-Debrid bevorzugt Web. Die API bleibt als Fallback erhalten.</div>
|
<div className="account-modal-note">Dieser Account nutzt nur Mega-Debrid Web. Kein API-Fallback.</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{accountDialogOption.service === "alldebrid" && allDebridHostInfo && (
|
{accountDialogOption.service === "alldebrid" && allDebridHostInfo && (
|
||||||
|
|||||||
@ -14,7 +14,17 @@ 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" | "onefichier" | "debridlink" | "linksnappy";
|
export type DebridProvider =
|
||||||
|
| "realdebrid"
|
||||||
|
| "megadebrid"
|
||||||
|
| "megadebrid-api"
|
||||||
|
| "megadebrid-web"
|
||||||
|
| "bestdebrid"
|
||||||
|
| "alldebrid"
|
||||||
|
| "ddownload"
|
||||||
|
| "onefichier"
|
||||||
|
| "debridlink"
|
||||||
|
| "linksnappy";
|
||||||
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";
|
||||||
@ -41,6 +51,8 @@ export interface AppSettings {
|
|||||||
realDebridUseWebLogin: boolean;
|
realDebridUseWebLogin: boolean;
|
||||||
megaLogin: string;
|
megaLogin: string;
|
||||||
megaPassword: string;
|
megaPassword: string;
|
||||||
|
megaDebridApiEnabled: boolean;
|
||||||
|
megaDebridWebEnabled: boolean;
|
||||||
megaDebridPreferApi: boolean;
|
megaDebridPreferApi: boolean;
|
||||||
bestToken: string;
|
bestToken: string;
|
||||||
bestDebridUseWebLogin: boolean;
|
bestDebridUseWebLogin: boolean;
|
||||||
|
|||||||
@ -444,6 +444,68 @@ describe("debrid service", () => {
|
|||||||
expect(megaWeb).toHaveBeenCalledTimes(1);
|
expect(megaWeb).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not fallback from Mega API to Mega Web unless Mega Web is a separate provider in the order", async () => {
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "",
|
||||||
|
bestToken: "",
|
||||||
|
allDebridToken: "",
|
||||||
|
megaLogin: "user",
|
||||||
|
megaPassword: "pass",
|
||||||
|
megaDebridApiEnabled: true,
|
||||||
|
megaDebridWebEnabled: true,
|
||||||
|
providerPrimary: "megadebrid-api" as const,
|
||||||
|
providerSecondary: "none" as const,
|
||||||
|
providerTertiary: "none" as const,
|
||||||
|
autoProviderFallback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.fetch = (async () => new Response("not-found", { status: 404 })) as typeof fetch;
|
||||||
|
|
||||||
|
const megaWeb = vi.fn(async () => ({
|
||||||
|
fileName: "should-not-run.rar",
|
||||||
|
directUrl: "https://unused",
|
||||||
|
fileSize: null,
|
||||||
|
retriesUsed: 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||||
|
await expect(service.unrestrictLink("https://rapidgator.net/file/mega-api-only.rar.html")).rejects.toThrow(/mega-debrid api/i);
|
||||||
|
expect(megaWeb).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses Mega Web only when it is configured as a separate fallback provider", async () => {
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings(),
|
||||||
|
token: "",
|
||||||
|
bestToken: "",
|
||||||
|
allDebridToken: "",
|
||||||
|
megaLogin: "user",
|
||||||
|
megaPassword: "pass",
|
||||||
|
megaDebridApiEnabled: true,
|
||||||
|
megaDebridWebEnabled: true,
|
||||||
|
providerPrimary: "megadebrid-api" as const,
|
||||||
|
providerSecondary: "megadebrid-web" as const,
|
||||||
|
providerTertiary: "none" as const,
|
||||||
|
autoProviderFallback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.fetch = (async () => new Response("not-found", { status: 404 })) as typeof fetch;
|
||||||
|
|
||||||
|
const megaWeb = vi.fn(async () => ({
|
||||||
|
fileName: "from-separate-web.rar",
|
||||||
|
directUrl: "https://mega-web.example/from-separate-web.rar",
|
||||||
|
fileSize: null,
|
||||||
|
retriesUsed: 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
const service = new DebridService(settings, { megaWebUnrestrict: megaWeb });
|
||||||
|
const result = await service.unrestrictLink("https://rapidgator.net/file/from-separate-web.rar.html");
|
||||||
|
expect(result.provider).toBe("megadebrid-web");
|
||||||
|
expect(result.directUrl).toBe("https://mega-web.example/from-separate-web.rar");
|
||||||
|
expect(megaWeb).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("aborts Mega web unrestrict when caller signal is cancelled", async () => {
|
it("aborts Mega web unrestrict when caller signal is cancelled", async () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
|
|||||||
@ -146,6 +146,36 @@ describe("settings storage", () => {
|
|||||||
expect(normalized.providerTertiary).toBe("none");
|
expect(normalized.providerTertiary).toBe("none");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("migrates legacy MegaDebrid provider selections to explicit API/Web providers", () => {
|
||||||
|
const apiNormalized = normalizeSettings({
|
||||||
|
...defaultSettings(),
|
||||||
|
megaLogin: "mega-user",
|
||||||
|
megaPassword: "mega-pass",
|
||||||
|
megaDebridPreferApi: true,
|
||||||
|
providerPrimary: "megadebrid" as unknown as AppSettings["providerPrimary"],
|
||||||
|
providerSecondary: "megadebrid" as unknown as AppSettings["providerSecondary"],
|
||||||
|
disabledProviders: ["megadebrid" as unknown as AppSettings["providerPrimary"]]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiNormalized.providerPrimary).toBe("megadebrid-api");
|
||||||
|
expect(apiNormalized.providerSecondary).toBe("none");
|
||||||
|
expect(apiNormalized.disabledProviders).toEqual(["megadebrid-api", "megadebrid-web"]);
|
||||||
|
|
||||||
|
const webNormalized = normalizeSettings({
|
||||||
|
...defaultSettings(),
|
||||||
|
megaLogin: "mega-user",
|
||||||
|
megaPassword: "mega-pass",
|
||||||
|
megaDebridPreferApi: false,
|
||||||
|
megaDebridApiEnabled: false,
|
||||||
|
megaDebridWebEnabled: true,
|
||||||
|
providerPrimary: "megadebrid" as unknown as AppSettings["providerPrimary"],
|
||||||
|
hosterRouting: { rapidgator: "megadebrid" as unknown as AppSettings["providerPrimary"] }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(webNormalized.providerPrimary).toBe("megadebrid-web");
|
||||||
|
expect(webNormalized.hosterRouting.rapidgator).toBe("megadebrid-web");
|
||||||
|
});
|
||||||
|
|
||||||
it("normalizes archive password list line endings", () => {
|
it("normalizes archive password list line endings", () => {
|
||||||
const normalized = normalizeSettings({
|
const normalized = normalizeSettings({
|
||||||
...defaultSettings(),
|
...defaultSettings(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user