Release v1.7.1
This commit is contained in:
@@ -22,7 +22,7 @@ import { resetDebridLinkApiKeyDailyUsage, resetProviderDailyUsage } from "../sha
|
||||
import { importDlcContainers } from "./container";
|
||||
import { APP_VERSION } from "./constants";
|
||||
import { DownloadManager } from "./download-manager";
|
||||
import { fetchAllDebridHostInfo } from "./debrid";
|
||||
import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid";
|
||||
import { parseCollectorInput } from "./link-parser";
|
||||
import { configureLogger, getLogFilePath, logger } from "./logger";
|
||||
import { AllDebridWebFallback } from "./all-debrid-web";
|
||||
@@ -248,6 +248,10 @@ export class AppController {
|
||||
return fetchAllDebridHostInfo(token, host);
|
||||
}
|
||||
|
||||
public async getDebridLinkHostLimits(host = "rapidgator") {
|
||||
return fetchDebridLinkHostLimits(this.settings.debridLinkApiKeys, host);
|
||||
}
|
||||
|
||||
public async checkUpdates(): Promise<UpdateCheckResult> {
|
||||
const result = await checkGitHubUpdate(this.settings.updateRepo);
|
||||
if (!result.error) {
|
||||
|
||||
@@ -56,6 +56,7 @@ export function defaultSettings(): AppSettings {
|
||||
ddownloadPassword: "",
|
||||
oneFichierApiKey: "",
|
||||
debridLinkApiKeys: "",
|
||||
debridLinkDisabledKeyIds: [],
|
||||
linkSnappyLogin: "",
|
||||
linkSnappyPassword: "",
|
||||
archivePasswordList: "",
|
||||
|
||||
+165
-6
@@ -1,5 +1,5 @@
|
||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||
import { AllDebridHostInfo, AppSettings, DebridFallbackProvider, DebridProvider } from "../shared/types";
|
||||
import { AllDebridHostInfo, AppSettings, DebridFallbackProvider, DebridLinkHostLimitInfo, DebridProvider } from "../shared/types";
|
||||
import { isDebridLinkApiKeyDailyLimitReached, isProviderDailyLimitReached } from "../shared/provider-daily-limits";
|
||||
import { APP_VERSION, REQUEST_RETRIES } from "./constants";
|
||||
import { logger } from "./logger";
|
||||
@@ -68,6 +68,7 @@ function cloneSettings(settings: AppSettings): AppSettings {
|
||||
return {
|
||||
...settings,
|
||||
bandwidthSchedules: (settings.bandwidthSchedules || []).map((entry) => ({ ...entry })),
|
||||
debridLinkDisabledKeyIds: [...(settings.debridLinkDisabledKeyIds || [])],
|
||||
providerDailyLimitBytes: { ...(settings.providerDailyLimitBytes || {}) },
|
||||
providerDailyUsageBytes: { ...(settings.providerDailyUsageBytes || {}) },
|
||||
debridLinkApiKeyDailyLimitBytes: { ...(settings.debridLinkApiKeyDailyLimitBytes || {}) },
|
||||
@@ -75,9 +76,13 @@ function cloneSettings(settings: AppSettings): AppSettings {
|
||||
};
|
||||
}
|
||||
|
||||
function getAvailableDebridLinkApiKeys(settings: AppSettings, epochMs = Date.now()) {
|
||||
export function isDebridLinkApiKeyDisabled(settings: AppSettings, keyId: string): boolean {
|
||||
return (settings.debridLinkDisabledKeyIds || []).includes(keyId);
|
||||
}
|
||||
|
||||
export function getAvailableDebridLinkApiKeys(settings: AppSettings, epochMs = Date.now()) {
|
||||
return parseDebridLinkApiKeys(settings.debridLinkApiKeys).filter(
|
||||
(entry) => !isDebridLinkApiKeyDailyLimitReached(settings, entry.id, epochMs)
|
||||
(entry) => !isDebridLinkApiKeyDisabled(settings, entry.id) && !isDebridLinkApiKeyDailyLimitReached(settings, entry.id, epochMs)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -312,6 +317,126 @@ function toAllDebridHostStatusLabel(state: AllDebridHostInfo["state"]): string {
|
||||
return "Unbekannt";
|
||||
}
|
||||
|
||||
function normalizeDebridLinkHostKey(value: string): string {
|
||||
return String(value || "").replace(/[^a-z0-9]+/gi, "").toLowerCase();
|
||||
}
|
||||
|
||||
function parseDebridLinkSuccess(payload: Record<string, unknown> | null): boolean {
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
if (typeof payload.success === "boolean") {
|
||||
return payload.success;
|
||||
}
|
||||
return pickString(payload, ["result"]).toUpperCase() === "OK";
|
||||
}
|
||||
|
||||
function parseDebridLinkHosters(payload: Record<string, unknown> | null): Record<string, unknown>[] {
|
||||
const value = asRecord(payload?.value);
|
||||
const hosters = value?.hosters ?? payload?.hosters;
|
||||
if (Array.isArray(hosters)) {
|
||||
return hosters.filter((entry): entry is Record<string, unknown> => Boolean(asRecord(entry))).map((entry) => entry as Record<string, unknown>);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function findDebridLinkHostEntry(payload: Record<string, unknown> | null, host: string): Record<string, unknown> | null {
|
||||
const wanted = normalizeDebridLinkHostKey(host);
|
||||
for (const entry of parseDebridLinkHosters(payload)) {
|
||||
const name = normalizeDebridLinkHostKey(pickString(entry, ["name", "host"]));
|
||||
if (name === wanted) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchDebridLinkHostLimitForKey(apiKey: { id: string; label: string; token: string }, host: string, signal?: AbortSignal): Promise<DebridLinkHostLimitInfo> {
|
||||
let lastError = "";
|
||||
const hostLabel = host.trim() || "rapidgator";
|
||||
const endpoints = [`${DEBRID_LINK_API_BASE}/downloader/limits/all`, `${DEBRID_LINK_API_BASE}/downloader/limits`];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
for (let attempt = 1; attempt <= REQUEST_RETRIES; attempt += 1) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey.token}`,
|
||||
"User-Agent": DEBRID_USER_AGENT
|
||||
},
|
||||
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
||||
});
|
||||
const text = await response.text();
|
||||
const payload = parseJsonSafe(text);
|
||||
|
||||
if (response.status === 404 && endpoint.endsWith("/all")) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const reason = parseError(response.status, text, payload);
|
||||
if (shouldRetryStatus(response.status) && attempt < REQUEST_RETRIES) {
|
||||
await sleepWithSignal(retryDelayForResponse(response, attempt), signal);
|
||||
continue;
|
||||
}
|
||||
throw new Error(reason);
|
||||
}
|
||||
|
||||
if (!payload) {
|
||||
throw new Error("Debrid-Link Limits Antwort ist kein JSON-Objekt");
|
||||
}
|
||||
if (!parseDebridLinkSuccess(payload)) {
|
||||
throw new Error(pickString(payload, ["error_description", "error", "message"]) || "Debrid-Link Limits fehlgeschlagen");
|
||||
}
|
||||
|
||||
const hostEntry = findDebridLinkHostEntry(payload, hostLabel);
|
||||
if (!hostEntry) {
|
||||
if (endpoint.endsWith("/all")) {
|
||||
return {
|
||||
keyId: apiKey.id,
|
||||
keyLabel: apiKey.label,
|
||||
host: hostLabel,
|
||||
fetchedAt: Date.now(),
|
||||
trafficCurrentBytes: null,
|
||||
trafficMaxBytes: null,
|
||||
linksCurrent: null,
|
||||
linksMax: null,
|
||||
note: `${hostLabel} nicht in der Debrid-Link-Limits-Antwort vorhanden.`
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const daySize = asRecord(hostEntry.daySize);
|
||||
const dayCount = asRecord(hostEntry.dayCount);
|
||||
return {
|
||||
keyId: apiKey.id,
|
||||
keyLabel: apiKey.label,
|
||||
host: pickString(hostEntry, ["name", "host"]) || hostLabel,
|
||||
fetchedAt: Date.now(),
|
||||
trafficCurrentBytes: pickNumber(daySize, ["current"]),
|
||||
trafficMaxBytes: pickNumber(daySize, ["value", "max"]),
|
||||
linksCurrent: pickNumber(dayCount, ["current"]),
|
||||
linksMax: pickNumber(dayCount, ["value", "max"]),
|
||||
note: ""
|
||||
};
|
||||
} catch (error) {
|
||||
lastError = compactErrorText(error);
|
||||
if (signal?.aborted || (/aborted/i.test(lastError) && !/timeout/i.test(lastError))) {
|
||||
break;
|
||||
}
|
||||
if (attempt >= REQUEST_RETRIES || !isRetryableErrorText(lastError)) {
|
||||
break;
|
||||
}
|
||||
await sleepWithSignal(retryDelay(attempt), signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(String(lastError || `Debrid-Link Limits für ${apiKey.label} fehlgeschlagen`).replace(/^Error:\s*/i, ""));
|
||||
}
|
||||
|
||||
function uniqueProviderOrder(order: DebridProvider[]): DebridProvider[] {
|
||||
const seen = new Set<DebridProvider>();
|
||||
const result: DebridProvider[] = [];
|
||||
@@ -1314,6 +1439,19 @@ export async function fetchAllDebridHostInfo(token: string, host = "rapidgator",
|
||||
return new AllDebridClient(token).getHostInfo(host, signal);
|
||||
}
|
||||
|
||||
export async function fetchDebridLinkHostLimits(apiKeysRaw: string, host = "rapidgator", signal?: AbortSignal): Promise<DebridLinkHostLimitInfo[]> {
|
||||
const apiKeys = parseDebridLinkApiKeys(apiKeysRaw);
|
||||
if (apiKeys.length === 0) {
|
||||
throw new Error("Debrid-Link ist nicht konfiguriert");
|
||||
}
|
||||
|
||||
const results: DebridLinkHostLimitInfo[] = [];
|
||||
for (const apiKey of apiKeys) {
|
||||
results.push(await fetchDebridLinkHostLimitForKey(apiKey, host, signal));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// ── Debrid-Link Client ──
|
||||
|
||||
class DebridLinkClient {
|
||||
@@ -1330,7 +1468,7 @@ class DebridLinkClient {
|
||||
}
|
||||
|
||||
if (getAvailableDebridLinkApiKeys(settings).length === 0) {
|
||||
throw new Error(`Debrid-Link: Alle ${this.apiKeys.length} API-Keys haben ihr Tageslimit erreicht`);
|
||||
throw new Error("Debrid-Link: Kein aktiver API-Key verfuegbar (deaktiviert oder am Tageslimit)");
|
||||
}
|
||||
|
||||
let checkedKeys = 0;
|
||||
@@ -1338,7 +1476,13 @@ class DebridLinkClient {
|
||||
const apiKey = this.apiKeys[this.currentKeyIndex];
|
||||
checkedKeys += 1;
|
||||
const keyLabel = this.apiKeys.length > 1 ? ` (${apiKey.label})` : "";
|
||||
if (isDebridLinkApiKeyDisabled(settings, apiKey.id)) {
|
||||
logger.info(`Debrid-Link${keyLabel}: uebersprungen (manuell deaktiviert), pruefe naechsten Key`);
|
||||
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length;
|
||||
continue;
|
||||
}
|
||||
if (isDebridLinkApiKeyDailyLimitReached(settings, apiKey.id)) {
|
||||
logger.info(`Debrid-Link${keyLabel}: uebersprungen (lokales Tageslimit erreicht), pruefe naechsten Key`);
|
||||
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length;
|
||||
continue;
|
||||
}
|
||||
@@ -1364,6 +1508,7 @@ class DebridLinkClient {
|
||||
const errorDesc = String(json.error_description || json.error || "Unbekannter Debrid-Link-Fehler");
|
||||
|
||||
if (DEBRID_LINK_QUOTA_ERRORS.has(errorCode)) {
|
||||
logger.warn(`Debrid-Link${keyLabel}: API-Quota erreicht (${errorCode}: ${errorDesc}), wechsle zum naechsten Key`);
|
||||
logger.warn(`Debrid-Link Quota erreicht${keyLabel}: ${errorCode} – ${errorDesc}`);
|
||||
break;
|
||||
}
|
||||
@@ -1411,6 +1556,7 @@ class DebridLinkClient {
|
||||
if (/Ungültig|abgelaufen/i.test(lastError)) {
|
||||
throw error;
|
||||
}
|
||||
logger.warn(`Debrid-Link${keyLabel}: Fehler bei Unrestrict-Versuch ${attempt}/${REQUEST_RETRIES}: ${lastError}`);
|
||||
if (attempt < REQUEST_RETRIES) {
|
||||
await sleep(retryDelay(attempt), signal);
|
||||
}
|
||||
@@ -1418,9 +1564,14 @@ class DebridLinkClient {
|
||||
}
|
||||
|
||||
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length;
|
||||
if (checkedKeys < this.apiKeys.length) {
|
||||
const nextKey = this.apiKeys[this.currentKeyIndex];
|
||||
const nextKeyLabel = this.apiKeys.length > 1 ? ` (${nextKey.label})` : "";
|
||||
logger.info(`Debrid-Link${keyLabel}: kein Erfolg, wechsle zu naechstem Key${nextKeyLabel}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Debrid-Link: Alle ${this.apiKeys.length} API-Keys haben ihr Limit erreicht`);
|
||||
throw new Error("Debrid-Link: Kein aktiver API-Key verfuegbar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1948,7 +2099,7 @@ export class DebridService {
|
||||
private formatProviderLimitMessage(settings: AppSettings, provider: DebridProvider): string {
|
||||
const effectiveProvider = resolveMegaDebridProvider(settings, provider);
|
||||
if (effectiveProvider === "debridlink" && parseDebridLinkApiKeys(settings.debridLinkApiKeys).length > 0 && getAvailableDebridLinkApiKeys(settings).length === 0) {
|
||||
return "Debrid-Link Tageslimit erreicht (alle API-Keys ausgeschopft)";
|
||||
return "Debrid-Link nicht verfuegbar (alle aktiven API-Keys deaktiviert oder ausgeschopft)";
|
||||
}
|
||||
return `${PROVIDER_LABELS[effectiveProvider]} Tageslimit erreicht`;
|
||||
}
|
||||
@@ -2084,11 +2235,13 @@ export class DebridService {
|
||||
configuredFound = true;
|
||||
if (this.isProviderDailyLimited(settings, provider)) {
|
||||
limitReachedFound = true;
|
||||
logger.info(`Provider-Kette: ${PROVIDER_LABELS[provider]} uebersprungen (${this.formatProviderLimitMessage(settings, provider)})`);
|
||||
attempts.push(this.formatProviderLimitMessage(settings, provider));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Provider-Kette: versuche ${PROVIDER_LABELS[provider]}`);
|
||||
const result = await this.unrestrictViaProvider(settings, provider, link, signal);
|
||||
let fileName = result.fileName;
|
||||
if (isRapidgatorLink(link) && looksLikeOpaqueFilename(fileName || filenameFromUrl(link))) {
|
||||
@@ -2108,6 +2261,12 @@ export class DebridService {
|
||||
if (signal?.aborted || (/aborted/i.test(errorText) && !/timeout/i.test(errorText))) {
|
||||
throw error;
|
||||
}
|
||||
const nextProvider = order.slice(order.indexOf(provider) + 1).find((candidate) => this.isProviderSelectableFor(settings, candidate));
|
||||
if (nextProvider) {
|
||||
logger.warn(`Provider-Kette: ${PROVIDER_LABELS[provider]} fehlgeschlagen (${errorText}), Fallback auf ${PROVIDER_LABELS[nextProvider]}`);
|
||||
} else {
|
||||
logger.warn(`Provider-Kette: ${PROVIDER_LABELS[provider]} fehlgeschlagen (${errorText}), kein weiterer Provider verfuegbar`);
|
||||
}
|
||||
attempts.push(`${PROVIDER_LABELS[provider]}: ${compactErrorText(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
UiSnapshot
|
||||
} from "../shared/types";
|
||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||
import { addDebridLinkApiKeyDailyUsageBytes, addProviderDailyUsageBytes, getProviderUsageDayKey, isDebridLinkApiKeyDailyLimitReached, isProviderDailyLimitReached } from "../shared/provider-daily-limits";
|
||||
import { addDebridLinkApiKeyDailyUsageBytes, addProviderDailyUsageBytes, getProviderUsageDayKey, isProviderDailyLimitReached } from "../shared/provider-daily-limits";
|
||||
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, SPEED_WINDOW_SECONDS, WRITE_BUFFER_SIZE, WRITE_FLUSH_TIMEOUT_MS, ALLOCATION_UNIT_SIZE, STREAM_HIGH_WATER_MARK, DISK_BUSY_THRESHOLD_MS } from "./constants";
|
||||
|
||||
// Reference counter for NODE_TLS_REJECT_UNAUTHORIZED to avoid race conditions
|
||||
@@ -41,7 +41,7 @@ function releaseTlsSkip(): void {
|
||||
}
|
||||
}
|
||||
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
|
||||
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo } from "./debrid";
|
||||
import { AllDebridWebUnrestrictor, BestDebridWebUnrestrictor, DebridService, MegaWebUnrestrictor, RealDebridWebUnrestrictor, checkRapidgatorOnline, fetchAllDebridHostInfo, getAvailableDebridLinkApiKeys } from "./debrid";
|
||||
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree } from "./extractor";
|
||||
import { validateFileAgainstManifest } from "./integrity";
|
||||
import { logger } from "./logger";
|
||||
@@ -459,6 +459,7 @@ function toWindowsLongPathIfNeeded(filePath: string): string {
|
||||
const SCENE_RELEASE_FOLDER_RE = /-(?:4sf|4sj)$/i;
|
||||
const SCENE_GROUP_SUFFIX_RE = /-(?=[A-Za-z0-9]{2,}$)(?=[A-Za-z0-9]*[A-Z])[A-Za-z0-9]+$/;
|
||||
const SCENE_EPISODE_RE = /(?:^|[._\-\s])s(\d{1,2})e(\d{1,3})(?:e(\d{1,3}))?(?:[._\-\s]|$)/i;
|
||||
const SCENE_EPISODE_JOINED_RE = /s(\d{1,2})e(\d{1,3})(?:e(\d{1,3}))?(?:[._\-\s]|$)/i;
|
||||
const SCENE_SEASON_ONLY_RE = /(^|[._\-\s])s\d{1,2}(?=[._\-\s]|$)/i;
|
||||
const SCENE_SEASON_CAPTURE_RE = /(?:^|[._\-\s])s(\d{1,2})(?=[._\-\s]|$)/i;
|
||||
const SCENE_EPISODE_ONLY_RE = /(?:^|[._\-\s])e(?:p(?:isode)?)?\s*0*(\d{1,3})(?:[._\-\s]|$)/i;
|
||||
@@ -518,7 +519,8 @@ function hasSceneGroupSuffix(fileName: string): boolean {
|
||||
}
|
||||
|
||||
export function extractEpisodeToken(fileName: string): string | null {
|
||||
const match = String(fileName || "").match(SCENE_EPISODE_RE);
|
||||
const text = String(fileName || "");
|
||||
const match = text.match(SCENE_EPISODE_RE) || text.match(SCENE_EPISODE_JOINED_RE);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
@@ -4483,7 +4485,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
if (effectiveProvider === "debridlink") {
|
||||
const configuredKeys = parseDebridLinkApiKeys(this.settings.debridLinkApiKeys);
|
||||
return configuredKeys.some((entry) => !isDebridLinkApiKeyDailyLimitReached(this.settings, entry.id));
|
||||
return configuredKeys.length > 0 && getAvailableDebridLinkApiKeys(this.settings).length > 0;
|
||||
}
|
||||
if (provider === "linksnappy") {
|
||||
return Boolean(this.settings.linkSnappyLogin.trim() && this.settings.linkSnappyPassword.trim());
|
||||
|
||||
+7
-3
@@ -74,8 +74,8 @@ function isDevMode(): boolean {
|
||||
|
||||
function createWindow(): BrowserWindow {
|
||||
const window = new BrowserWindow({
|
||||
width: 1440,
|
||||
height: 940,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
minWidth: 1120,
|
||||
minHeight: 760,
|
||||
backgroundColor: "#070b14",
|
||||
@@ -94,7 +94,7 @@ function createWindow(): BrowserWindow {
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
"Content-Security-Policy": [
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.real-debrid.com https://codeberg.org https://bestdebrid.com https://api.alldebrid.com https://www.mega-debrid.eu https://git.24-music.de https://ddownload.com https://ddl.to"
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.real-debrid.com https://codeberg.org https://bestdebrid.com https://api.alldebrid.com https://www.mega-debrid.eu https://git.24-music.de https://ddownload.com https://ddl.to https://debrid-link.com"
|
||||
]
|
||||
}
|
||||
});
|
||||
@@ -527,6 +527,10 @@ function registerIpcHandlers(): void {
|
||||
return controller.getAllDebridHostInfo();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_DEBRIDLINK_HOST_LIMITS, async () => {
|
||||
return controller.getDebridLinkHostLimits();
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.IMPORT_BACKUP, async () => {
|
||||
const options = {
|
||||
properties: ["openFile"] as Array<"openFile">,
|
||||
|
||||
@@ -196,6 +196,25 @@ function normalizeNamedByteMap(raw: unknown, allowedKeys: readonly string[]): Re
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalizeStringList(raw: unknown, allowedKeys: readonly string[]): string[] {
|
||||
if (!Array.isArray(raw)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allowed = new Set(allowedKeys);
|
||||
const seen = new Set<string>();
|
||||
const result: string[] = [];
|
||||
for (const entry of raw) {
|
||||
const normalized = String(entry || "").trim();
|
||||
if (!normalized || !allowed.has(normalized) || seen.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(normalized);
|
||||
result.push(normalized);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalizeHosterRouting(raw: unknown, megaDebridPreferApi: boolean, megaDebridApiEnabled: boolean, megaDebridWebEnabled: boolean): Record<string, DebridProvider> {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
||||
const result: Record<string, DebridProvider> = {};
|
||||
@@ -287,6 +306,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
||||
settings.debridLinkApiKeyDailyUsageBytes,
|
||||
debridLinkApiKeyIds
|
||||
);
|
||||
const debridLinkDisabledKeyIds = normalizeStringList(settings.debridLinkDisabledKeyIds, debridLinkApiKeyIds);
|
||||
const normalized: AppSettings = {
|
||||
token: asText(settings.token),
|
||||
realDebridUseWebLogin: Boolean(settings.realDebridUseWebLogin),
|
||||
@@ -303,6 +323,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
||||
ddownloadPassword: asText(settings.ddownloadPassword),
|
||||
oneFichierApiKey: asText(settings.oneFichierApiKey),
|
||||
debridLinkApiKeys: String(settings.debridLinkApiKeys ?? "").replace(/\r\n|\r/g, "\n").trim(),
|
||||
debridLinkDisabledKeyIds,
|
||||
linkSnappyLogin: asText(settings.linkSnappyLogin),
|
||||
linkSnappyPassword: asText(settings.linkSnappyPassword),
|
||||
archivePasswordList: String(settings.archivePasswordList ?? "").replace(/\r\n|\r/g, "\n"),
|
||||
|
||||
Reference in New Issue
Block a user