fix(accounts): separate Debrid-Link username and email

Persist the username and email returned by the Debrid-Link account endpoint as distinct identity fields and project them into their matching account-table columns. Migrate cached legacy statuses that stored a username in the email field, preserve both fields through renderer validation and sanitization, and cover API responses with and without an email address.
This commit is contained in:
Sucukdeluxe
2026-08-15 02:11:59 +02:00
parent 90fc77f203
commit d91e2a4ab9
10 changed files with 99 additions and 42 deletions
+11 -9
View File
@@ -245,15 +245,17 @@ export async function checkDebridLinkKey(
const premiumLeftSec = Number(value.premiumLeft || 0);
const accountType = Number(value.accountType || 0);
const premiumUntilMs = Number.isFinite(premiumLeftSec) && premiumLeftSec > 0 ? now + premiumLeftSec * 1000 : 0;
const isPremium = premiumUntilMs > now || accountType > 0;
const username = String(value.username || "").trim() || undefined;
return {
...base,
valid: true,
isPremium,
premiumUntilMs: premiumUntilMs > 0 ? premiumUntilMs : (accountType > 0 ? null : 0),
email: username,
message: premiumUntilMs > 0
const isPremium = premiumUntilMs > now || accountType > 0;
const username = String(value.username || "").trim() || undefined;
const email = String(value.email || "").trim() || undefined;
return {
...base,
valid: true,
isPremium,
premiumUntilMs: premiumUntilMs > 0 ? premiumUntilMs : (accountType > 0 ? null : 0),
username,
email,
message: premiumUntilMs > 0
? formatRemaining(premiumUntilMs, now)
: (accountType > 0 ? "Premium aktiv" : "Kein Premium (Free)")
};
+1
View File
@@ -103,6 +103,7 @@ export function sanitizeDebridAccountStatus(status: DebridAccountStatus, redacti
accountId: sanitizeAccountStatusText(status.accountId, redactions),
label: sanitizeAccountStatusText(status.label, redactions),
maskedLogin: sanitizeAccountStatusText(status.maskedLogin, redactions),
username: status.username ? sanitizeAccountStatusText(status.username, redactions) : undefined,
email: status.email ? sanitizeAccountStatusText(status.email, redactions) : undefined,
message: sanitizeAccountStatusText(status.message, redactions)
};
+21 -13
View File
@@ -280,22 +280,30 @@ function normalizeDebridAccountStatuses(
continue;
}
const entry = raw as Partial<DebridAccountStatus>;
if (typeof entry.accountId !== "string" || typeof entry.checkedAt !== "number") {
continue;
}
result[key] = {
accountId: entry.accountId,
provider: entry.provider === "debridlink"
? "debridlink"
: entry.provider === "realdebrid"
? "realdebrid"
: "megadebrid",
if (typeof entry.accountId !== "string" || typeof entry.checkedAt !== "number") {
continue;
}
const provider = entry.provider === "debridlink"
? "debridlink"
: entry.provider === "realdebrid"
? "realdebrid"
: "megadebrid";
let username = typeof entry.username === "string" ? entry.username : undefined;
let email = typeof entry.email === "string" ? entry.email : undefined;
if (provider === "debridlink" && !username && email && !email.includes("@")) {
username = email;
email = undefined;
}
result[key] = {
accountId: entry.accountId,
provider,
label: String(entry.label || ""),
maskedLogin: String(entry.maskedLogin || ""),
valid: Boolean(entry.valid),
isPremium: Boolean(entry.isPremium),
premiumUntilMs: typeof entry.premiumUntilMs === "number" ? entry.premiumUntilMs : null,
email: typeof entry.email === "string" ? entry.email : undefined,
isPremium: Boolean(entry.isPremium),
premiumUntilMs: typeof entry.premiumUntilMs === "number" ? entry.premiumUntilMs : null,
username,
email,
message: String(entry.message || ""),
checkedAt: entry.checkedAt
};
+4 -2
View File
@@ -2952,8 +2952,9 @@ export function App(): ReactElement {
setAccountContextMenu(null);
void (async () => {
const row = rows[0];
const checkedStatus = row.accountId ? snapshot.settings.debridAccountStatuses?.[row.accountId] : undefined;
const username = rows.length === 1
? resolveAccountUsername(row.username, row.accountId ? snapshot.settings.debridAccountStatuses?.[row.accountId]?.email : undefined)
? resolveAccountUsername(row.username, checkedStatus?.username || checkedStatus?.email)
: "—";
const confirmed = await askConfirmPrompt({
title: rows.length === 1 ? `${row.hosterLabel} entfernen` : `${rows.length} Accounts entfernen`,
@@ -4893,6 +4894,7 @@ export function App(): ReactElement {
state,
message: checkedStatus?.message || row.entry.statusLabel,
premiumUntilMs: checkedStatus?.premiumUntilMs ?? null,
username: checkedStatus?.username,
email: checkedStatus?.email
},
dailyLimitBytes: row.dailyLimitBytes,
@@ -5331,7 +5333,7 @@ export function App(): ReactElement {
open: true,
hoster: accountEditOption.serviceLabel,
mode: accountEditOption.modeLabel,
identity: resolveAccountUsername(accountEditRow?.username || accountEditDialog.login, accountEditStatus?.email),
identity: resolveAccountUsername(accountEditRow?.username || accountEditDialog.login, accountEditStatus?.username || accountEditStatus?.email),
enabled: !accountEditRow?.disabled,
fields: accountEditFields,
error: "",
@@ -136,6 +136,7 @@ export interface AccountRowSource {
state: AccountStatusSourceState;
message: string;
premiumUntilMs: number | null;
username?: string;
email?: string;
};
dailyLimitBytes?: number;
@@ -654,8 +655,9 @@ function projectCredential(kind: AccountRowSource["credentialKind"]): string {
return kind === "password" ? "••••••" : "Geschützter Zugang";
}
function projectAccountIdentity(username: string, checkedEmail?: string): { username: string; email: string } {
const stored = username.trim();
function projectAccountIdentity(username: string, checkedUsername?: string, checkedEmail?: string): { username: string; email: string } {
const verifiedUsername = checkedUsername?.trim() || "";
const stored = verifiedUsername || username.trim();
const verifiedEmail = checkedEmail?.trim() || "";
const storedIsEmail = stored.includes("@");
return {
@@ -676,7 +678,7 @@ export function projectAccountRows(
const premiumUntilMs = source.status.premiumUntilMs && source.status.premiumUntilMs > nowMs
? source.status.premiumUntilMs
: null;
const identity = projectAccountIdentity(source.username, source.status.email);
const identity = projectAccountIdentity(source.username, source.status.username, source.status.email);
return {
id,
service: source.service,
+5 -4
View File
@@ -61,10 +61,11 @@ export interface DebridAccountStatus {
label: string;
maskedLogin: string;
valid: boolean;
isPremium: boolean;
premiumUntilMs: number | null;
email?: string;
message: string;
isPremium: boolean;
premiumUntilMs: number | null;
username?: string;
email?: string;
message: string;
checkedAt: number;
}