Initial public release v1.7.233
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
export interface DebridLinkApiKeyEntry {
|
||||
id: string;
|
||||
token: string;
|
||||
index: number;
|
||||
label: string;
|
||||
masked: string;
|
||||
}
|
||||
|
||||
const FNV64_OFFSET_BASIS = 0xcbf29ce484222325n;
|
||||
const FNV64_PRIME = 0x100000001b3n;
|
||||
const FNV64_MASK = 0xffffffffffffffffn;
|
||||
|
||||
function fnv1a64(text: string): string {
|
||||
let hash = FNV64_OFFSET_BASIS;
|
||||
for (const char of text) {
|
||||
hash ^= BigInt(char.codePointAt(0) || 0);
|
||||
hash = (hash * FNV64_PRIME) & FNV64_MASK;
|
||||
}
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
export function maskDebridLinkApiKey(token: string): string {
|
||||
const trimmed = token.trim();
|
||||
if (!trimmed) {
|
||||
return "Nicht hinterlegt";
|
||||
}
|
||||
if (trimmed.length <= 6) {
|
||||
return "*".repeat(trimmed.length);
|
||||
}
|
||||
return `${trimmed.slice(0, 3)}${"*".repeat(Math.max(4, trimmed.length - 6))}${trimmed.slice(-3)}`;
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyId(token: string): string {
|
||||
return `dlk_${fnv1a64(token.trim())}`;
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyLabel(index: number): string {
|
||||
return `Key ${index + 1}`;
|
||||
}
|
||||
|
||||
export function parseDebridLinkApiKeys(raw: string): DebridLinkApiKeyEntry[] {
|
||||
const seen = new Set<string>();
|
||||
const tokens = String(raw || "")
|
||||
.split(/[\n,]+/)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
.filter((token) => {
|
||||
if (seen.has(token)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(token);
|
||||
return true;
|
||||
});
|
||||
|
||||
return tokens.map((token, index) => ({
|
||||
id: getDebridLinkApiKeyId(token),
|
||||
token,
|
||||
index,
|
||||
label: getDebridLinkApiKeyLabel(index),
|
||||
masked: maskDebridLinkApiKey(token)
|
||||
}));
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyIds(raw: string): string[] {
|
||||
return parseDebridLinkApiKeys(raw).map((entry) => entry.id);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
export const IPC_CHANNELS = {
|
||||
GET_SNAPSHOT: "app:get-snapshot",
|
||||
GET_VERSION: "app:get-version",
|
||||
CHECK_UPDATES: "app:check-updates",
|
||||
INSTALL_UPDATE: "app:install-update",
|
||||
UPDATE_INSTALL_PROGRESS: "app:update-install-progress",
|
||||
OPEN_EXTERNAL: "app:open-external",
|
||||
UPDATE_SETTINGS: "app:update-settings",
|
||||
RESET_PROVIDER_DAILY_USAGE: "app:reset-provider-daily-usage",
|
||||
RESET_DEBRID_LINK_API_KEY_DAILY_USAGE: "app:reset-debrid-link-api-key-daily-usage",
|
||||
ADD_LINKS: "queue:add-links",
|
||||
ADD_CONTAINERS: "queue:add-containers",
|
||||
GET_START_CONFLICTS: "queue:get-start-conflicts",
|
||||
RESOLVE_START_CONFLICT: "queue:resolve-start-conflict",
|
||||
CLEAR_ALL: "queue:clear-all",
|
||||
START: "queue:start",
|
||||
START_PACKAGES: "queue:start-packages",
|
||||
STOP: "queue:stop",
|
||||
TOGGLE_PAUSE: "queue:toggle-pause",
|
||||
CANCEL_PACKAGE: "queue:cancel-package",
|
||||
RENAME_PACKAGE: "queue:rename-package",
|
||||
REORDER_PACKAGES: "queue:reorder-packages",
|
||||
REMOVE_ITEM: "queue:remove-item",
|
||||
TOGGLE_PACKAGE: "queue:toggle-package",
|
||||
EXPORT_PACKAGE_SELECTION: "queue:export-package-selection",
|
||||
EXPORT_ITEM_SELECTION: "queue:export-item-selection",
|
||||
EXPORT_QUEUE: "queue:export",
|
||||
IMPORT_QUEUE: "queue:import",
|
||||
PICK_FOLDER: "dialog:pick-folder",
|
||||
PICK_CONTAINERS: "dialog:pick-containers",
|
||||
STATE_UPDATE: "state:update",
|
||||
CLIPBOARD_DETECTED: "clipboard:detected",
|
||||
TOGGLE_CLIPBOARD: "clipboard:toggle",
|
||||
GET_SESSION_STATS: "stats:get-session-stats",
|
||||
RESET_SESSION_STATS: "stats:reset-session",
|
||||
RESET_DOWNLOAD_STATS: "stats:reset-download",
|
||||
RESTART: "app:restart",
|
||||
QUIT: "app:quit",
|
||||
EXPORT_BACKUP: "app:export-backup",
|
||||
IMPORT_BACKUP: "app:import-backup",
|
||||
EXPORT_SUPPORT_BUNDLE: "app:export-support-bundle",
|
||||
OPEN_LOG: "app:open-log",
|
||||
OPEN_AUDIT_LOG: "app:open-audit-log",
|
||||
OPEN_RENAME_LOG: "app:open-rename-log",
|
||||
OPEN_SESSION_LOG: "app:open-session-log",
|
||||
OPEN_TRACE_LOG: "app:open-trace-log",
|
||||
OPEN_PACKAGE_LOG: "app:open-package-log",
|
||||
OPEN_ITEM_LOG: "app:open-item-log",
|
||||
GET_DEBUG_SETUP_CHECK: "app:get-debug-setup-check",
|
||||
GET_RECENT_ERRORS: "app:get-recent-errors",
|
||||
TEST_NOTIFY: "app:test-notify",
|
||||
GET_TRACE_CONFIG: "app:get-trace-config",
|
||||
SET_TRACE_ENABLED: "app:set-trace-enabled",
|
||||
ROTATE_DEBUG_TOKEN: "app:rotate-debug-token",
|
||||
GET_REMOTE_DIAGNOSTICS: "app:get-remote-diagnostics",
|
||||
ENABLE_REMOTE_DIAGNOSTICS: "app:enable-remote-diagnostics",
|
||||
DISABLE_REMOTE_DIAGNOSTICS: "app:disable-remote-diagnostics",
|
||||
ROTATE_REMOTE_DIAGNOSTICS_TOKEN: "app:rotate-remote-diagnostics-token",
|
||||
OPEN_REALDEBRID_LOGIN: "app:open-realdebrid-login",
|
||||
OPEN_ALLDEBRID_LOGIN: "app:open-alldebrid-login",
|
||||
IMPORT_BESTDEBRID_COOKIES: "app:import-bestdebrid-cookies",
|
||||
GET_ALLDEBRID_HOST_INFO: "app:get-alldebrid-host-info",
|
||||
GET_DEBRIDLINK_HOST_LIMITS: "app:get-debridlink-host-limits",
|
||||
CHECK_DEBRID_ACCOUNTS: "app:check-debrid-accounts",
|
||||
CHECK_MEGA_DEBRID_ACCOUNT: "app:check-mega-debrid-account",
|
||||
RETRY_EXTRACTION: "queue:retry-extraction",
|
||||
EXTRACT_NOW: "queue:extract-now",
|
||||
RESET_PACKAGE: "queue:reset-package",
|
||||
GET_HISTORY: "history:get",
|
||||
CLEAR_HISTORY: "history:clear",
|
||||
REMOVE_HISTORY_ENTRY: "history:remove-entry",
|
||||
SET_PACKAGE_PRIORITY: "queue:set-package-priority",
|
||||
SKIP_ITEMS: "queue:skip-items",
|
||||
RESET_ITEMS: "queue:reset-items",
|
||||
START_ITEMS: "queue:start-items",
|
||||
LOG_RENDERER_ERROR: "log:renderer-error"
|
||||
} as const;
|
||||
@@ -0,0 +1,90 @@
|
||||
export interface MegaDebridAccountEntry {
|
||||
id: string;
|
||||
login: string;
|
||||
password: string;
|
||||
index: number;
|
||||
label: string;
|
||||
maskedLogin: string;
|
||||
}
|
||||
|
||||
const FNV64_OFFSET_BASIS = 0xcbf29ce484222325n;
|
||||
const FNV64_PRIME = 0x100000001b3n;
|
||||
const FNV64_MASK = 0xffffffffffffffffn;
|
||||
|
||||
function fnv1a64(text: string): string {
|
||||
let hash = FNV64_OFFSET_BASIS;
|
||||
for (const char of text) {
|
||||
hash ^= BigInt(char.codePointAt(0) || 0);
|
||||
hash = (hash * FNV64_PRIME) & FNV64_MASK;
|
||||
}
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountId(login: string): string {
|
||||
return `mda_${fnv1a64(login.trim().toLowerCase())}`;
|
||||
}
|
||||
|
||||
export function maskMegaDebridLogin(login: string): string {
|
||||
const trimmed = login.trim();
|
||||
if (!trimmed) {
|
||||
return "Nicht hinterlegt";
|
||||
}
|
||||
if (trimmed.length <= 4) {
|
||||
return `${trimmed[0]}${"*".repeat(trimmed.length - 1)}`;
|
||||
}
|
||||
return `${trimmed.slice(0, 2)}${"*".repeat(Math.max(3, trimmed.length - 4))}${trimmed.slice(-2)}`;
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountLabel(index: number): string {
|
||||
return `Account ${index + 1}`;
|
||||
}
|
||||
|
||||
export function parseMegaDebridAccounts(raw: string, legacyPassword = ""): MegaDebridAccountEntry[] {
|
||||
const seen = new Set<string>();
|
||||
const lines = String(raw || "")
|
||||
.split(/\n+/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const entries: MegaDebridAccountEntry[] = [];
|
||||
for (const line of lines) {
|
||||
const colonIdx = line.indexOf(":");
|
||||
let login: string;
|
||||
let password: string;
|
||||
if (colonIdx >= 0) {
|
||||
login = line.slice(0, colonIdx).trim();
|
||||
password = line.slice(colonIdx + 1).trim();
|
||||
} else {
|
||||
login = line;
|
||||
password = legacyPassword;
|
||||
}
|
||||
if (!login || !password) {
|
||||
continue;
|
||||
}
|
||||
const key = login.toLowerCase();
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
entries.push({
|
||||
id: getMegaDebridAccountId(login),
|
||||
login,
|
||||
password,
|
||||
index: entries.length,
|
||||
label: getMegaDebridAccountLabel(entries.length),
|
||||
maskedLogin: maskMegaDebridLogin(login)
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function serializeMegaDebridAccounts(accounts: { login: string; password: string }[]): string {
|
||||
return accounts
|
||||
.filter((a) => a.login.trim() && a.password.trim())
|
||||
.map((a) => `${a.login.trim()}:${a.password.trim()}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountIds(raw: string, legacyPassword = ""): string[] {
|
||||
return parseMegaDebridAccounts(raw, legacyPassword).map((entry) => entry.id);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export function isMegaDebridResolveFailure(errorText: string): boolean {
|
||||
const text = String(errorText || "").toLowerCase();
|
||||
return /supprim/.test(text)
|
||||
|| text.includes("introuvable")
|
||||
|| text.includes("n'existe plus")
|
||||
|| text.includes("n existe plus")
|
||||
|| text.includes("fichier inexistant");
|
||||
}
|
||||
|
||||
export function isMegaDebridTransientResolveFailure(errorText: string): boolean {
|
||||
const text = String(errorText || "").toLowerCase();
|
||||
return isMegaDebridResolveFailure(text)
|
||||
|| text.includes("datei beim hoster gerade nicht abrufbar")
|
||||
|| text.includes("datei beim hoster nicht gefunden");
|
||||
}
|
||||
|
||||
export function germanMegaDebridResolveReason(errorText: string): string {
|
||||
const text = String(errorText || "").toLowerCase();
|
||||
if (text.includes("datei beim hoster nicht gefunden")
|
||||
|| text.includes("introuvable") || text.includes("fichier inexistant") || text.includes("n'existe plus") || text.includes("n existe plus")) {
|
||||
return "Datei beim Hoster nicht gefunden";
|
||||
}
|
||||
return "Datei beim Hoster gerade nicht abrufbar";
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import type {
|
||||
AddLinksPayload,
|
||||
AllDebridHostInfo,
|
||||
AppSettings,
|
||||
DebridAccountStatus,
|
||||
DebugSetupCheckResult,
|
||||
DebridLinkHostLimitInfo,
|
||||
DebridProvider,
|
||||
DuplicatePolicy,
|
||||
EnableRemoteDiagnosticsInput,
|
||||
HistoryEntry,
|
||||
PackagePriority,
|
||||
RemoteDiagnosticsInfo,
|
||||
RendererErrorReport,
|
||||
SessionStats,
|
||||
StartConflictEntry,
|
||||
StartConflictResolutionResult,
|
||||
SupportTraceConfig,
|
||||
UiSnapshot,
|
||||
UpdateCheckResult,
|
||||
UpdateInstallProgress,
|
||||
UpdateInstallResult
|
||||
} from "./types";
|
||||
|
||||
export interface ElectronApi {
|
||||
getSnapshot: () => Promise<UiSnapshot>;
|
||||
getVersion: () => Promise<string>;
|
||||
checkUpdates: () => Promise<UpdateCheckResult>;
|
||||
installUpdate: () => Promise<UpdateInstallResult>;
|
||||
openExternal: (url: string) => Promise<boolean>;
|
||||
updateSettings: (settings: Partial<AppSettings>) => Promise<AppSettings>;
|
||||
resetProviderDailyUsage: (provider: DebridProvider) => Promise<AppSettings>;
|
||||
resetDebridLinkApiKeyDailyUsage: (keyId: string) => Promise<AppSettings>;
|
||||
addLinks: (payload: AddLinksPayload) => Promise<{ addedPackages: number; addedLinks: number; invalidCount: number }>;
|
||||
addContainers: (filePaths: string[]) => Promise<{ addedPackages: number; addedLinks: number }>;
|
||||
getStartConflicts: () => Promise<StartConflictEntry[]>;
|
||||
resolveStartConflict: (packageId: string, policy: DuplicatePolicy) => Promise<StartConflictResolutionResult>;
|
||||
clearAll: () => Promise<void>;
|
||||
start: () => Promise<void>;
|
||||
startPackages: (packageIds: string[]) => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
togglePause: () => Promise<boolean>;
|
||||
cancelPackage: (packageId: string) => Promise<void>;
|
||||
renamePackage: (packageId: string, newName: string) => Promise<void>;
|
||||
reorderPackages: (packageIds: string[]) => Promise<void>;
|
||||
removeItem: (itemId: string) => Promise<void>;
|
||||
togglePackage: (packageId: string) => Promise<void>;
|
||||
exportPackageSelection: (packageIds: string[]) => Promise<{ saved: boolean; packageCount: number; linkCount: number; filePath?: string }>;
|
||||
exportItemSelection: (itemIds: string[]) => Promise<{ saved: boolean; packageCount: number; linkCount: number; filePath?: string }>;
|
||||
exportQueue: () => Promise<{ saved: boolean }>;
|
||||
importQueue: (json: string) => Promise<{ addedPackages: number; addedLinks: number }>;
|
||||
toggleClipboard: () => Promise<boolean>;
|
||||
pickFolder: () => Promise<string | null>;
|
||||
pickContainers: () => Promise<string[]>;
|
||||
getSessionStats: () => Promise<SessionStats>;
|
||||
resetSessionStats: () => Promise<void>;
|
||||
resetDownloadStats: () => Promise<void>;
|
||||
restart: () => Promise<void>;
|
||||
quit: () => Promise<void>;
|
||||
exportBackup: () => Promise<{ saved: boolean }>;
|
||||
importBackup: () => Promise<{ restored: boolean; relaunch: boolean; message: string }>;
|
||||
exportSupportBundle: () => Promise<{ saved: boolean; filePath?: string }>;
|
||||
openLog: () => Promise<void>;
|
||||
openAuditLog: () => Promise<void>;
|
||||
openRenameLog: () => Promise<void>;
|
||||
openSessionLog: () => Promise<void>;
|
||||
openTraceLog: () => Promise<void>;
|
||||
openPackageLog: (packageId: string) => Promise<void>;
|
||||
openItemLog: (itemId: string) => Promise<void>;
|
||||
getDebugSetupCheck: () => Promise<DebugSetupCheckResult>;
|
||||
getRecentErrors: () => Promise<Array<{ ts: string; level: string; message: string }>>;
|
||||
testNotification: (url: string, mention: string) => Promise<boolean>;
|
||||
getTraceConfig: () => Promise<SupportTraceConfig>;
|
||||
setTraceEnabled: (enabled: boolean, note?: string, durationMinutes?: number) => Promise<SupportTraceConfig>;
|
||||
rotateDebugToken: () => Promise<{ path: string }>;
|
||||
getRemoteDiagnostics: () => Promise<RemoteDiagnosticsInfo>;
|
||||
enableRemoteDiagnostics: (input: EnableRemoteDiagnosticsInput) => Promise<RemoteDiagnosticsInfo>;
|
||||
disableRemoteDiagnostics: () => Promise<RemoteDiagnosticsInfo>;
|
||||
rotateRemoteDiagnosticsToken: () => Promise<RemoteDiagnosticsInfo>;
|
||||
openRealDebridLogin: () => Promise<void>;
|
||||
openAllDebridLogin: () => Promise<void>;
|
||||
importBestDebridCookies: () => Promise<number>;
|
||||
getAllDebridHostInfo: () => Promise<AllDebridHostInfo>;
|
||||
getDebridLinkHostLimits: () => Promise<DebridLinkHostLimitInfo[]>;
|
||||
checkDebridAccounts: () => Promise<DebridAccountStatus[]>;
|
||||
checkMegaDebridAccount: (login: string, password: string) => Promise<DebridAccountStatus | null>;
|
||||
retryExtraction: (packageId: string) => Promise<void>;
|
||||
extractNow: (packageId: string) => Promise<void>;
|
||||
resetPackage: (packageId: string) => Promise<void>;
|
||||
getHistory: () => Promise<HistoryEntry[]>;
|
||||
clearHistory: () => Promise<void>;
|
||||
removeHistoryEntry: (entryId: string) => Promise<void>;
|
||||
setPackagePriority: (packageId: string, priority: PackagePriority) => Promise<void>;
|
||||
skipItems: (itemIds: string[]) => Promise<void>;
|
||||
resetItems: (itemIds: string[]) => Promise<void>;
|
||||
startItems: (itemIds: string[]) => Promise<void>;
|
||||
reportRendererError: (report: RendererErrorReport) => void;
|
||||
onStateUpdate: (callback: (snapshot: UiSnapshot) => void) => () => void;
|
||||
onClipboardDetected: (callback: (links: string[]) => void) => () => void;
|
||||
onUpdateInstallProgress: (callback: (progress: UpdateInstallProgress) => void) => () => void;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
import type { AppSettings, DebridProvider } from "./types";
|
||||
|
||||
export type ProviderByteMap = Partial<Record<DebridProvider, number>>;
|
||||
export type DebridLinkKeyByteMap = Record<string, number>;
|
||||
|
||||
type ProviderDailySettings =
|
||||
Pick<AppSettings, "providerDailyLimitBytes" | "providerDailyUsageBytes" | "providerDailyUsageDay">
|
||||
& Partial<Pick<AppSettings, "debridLinkApiKeyDailyLimitBytes" | "debridLinkApiKeyDailyUsageBytes">>
|
||||
& Partial<Pick<AppSettings, "megaDebridDisabledAccountIds" | "megaDebridAccountDailyLimitBytes" | "megaDebridAccountDailyUsageBytes">>;
|
||||
|
||||
type ProviderUsageSettings =
|
||||
ProviderDailySettings
|
||||
& Partial<Pick<AppSettings, "providerTotalUsageBytes" | "debridLinkApiKeyTotalUsageBytes">>
|
||||
& Partial<Pick<AppSettings, "megaDebridAccountTotalUsageBytes">>;
|
||||
|
||||
function normalizePositiveBytes(value: unknown): number {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(numeric);
|
||||
}
|
||||
|
||||
export function getProviderUsageDayKey(epochMs = Date.now()): string {
|
||||
const current = new Date(epochMs);
|
||||
const year = current.getFullYear();
|
||||
const month = String(current.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(current.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function getProviderDailyLimitBytes(settings: ProviderDailySettings, provider: DebridProvider): number {
|
||||
return normalizePositiveBytes(settings.providerDailyLimitBytes?.[provider]);
|
||||
}
|
||||
|
||||
export function getProviderDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
provider: DebridProvider,
|
||||
epochMs = Date.now()
|
||||
): number {
|
||||
if (settings.providerDailyUsageDay !== getProviderUsageDayKey(epochMs)) {
|
||||
return 0;
|
||||
}
|
||||
return normalizePositiveBytes(settings.providerDailyUsageBytes?.[provider]);
|
||||
}
|
||||
|
||||
export function getProviderDailyRemainingBytes(
|
||||
settings: ProviderDailySettings,
|
||||
provider: DebridProvider,
|
||||
epochMs = Date.now()
|
||||
): number | null {
|
||||
const limit = getProviderDailyLimitBytes(settings, provider);
|
||||
if (limit <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.max(0, limit - getProviderDailyUsageBytes(settings, provider, epochMs));
|
||||
}
|
||||
|
||||
export function isProviderDailyLimitReached(
|
||||
settings: ProviderDailySettings,
|
||||
provider: DebridProvider,
|
||||
epochMs = Date.now()
|
||||
): boolean {
|
||||
const limit = getProviderDailyLimitBytes(settings, provider);
|
||||
return limit > 0 && getProviderDailyUsageBytes(settings, provider, epochMs) >= limit;
|
||||
}
|
||||
|
||||
export function getProviderTotalUsageBytes(settings: ProviderUsageSettings, provider: DebridProvider): number {
|
||||
return normalizePositiveBytes(settings.providerTotalUsageBytes?.[provider]);
|
||||
}
|
||||
|
||||
export function resetProviderDailyUsage(
|
||||
settings: ProviderDailySettings,
|
||||
provider?: DebridProvider,
|
||||
epochMs = Date.now()
|
||||
): Pick<AppSettings, "providerDailyUsageDay" | "providerDailyUsageBytes"> {
|
||||
const dayKey = getProviderUsageDayKey(epochMs);
|
||||
if (!provider) {
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
providerDailyUsageBytes: {}
|
||||
};
|
||||
}
|
||||
|
||||
const nextUsageBytes = settings.providerDailyUsageDay === dayKey
|
||||
? { ...(settings.providerDailyUsageBytes || {}) }
|
||||
: {};
|
||||
delete nextUsageBytes[provider];
|
||||
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
providerDailyUsageBytes: nextUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function addProviderDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
provider: DebridProvider,
|
||||
byteDelta: number,
|
||||
epochMs = Date.now()
|
||||
): Pick<AppSettings, "providerDailyUsageDay" | "providerDailyUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const dayKey = getProviderUsageDayKey(epochMs);
|
||||
const currentUsageBytes = settings.providerDailyUsageDay === dayKey
|
||||
? { ...(settings.providerDailyUsageBytes || {}) }
|
||||
: {};
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
providerDailyUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
const nextUsageBytes = currentUsageBytes;
|
||||
nextUsageBytes[provider] = normalizePositiveBytes(nextUsageBytes[provider]) + increment;
|
||||
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
providerDailyUsageBytes: nextUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function addProviderTotalUsageBytes(
|
||||
settings: ProviderUsageSettings,
|
||||
provider: DebridProvider,
|
||||
byteDelta: number
|
||||
): Pick<AppSettings, "providerTotalUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const currentUsageBytes = { ...(settings.providerTotalUsageBytes || {}) };
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
providerTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
currentUsageBytes[provider] = normalizePositiveBytes(currentUsageBytes[provider]) + increment;
|
||||
|
||||
return {
|
||||
providerTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyDailyLimitBytes(settings: ProviderDailySettings, keyId: string): number {
|
||||
return normalizePositiveBytes(settings.debridLinkApiKeyDailyLimitBytes?.[keyId]);
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
keyId: string,
|
||||
epochMs = Date.now()
|
||||
): number {
|
||||
if (settings.providerDailyUsageDay !== getProviderUsageDayKey(epochMs)) {
|
||||
return 0;
|
||||
}
|
||||
return normalizePositiveBytes(settings.debridLinkApiKeyDailyUsageBytes?.[keyId]);
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyDailyRemainingBytes(
|
||||
settings: ProviderDailySettings,
|
||||
keyId: string,
|
||||
epochMs = Date.now()
|
||||
): number | null {
|
||||
const limit = getDebridLinkApiKeyDailyLimitBytes(settings, keyId);
|
||||
if (limit <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.max(0, limit - getDebridLinkApiKeyDailyUsageBytes(settings, keyId, epochMs));
|
||||
}
|
||||
|
||||
export function isDebridLinkApiKeyDailyLimitReached(
|
||||
settings: ProviderDailySettings,
|
||||
keyId: string,
|
||||
epochMs = Date.now()
|
||||
): boolean {
|
||||
const limit = getDebridLinkApiKeyDailyLimitBytes(settings, keyId);
|
||||
return limit > 0 && getDebridLinkApiKeyDailyUsageBytes(settings, keyId, epochMs) >= limit;
|
||||
}
|
||||
|
||||
export function getDebridLinkApiKeyTotalUsageBytes(settings: ProviderUsageSettings, keyId: string): number {
|
||||
return normalizePositiveBytes(settings.debridLinkApiKeyTotalUsageBytes?.[keyId]);
|
||||
}
|
||||
|
||||
export function resetDebridLinkApiKeyDailyUsage(
|
||||
settings: ProviderDailySettings,
|
||||
keyId?: string,
|
||||
epochMs = Date.now()
|
||||
): Pick<AppSettings, "providerDailyUsageDay" | "debridLinkApiKeyDailyUsageBytes"> {
|
||||
const dayKey = getProviderUsageDayKey(epochMs);
|
||||
if (!keyId) {
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
debridLinkApiKeyDailyUsageBytes: {}
|
||||
};
|
||||
}
|
||||
|
||||
const nextUsageBytes = settings.providerDailyUsageDay === dayKey
|
||||
? { ...(settings.debridLinkApiKeyDailyUsageBytes || {}) }
|
||||
: {};
|
||||
delete nextUsageBytes[keyId];
|
||||
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
debridLinkApiKeyDailyUsageBytes: nextUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function addDebridLinkApiKeyDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
keyId: string,
|
||||
byteDelta: number,
|
||||
epochMs = Date.now()
|
||||
): Pick<AppSettings, "providerDailyUsageDay" | "debridLinkApiKeyDailyUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const dayKey = getProviderUsageDayKey(epochMs);
|
||||
const currentUsageBytes = settings.providerDailyUsageDay === dayKey
|
||||
? { ...(settings.debridLinkApiKeyDailyUsageBytes || {}) }
|
||||
: {};
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
debridLinkApiKeyDailyUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
currentUsageBytes[keyId] = normalizePositiveBytes(currentUsageBytes[keyId]) + increment;
|
||||
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
debridLinkApiKeyDailyUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function addDebridLinkApiKeyTotalUsageBytes(
|
||||
settings: ProviderUsageSettings,
|
||||
keyId: string,
|
||||
byteDelta: number
|
||||
): Pick<AppSettings, "debridLinkApiKeyTotalUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const currentUsageBytes = { ...(settings.debridLinkApiKeyTotalUsageBytes || {}) };
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
debridLinkApiKeyTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
currentUsageBytes[keyId] = normalizePositiveBytes(currentUsageBytes[keyId]) + increment;
|
||||
|
||||
return {
|
||||
debridLinkApiKeyTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function isMegaDebridAccountDisabled(settings: ProviderDailySettings, accountId: string): boolean {
|
||||
return Array.isArray(settings.megaDebridDisabledAccountIds) && settings.megaDebridDisabledAccountIds.includes(accountId);
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountDailyLimitBytes(settings: ProviderDailySettings, accountId: string): number {
|
||||
return normalizePositiveBytes(settings.megaDebridAccountDailyLimitBytes?.[accountId]);
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
accountId: string,
|
||||
epochMs = Date.now()
|
||||
): number {
|
||||
if (settings.providerDailyUsageDay !== getProviderUsageDayKey(epochMs)) {
|
||||
return 0;
|
||||
}
|
||||
return normalizePositiveBytes(settings.megaDebridAccountDailyUsageBytes?.[accountId]);
|
||||
}
|
||||
|
||||
export function isMegaDebridAccountDailyLimitReached(
|
||||
settings: ProviderDailySettings,
|
||||
accountId: string,
|
||||
epochMs = Date.now()
|
||||
): boolean {
|
||||
const limit = getMegaDebridAccountDailyLimitBytes(settings, accountId);
|
||||
return limit > 0 && getMegaDebridAccountDailyUsageBytes(settings, accountId, epochMs) >= limit;
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountTotalUsageBytes(settings: ProviderUsageSettings, accountId: string): number {
|
||||
return normalizePositiveBytes(settings.megaDebridAccountTotalUsageBytes?.[accountId]);
|
||||
}
|
||||
|
||||
export function addMegaDebridAccountDailyUsageBytes(
|
||||
settings: ProviderDailySettings,
|
||||
accountId: string,
|
||||
byteDelta: number,
|
||||
epochMs = Date.now()
|
||||
): Pick<AppSettings, "providerDailyUsageDay" | "megaDebridAccountDailyUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const dayKey = getProviderUsageDayKey(epochMs);
|
||||
const currentUsageBytes = settings.providerDailyUsageDay === dayKey
|
||||
? { ...(settings.megaDebridAccountDailyUsageBytes || {}) }
|
||||
: {};
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
megaDebridAccountDailyUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
currentUsageBytes[accountId] = normalizePositiveBytes(currentUsageBytes[accountId]) + increment;
|
||||
|
||||
return {
|
||||
providerDailyUsageDay: dayKey,
|
||||
megaDebridAccountDailyUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
export function addMegaDebridAccountTotalUsageBytes(
|
||||
settings: ProviderUsageSettings,
|
||||
accountId: string,
|
||||
byteDelta: number
|
||||
): Pick<AppSettings, "megaDebridAccountTotalUsageBytes"> {
|
||||
const increment = normalizePositiveBytes(byteDelta);
|
||||
const currentUsageBytes = { ...(settings.megaDebridAccountTotalUsageBytes || {}) };
|
||||
if (increment <= 0) {
|
||||
return {
|
||||
megaDebridAccountTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
|
||||
currentUsageBytes[accountId] = normalizePositiveBytes(currentUsageBytes[accountId]) + increment;
|
||||
|
||||
return {
|
||||
megaDebridAccountTotalUsageBytes: currentUsageBytes
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
export type DownloadStatus =
|
||||
| "queued"
|
||||
| "validating"
|
||||
| "downloading"
|
||||
| "paused"
|
||||
| "reconnect_wait"
|
||||
| "extracting"
|
||||
| "integrity_check"
|
||||
| "completed"
|
||||
| "failed"
|
||||
| "cancelled";
|
||||
|
||||
export type CleanupMode = "none" | "trash" | "delete";
|
||||
export type ConflictMode = "overwrite" | "skip" | "rename" | "ask";
|
||||
export type SpeedMode = "global" | "per_download";
|
||||
export type FinishedCleanupPolicy = "never" | "immediate" | "on_start" | "package_done";
|
||||
export type DebridProvider =
|
||||
| "realdebrid"
|
||||
| "megadebrid"
|
||||
| "megadebrid-api"
|
||||
| "megadebrid-web"
|
||||
| "bestdebrid"
|
||||
| "alldebrid"
|
||||
| "ddownload"
|
||||
| "onefichier"
|
||||
| "debridlink"
|
||||
| "linksnappy";
|
||||
export type DebridFallbackProvider = DebridProvider | "none";
|
||||
export type AppTheme = "dark" | "light";
|
||||
export type PackagePriority = "high" | "normal" | "low";
|
||||
export type ExtractCpuPriority = "high" | "middle" | "low";
|
||||
export type HistoryRetentionMode = "never" | "session" | "permanent";
|
||||
|
||||
export interface BandwidthScheduleEntry {
|
||||
id: string;
|
||||
startHour: number;
|
||||
endHour: number;
|
||||
speedLimitKbps: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface DownloadStats {
|
||||
totalDownloaded: number;
|
||||
totalDownloadedAllTime: number;
|
||||
totalFiles?: number;
|
||||
totalFilesSession: number;
|
||||
totalFilesAllTime: number;
|
||||
totalPackages: number;
|
||||
sessionStartedAt: number;
|
||||
appSessionStartedAt: number;
|
||||
sessionRuntimeMs: number;
|
||||
totalRuntimeMs: number;
|
||||
runtimeMeasuredAt: number;
|
||||
}
|
||||
|
||||
export interface DebridAccountStatus {
|
||||
accountId: string;
|
||||
provider: "megadebrid" | "debridlink";
|
||||
label: string;
|
||||
maskedLogin: string;
|
||||
valid: boolean;
|
||||
isPremium: boolean;
|
||||
premiumUntilMs: number | null;
|
||||
email?: string;
|
||||
message: string;
|
||||
checkedAt: number;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
token: string;
|
||||
realDebridUseWebLogin: boolean;
|
||||
megaLogin: string;
|
||||
megaPassword: string;
|
||||
megaCredentials: string;
|
||||
megaDebridApiEnabled: boolean;
|
||||
megaDebridWebEnabled: boolean;
|
||||
megaDebridPreferApi: boolean;
|
||||
bestToken: string;
|
||||
bestDebridUseWebLogin: boolean;
|
||||
allDebridToken: string;
|
||||
allDebridUseWebLogin: boolean;
|
||||
ddownloadLogin: string;
|
||||
ddownloadPassword: string;
|
||||
oneFichierApiKey: string;
|
||||
debridLinkApiKeys: string;
|
||||
debridLinkDisabledKeyIds: string[];
|
||||
linkSnappyLogin: string;
|
||||
linkSnappyPassword: string;
|
||||
archivePasswordList: string;
|
||||
rememberToken: boolean;
|
||||
providerOrder: readonly DebridProvider[];
|
||||
providerPrimary: DebridProvider;
|
||||
providerSecondary: DebridFallbackProvider;
|
||||
providerTertiary: DebridFallbackProvider;
|
||||
autoProviderFallback: boolean;
|
||||
outputDir: string;
|
||||
packageName: string;
|
||||
autoExtract: boolean;
|
||||
autoRename4sf4sj: boolean;
|
||||
keepGermanAudioOnly: boolean;
|
||||
germanAudioMode: "tag" | "first";
|
||||
extractDir: string;
|
||||
collectMkvToLibrary: boolean;
|
||||
mkvLibraryDir: string;
|
||||
createExtractSubfolder: boolean;
|
||||
hybridExtract: boolean;
|
||||
cleanupMode: CleanupMode;
|
||||
extractConflictMode: ConflictMode;
|
||||
removeLinkFilesAfterExtract: boolean;
|
||||
removeSamplesAfterExtract: boolean;
|
||||
enableIntegrityCheck: boolean;
|
||||
autoResumeOnStart: boolean;
|
||||
autoReconnect: boolean;
|
||||
reconnectWaitSeconds: number;
|
||||
completedCleanupPolicy: FinishedCleanupPolicy;
|
||||
maxParallel: number;
|
||||
maxParallelExtract: number;
|
||||
retryLimit: number;
|
||||
speedLimitEnabled: boolean;
|
||||
speedLimitKbps: number;
|
||||
speedLimitMode: SpeedMode;
|
||||
updateRepo: string;
|
||||
autoUpdateCheck: boolean;
|
||||
clipboardWatch: boolean;
|
||||
minimizeToTray: boolean;
|
||||
theme: AppTheme;
|
||||
collapseNewPackages: boolean;
|
||||
historyRetentionMode: HistoryRetentionMode;
|
||||
historyMaxEntries: number;
|
||||
historyMaxAgeDays: number;
|
||||
accountListShowDetailedDebridLinkKeys: boolean;
|
||||
autoSortPackagesByProgress: boolean;
|
||||
autoSkipExtracted: boolean;
|
||||
hideExtractedItems: boolean;
|
||||
confirmDeleteSelection: boolean;
|
||||
backupIncludeDownloads: boolean;
|
||||
backupIncludeRemoteDiagnostics: boolean;
|
||||
notifyUrl: string;
|
||||
notifyMention: string;
|
||||
notifyOnPackageCompleted: boolean;
|
||||
notifyOnPackageFailed: boolean;
|
||||
notifyOnRunFinished: boolean;
|
||||
totalDownloadedAllTime: number;
|
||||
totalCompletedFilesAllTime: number;
|
||||
totalRuntimeAllTimeMs: number;
|
||||
bandwidthSchedules: BandwidthScheduleEntry[];
|
||||
columnOrder: string[];
|
||||
extractCpuPriority: ExtractCpuPriority;
|
||||
autoExtractWhenStopped: boolean;
|
||||
disabledProviders: DebridProvider[];
|
||||
hosterRouting: Record<string, DebridProvider>;
|
||||
providerDailyLimitBytes: Partial<Record<DebridProvider, number>>;
|
||||
providerDailyUsageBytes: Partial<Record<DebridProvider, number>>;
|
||||
providerTotalUsageBytes: Partial<Record<DebridProvider, number>>;
|
||||
debridLinkApiKeyDailyLimitBytes: Record<string, number>;
|
||||
debridLinkApiKeyDailyUsageBytes: Record<string, number>;
|
||||
debridLinkApiKeyTotalUsageBytes: Record<string, number>;
|
||||
megaDebridDisabledAccountIds: string[];
|
||||
megaDebridAccountDailyLimitBytes: Record<string, number>;
|
||||
megaDebridAccountDailyUsageBytes: Record<string, number>;
|
||||
megaDebridAccountTotalUsageBytes: Record<string, number>;
|
||||
debridAccountStatuses: Record<string, DebridAccountStatus>;
|
||||
providerDailyUsageDay: string;
|
||||
scheduledStartEpochMs: number;
|
||||
}
|
||||
|
||||
export interface DownloadItem {
|
||||
id: string;
|
||||
packageId: string;
|
||||
url: string;
|
||||
provider: DebridProvider | null;
|
||||
providerLabel?: string;
|
||||
providerAccountId?: string;
|
||||
providerAccountLabel?: string;
|
||||
status: DownloadStatus;
|
||||
retries: number;
|
||||
speedBps: number;
|
||||
downloadedBytes: number;
|
||||
totalBytes: number | null;
|
||||
progressPercent: number;
|
||||
fileName: string;
|
||||
targetPath: string;
|
||||
resumable: boolean;
|
||||
attempts: number;
|
||||
lastError: string;
|
||||
fullStatus: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
onlineStatus?: "online" | "offline" | "checking";
|
||||
}
|
||||
|
||||
export interface AudioStripFileResult {
|
||||
name: string;
|
||||
action: string;
|
||||
reason: string;
|
||||
languages?: string;
|
||||
}
|
||||
|
||||
export interface AudioStripSummary {
|
||||
at: number;
|
||||
candidates: number;
|
||||
remuxed: number;
|
||||
keptSingle: number;
|
||||
skippedNoGerman: number;
|
||||
skippedNoTool: number;
|
||||
failed: number;
|
||||
files: AudioStripFileResult[];
|
||||
}
|
||||
|
||||
export interface PackageEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
outputDir: string;
|
||||
extractDir: string;
|
||||
status: DownloadStatus;
|
||||
itemIds: string[];
|
||||
cancelled: boolean;
|
||||
enabled: boolean;
|
||||
priority?: PackagePriority;
|
||||
postProcessLabel?: string;
|
||||
audioStripSummary?: AudioStripSummary;
|
||||
downloadStartedAt?: number;
|
||||
downloadCompletedAt?: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface SessionState {
|
||||
version: number;
|
||||
packageOrder: string[];
|
||||
packages: Record<string, PackageEntry>;
|
||||
items: Record<string, DownloadItem>;
|
||||
runStartedAt: number;
|
||||
totalDownloadedBytes: number;
|
||||
summaryText: string;
|
||||
reconnectUntil: number;
|
||||
reconnectReason: string;
|
||||
paused: boolean;
|
||||
running: boolean;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface DownloadSummary {
|
||||
total: number;
|
||||
success: number;
|
||||
failed: number;
|
||||
cancelled: number;
|
||||
extracted: number;
|
||||
durationSeconds: number;
|
||||
averageSpeedBps: number;
|
||||
}
|
||||
|
||||
export interface ParsedPackageInput {
|
||||
name: string;
|
||||
links: string[];
|
||||
fileNames?: string[];
|
||||
}
|
||||
|
||||
export interface ContainerImportResult {
|
||||
packages: ParsedPackageInput[];
|
||||
source: "dlc";
|
||||
}
|
||||
|
||||
export interface RotationEvent {
|
||||
id: string;
|
||||
at: number;
|
||||
level: "INFO" | "WARN" | "ERROR";
|
||||
provider: string;
|
||||
accountLabel: string;
|
||||
event: string;
|
||||
reason?: string;
|
||||
category?: string;
|
||||
cooldownSec?: number;
|
||||
next?: string;
|
||||
}
|
||||
|
||||
export interface UiSnapshot {
|
||||
settings: AppSettings;
|
||||
session: SessionState;
|
||||
summary: DownloadSummary | null;
|
||||
stats: DownloadStats;
|
||||
speedText: string;
|
||||
etaText: string;
|
||||
canStart: boolean;
|
||||
canStop: boolean;
|
||||
canPause: boolean;
|
||||
clipboardActive: boolean;
|
||||
reconnectSeconds: number;
|
||||
packageSpeedBps: Record<string, number>;
|
||||
payloadKind?: "full" | "delta";
|
||||
removedItemIds?: string[];
|
||||
removedPackageIds?: string[];
|
||||
rotationEvents?: RotationEvent[];
|
||||
}
|
||||
|
||||
export interface AddLinksPayload {
|
||||
rawText: string;
|
||||
packageName?: string;
|
||||
duplicatePolicy?: DuplicatePolicy;
|
||||
}
|
||||
|
||||
export interface AddContainerPayload {
|
||||
filePaths: string[];
|
||||
}
|
||||
|
||||
export type DuplicatePolicy = "keep" | "skip" | "overwrite";
|
||||
|
||||
export interface QueueAddResult {
|
||||
addedPackages: number;
|
||||
addedLinks: number;
|
||||
skippedExistingPackages: string[];
|
||||
overwrittenPackages: string[];
|
||||
}
|
||||
|
||||
export interface ContainerConflictResult {
|
||||
conflicts: string[];
|
||||
packageCount: number;
|
||||
linkCount: number;
|
||||
}
|
||||
|
||||
export interface StartConflictEntry {
|
||||
packageId: string;
|
||||
packageName: string;
|
||||
extractDir: string;
|
||||
}
|
||||
|
||||
export interface StartConflictResolutionResult {
|
||||
skipped: boolean;
|
||||
overwritten: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateCheckResult {
|
||||
updateAvailable: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
latestTag: string;
|
||||
releaseUrl: string;
|
||||
setupAssetUrl?: string;
|
||||
setupAssetName?: string;
|
||||
setupAssetDigest?: string;
|
||||
releaseNotes?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface UpdateInstallResult {
|
||||
started: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface UpdateInstallProgress {
|
||||
stage: "starting" | "downloading" | "verifying" | "launching" | "done" | "error";
|
||||
percent: number | null;
|
||||
downloadedBytes: number;
|
||||
totalBytes: number | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type AllDebridHostState = "up" | "down" | "not_tracked" | "unknown";
|
||||
export type AllDebridHostInfoSource = "api" | "web";
|
||||
export type DebridLinkHostState = "up" | "down" | "unknown";
|
||||
export type DebridLinkKeyState = "ready" | "cooldown" | "invalid" | "quota" | "rate_limit" | "error" | "unknown";
|
||||
|
||||
export interface AllDebridHostInfo {
|
||||
host: string;
|
||||
source: AllDebridHostInfoSource;
|
||||
state: AllDebridHostState;
|
||||
statusLabel: string;
|
||||
fetchedAt: number;
|
||||
lastCheckedAt: number | null;
|
||||
quota: number | null;
|
||||
quotaMax: number | null;
|
||||
quotaType: string;
|
||||
limitSimuDl: number | null;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface DebridLinkHostLimitInfo {
|
||||
keyId: string;
|
||||
keyLabel: string;
|
||||
host: string;
|
||||
fetchedAt: number;
|
||||
trafficCurrentBytes: number | null;
|
||||
trafficMaxBytes: number | null;
|
||||
linksCurrent: number | null;
|
||||
linksMax: number | null;
|
||||
note: string;
|
||||
state: DebridLinkKeyState;
|
||||
stateLabel: string;
|
||||
stateDetail: string;
|
||||
cooldownUntil: number | null;
|
||||
cooldownRemainingMs: number;
|
||||
lastCheckedAt: number | null;
|
||||
hostState: DebridLinkHostState;
|
||||
hostStateLabel: string;
|
||||
hostNote: string;
|
||||
}
|
||||
|
||||
export interface ParsedHashEntry {
|
||||
fileName: string;
|
||||
algorithm: "crc32" | "md5" | "sha1";
|
||||
digest: string;
|
||||
}
|
||||
|
||||
export interface BandwidthSample {
|
||||
timestamp: number;
|
||||
speedBps: number;
|
||||
}
|
||||
|
||||
export interface BandwidthStats {
|
||||
samples: BandwidthSample[];
|
||||
currentSpeedBps: number;
|
||||
averageSpeedBps: number;
|
||||
maxSpeedBps: number;
|
||||
totalBytesSession: number;
|
||||
sessionDurationSeconds: number;
|
||||
}
|
||||
|
||||
export interface SessionStats {
|
||||
bandwidth: BandwidthStats;
|
||||
totalDownloads: number;
|
||||
completedDownloads: number;
|
||||
failedDownloads: number;
|
||||
activeDownloads: number;
|
||||
queuedDownloads: number;
|
||||
}
|
||||
|
||||
export interface SupportTraceConfig {
|
||||
enabled: boolean;
|
||||
includeMainLog: boolean;
|
||||
includeAudit: boolean;
|
||||
logDebugRequests: boolean;
|
||||
autoDisableAt: string | null;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface SupportFileSizeInfo {
|
||||
path: string | null;
|
||||
exists: boolean;
|
||||
bytes: number;
|
||||
}
|
||||
|
||||
export interface SupportDirectorySizeInfo {
|
||||
path: string;
|
||||
exists: boolean;
|
||||
fileCount: number;
|
||||
bytes: number;
|
||||
}
|
||||
|
||||
export interface SupportDiskSpaceInfo {
|
||||
path: string;
|
||||
totalBytes: number | null;
|
||||
freeBytes: number | null;
|
||||
freePercent: number | null;
|
||||
}
|
||||
|
||||
export interface SupportBundleEstimate {
|
||||
estimatedBytes: number;
|
||||
estimatedEntries: number;
|
||||
duplicatedLiveLogBytes: number;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface DebugSetupCheckResult {
|
||||
status: "ok" | "warn";
|
||||
enabled: boolean;
|
||||
runtimeBaseDir: string;
|
||||
host: string;
|
||||
port: number;
|
||||
localOnly: boolean;
|
||||
tokenConfigured: boolean;
|
||||
tokenPath: string;
|
||||
supportManifestPath: string;
|
||||
supportManifestPresent: boolean;
|
||||
traceConfigPath: string | null;
|
||||
traceLogPath: string | null;
|
||||
traceEnabled: boolean;
|
||||
traceAutoDisableAt: string | null;
|
||||
diskSpace: {
|
||||
runtime: SupportDiskSpaceInfo;
|
||||
output: SupportDiskSpaceInfo;
|
||||
extract: SupportDiskSpaceInfo;
|
||||
};
|
||||
logSummary: {
|
||||
totalBytes: number;
|
||||
main: SupportFileSizeInfo;
|
||||
mainBackup: SupportFileSizeInfo;
|
||||
audit: SupportFileSizeInfo;
|
||||
auditBackup: SupportFileSizeInfo;
|
||||
rename: SupportFileSizeInfo;
|
||||
renameBackup: SupportFileSizeInfo;
|
||||
session: SupportFileSizeInfo;
|
||||
trace: SupportFileSizeInfo;
|
||||
traceBackup: SupportFileSizeInfo;
|
||||
sessionLogs: SupportDirectorySizeInfo;
|
||||
packageLogs: SupportDirectorySizeInfo;
|
||||
itemLogs: SupportDirectorySizeInfo;
|
||||
};
|
||||
supportBundle: SupportBundleEstimate;
|
||||
warnings: string[];
|
||||
notes: string[];
|
||||
localUrls: {
|
||||
health: string;
|
||||
meta: string;
|
||||
diagnostics: string;
|
||||
};
|
||||
remoteUrlTemplates: {
|
||||
health: string;
|
||||
meta: string;
|
||||
diagnostics: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HistoryEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
totalBytes: number;
|
||||
downloadedBytes: number;
|
||||
fileCount: number;
|
||||
provider: DebridProvider | null;
|
||||
completedAt: number;
|
||||
durationSeconds: number;
|
||||
status: "completed" | "deleted";
|
||||
outputDir: string;
|
||||
urls?: string[];
|
||||
}
|
||||
|
||||
export interface HistoryState {
|
||||
entries: HistoryEntry[];
|
||||
maxEntries: number;
|
||||
}
|
||||
|
||||
export interface RendererErrorReport {
|
||||
kind: "error" | "unhandledrejection" | "react";
|
||||
message: string;
|
||||
stack?: string;
|
||||
source?: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
componentStack?: string;
|
||||
}
|
||||
|
||||
export interface RemoteDiagnosticsStatus {
|
||||
running: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
hasToken: boolean;
|
||||
localOnly: boolean;
|
||||
allowlistCount: number;
|
||||
}
|
||||
|
||||
export interface RemoteDiagnosticsInfo {
|
||||
status: RemoteDiagnosticsStatus;
|
||||
code: string | null;
|
||||
publicHost: string;
|
||||
name: string;
|
||||
allowlist: string[];
|
||||
suggestedHosts: string[];
|
||||
}
|
||||
|
||||
export interface EnableRemoteDiagnosticsInput {
|
||||
hostMode: "local" | "network";
|
||||
publicHost: string;
|
||||
port?: number;
|
||||
allowlist: string[];
|
||||
name?: string;
|
||||
rotateToken?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user