feat(accounts): manage Real-Debrid account pool

This commit is contained in:
Sucukdeluxe
2026-08-15 20:17:41 +02:00
parent 56a05fa66f
commit af710610fd
14 changed files with 539 additions and 178 deletions
+55 -13
View File
@@ -1,6 +1,7 @@
import type { AccountCheckScope, AppSettings, DebridAccountStatus, DebridProvider } from "../shared/types";
import { getMegaDebridAccountsForMode, getMegaDebridDisabledAccountIdsForMode, parseMegaDebridAccounts, type MegaDebridAccountEntry } from "../shared/mega-debrid-accounts";
import { parseDebridLinkApiKeys, type DebridLinkApiKeyEntry } from "../shared/debrid-link-keys";
import { parseDebridLinkApiKeys, type DebridLinkApiKeyEntry } from "../shared/debrid-link-keys";
import { getRealDebridAccounts, type RealDebridAccountEntry } from "../shared/real-debrid-accounts";
import { logger } from "./logger";
import { compactErrorText } from "./utils";
@@ -23,6 +24,15 @@ export interface RealDebridSessionProbeResult {
}
export type RealDebridSessionProbe = (signal?: AbortSignal) => Promise<RealDebridSessionProbeResult>;
export type RealDebridSessionProbeResolver = (accountId: string, signal?: AbortSignal) => Promise<RealDebridSessionProbeResult>;
export function retainConfiguredRealDebridStatuses(settings: AppSettings, statuses: readonly DebridAccountStatus[]): DebridAccountStatus[] {
const accountIds = new Set(getRealDebridAccounts(settings).map((account) => account.id));
if (accountIds.size === 0 && (settings.realDebridUseWebLogin || settings.token.trim())) {
accountIds.add(REAL_DEBRID_STATUS_ID);
}
return statuses.filter((status) => status.provider !== "realdebrid" || accountIds.has(status.accountId));
}
function timeoutSignal(signal: AbortSignal | undefined, ms: number): AbortSignal {
const timeout = AbortSignal.timeout(ms);
@@ -69,18 +79,30 @@ function maskSecret(value: string): string {
}
export async function checkRealDebridAccount(
settings: AppSettings,
accountOrSettings: RealDebridAccountEntry | AppSettings,
signal?: AbortSignal,
now = Date.now(),
probeWebSession?: RealDebridSessionProbe
): Promise<DebridAccountStatus> {
const token = String(settings.token || "").trim();
const useWebLogin = Boolean(settings.realDebridUseWebLogin);
const isAccount = typeof (accountOrSettings as RealDebridAccountEntry).kind === "string";
const account = isAccount
? accountOrSettings as RealDebridAccountEntry
: {
id: REAL_DEBRID_STATUS_ID,
kind: (accountOrSettings as AppSettings).realDebridUseWebLogin ? "web" as const : "api" as const,
index: 0,
label: "Real-Debrid",
maskedLogin: (accountOrSettings as AppSettings).realDebridUseWebLogin ? "Browser-Login" : maskSecret(String((accountOrSettings as AppSettings).token || "")),
enabled: true,
...((accountOrSettings as AppSettings).realDebridUseWebLogin ? {} : { token: String((accountOrSettings as AppSettings).token || "").trim() })
} as RealDebridAccountEntry;
const token = account.kind === "api" ? account.token.trim() : "";
const useWebLogin = account.kind === "web";
const base: DebridAccountStatus = {
accountId: REAL_DEBRID_STATUS_ID,
accountId: account.id,
provider: "realdebrid",
label: "Real-Debrid",
maskedLogin: useWebLogin ? "Browser-Login" : maskSecret(token),
label: account.label,
maskedLogin: account.maskedLogin,
valid: false,
isPremium: false,
premiumUntilMs: null,
@@ -274,7 +296,7 @@ export async function checkDebridLinkKey(
export async function checkAllDebridAccounts(
settings: AppSettings,
signal?: AbortSignal,
probeRealDebridWebSession?: RealDebridSessionProbe,
probeRealDebridWebSession?: RealDebridSessionProbeResolver,
scope: AccountCheckScope = "all"
): Promise<DebridAccountStatus[]> {
const now = Date.now();
@@ -298,13 +320,33 @@ export async function checkAllDebridAccounts(
: providerEnabled("debridlink")
? configuredDebridLinkKeys.filter((key) => !(settings.debridLinkDisabledKeyIds || []).includes(key.id))
: [];
const checkRealDebrid = Boolean(settings.realDebridUseWebLogin || String(settings.token || "").trim())
&& (scope === "all" || providerEnabled("realdebrid"));
const configuredRealDebridAccounts = getRealDebridAccounts(settings);
const legacyRealDebridAccounts: RealDebridAccountEntry[] = configuredRealDebridAccounts.length === 0
&& (settings.realDebridUseWebLogin || String(settings.token || "").trim())
? [{
id: REAL_DEBRID_STATUS_ID,
kind: settings.realDebridUseWebLogin ? "web" : "api",
index: 0,
label: "Real-Debrid",
maskedLogin: settings.realDebridUseWebLogin ? "Browser-Login" : maskSecret(settings.token),
enabled: true,
...(settings.realDebridUseWebLogin ? {} : { token: settings.token.trim() })
} as RealDebridAccountEntry]
: [];
const allRealDebridAccounts = configuredRealDebridAccounts.length > 0 ? configuredRealDebridAccounts : legacyRealDebridAccounts;
const realDebridAccounts = scope === "all"
? allRealDebridAccounts
: providerEnabled("realdebrid") ? allRealDebridAccounts.filter((account) => account.enabled) : [];
const taskFns: Array<() => Promise<DebridAccountStatus>> = [
...(checkRealDebrid
? [() => checkRealDebridAccount(settings, signal, now, probeRealDebridWebSession)]
: []),
...realDebridAccounts.map((account) => () => checkRealDebridAccount(
account,
signal,
now,
account.kind === "web" && probeRealDebridWebSession
? (probeSignal) => probeRealDebridWebSession(account.id, probeSignal)
: undefined
)),
...megaAccounts.map((account) => () => checkMegaDebridAccount(account, signal, now)),
...debridLinkKeys.map((key) => () => checkDebridLinkKey(key, signal, now))
];
+131 -1
View File
@@ -1,4 +1,5 @@
import { getDebridLinkApiKeyId, parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
import { randomUUID } from "node:crypto";
import {
getMegaDebridAccountId,
getMegaDebridAccountsForMode,
@@ -9,6 +10,7 @@ import {
serializeMegaDebridAccounts,
type MegaDebridAccountMode
} from "../shared/mega-debrid-accounts";
import { getRealDebridAccounts, isRealDebridWebAccountId, parseRealDebridApiAccounts, serializeRealDebridApiAccounts } from "../shared/real-debrid-accounts";
import type { AccountCommand, AccountCredentialCheckInput, AccountSecretRequest, AppSettings, DebridProvider, RendererAccountKind } from "../shared/types";
export interface AppliedAccountCommand {
@@ -150,6 +152,13 @@ function storedSecretMissing(): never {
}
export function resolveStoredAccountSecret(settings: AppSettings, request: AccountSecretRequest): string {
if (request.kind === "realdebrid-api") {
const accounts = parseRealDebridApiAccounts(settings.realDebridApiTokens);
const account = accounts.find((entry) => entry.id === request.accountId);
if (account?.token) return account.token;
if (accounts.length === 0 && request.accountId === REAL_DEBRID_LEGACY_ID && settings.token) return settings.token;
return storedSecretMissing();
}
if (request.kind === "megadebrid-api" || request.kind === "megadebrid-web") {
const mode = request.kind === "megadebrid-web" ? "web" : "api";
const account = getMegaDebridAccountsForMode(settings, mode).find((entry) => entry.id === request.accountId);
@@ -163,7 +172,6 @@ export function resolveStoredAccountSecret(settings: AppSettings, request: Accou
}
const provider = singleProvider(request.kind);
if (request.accountId !== `svc-${provider}` || !singleConfigured(settings, request.kind)) storedSecretMissing();
if (request.kind === "realdebrid-api" && settings.token) return settings.token;
if (request.kind === "bestdebrid-api" && settings.bestToken) return settings.bestToken;
if (request.kind === "alldebrid-api" && settings.allDebridToken) return settings.allDebridToken;
if (request.kind === "ddownload-login" && settings.ddownloadPassword) return settings.ddownloadPassword;
@@ -172,6 +180,122 @@ export function resolveStoredAccountSecret(settings: AppSettings, request: Accou
return storedSecretMissing();
}
const REAL_DEBRID_LEGACY_ID = "svc-realdebrid";
function syncRealDebridLegacyFields(settings: AppSettings): AppSettings {
const firstApi = parseRealDebridApiAccounts(settings.realDebridApiTokens)[0];
return {
...settings,
token: firstApi?.token || "",
realDebridUseWebLogin: settings.realDebridWebAccountIds.length > 0
};
}
function normalizeRealDebridCommandSettings(settings: AppSettings): AppSettings {
const rawPool = settings.realDebridApiTokens.trim() || (settings.realDebridUseWebLogin ? "" : settings.token.trim());
const apiAccounts = parseRealDebridApiAccounts(rawPool).map((account) => ({
id: account.id.startsWith("rda_legacy_") ? `rda_${randomUUID().replace(/-/g, "")}` : account.id,
token: account.token
}));
return {
...settings,
realDebridApiTokens: serializeRealDebridApiAccounts(apiAccounts),
realDebridWebAccountIds: settings.realDebridWebAccountIds.length > 0
? [...settings.realDebridWebAccountIds]
: settings.realDebridUseWebLogin ? ["rdw_legacy"] : []
};
}
export function setRealDebridAccountEnabled(settings: AppSettings, accountId: string, enabled: boolean): AppSettings {
const normalized = normalizeRealDebridCommandSettings(settings);
if (!getRealDebridAccounts(normalized).some((account) => account.id === accountId)) invalid();
return {
...normalized,
realDebridDisabledAccountIds: enabled
? normalized.realDebridDisabledAccountIds.filter((id) => id !== accountId)
: [...new Set([...normalized.realDebridDisabledAccountIds, accountId])]
};
}
function migrateRealDebridMetadata(settings: AppSettings, oldId: string, newId: string, limit: number | undefined): AppSettings {
const idChanged = oldId !== newId;
return {
...settings,
realDebridDisabledAccountIds: idChanged
? migrateIdList(settings.realDebridDisabledAccountIds, oldId, newId)
: [...settings.realDebridDisabledAccountIds],
realDebridAccountDailyLimitBytes: migrateLimit(settings.realDebridAccountDailyLimitBytes, oldId, newId, limit),
realDebridAccountDailyUsageBytes: idChanged ? withoutKeys(settings.realDebridAccountDailyUsageBytes, oldId, newId) : { ...settings.realDebridAccountDailyUsageBytes },
realDebridAccountTotalUsageBytes: idChanged ? withoutKeys(settings.realDebridAccountTotalUsageBytes, oldId, newId) : { ...settings.realDebridAccountTotalUsageBytes },
debridAccountStatuses: idChanged ? withoutKeys(settings.debridAccountStatuses, oldId, newId) : { ...settings.debridAccountStatuses }
};
}
function createRealDebrid(settings: AppSettings, command: Extract<AccountCommand, { action: "create" }>): AppliedAccountCommand {
if (command.kind === "realdebrid-api") {
const token = validateSecret(command.secret || "");
const accounts = parseRealDebridApiAccounts(settings.realDebridApiTokens);
if (accounts.some((account) => account.token === token)) invalid();
const accountId = `rda_${randomUUID().replace(/-/g, "")}`;
const next = syncRealDebridLegacyFields({
...settings,
realDebridApiTokens: serializeRealDebridApiAccounts([...accounts, { id: accountId, token }]),
realDebridDisabledAccountIds: settings.realDebridDisabledAccountIds.filter((id) => id !== accountId),
realDebridAccountDailyLimitBytes: setLimit(settings.realDebridAccountDailyLimitBytes, accountId, command.dailyLimitBytes)
});
return { settings: next, response: { accountId } };
}
const requestedId = String(command.identity || "").trim();
const accountId = requestedId && isRealDebridWebAccountId(requestedId) ? requestedId : `rdw_${randomUUID().replace(/-/g, "")}`;
if (settings.realDebridWebAccountIds.includes(accountId)) invalid();
const next = syncRealDebridLegacyFields({
...settings,
realDebridWebAccountIds: [...settings.realDebridWebAccountIds, accountId],
realDebridDisabledAccountIds: settings.realDebridDisabledAccountIds.filter((id) => id !== accountId),
realDebridAccountDailyLimitBytes: setLimit(settings.realDebridAccountDailyLimitBytes, accountId, command.dailyLimitBytes)
});
return { settings: next, response: { accountId } };
}
function replaceRealDebrid(settings: AppSettings, command: Extract<AccountCommand, { action: "replace" }>): AppliedAccountCommand {
const accounts = getRealDebridAccounts(settings);
const current = accounts.find((account) => account.id === command.accountId);
if (!current || (current.kind === "api") !== (command.kind === "realdebrid-api")) invalid();
if (current.kind === "web") {
const next = syncRealDebridLegacyFields(migrateRealDebridMetadata(settings, current.id, current.id, command.dailyLimitBytes));
return { settings: next, response: { accountId: current.id } };
}
const token = command.secret?.trim() ? validateSecret(command.secret) : current.token;
if (accounts.some((account) => account.id !== current.id && account.kind === "api" && account.token === token)) invalid();
const credentials = parseRealDebridApiAccounts(settings.realDebridApiTokens).map((account) => ({
id: account.id,
token: account.id === current.id ? token : account.token
}));
const next = syncRealDebridLegacyFields(migrateRealDebridMetadata({
...settings,
realDebridApiTokens: serializeRealDebridApiAccounts(credentials)
}, current.id, current.id, command.dailyLimitBytes));
return { settings: next, response: { accountId: current.id } };
}
function deleteRealDebrid(settings: AppSettings, command: Extract<AccountCommand, { action: "delete" }>): AppliedAccountCommand {
const account = getRealDebridAccounts(settings).find((entry) => entry.id === command.accountId);
if (!account || (account.kind === "api") !== (command.kind === "realdebrid-api")) invalid();
const next = syncRealDebridLegacyFields({
...settings,
realDebridApiTokens: account.kind === "api"
? serializeRealDebridApiAccounts(parseRealDebridApiAccounts(settings.realDebridApiTokens).filter((entry) => entry.id !== account.id))
: settings.realDebridApiTokens,
realDebridWebAccountIds: account.kind === "web" ? settings.realDebridWebAccountIds.filter((id) => id !== account.id) : [...settings.realDebridWebAccountIds],
realDebridDisabledAccountIds: settings.realDebridDisabledAccountIds.filter((id) => id !== account.id),
realDebridAccountDailyLimitBytes: withoutKeys(settings.realDebridAccountDailyLimitBytes, account.id),
realDebridAccountDailyUsageBytes: withoutKeys(settings.realDebridAccountDailyUsageBytes, account.id),
realDebridAccountTotalUsageBytes: withoutKeys(settings.realDebridAccountTotalUsageBytes, account.id),
debridAccountStatuses: withoutKeys(settings.debridAccountStatuses, account.id)
});
return { settings: next, response: { accountId: getRealDebridAccounts(next)[0]?.id || null } };
}
function withoutKeys<T>(record: Record<string, T>, ...keys: string[]): Record<string, T> {
const removed = new Set(keys);
return Object.fromEntries(Object.entries(record).filter(([key]) => !removed.has(key)));
@@ -492,6 +616,12 @@ export function applyAccountCommand(settings: AppSettings, command: AccountComma
};
return applyAccountCommand(settings, replace);
}
if (command.kind === "realdebrid-api" || command.kind === "realdebrid-web") {
const normalizedSettings = normalizeRealDebridCommandSettings(settings);
if (command.action === "create") return createRealDebrid(normalizedSettings, command);
if (command.action === "replace") return replaceRealDebrid(normalizedSettings, command);
return deleteRealDebrid(normalizedSettings, command);
}
if (command.kind === "megadebrid-api" || command.kind === "megadebrid-web") {
if (command.action === "create") return createMega(settings, command);
if (command.action === "replace") return replaceMega(settings, command);
+29 -11
View File
@@ -1,6 +1,7 @@
import path from "node:path";
import os from "node:os";
import v8 from "node:v8";
import v8 from "node:v8";
import { randomUUID } from "node:crypto";
import { app } from "electron";
import {
AddLinksPayload,
@@ -33,10 +34,11 @@ import { importDlcContainers } from "./container";
import { APP_VERSION, ONLINE_BACKUP_API_URL } from "./constants";
import { DownloadManager } from "./download-manager";
import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid";
import { checkAllDebridAccounts, checkDebridLinkKey, checkMegaDebridAccount, checkRealDebridAccount } from "./account-check";
import { checkAllDebridAccounts, checkDebridLinkKey, checkMegaDebridAccount, checkRealDebridAccount, retainConfiguredRealDebridStatuses } from "./account-check";
import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
import { getMegaDebridAccountsForMode } from "../shared/mega-debrid-accounts";
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
import { getRealDebridAccounts } from "../shared/real-debrid-accounts";
import { applyAccountCommand, resolveStoredAccountSecret } from "./account-commands";
import { collectAccountStatusRedactionValues, sanitizeDebridAccountStatus, sanitizeDebridAccountStatuses } from "./account-status-sanitizer";
import { createRendererState } from "./renderer-state";
@@ -524,6 +526,11 @@ export class AppController {
if (!key) throw new Error("Account-Payload ist ungültig");
checkedStatus = await checkDebridLinkKey(key);
}
if (command.action !== "delete" && applied.response.accountId && command.kind === "realdebrid-api") {
const account = getRealDebridAccounts(applied.settings).find((entry) => entry.id === applied.response.accountId && entry.kind === "api");
if (!account) throw new Error("Account-Payload ist ungültig");
checkedStatus = await checkRealDebridAccount(account);
}
if (checkedStatus) {
checkedStatus = sanitizeDebridAccountStatus(checkedStatus, redactions);
}
@@ -547,19 +554,28 @@ export class AppController {
const redactions = collectAccountStatusRedactionValues(this.settings, input);
if (input.kind === "realdebrid-api" || input.kind === "realdebrid-web") {
const useWebLogin = input.kind === "realdebrid-web";
const settings = input.secret?.trim()
? { ...this.settings, token: input.secret.trim(), realDebridUseWebLogin: useWebLogin }
: { ...this.settings, realDebridUseWebLogin: useWebLogin };
const account = input.secret?.trim() && !useWebLogin
? {
id: `rda_${randomUUID().replace(/-/g, "")}`,
kind: "api" as const,
index: 0,
label: "Real-Debrid",
maskedLogin: "Geschützter API-Token",
enabled: true,
token: input.secret.trim()
}
: getRealDebridAccounts(this.settings).find((entry) => entry.id === input.accountId && entry.kind === (useWebLogin ? "web" : "api"));
if (!account) throw new Error("Account-Payload ist ungültig");
const status = sanitizeDebridAccountStatus(
await checkRealDebridAccount(
settings,
account,
undefined,
Date.now(),
useWebLogin ? (signal) => this.realDebridWebFallback.probeLoginState(signal) : undefined
),
redactions
);
if (!input.secret && useWebLogin === this.settings.realDebridUseWebLogin) {
if (!input.secret && getRealDebridAccounts(this.settings).some((entry) => entry.id === status.accountId)) {
this.manager.applyDebridAccountStatuses([status]);
}
return status;
@@ -613,12 +629,13 @@ export class AppController {
}
private async refreshRealDebridWebStatus(): Promise<void> {
if (!this.settings.realDebridUseWebLogin) {
const account = getRealDebridAccounts(this.settings).find((entry) => entry.kind === "web");
if (!account) {
return;
}
const status = sanitizeDebridAccountStatus(
await checkRealDebridAccount(
this.settings,
account,
undefined,
Date.now(),
(signal) => this.realDebridWebFallback.probeLoginState(signal)
@@ -658,15 +675,16 @@ export class AppController {
}
public async checkDebridAccounts(scope: AccountCheckScope = "active"): Promise<DebridAccountStatus[]> {
const statuses = sanitizeDebridAccountStatuses(
const checkedStatuses = sanitizeDebridAccountStatuses(
await checkAllDebridAccounts(
this.settings,
undefined,
(signal) => this.realDebridWebFallback.probeLoginState(signal),
(_accountId, signal) => this.realDebridWebFallback.probeLoginState(signal),
scope
),
collectAccountStatusRedactionValues(this.settings)
);
const statuses = retainConfiguredRealDebridStatuses(this.settings, checkedStatuses);
this.manager.applyDebridAccountStatuses(statuses);
this.audit("INFO", "Debrid-Accounts geprueft", {
total: statuses.length,
+19 -1
View File
@@ -1,5 +1,6 @@
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
import { getMegaDebridAccountsForMode, getMegaDebridDisabledAccountIdsForMode } from "../shared/mega-debrid-accounts";
import { getRealDebridAccounts } from "../shared/real-debrid-accounts";
import type { AppSettings, DebridAccountStatus, DebridProvider, RendererAccount, RendererAccountKind, RendererSettings } from "../shared/types";
import { collectAccountStatusRedactionValues, sanitizeDebridAccountStatus } from "./account-status-sanitizer";
@@ -51,7 +52,24 @@ function singleAccount(
export function createRendererAccounts(settings: AppSettings): RendererAccount[] {
const accounts: RendererAccount[] = [];
const redactions = collectAccountStatusRedactionValues(settings);
if (settings.realDebridUseWebLogin || settings.token.trim()) {
const realDebridAccounts = getRealDebridAccounts(settings);
for (const account of realDebridAccounts) {
const status = safeStatus(settings.debridAccountStatuses[account.id], redactions);
accounts.push({
accountId: account.id,
kind: account.kind === "web" ? "realdebrid-web" : "realdebrid-api",
provider: "realdebrid",
identity: status?.username || "",
maskedIdentity: account.maskedLogin,
hasSecret: true,
enabled: providerEnabled(settings, "realdebrid") && account.enabled,
dailyLimitBytes: settings.realDebridAccountDailyLimitBytes[account.id] || 0,
dailyUsageBytes: settings.realDebridAccountDailyUsageBytes[account.id] || 0,
totalUsageBytes: settings.realDebridAccountTotalUsageBytes[account.id] || 0,
status
});
}
if (realDebridAccounts.length === 0 && (settings.realDebridUseWebLogin || settings.token.trim())) {
accounts.push(singleAccount(
settings,
settings.realDebridUseWebLogin ? "realdebrid-web" : "realdebrid-api",
+12 -3
View File
@@ -1,14 +1,17 @@
import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys";
import { getMegaDebridAccountIds, mergeMegaDebridCredentialPools } from "../shared/mega-debrid-accounts";
import { getRealDebridAccountIds } from "../shared/real-debrid-accounts";
import type { AppSettings } from "../shared/types";
export function overlayLiveUsageCounters(target: AppSettings, liveSettings: AppSettings, liveTotalRuntimeMs: number): void {
const debridLinkKeyIds = new Set(getDebridLinkApiKeyIds(target.debridLinkApiKeys));
const megaAccountIds = new Set(getMegaDebridAccountIds(mergeMegaDebridCredentialPools(target.megaDebridApiCredentials || "", target.megaDebridWebCredentials || "") || target.megaCredentials || "", target.megaPassword || ""));
const realDebridAccountIds = new Set(getRealDebridAccountIds(target));
const validAccountIds = new Set([
...debridLinkKeyIds,
...megaAccountIds,
...(target.realDebridUseWebLogin || target.token.trim() ? ["svc-realdebrid"] : [])
...realDebridAccountIds,
...(realDebridAccountIds.size === 0 && (target.realDebridUseWebLogin || target.token.trim()) ? ["svc-realdebrid"] : [])
]);
target.totalDownloadedAllTime = Math.max(target.totalDownloadedAllTime || 0, liveSettings.totalDownloadedAllTime || 0);
target.totalCompletedFilesAllTime = Math.max(target.totalCompletedFilesAllTime || 0, liveSettings.totalCompletedFilesAllTime || 0);
@@ -25,9 +28,15 @@ export function overlayLiveUsageCounters(target: AppSettings, liveSettings: AppS
target.megaDebridAccountDailyUsageBytes = Object.fromEntries(
Object.entries(liveSettings.megaDebridAccountDailyUsageBytes || {}).filter(([accountId]) => megaAccountIds.has(accountId))
);
target.megaDebridAccountTotalUsageBytes = Object.fromEntries(
target.megaDebridAccountTotalUsageBytes = Object.fromEntries(
Object.entries(liveSettings.megaDebridAccountTotalUsageBytes || {}).filter(([accountId]) => megaAccountIds.has(accountId))
);
);
target.realDebridAccountDailyUsageBytes = Object.fromEntries(
Object.entries(liveSettings.realDebridAccountDailyUsageBytes || {}).filter(([accountId]) => realDebridAccountIds.has(accountId))
);
target.realDebridAccountTotalUsageBytes = Object.fromEntries(
Object.entries(liveSettings.realDebridAccountTotalUsageBytes || {}).filter(([accountId]) => realDebridAccountIds.has(accountId))
);
target.debridAccountStatuses = Object.fromEntries(
Object.entries(liveSettings.debridAccountStatuses || {}).filter(([accountId]) => validAccountIds.has(accountId))
);
+9 -7
View File
@@ -1,7 +1,8 @@
import fs from "node:fs";
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import path from "node:path";
import { randomUUID } from "node:crypto";
import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys";
import { getMegaDebridAccountIds, mergeMegaDebridCredentialPools, parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
import { AppSettings, AudioStripSummary, BandwidthScheduleEntry, DebridAccountStatus, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, HistoryRetentionMode, LogStorageLocation, PackageEntry, PackagePriority, SessionState } from "../shared/types";
@@ -447,12 +448,13 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
const megaDebridDisabledAccountIds = [...new Set([...megaDebridApiDisabledAccountIds, ...megaDebridWebDisabledAccountIds])];
const legacyRealDebridToken = asText(settings.token);
const hasRealDebridApiPool = Object.prototype.hasOwnProperty.call(settings, "realDebridApiTokens");
const storedRealDebridApiTokens = serializeRealDebridApiAccounts(
parseRealDebridApiAccounts(String(settings.realDebridApiTokens ?? "")).map((entry) => entry.token)
);
const realDebridApiTokens = hasRealDebridApiPool
? storedRealDebridApiTokens
: serializeRealDebridApiAccounts([legacyRealDebridToken]);
const rawRealDebridApiAccounts = hasRealDebridApiPool
? parseRealDebridApiAccounts(String(settings.realDebridApiTokens ?? ""))
: legacyRealDebridToken ? parseRealDebridApiAccounts(legacyRealDebridToken) : [];
const realDebridApiTokens = serializeRealDebridApiAccounts(rawRealDebridApiAccounts.map((account) => ({
id: account.id.startsWith("rda_legacy_") ? `rda_${randomUUID().replace(/-/g, "")}` : account.id,
token: account.token
})));
const hasRealDebridWebPool = Object.prototype.hasOwnProperty.call(settings, "realDebridWebAccountIds");
let realDebridWebAccountIds = normalizeRealDebridWebAccountIds(settings.realDebridWebAccountIds);
if (!hasRealDebridWebPool && Boolean(settings.realDebridUseWebLogin)) {