real-debrid-downloader/src/shared/provider-daily-limits.ts
Sucukdeluxe e212ccc86f Add daily traffic limits, auto-sort packages, Debrid-Link multi-key improvements
Daily traffic limits:
- Per-provider daily download limit (configurable in GB per provider)
- Per Debrid-Link API key daily limit (individual limits per key)
- Usage tracking with automatic daily reset at midnight
- Provider is skipped when daily limit reached, falls back to next provider
- Reset button per provider and per Debrid-Link key in account settings
- Hoster routing skips daily-limited providers gracefully

Debrid-Link multi-key improvements:
- Keys now display with labels (#1, #2...) and masked tokens in account list
- Option to show detailed per-key view with individual usage stats
- Keys that hit their daily limit are automatically skipped
- providerAccountId/providerAccountLabel stored per download item

Auto-sort packages by progress:
- Active packages automatically sorted to top during downloads
- Sorted by completion ratio, then downloaded bytes
- Toggle in settings (autoSortPackagesByProgress)

UI polish:
- Package column headers: flatter, more transparent design
- LinkSnappy mode label: "Login" renamed to "Web"
- Account list: new toggle for detailed Debrid-Link key display
- Account usage stats section with warning styling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:29:48 +01:00

198 lines
6.1 KiB
TypeScript

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">>;
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 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 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 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
};
}