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>
67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
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);
|
|
}
|