release: ship v2.0.23 queue and account reliability fixes
Normalize RapidGator aliases, stabilize active queue ordering, preserve user-controlled package expansion, and align compact queue status presentation. Separate Mega-Debrid API and Web credential pools, migrate legacy credentials and disabled states safely, refresh live account availability without restart, honor disabled credential persistence, and invalidate Web sessions without allowing stale in-flight, retry, or queued logins to restore old cookies. Expand regression coverage for account isolation, legacy migration, session races, live scheduler refresh, update notes, and responsive queue behavior.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
const DOMAIN_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
||||
"rapidgator.net": "rapidgator",
|
||||
"rapidgator.asia": "rapidgator",
|
||||
"rg.to": "rapidgator"
|
||||
});
|
||||
|
||||
export function normalizeHosterHostname(hostname: string): string {
|
||||
const normalized = hostname.trim().toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
|
||||
for (const [domain, hoster] of Object.entries(DOMAIN_ALIASES)) {
|
||||
if (normalized === domain || normalized.endsWith(`.${domain}`)) {
|
||||
return hoster;
|
||||
}
|
||||
}
|
||||
const parts = normalized.split(".").filter(Boolean);
|
||||
return parts.length >= 2 ? parts[parts.length - 2] : normalized;
|
||||
}
|
||||
|
||||
export function extractHosterFromUrl(url: string): string {
|
||||
try {
|
||||
return normalizeHosterHostname(new URL(url).hostname);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
export interface MegaDebridAccountEntry {
|
||||
export interface MegaDebridAccountEntry {
|
||||
id: string;
|
||||
login: string;
|
||||
password: string;
|
||||
index: number;
|
||||
label: string;
|
||||
maskedLogin: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type MegaDebridAccountMode = "api" | "web";
|
||||
|
||||
type MegaDebridModeSettings = {
|
||||
megaCredentials?: string;
|
||||
megaPassword?: string;
|
||||
megaDebridApiCredentials?: string;
|
||||
megaDebridWebCredentials?: string;
|
||||
megaDebridApiEnabled?: boolean;
|
||||
megaDebridWebEnabled?: boolean;
|
||||
megaDebridPreferApi?: boolean;
|
||||
megaDebridDisabledAccountIds?: string[];
|
||||
megaDebridApiDisabledAccountIds?: string[];
|
||||
megaDebridWebDisabledAccountIds?: string[];
|
||||
};
|
||||
|
||||
const FNV64_OFFSET_BASIS = 0xcbf29ce484222325n;
|
||||
const FNV64_PRIME = 0x100000001b3n;
|
||||
@@ -85,6 +100,52 @@ export function serializeMegaDebridAccounts(accounts: { login: string; password:
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountIds(raw: string, legacyPassword = ""): string[] {
|
||||
return parseMegaDebridAccounts(raw, legacyPassword).map((entry) => entry.id);
|
||||
}
|
||||
export function getMegaDebridAccountIds(raw: string, legacyPassword = ""): string[] {
|
||||
return parseMegaDebridAccounts(raw, legacyPassword).map((entry) => entry.id);
|
||||
}
|
||||
|
||||
export function getMegaDebridCredentialsForMode(settings: MegaDebridModeSettings, mode: MegaDebridAccountMode): string {
|
||||
const dedicated = mode === "api" ? settings.megaDebridApiCredentials : settings.megaDebridWebCredentials;
|
||||
const otherDedicated = mode === "api" ? settings.megaDebridWebCredentials : settings.megaDebridApiCredentials;
|
||||
if (String(dedicated || "").trim() || String(otherDedicated || "").trim()) {
|
||||
return String(dedicated || "").trim();
|
||||
}
|
||||
const legacy = String(settings.megaCredentials || "").trim();
|
||||
if (!legacy) {
|
||||
return "";
|
||||
}
|
||||
const apiEnabled = Boolean(settings.megaDebridApiEnabled);
|
||||
const webEnabled = Boolean(settings.megaDebridWebEnabled);
|
||||
const preferredMode: MegaDebridAccountMode = settings.megaDebridPreferApi === false ? "web" : "api";
|
||||
if (apiEnabled !== webEnabled) {
|
||||
return mode === (apiEnabled ? "api" : "web") ? legacy : "";
|
||||
}
|
||||
return mode === preferredMode ? legacy : "";
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountsForMode(settings: MegaDebridModeSettings, mode: MegaDebridAccountMode): MegaDebridAccountEntry[] {
|
||||
return parseMegaDebridAccounts(getMegaDebridCredentialsForMode(settings, mode), settings.megaPassword || "");
|
||||
}
|
||||
|
||||
export function getMegaDebridDisabledAccountIdsForMode(settings: MegaDebridModeSettings, mode: MegaDebridAccountMode): string[] {
|
||||
const dedicated = mode === "api" ? settings.megaDebridApiDisabledAccountIds : settings.megaDebridWebDisabledAccountIds;
|
||||
const hasDedicatedCredentials = Boolean(
|
||||
String(settings.megaDebridApiCredentials || "").trim()
|
||||
|| String(settings.megaDebridWebCredentials || "").trim()
|
||||
);
|
||||
if ((hasDedicatedCredentials || !getMegaDebridCredentialsForMode(settings, mode)) && Array.isArray(dedicated)) {
|
||||
return [...dedicated];
|
||||
}
|
||||
return Array.isArray(settings.megaDebridDisabledAccountIds) ? [...settings.megaDebridDisabledAccountIds] : [];
|
||||
}
|
||||
|
||||
export function mergeMegaDebridCredentialPools(apiCredentials: string, webCredentials: string): string {
|
||||
const merged = new Map<string, { login: string; password: string }>();
|
||||
for (const entry of [...parseMegaDebridAccounts(apiCredentials), ...parseMegaDebridAccounts(webCredentials)]) {
|
||||
const key = entry.login.trim().toLowerCase();
|
||||
if (!merged.has(key)) {
|
||||
merged.set(key, { login: entry.login, password: entry.password });
|
||||
}
|
||||
}
|
||||
return serializeMegaDebridAccounts([...merged.values()]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AppSettings, DebridProvider } from "./types";
|
||||
import type { AppSettings, DebridProvider } from "./types";
|
||||
import { getMegaDebridDisabledAccountIdsForMode, type MegaDebridAccountMode } from "./mega-debrid-accounts";
|
||||
|
||||
export type ProviderByteMap = Partial<Record<DebridProvider, number>>;
|
||||
export type DebridLinkKeyByteMap = Record<string, number>;
|
||||
@@ -6,7 +7,7 @@ export type DebridLinkKeyByteMap = Record<string, number>;
|
||||
type ProviderDailySettings =
|
||||
Pick<AppSettings, "providerDailyLimitBytes" | "providerDailyUsageBytes" | "providerDailyUsageDay">
|
||||
& Partial<Pick<AppSettings, "debridLinkApiKeyDailyLimitBytes" | "debridLinkApiKeyDailyUsageBytes">>
|
||||
& Partial<Pick<AppSettings, "megaDebridDisabledAccountIds" | "megaDebridAccountDailyLimitBytes" | "megaDebridAccountDailyUsageBytes">>;
|
||||
& Partial<Pick<AppSettings, "megaDebridDisabledAccountIds" | "megaDebridApiDisabledAccountIds" | "megaDebridWebDisabledAccountIds" | "megaDebridAccountDailyLimitBytes" | "megaDebridAccountDailyUsageBytes">>;
|
||||
|
||||
type ProviderUsageSettings =
|
||||
ProviderDailySettings
|
||||
@@ -250,9 +251,12 @@ export function addDebridLinkApiKeyTotalUsageBytes(
|
||||
};
|
||||
}
|
||||
|
||||
export function isMegaDebridAccountDisabled(settings: ProviderDailySettings, accountId: string): boolean {
|
||||
return Array.isArray(settings.megaDebridDisabledAccountIds) && settings.megaDebridDisabledAccountIds.includes(accountId);
|
||||
}
|
||||
export function isMegaDebridAccountDisabled(settings: ProviderDailySettings, accountId: string, mode?: MegaDebridAccountMode): boolean {
|
||||
if (mode) {
|
||||
return getMegaDebridDisabledAccountIdsForMode(settings, mode).includes(accountId);
|
||||
}
|
||||
return Array.isArray(settings.megaDebridDisabledAccountIds) && settings.megaDebridDisabledAccountIds.includes(accountId);
|
||||
}
|
||||
|
||||
export function getMegaDebridAccountDailyLimitBytes(settings: ProviderDailySettings, accountId: string): number {
|
||||
return normalizePositiveBytes(settings.megaDebridAccountDailyLimitBytes?.[accountId]);
|
||||
|
||||
+9
-5
@@ -71,10 +71,12 @@ export interface AppSettings {
|
||||
language: AppLanguage;
|
||||
token: string;
|
||||
realDebridUseWebLogin: boolean;
|
||||
megaLogin: string;
|
||||
megaPassword: string;
|
||||
megaCredentials: string;
|
||||
megaDebridApiEnabled: boolean;
|
||||
megaLogin: string;
|
||||
megaPassword: string;
|
||||
megaCredentials: string;
|
||||
megaDebridApiCredentials: string;
|
||||
megaDebridWebCredentials: string;
|
||||
megaDebridApiEnabled: boolean;
|
||||
megaDebridWebEnabled: boolean;
|
||||
megaDebridPreferApi: boolean;
|
||||
bestToken: string;
|
||||
@@ -158,7 +160,9 @@ export interface AppSettings {
|
||||
debridLinkApiKeyDailyLimitBytes: Record<string, number>;
|
||||
debridLinkApiKeyDailyUsageBytes: Record<string, number>;
|
||||
debridLinkApiKeyTotalUsageBytes: Record<string, number>;
|
||||
megaDebridDisabledAccountIds: string[];
|
||||
megaDebridDisabledAccountIds: string[];
|
||||
megaDebridApiDisabledAccountIds: string[];
|
||||
megaDebridWebDisabledAccountIds: string[];
|
||||
megaDebridAccountDailyLimitBytes: Record<string, number>;
|
||||
megaDebridAccountDailyUsageBytes: Record<string, number>;
|
||||
megaDebridAccountTotalUsageBytes: Record<string, number>;
|
||||
|
||||
Reference in New Issue
Block a user