diff --git a/src/core/services/anilist/anilist-auth.ts b/src/core/services/anilist/anilist-auth.ts deleted file mode 100644 index 94e02cf..0000000 --- a/src/core/services/anilist/anilist-auth.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as childProcess from "child_process"; - -const SECRET_COMMAND_PATTERN = /^\((.*)\)$/s; -const COMMAND_CACHE = new Map(); -const COMMAND_PENDING = new Map>(); - -function executeCommand(command: string): Promise { - return new Promise((resolve, reject) => { - childProcess.exec(command, { timeout: 10_000 }, (err, stdout) => { - if (err) { - reject(err); - return; - } - resolve(stdout); - }); - }); -} - -export function clearAnilistClientSecretCache(): void { - COMMAND_CACHE.clear(); - COMMAND_PENDING.clear(); -} - -function resolveCommand(rawSecret: string): string | null { - const commandMatch = rawSecret.match(SECRET_COMMAND_PATTERN); - if (!commandMatch || commandMatch[1] === undefined) { - return null; - } - - const command = commandMatch[1].trim(); - return command.length > 0 ? command : null; -} - -export async function resolveAnilistClientSecret(rawSecret: string): Promise { - const trimmedSecret = rawSecret.trim(); - if (trimmedSecret.length === 0) { - throw new Error("cannot authenticate without client secret"); - } - - const command = resolveCommand(trimmedSecret); - if (!command) { - return trimmedSecret; - } - - const cachedValue = COMMAND_CACHE.get(trimmedSecret); - if (cachedValue !== undefined) { - return cachedValue; - } - - const pending = COMMAND_PENDING.get(trimmedSecret); - if (pending !== undefined) { - return pending; - } - - const promise = executeCommand(command) - .then((stdout) => { - const trimmed = stdout.trim(); - if (!trimmed) { - throw new Error("secret command returned empty value"); - } - COMMAND_CACHE.set(trimmedSecret, trimmed); - COMMAND_PENDING.delete(trimmedSecret); - return trimmed; - }) - .catch((error) => { - COMMAND_PENDING.delete(trimmedSecret); - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage === "secret command returned empty value") { - throw error; - } - throw new Error(`secret command failed: ${errorMessage}`); - }); - - COMMAND_PENDING.set(trimmedSecret, promise); - return promise; -} diff --git a/src/main.ts b/src/main.ts index 89122d2..07a9e8d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -209,6 +209,7 @@ const ANILIST_DEVELOPER_SETTINGS_URL = "https://anilist.co/settings/developer"; const ANILIST_UPDATE_MIN_WATCH_RATIO = 0.85; const ANILIST_UPDATE_MIN_WATCH_SECONDS = 10 * 60; const ANILIST_DURATION_RETRY_INTERVAL_MS = 15_000; +const ANILIST_MAX_ATTEMPTED_UPDATE_KEYS = 1000; let anilistCurrentMediaKey: string | null = null; let anilistCurrentMediaDurationSec: number | null = null; @@ -864,6 +865,17 @@ function buildAnilistAttemptKey(mediaKey: string, episode: number): string { return `${mediaKey}::${episode}`; } +function rememberAnilistAttemptedUpdateKey(key: string): void { + anilistAttemptedUpdateKeys.add(key); + if (anilistAttemptedUpdateKeys.size <= ANILIST_MAX_ATTEMPTED_UPDATE_KEYS) { + return; + } + const oldestKey = anilistAttemptedUpdateKeys.values().next().value; + if (typeof oldestKey === "string") { + anilistAttemptedUpdateKeys.delete(oldestKey); + } +} + async function maybeRunAnilistPostWatchUpdate(): Promise { if (anilistUpdateInFlight) { return; @@ -921,13 +933,13 @@ async function maybeRunAnilistPostWatchUpdate(): Promise { guess.episode, ); if (result.status === "updated") { - anilistAttemptedUpdateKeys.add(attemptKey); + rememberAnilistAttemptedUpdateKey(attemptKey); showMpvOsd(result.message); logger.info(result.message); return; } if (result.status === "skipped") { - anilistAttemptedUpdateKeys.add(attemptKey); + rememberAnilistAttemptedUpdateKey(attemptKey); logger.info(result.message); return; }