fix(anki): force Yomitan proxy server sync for card auto-enhancement

This commit is contained in:
2026-03-04 22:43:43 -08:00
parent 69ab87c25f
commit 0a36d1aa99
3 changed files with 86 additions and 13 deletions

View File

@@ -41,6 +41,8 @@ test('syncYomitanDefaultAnkiServer updates default profile server when script re
assert.equal(updated, true); assert.equal(updated, true);
assert.match(scriptValue, /optionsGetFull/); assert.match(scriptValue, /optionsGetFull/);
assert.match(scriptValue, /setAllSettings/); assert.match(scriptValue, /setAllSettings/);
assert.match(scriptValue, /profileCurrent/);
assert.match(scriptValue, /forceOverride = false/);
assert.equal(infoLogs.length, 1); assert.equal(infoLogs.length, 1);
}); });
@@ -59,6 +61,45 @@ test('syncYomitanDefaultAnkiServer returns true when script reports no change',
assert.equal(infoLogCount, 0); 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 () => { test('syncYomitanDefaultAnkiServer logs and returns false on script failure', async () => {
const deps = createDeps(async () => { const deps = createDeps(async () => {
throw new Error('execute failed'); throw new Error('execute failed');

View File

@@ -848,11 +848,15 @@ export async function syncYomitanDefaultAnkiServer(
serverUrl: string, serverUrl: string,
deps: YomitanParserRuntimeDeps, deps: YomitanParserRuntimeDeps,
logger: LoggerLike, logger: LoggerLike,
options?: {
forceOverride?: boolean;
},
): Promise<boolean> { ): Promise<boolean> {
const normalizedTargetServer = serverUrl.trim(); const normalizedTargetServer = serverUrl.trim();
if (!normalizedTargetServer) { if (!normalizedTargetServer) {
return false; return false;
} }
const forceOverride = options?.forceOverride === true;
const isReady = await ensureYomitanParserWindow(deps, logger); const isReady = await ensureYomitanParserWindow(deps, logger);
const parserWindow = deps.getYomitanParserWindow(); const parserWindow = deps.getYomitanParserWindow();
@@ -882,35 +886,42 @@ export async function syncYomitanDefaultAnkiServer(
}); });
const targetServer = ${JSON.stringify(normalizedTargetServer)}; const targetServer = ${JSON.stringify(normalizedTargetServer)};
const forceOverride = ${forceOverride ? 'true' : 'false'};
const optionsFull = await invoke("optionsGetFull", undefined); const optionsFull = await invoke("optionsGetFull", undefined);
const profiles = Array.isArray(optionsFull.profiles) ? optionsFull.profiles : []; const profiles = Array.isArray(optionsFull.profiles) ? optionsFull.profiles : [];
if (profiles.length === 0) { if (profiles.length === 0) {
return { updated: false, reason: "no-profiles" }; return { updated: false, reason: "no-profiles" };
} }
const defaultProfile = profiles[0]; const profileCurrent = Number.isInteger(optionsFull.profileCurrent)
if (!defaultProfile || typeof defaultProfile !== "object") { ? optionsFull.profileCurrent
: 0;
const targetProfile = profiles[profileCurrent];
if (!targetProfile || typeof targetProfile !== "object") {
return { updated: false, reason: "invalid-default-profile" }; return { updated: false, reason: "invalid-default-profile" };
} }
defaultProfile.options = defaultProfile.options && typeof defaultProfile.options === "object" targetProfile.options = targetProfile.options && typeof targetProfile.options === "object"
? defaultProfile.options ? targetProfile.options
: {}; : {};
defaultProfile.options.anki = defaultProfile.options.anki && typeof defaultProfile.options.anki === "object" targetProfile.options.anki = targetProfile.options.anki && typeof targetProfile.options.anki === "object"
? defaultProfile.options.anki ? targetProfile.options.anki
: {}; : {};
const currentServerRaw = defaultProfile.options.anki.server; const currentServerRaw = targetProfile.options.anki.server;
const currentServer = typeof currentServerRaw === "string" ? currentServerRaw.trim() : ""; const currentServer = typeof currentServerRaw === "string" ? currentServerRaw.trim() : "";
const canReplaceDefault = if (currentServer === targetServer) {
currentServer.length === 0 || currentServer === "http://127.0.0.1:8765"; return { updated: false, matched: true, reason: "already-target", currentServer, targetServer };
if (!canReplaceDefault || currentServer === targetServer) { }
return { updated: false, reason: "no-change", 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" }); 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}`); logger.info?.(`Updated Yomitan default profile Anki server to ${normalizedTargetServer}`);
return true; 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 = const checkedWithoutUpdate =
typeof result === 'object' && typeof result === 'object' &&
result !== null && result !== null &&

View File

@@ -2663,6 +2663,9 @@ async function syncYomitanDefaultProfileAnkiServer(): Promise<void> {
logger.info(message, ...args); logger.info(message, ...args);
}, },
}, },
{
forceOverride: getResolvedConfig().ankiConnect.proxy?.enabled === true,
},
); );
if (synced) { if (synced) {