From 0a36d1aa99d6711a44c8550a639e8658b2df9ca4 Mon Sep 17 00:00:00 2001 From: sudacode Date: Wed, 4 Mar 2026 22:43:43 -0800 Subject: [PATCH] fix(anki): force Yomitan proxy server sync for card auto-enhancement --- .../tokenizer/yomitan-parser-runtime.test.ts | 41 ++++++++++++++ .../tokenizer/yomitan-parser-runtime.ts | 55 ++++++++++++++----- src/main.ts | 3 + 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/core/services/tokenizer/yomitan-parser-runtime.test.ts b/src/core/services/tokenizer/yomitan-parser-runtime.test.ts index 9f9586f..ae83654 100644 --- a/src/core/services/tokenizer/yomitan-parser-runtime.test.ts +++ b/src/core/services/tokenizer/yomitan-parser-runtime.test.ts @@ -41,6 +41,8 @@ test('syncYomitanDefaultAnkiServer updates default profile server when script re assert.equal(updated, true); assert.match(scriptValue, /optionsGetFull/); assert.match(scriptValue, /setAllSettings/); + assert.match(scriptValue, /profileCurrent/); + assert.match(scriptValue, /forceOverride = false/); assert.equal(infoLogs.length, 1); }); @@ -59,6 +61,45 @@ test('syncYomitanDefaultAnkiServer returns true when script reports no change', assert.equal(infoLogCount, 0); }); +test('syncYomitanDefaultAnkiServer returns false when existing non-default server blocks update', async () => { + const deps = createDeps(async () => ({ + updated: false, + matched: false, + reason: 'blocked-existing-server', + })); + const infoLogs: string[] = []; + + const synced = await syncYomitanDefaultAnkiServer('http://127.0.0.1:8766', deps, { + error: () => undefined, + info: (message) => infoLogs.push(message), + }); + + assert.equal(synced, false); + assert.equal(infoLogs.length, 1); + assert.match(infoLogs[0] ?? '', /blocked-existing-server/); +}); + +test('syncYomitanDefaultAnkiServer injects force override when enabled', async () => { + let scriptValue = ''; + const deps = createDeps(async (script) => { + scriptValue = script; + return { updated: false, matched: true }; + }); + + const synced = await syncYomitanDefaultAnkiServer( + 'http://127.0.0.1:8766', + deps, + { + error: () => undefined, + info: () => undefined, + }, + { forceOverride: true }, + ); + + assert.equal(synced, true); + assert.match(scriptValue, /forceOverride = true/); +}); + test('syncYomitanDefaultAnkiServer logs and returns false on script failure', async () => { const deps = createDeps(async () => { throw new Error('execute failed'); diff --git a/src/core/services/tokenizer/yomitan-parser-runtime.ts b/src/core/services/tokenizer/yomitan-parser-runtime.ts index fbb4ac5..4510459 100644 --- a/src/core/services/tokenizer/yomitan-parser-runtime.ts +++ b/src/core/services/tokenizer/yomitan-parser-runtime.ts @@ -848,11 +848,15 @@ export async function syncYomitanDefaultAnkiServer( serverUrl: string, deps: YomitanParserRuntimeDeps, logger: LoggerLike, + options?: { + forceOverride?: boolean; + }, ): Promise { const normalizedTargetServer = serverUrl.trim(); if (!normalizedTargetServer) { return false; } + const forceOverride = options?.forceOverride === true; const isReady = await ensureYomitanParserWindow(deps, logger); const parserWindow = deps.getYomitanParserWindow(); @@ -882,35 +886,42 @@ export async function syncYomitanDefaultAnkiServer( }); const targetServer = ${JSON.stringify(normalizedTargetServer)}; + const forceOverride = ${forceOverride ? 'true' : 'false'}; const optionsFull = await invoke("optionsGetFull", undefined); const profiles = Array.isArray(optionsFull.profiles) ? optionsFull.profiles : []; if (profiles.length === 0) { return { updated: false, reason: "no-profiles" }; } - const defaultProfile = profiles[0]; - if (!defaultProfile || typeof defaultProfile !== "object") { + const profileCurrent = Number.isInteger(optionsFull.profileCurrent) + ? optionsFull.profileCurrent + : 0; + const targetProfile = profiles[profileCurrent]; + if (!targetProfile || typeof targetProfile !== "object") { return { updated: false, reason: "invalid-default-profile" }; } - defaultProfile.options = defaultProfile.options && typeof defaultProfile.options === "object" - ? defaultProfile.options + targetProfile.options = targetProfile.options && typeof targetProfile.options === "object" + ? targetProfile.options : {}; - defaultProfile.options.anki = defaultProfile.options.anki && typeof defaultProfile.options.anki === "object" - ? defaultProfile.options.anki + targetProfile.options.anki = targetProfile.options.anki && typeof targetProfile.options.anki === "object" + ? targetProfile.options.anki : {}; - const currentServerRaw = defaultProfile.options.anki.server; + const currentServerRaw = targetProfile.options.anki.server; const currentServer = typeof currentServerRaw === "string" ? currentServerRaw.trim() : ""; - const canReplaceDefault = - currentServer.length === 0 || currentServer === "http://127.0.0.1:8765"; - if (!canReplaceDefault || currentServer === targetServer) { - return { updated: false, reason: "no-change", currentServer, targetServer }; + if (currentServer === targetServer) { + return { updated: false, matched: true, reason: "already-target", currentServer, targetServer }; + } + const canReplaceCurrent = + forceOverride || currentServer.length === 0 || currentServer === "http://127.0.0.1:8765"; + if (!canReplaceCurrent) { + return { updated: false, matched: false, reason: "blocked-existing-server", currentServer, targetServer }; } - defaultProfile.options.anki.server = targetServer; + targetProfile.options.anki.server = targetServer; await invoke("setAllSettings", { value: optionsFull, source: "subminer" }); - return { updated: true, currentServer, targetServer }; + return { updated: true, matched: true, currentServer, targetServer }; })(); `; @@ -924,6 +935,24 @@ export async function syncYomitanDefaultAnkiServer( logger.info?.(`Updated Yomitan default profile Anki server to ${normalizedTargetServer}`); return true; } + const matchedWithoutUpdate = + isObject(result) && + result.updated === false && + (result as { matched?: unknown }).matched === true; + if (matchedWithoutUpdate) { + return true; + } + const blockedByExistingServer = + isObject(result) && + result.updated === false && + (result as { matched?: unknown }).matched === false && + typeof (result as { reason?: unknown }).reason === 'string'; + if (blockedByExistingServer) { + logger.info?.( + `Skipped syncing Yomitan Anki server (reason=${String((result as { reason: string }).reason)})`, + ); + return false; + } const checkedWithoutUpdate = typeof result === 'object' && result !== null && diff --git a/src/main.ts b/src/main.ts index 67cd6c2..824ca7b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2663,6 +2663,9 @@ async function syncYomitanDefaultProfileAnkiServer(): Promise { logger.info(message, ...args); }, }, + { + forceOverride: getResolvedConfig().ankiConnect.proxy?.enabled === true, + }, ); if (synced) {