Refactor Mega-Debrid account UI from textarea to proper account list
Replace the raw login:password textarea with a proper form-based UI: - Individual Login + Password input fields with "Hinzufügen" button - List of configured accounts with masked logins and "Entfernen" button - Duplicate login detection - Storage format unchanged (megaCredentials stays login:password pairs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c182bc9269
commit
65650737b1
@ -1,6 +1,6 @@
|
|||||||
import { CSSProperties, DragEvent, KeyboardEvent as ReactKeyboardEvent, ReactElement, memo, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import { CSSProperties, DragEvent, KeyboardEvent as ReactKeyboardEvent, ReactElement, memo, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
import { parseDebridLinkApiKeys } from "../shared/debrid-link-keys";
|
||||||
import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
|
import { parseMegaDebridAccounts, serializeMegaDebridAccounts, maskMegaDebridLogin } from "../shared/mega-debrid-accounts";
|
||||||
import type {
|
import type {
|
||||||
AllDebridHostInfo,
|
AllDebridHostInfo,
|
||||||
AppSettings,
|
AppSettings,
|
||||||
@ -99,6 +99,11 @@ interface AccountOption {
|
|||||||
needsCredentials?: boolean;
|
needsCredentials?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MegaDialogAccount {
|
||||||
|
login: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface AccountDialogState {
|
interface AccountDialogState {
|
||||||
mode: "create" | "edit";
|
mode: "create" | "edit";
|
||||||
kind: AccountKind | null;
|
kind: AccountKind | null;
|
||||||
@ -107,6 +112,9 @@ interface AccountDialogState {
|
|||||||
password: string;
|
password: string;
|
||||||
dailyLimitGb: string;
|
dailyLimitGb: string;
|
||||||
keyDailyLimitGbById: Record<string, string>;
|
keyDailyLimitGbById: Record<string, string>;
|
||||||
|
megaAccounts: MegaDialogAccount[];
|
||||||
|
megaNewLogin: string;
|
||||||
|
megaNewPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DebridLinkAccountKeyEntry {
|
interface DebridLinkAccountKeyEntry {
|
||||||
@ -576,6 +584,7 @@ function summarizeAccountLines(kind: AccountKind, settings: AppSettings): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | null, settings: AppSettings): AccountDialogState {
|
function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | null, settings: AppSettings): AccountDialogState {
|
||||||
|
const baseMega: Pick<AccountDialogState, "megaAccounts" | "megaNewLogin" | "megaNewPassword"> = { megaAccounts: [], megaNewLogin: "", megaNewPassword: "" };
|
||||||
if (!kind) {
|
if (!kind) {
|
||||||
return {
|
return {
|
||||||
mode,
|
mode,
|
||||||
@ -584,37 +593,40 @@ function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | n
|
|||||||
login: "",
|
login: "",
|
||||||
password: "",
|
password: "",
|
||||||
dailyLimitGb: "",
|
dailyLimitGb: "",
|
||||||
keyDailyLimitGbById: {}
|
keyDailyLimitGbById: {},
|
||||||
|
...baseMega
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const provider = getAccountServiceProvider(findAccountOption(kind).service);
|
const provider = getAccountServiceProvider(findAccountOption(kind).service);
|
||||||
const dailyLimitGb = formatAccountDailyLimitInput(getProviderDailyLimitBytes(settings, provider));
|
const dailyLimitGb = formatAccountDailyLimitInput(getProviderDailyLimitBytes(settings, provider));
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "realdebrid-api":
|
case "realdebrid-api":
|
||||||
return { mode, kind, token: settings.token, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: settings.token, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "realdebrid-web":
|
case "realdebrid-web":
|
||||||
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "megadebrid-api":
|
case "megadebrid-api":
|
||||||
case "megadebrid-web": {
|
case "megadebrid-web": {
|
||||||
// Populate token field with megaCredentials, or build from legacy megaLogin/megaPassword
|
// Populate megaAccounts from megaCredentials, or build from legacy megaLogin/megaPassword
|
||||||
let megaToken = (settings.megaCredentials || "").trim();
|
let megaToken = (settings.megaCredentials || "").trim();
|
||||||
if (!megaToken && settings.megaLogin.trim() && settings.megaPassword.trim()) {
|
if (!megaToken && settings.megaLogin.trim() && settings.megaPassword.trim()) {
|
||||||
megaToken = `${settings.megaLogin.trim()}:${settings.megaPassword.trim()}`;
|
megaToken = `${settings.megaLogin.trim()}:${settings.megaPassword.trim()}`;
|
||||||
}
|
}
|
||||||
return { mode, kind, token: megaToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
const parsed = parseMegaDebridAccounts(megaToken);
|
||||||
|
const megaAccounts = parsed.map((a) => ({ login: a.login, password: a.password }));
|
||||||
|
return { mode, kind, token: megaToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, megaAccounts, megaNewLogin: "", megaNewPassword: "" };
|
||||||
}
|
}
|
||||||
case "bestdebrid-api":
|
case "bestdebrid-api":
|
||||||
return { mode, kind, token: settings.bestToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: settings.bestToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "bestdebrid-web":
|
case "bestdebrid-web":
|
||||||
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "alldebrid-api":
|
case "alldebrid-api":
|
||||||
return { mode, kind, token: settings.allDebridToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: settings.allDebridToken, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "alldebrid-web":
|
case "alldebrid-web":
|
||||||
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "ddownload-login":
|
case "ddownload-login":
|
||||||
return { mode, kind, token: "", login: settings.ddownloadLogin, password: settings.ddownloadPassword, dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: settings.ddownloadLogin, password: settings.ddownloadPassword, dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "onefichier-api":
|
case "onefichier-api":
|
||||||
return { mode, kind, token: settings.oneFichierApiKey, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: settings.oneFichierApiKey, login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
case "debridlink-api":
|
case "debridlink-api":
|
||||||
return {
|
return {
|
||||||
mode,
|
mode,
|
||||||
@ -623,12 +635,13 @@ function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | n
|
|||||||
login: "",
|
login: "",
|
||||||
password: "",
|
password: "",
|
||||||
dailyLimitGb,
|
dailyLimitGb,
|
||||||
keyDailyLimitGbById: buildDebridLinkKeyLimitInputs(settings.debridLinkApiKeys || "", undefined, settings)
|
keyDailyLimitGbById: buildDebridLinkKeyLimitInputs(settings.debridLinkApiKeys || "", undefined, settings),
|
||||||
|
...baseMega
|
||||||
};
|
};
|
||||||
case "linksnappy-login":
|
case "linksnappy-login":
|
||||||
return { mode, kind, token: "", login: settings.linkSnappyLogin || "", password: settings.linkSnappyPassword || "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: settings.linkSnappyLogin || "", password: settings.linkSnappyPassword || "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
default:
|
default:
|
||||||
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {} };
|
return { mode, kind, token: "", login: "", password: "", dailyLimitGb, keyDailyLimitGbById: {}, ...baseMega };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,16 +674,18 @@ function applyAccountDialogToSettings(settings: AppSettings, dialog: AccountDial
|
|||||||
case "realdebrid-web":
|
case "realdebrid-web":
|
||||||
return { ...settings, token: "", realDebridUseWebLogin: true, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
return { ...settings, token: "", realDebridUseWebLogin: true, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||||
case "megadebrid-api": {
|
case "megadebrid-api": {
|
||||||
const megaAccounts = parseMegaDebridAccounts(token);
|
const megaSerialized = serializeMegaDebridAccounts(dialog.megaAccounts);
|
||||||
const firstLogin = megaAccounts.length > 0 ? megaAccounts[0].login : "";
|
const megaParsed = parseMegaDebridAccounts(megaSerialized);
|
||||||
const firstPassword = megaAccounts.length > 0 ? megaAccounts[0].password : "";
|
const firstLogin = megaParsed.length > 0 ? megaParsed[0].login : "";
|
||||||
return { ...settings, megaCredentials: token, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridApiEnabled: true, megaDebridPreferApi: true, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
const firstPassword = megaParsed.length > 0 ? megaParsed[0].password : "";
|
||||||
|
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridApiEnabled: true, megaDebridPreferApi: true, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||||
}
|
}
|
||||||
case "megadebrid-web": {
|
case "megadebrid-web": {
|
||||||
const megaAccounts = parseMegaDebridAccounts(token);
|
const megaSerialized = serializeMegaDebridAccounts(dialog.megaAccounts);
|
||||||
const firstLogin = megaAccounts.length > 0 ? megaAccounts[0].login : "";
|
const megaParsed = parseMegaDebridAccounts(megaSerialized);
|
||||||
const firstPassword = megaAccounts.length > 0 ? megaAccounts[0].password : "";
|
const firstLogin = megaParsed.length > 0 ? megaParsed[0].login : "";
|
||||||
return { ...settings, megaCredentials: token, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridWebEnabled: true, megaDebridPreferApi: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
const firstPassword = megaParsed.length > 0 ? megaParsed[0].password : "";
|
||||||
|
return { ...settings, megaCredentials: megaSerialized, megaLogin: firstLogin, megaPassword: firstPassword, megaDebridWebEnabled: true, megaDebridPreferApi: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||||
}
|
}
|
||||||
case "bestdebrid-api":
|
case "bestdebrid-api":
|
||||||
return { ...settings, bestToken: token, bestDebridUseWebLogin: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
return { ...settings, bestToken: token, bestDebridUseWebLogin: false, providerDailyLimitBytes: nextProviderDailyLimitBytes };
|
||||||
@ -752,13 +767,12 @@ function validateAccountDialog(dialog: AccountDialogState): string | null {
|
|||||||
return "Bitte zuerst einen Account-Typ auswählen.";
|
return "Bitte zuerst einen Account-Typ auswählen.";
|
||||||
}
|
}
|
||||||
const option = findAccountOption(dialog.kind);
|
const option = findAccountOption(dialog.kind);
|
||||||
if (option.needsToken && !dialog.token.trim()) {
|
if (dialog.kind === "megadebrid-api" || dialog.kind === "megadebrid-web") {
|
||||||
return `${option.title}: Bitte Zugangstoken eintragen.`;
|
if (dialog.megaAccounts.length === 0) {
|
||||||
}
|
return `${option.title}: Mindestens einen Account hinzufügen.`;
|
||||||
if ((dialog.kind === "megadebrid-api" || dialog.kind === "megadebrid-web") && dialog.token.trim()) {
|
|
||||||
if (parseMegaDebridAccounts(dialog.token).length === 0) {
|
|
||||||
return `${option.title}: Mindestens ein gültiges Login:Passwort-Paar eintragen (Format: login:passwort, pro Zeile).`;
|
|
||||||
}
|
}
|
||||||
|
} else if (option.needsToken && !dialog.token.trim()) {
|
||||||
|
return `${option.title}: Bitte Zugangstoken eintragen.`;
|
||||||
}
|
}
|
||||||
if (option.needsCredentials) {
|
if (option.needsCredentials) {
|
||||||
if (!dialog.login.trim()) {
|
if (!dialog.login.trim()) {
|
||||||
@ -2477,6 +2491,11 @@ export function App(): ReactElement {
|
|||||||
next.login = prev.login;
|
next.login = prev.login;
|
||||||
next.password = prev.password;
|
next.password = prev.password;
|
||||||
}
|
}
|
||||||
|
if (kind === "megadebrid-api" || kind === "megadebrid-web") {
|
||||||
|
next.megaAccounts = prev.megaAccounts;
|
||||||
|
next.megaNewLogin = prev.megaNewLogin;
|
||||||
|
next.megaNewPassword = prev.megaNewPassword;
|
||||||
|
}
|
||||||
next.dailyLimitGb = prev.dailyLimitGb;
|
next.dailyLimitGb = prev.dailyLimitGb;
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
@ -5410,9 +5429,69 @@ export function App(): ReactElement {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="account-modal-fields">
|
<div className="account-modal-fields">
|
||||||
{accountDialogOption.needsToken && (
|
{(accountDialogOption.service === "megadebrid-api" || accountDialogOption.service === "megadebrid-web") && (
|
||||||
<div>
|
<div>
|
||||||
<label>{accountDialogOption.service === "alldebrid" || accountDialogOption.service === "onefichier" || accountDialogOption.service === "debridlink" ? "API-Key(s)" : accountDialogOption.service === "megadebrid-api" || accountDialogOption.service === "megadebrid-web" ? "Login:Passwort (pro Zeile)" : "Token"}</label>
|
<label>Account hinzufügen</label>
|
||||||
|
<div className="field-grid two" style={{ marginBottom: 8 }}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
placeholder="Login / E-Mail"
|
||||||
|
value={accountDialog.megaNewLogin}
|
||||||
|
onChange={(event) => setAccountDialog((prev) => prev ? { ...prev, megaNewLogin: event.target.value } : prev)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Passwort"
|
||||||
|
value={accountDialog.megaNewPassword}
|
||||||
|
onChange={(event) => setAccountDialog((prev) => prev ? { ...prev, megaNewPassword: event.target.value } : prev)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
disabled={!accountDialog.megaNewLogin.trim() || !accountDialog.megaNewPassword.trim()}
|
||||||
|
onClick={() => setAccountDialog((prev) => {
|
||||||
|
if (!prev || !prev.megaNewLogin.trim() || !prev.megaNewPassword.trim()) return prev;
|
||||||
|
const exists = prev.megaAccounts.some((a) => a.login.trim().toLowerCase() === prev.megaNewLogin.trim().toLowerCase());
|
||||||
|
if (exists) return prev;
|
||||||
|
const nextAccounts = [...prev.megaAccounts, { login: prev.megaNewLogin.trim(), password: prev.megaNewPassword.trim() }];
|
||||||
|
return { ...prev, megaAccounts: nextAccounts, megaNewLogin: "", megaNewPassword: "", token: serializeMegaDebridAccounts(nextAccounts) };
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Hinzufügen
|
||||||
|
</button>
|
||||||
|
{accountDialog.megaAccounts.length > 0 && (
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<label>Konfigurierte Accounts ({accountDialog.megaAccounts.length})</label>
|
||||||
|
<div className="account-dl-key-limit-list">
|
||||||
|
{accountDialog.megaAccounts.map((account, index) => (
|
||||||
|
<div key={index} className="account-dl-key-limit-row">
|
||||||
|
<div className="account-dl-key-meta">
|
||||||
|
<strong>Account {index + 1}</strong>
|
||||||
|
<span>{maskMegaDebridLogin(account.login)}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn danger"
|
||||||
|
onClick={() => setAccountDialog((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
const nextAccounts = prev.megaAccounts.filter((_, i) => i !== index);
|
||||||
|
return { ...prev, megaAccounts: nextAccounts, token: serializeMegaDebridAccounts(nextAccounts) };
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{accountDialogOption.needsToken && accountDialogOption.service !== "megadebrid-api" && accountDialogOption.service !== "megadebrid-web" && (
|
||||||
|
<div>
|
||||||
|
<label>{accountDialogOption.service === "alldebrid" || accountDialogOption.service === "onefichier" || accountDialogOption.service === "debridlink" ? "API-Key(s)" : "Token"}</label>
|
||||||
{accountDialogOption.service === "debridlink" ? (
|
{accountDialogOption.service === "debridlink" ? (
|
||||||
<textarea
|
<textarea
|
||||||
rows={4}
|
rows={4}
|
||||||
@ -5425,14 +5504,6 @@ export function App(): ReactElement {
|
|||||||
} : prev)}
|
} : prev)}
|
||||||
style={{ fontFamily: "monospace", resize: "vertical" }}
|
style={{ fontFamily: "monospace", resize: "vertical" }}
|
||||||
/>
|
/>
|
||||||
) : accountDialogOption.service === "megadebrid-api" || accountDialogOption.service === "megadebrid-web" ? (
|
|
||||||
<textarea
|
|
||||||
rows={4}
|
|
||||||
placeholder={"user1@example.com:passwort1\nuser2@example.com:passwort2"}
|
|
||||||
value={accountDialog.token}
|
|
||||||
onChange={(event) => setAccountDialog((prev) => prev ? { ...prev, token: event.target.value } : prev)}
|
|
||||||
style={{ fontFamily: "monospace", resize: "vertical" }}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<input type="password" value={accountDialog.token} onChange={(event) => setAccountDialog((prev) => prev ? { ...prev, token: event.target.value } : prev)} />
|
<input type="password" value={accountDialog.token} onChange={(event) => setAccountDialog((prev) => prev ? { ...prev, token: event.target.value } : prev)} />
|
||||||
)}
|
)}
|
||||||
@ -5502,10 +5573,10 @@ export function App(): ReactElement {
|
|||||||
<div className="account-modal-note">Der Web-Login nutzt ein echtes Browserfenster, damit reCAPTCHA sauber läuft.</div>
|
<div className="account-modal-note">Der Web-Login nutzt ein echtes Browserfenster, damit reCAPTCHA sauber läuft.</div>
|
||||||
)}
|
)}
|
||||||
{accountDialog.kind === "megadebrid-api" && (
|
{accountDialog.kind === "megadebrid-api" && (
|
||||||
<div className="account-modal-note">Ein Login:Passwort-Paar pro Zeile. Mehrere Accounts werden rotierend genutzt. Dieser Account nutzt nur die Mega-Debrid API. Kein Web-Fallback.</div>
|
<div className="account-modal-note">Mehrere Accounts werden rotierend genutzt. Dieser Account nutzt nur die Mega-Debrid API. Kein Web-Fallback.</div>
|
||||||
)}
|
)}
|
||||||
{accountDialog.kind === "megadebrid-web" && (
|
{accountDialog.kind === "megadebrid-web" && (
|
||||||
<div className="account-modal-note">Ein Login:Passwort-Paar pro Zeile. Mehrere Accounts werden rotierend genutzt. Dieser Account nutzt nur Mega-Debrid Web. Kein API-Fallback.</div>
|
<div className="account-modal-note">Mehrere Accounts werden rotierend genutzt. Dieser Account nutzt nur Mega-Debrid Web. Kein API-Fallback.</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{accountDialogOption.service === "alldebrid" && allDebridHostInfo && (
|
{accountDialogOption.service === "alldebrid" && allDebridHostInfo && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user