// 把预设里所有「像脚本」的东西挖出来:完整路径、长度、以及它碰了哪些酒馆 API。 // 用法: node read-preset-scripts.mjs [输出目录] import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; const presetFile = process.argv[2]; const outDir = process.argv[3] || path.join(path.dirname(presetFile), "script-dump"); const preset = JSON.parse(readFileSync(presetFile, "utf8")); mkdirSync(outDir, { recursive: true }); const APIS = [ "TavernHelper", "tavernHelper", "SillyTavern", "getContext", "eventOn", "eventOnce", "eventEmit", "registerFunctionTool", "unregisterFunctionTool", "getPrompts", "setPrompts", "replacePrompts", "injectPrompt", "uninjectPrompt", "getVariables", "setVariables", "getChatMessages", "setChatMessages", "createChatMessages", "deleteChatMessages", "substituteParams", "formatAsTavernRegexedString", "getLorebookEntries", "getCharLorebooks", "getCharacter", "getCurrentCharacterId", "triggerSlash", "getLastMessageId", "generate", "generateRaw", "stopGeneration", "getPreset", "getPresetNames", "loadPreset", "jQuery", "$(", "document.", "window.", "localStorage", "fetch(", "XMLHttpRequest", "postSysPrompt", "getTavernRegexes", "getWorldbook", "setWorldbook", "toastr", "console.log", "import(", "require(", "createElement", "addEventListener", "MutationObserver", "setTimeout", "__TAVERN", "stscript", "getAudio", "voice", "playAudio", "getRequestHeaders", "saveSettingsDebounced", ]; const candidates = []; function walk(node, trail, depth) { if (node == null || depth > 8) return; if (Array.isArray(node)) { node.forEach((v, i) => walk(v, [...trail, String(i)], depth + 1)); return; } if (typeof node === "string") { if (node.length > 200 && /(function|=>|await |const |let |var |TavernHelper|SillyTavern|\$\()/.test(node)) { candidates.push({ path: trail.join("."), code: node }); } return; } if (typeof node !== "object") return; for (const [k, v] of Object.entries(node)) walk(v, [...trail, k], depth + 1); } walk(preset, ["preset"], 0); // 也把所有 extensions 下的键结构打出来(含数组长度),判断「脚本」挂在哪个键 const extKeys = []; (function scan(node, trail, depth) { if (node == null || depth > 6) return; if (Array.isArray(node)) { extKeys.push([trail.join(".") + "[]", node.length]); return; } if (typeof node === "object") { for (const [k, v] of Object.entries(node)) scan(v, [...trail, k], depth + 1); return; } if (typeof node === "string" && node.length > 3000) extKeys.push([trail.join("."), node.length]); })(preset.extensions, ["extensions"], 0); console.log("=== extensions 下的大对象 / 长字符串(可能是脚本宿主) ==="); for (const [p, n] of extKeys.slice(0, 60)) console.log(" " + p + " → " + n); console.log("\n=== 候选脚本 " + candidates.length + " 段 ==="); let k = 0; for (const c of candidates) { k += 1; const hits = APIS.map((a) => [a, (c.code.match(new RegExp(a.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length]).filter(([, n]) => n > 0); console.log("\n--- [" + k + "] " + c.path + " (" + c.code.length + " 字符) ---"); console.log(" 碰到: " + (hits.length ? hits.map(([a, n]) => a + "×" + n).join(", ") : "(无已知 API)")); const file = path.join(outDir, "script-" + String(k).padStart(2, "0") + "-" + c.path.replace(/[^\w.\-]+/g, "_").slice(-60) + ".js"); writeFileSync(file, c.code, "utf8"); console.log(" 落盘: " + file); console.log(" 头 300 字: " + JSON.stringify(c.code.slice(0, 300))); }