rssany 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +52 -52
- package/README.md +156 -147
- package/app/plugins/builtin/email.rssany.js +84 -84
- package/app/plugins/builtin/rss.rssany.js +164 -164
- package/app/plugins/builtin/xiaohongshu.rssany.js +59 -2
- package/app/statics/README.md +7 -7
- package/app/webui/build/200.html +36 -36
- package/app/webui/build/_app/immutable/assets/0.BLOTwIuF.css +1 -0
- package/app/webui/build/_app/immutable/assets/10.CmGYYZFR.css +1 -0
- package/app/webui/build/_app/immutable/assets/11.Dkz3VS_N.css +1 -0
- package/app/webui/build/_app/immutable/assets/14.BCCBoMGj.css +1 -0
- package/app/webui/build/_app/immutable/assets/6.Cm_jpHOq.css +1 -0
- package/app/webui/build/_app/immutable/assets/7.CJ3BjogD.css +1 -0
- package/app/webui/build/_app/immutable/assets/9.CATKVZ-n.css +1 -0
- package/app/webui/build/_app/immutable/assets/{SourcesList.D5Lso0bo.css → SourcesList.ke66uOSi.css} +1 -1
- package/app/webui/build/_app/immutable/assets/chevron-down.CV-KWLNP.css +1 -0
- package/app/webui/build/_app/immutable/chunks/{CGCMIfh3.js → 4TuV_psf.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{DAdOEnFb.js → B0czyjwj.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CFwxUBGi.js → B553hBXT.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/B8StT3Do.js +6 -0
- package/app/webui/build/_app/immutable/chunks/BI_ale1m.js +1 -0
- package/app/webui/build/_app/immutable/chunks/BK0ygNWX.js +2 -0
- package/app/webui/build/_app/immutable/chunks/BKm6QCwp.js +1 -0
- package/app/webui/build/_app/immutable/chunks/BT6b4LcZ.js +36 -0
- package/app/webui/build/_app/immutable/chunks/BZY5aksi.js +36 -0
- package/app/webui/build/_app/immutable/chunks/{C8umpVpB.js → BnqaikL8.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/BsQ08Wq_.js +1 -0
- package/app/webui/build/_app/immutable/chunks/C9wTDiHH.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{B-CeeY89.js → CAKuIoAf.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/CEWi_rGa.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{ChUctqXA.js → Cc7aBSsN.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{BAJAS8BI.js → D8G961Hm.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CS53ooo0.js → DIeahUKq.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/DO5OXNYS.js +1 -0
- package/app/webui/build/_app/immutable/chunks/Dg_D3pjF.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{Dyvi1wBH.js → DptdhtA1.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{ClknbeNl.js → FDS7fbwH.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CqYSO3Dx.js → GeNMTUn1.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{DCEayuDt.js → IhDlsCxD.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/Nd0ktDhd.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{D6kzEN_P.js → SvdgnirT.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/WW6La7Nt.js +2 -0
- package/app/webui/build/_app/immutable/chunks/{DsxvjlCC.js → pd_p3yYy.js} +5 -5
- package/app/webui/build/_app/immutable/chunks/rNwPv4DZ.js +1 -0
- package/app/webui/build/_app/immutable/entry/app.BKLBG-4w.js +2 -0
- package/app/webui/build/_app/immutable/entry/start.D-X6pVtx.js +1 -0
- package/app/webui/build/_app/immutable/nodes/{0.DK_mcVDm.js → 0.CJDC_3s9.js} +3 -3
- package/app/webui/build/_app/immutable/nodes/1.DsKocFSb.js +1 -0
- package/app/webui/build/_app/immutable/nodes/10.BeejAn8z.js +1 -0
- package/app/webui/build/_app/immutable/nodes/11.D--uwkk0.js +3 -0
- package/app/webui/build/_app/immutable/nodes/12.BLyQ6rUu.js +1 -0
- package/app/webui/build/_app/immutable/nodes/13.Cl0WQK13.js +1 -0
- package/app/webui/build/_app/immutable/nodes/14.T9l5Rh19.js +1 -0
- package/app/webui/build/_app/immutable/nodes/15.DHfwIlBx.js +1 -0
- package/app/webui/build/_app/immutable/nodes/{16.zfSe93Ab.js → 16.BKDfR-KV.js} +2 -2
- package/app/webui/build/_app/immutable/nodes/17.DofB8HQB.js +1 -0
- package/app/webui/build/_app/immutable/nodes/2.BOYqXdCa.js +1 -0
- package/app/webui/build/_app/immutable/nodes/3.B9ucbp_W.js +1 -0
- package/app/webui/build/_app/immutable/nodes/5.9zgwFV6I.js +2 -0
- package/app/webui/build/_app/immutable/nodes/6.Bs32Ieii.js +2 -0
- package/app/webui/build/_app/immutable/nodes/7.Cigxrk0v.js +1 -0
- package/app/webui/build/_app/immutable/nodes/8.pG10rCF0.js +1 -0
- package/app/webui/build/_app/immutable/nodes/9.Bzqb3xHY.js +1 -0
- package/app/webui/build/_app/version.json +1 -1
- package/bin/rssany.js +55 -3
- package/dist/index.js +361 -99
- package/dist/index.js.map +1 -1
- package/package.json +107 -103
- package/scripts/dev.mjs +5 -1
- package/scripts/postinstall.mjs +44 -0
- package/scripts/reset.mjs +137 -135
- package/scripts/user-dir.mjs +52 -0
- package/app/webui/build/_app/immutable/assets/0.DsKls1SN.css +0 -1
- package/app/webui/build/_app/immutable/assets/10.Dj8_pmut.css +0 -1
- package/app/webui/build/_app/immutable/assets/13.Qu_tY6H9.css +0 -1
- package/app/webui/build/_app/immutable/assets/5.B-dPiwB7.css +0 -1
- package/app/webui/build/_app/immutable/assets/6.B27N7pdA.css +0 -1
- package/app/webui/build/_app/immutable/assets/8.Cgji2b15.css +0 -1
- package/app/webui/build/_app/immutable/assets/9.BsCIAvn3.css +0 -1
- package/app/webui/build/_app/immutable/chunks/6prdYIKP.js +0 -1
- package/app/webui/build/_app/immutable/chunks/B2cyTHdf.js +0 -2
- package/app/webui/build/_app/immutable/chunks/B6WG2Sd3.js +0 -1
- package/app/webui/build/_app/immutable/chunks/BA4Gucnq.js +0 -1
- package/app/webui/build/_app/immutable/chunks/BkD3yAYe.js +0 -1
- package/app/webui/build/_app/immutable/chunks/C4uF_YIK.js +0 -1
- package/app/webui/build/_app/immutable/chunks/CBY2biv-.js +0 -1
- package/app/webui/build/_app/immutable/chunks/CVW0ymE1.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DJ2e04vK.js +0 -36
- package/app/webui/build/_app/immutable/chunks/DL3Q5sfb.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DVa8Y-mQ.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DkamXS6W.js +0 -36
- package/app/webui/build/_app/immutable/chunks/DoRPmqLn.js +0 -2
- package/app/webui/build/_app/immutable/chunks/_qj9U-za.js +0 -1
- package/app/webui/build/_app/immutable/chunks/vtBo8kBV.js +0 -1
- package/app/webui/build/_app/immutable/entry/app.RFfWi3_i.js +0 -2
- package/app/webui/build/_app/immutable/entry/start.DU_kyeGS.js +0 -1
- package/app/webui/build/_app/immutable/nodes/1.0PRrU2uQ.js +0 -1
- package/app/webui/build/_app/immutable/nodes/10.CsxzlUER.js +0 -1
- package/app/webui/build/_app/immutable/nodes/11.D-PkhIRW.js +0 -1
- package/app/webui/build/_app/immutable/nodes/12.GGf-JLUY.js +0 -1
- package/app/webui/build/_app/immutable/nodes/13.DWWcH27k.js +0 -6
- package/app/webui/build/_app/immutable/nodes/14.COwSLwDN.js +0 -1
- package/app/webui/build/_app/immutable/nodes/15.nDN_AHrs.js +0 -1
- package/app/webui/build/_app/immutable/nodes/2.AJd2163d.js +0 -1
- package/app/webui/build/_app/immutable/nodes/3.CEVEHuaH.js +0 -1
- package/app/webui/build/_app/immutable/nodes/4.BT_N8pCh.js +0 -2
- package/app/webui/build/_app/immutable/nodes/5.BZScQ2CH.js +0 -2
- package/app/webui/build/_app/immutable/nodes/6.CkFk8X--.js +0 -1
- package/app/webui/build/_app/immutable/nodes/7.CuQJk7te.js +0 -1
- package/app/webui/build/_app/immutable/nodes/8.DIavWJnU.js +0 -1
- package/app/webui/build/_app/immutable/nodes/9.Db30M8x0.js +0 -1
- /package/app/webui/build/_app/immutable/assets/{11.qYZMiTb0.css → 12.qYZMiTb0.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{12.DfJcfUWl.css → 13.DfJcfUWl.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{14.DfMfOrS3.css → 15.DfMfOrS3.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{15.nNGjXhCQ.css → 17.nNGjXhCQ.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{4.Di6rvlY-.css → 5.Di6rvlY-.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{7.CrNxmd8B.css → 8.CrNxmd8B.css} +0 -0
- /package/app/webui/build/_app/immutable/nodes/{17.BtYZF6FM.js → 18.BtYZF6FM.js} +0 -0
- /package/app/webui/build/_app/immutable/nodes/{18.BIzqhTqv.js → 4.BIzqhTqv.js} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../app/scraper/sources/web/fetcher/purify.ts","../app/scraper/sources/web/fetcher/cdp.ts","../app/types/feedItem.ts","../app/utils/httpSourceRef.ts","../app/packageRoot.ts","../app/config/paths.ts","../app/db/index.ts","../app/core/logger/config.ts","../app/core/logger/index.ts","../app/scraper/sources/web/fetcher/browser.ts","../app/utils/refreshInterval.ts","../app/core/cacher/index.ts","../app/scraper/sources/web/extractor/extractor.ts","../app/core/llmConfig.ts","../app/core/llm.ts","../app/scraper/sources/web/parser/parser.ts","../app/scraper/sources/web/site.ts","../app/scraper/auth/errors.ts","../app/plugins/loader.ts","../app/scraper/sources/web/index.ts","../app/plugins/hostDeps.ts","../app/scraper/sources/context.ts","../app/scraper/sources/index.ts","../app/scraper/subscription/types.ts","../app/config/globalProxy.ts","../app/scraper/subscription/index.ts","../app/pipeline/qualityFilter.ts","../app/pipeline/tagger.ts","../app/pipeline/translator.ts","../app/pipeline/config.ts","../app/pipeline/index.ts","../app/feeder/rss.ts","../app/core/events/index.ts","../app/config/deliver.ts","../app/deliver/post.ts","../app/feeder/feeder.ts","../app/scheduler/index.ts","../app/scraper/scheduler/index.ts","../app/auth/middleware.ts","../app/router/routes/api/server.ts","../app/router/routes/api/rss.ts","../app/router/routes/api/scheduler.ts","../app/router/routes/api/plugins.ts","../app/router/routes/api/pipeline.ts","../app/router/routes/api/feed.ts","../app/router/routes/api/items.ts","../app/router/routes/api/logs.ts","../app/router/routes/api/admin.ts","../app/router/routes/api/sources.ts","../app/router/routes/api/topics.ts","../app/router/routes/api/deliver.ts","../app/config/llmSettings.ts","../app/router/routes/api/llm.ts","../app/router/routes/api/proxy.ts","../app/tasks/index.ts","../app/router/routes/api/tasks.ts","../app/router/routes/api/feed-favicon.ts","../app/router/routes/api/index.ts","../app/router/routes/auth.ts","../app/router/utils.ts","../app/router/routes/admin.ts","../app/router/routes/rss.ts","../app/router/webui.ts","../app/version.ts","../app/router/index.ts"],"sourcesContent":["// 基于 node-html-parser 的 HTML 净化:移除与 RSS 内容无关的标签、属性、注释\n\nimport { parse, NodeType } from \"node-html-parser\";\nimport type { HTMLElement, Node } from \"node-html-parser\";\n\n\nconst TAGS_TO_REMOVE = [\n \"script\", \"style\", \"svg\", \"symbol\", \"link\", \"meta\",\n \"input\", \"embed\", \"button\", \"select\", \"textarea\",\n \"nav\", \"iframe\", \"noscript\", \"template\", \"object\", \"canvas\",\n];\n\n\nconst BASE64_IMG_PATTERN = /^data:image\\/[^;\"'\\s]+;base64,/i;\n\n\nfunction collectCommentNodes(node: Node, out: Node[]): void {\n if (node.nodeType === NodeType.COMMENT_NODE) {\n out.push(node);\n return;\n }\n if (\"childNodes\" in node && Array.isArray(node.childNodes)) {\n for (const child of node.childNodes) {\n collectCommentNodes(child, out);\n }\n }\n}\n\n\nfunction stripRssIrrelevantAttributes(root: HTMLElement): void {\n const toProcess: HTMLElement[] = [root];\n const all = root.querySelectorAll(\"*\");\n for (const el of all) {\n if (el.nodeType === NodeType.ELEMENT_NODE) toProcess.push(el as HTMLElement);\n }\n for (const elem of toProcess) {\n elem.removeAttribute(\"class\");\n elem.removeAttribute(\"style\");\n const src = elem.getAttribute(\"src\");\n if (src && BASE64_IMG_PATTERN.test(src)) {\n elem.removeAttribute(\"src\");\n }\n }\n}\n\n\n/** 使用 node-html-parser 解析并净化 HTML,剥离与 RSS 内容无关的部分(默认开启) */\nexport function applyPurify(html: string, purify: boolean | undefined): string {\n if (purify === false) return html;\n const root = parse(html, { comment: true });\n for (const tag of TAGS_TO_REMOVE) {\n const list = root.querySelectorAll(tag);\n for (const el of list) {\n el.remove();\n }\n }\n const commentNodes: Node[] = [];\n collectCommentNodes(root, commentNodes);\n for (const node of commentNodes) {\n node.remove();\n }\n stripRssIrrelevantAttributes(root);\n return root.toString();\n}\n","// Chrome DevTools Protocol (CDP) 控制模块:支持连接到手动启动的 Chrome,避免安装 Puppeteer 专属 Chrome\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport puppeteerCore, { type Browser, type ConnectOptions, type LaunchOptions } from \"puppeteer-core\";\n\n\n/** 按平台枚举所有可能的 Chrome 路径,优先返回第一个实际存在的;全部不存在则返回 null */\nexport function findChromeExecutable(): string | null {\n const platformName = platform();\n const paths: string[] = [];\n const envChrome = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;\n if (envChrome) paths.push(envChrome);\n if (platformName === \"darwin\") {\n paths.push(\n \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\n \"/Applications/Chromium.app/Contents/MacOS/Chromium\",\n \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"\n );\n } else if (platformName === \"linux\") {\n paths.push(\n \"/usr/bin/google-chrome\",\n \"/usr/bin/google-chrome-stable\",\n \"/usr/bin/chromium\",\n \"/usr/bin/chromium-browser\",\n \"/snap/bin/chromium\"\n );\n } else if (platformName === \"win32\") {\n const programFiles = process.env[\"ProgramFiles\"] || \"C:\\\\Program Files\";\n const programFilesX86 = process.env[\"ProgramFiles(x86)\"] || \"C:\\\\Program Files (x86)\";\n paths.push(\n join(programFiles, \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n join(programFilesX86, \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n join(programFiles, \"Microsoft\", \"Edge\", \"Application\", \"msedge.exe\"),\n join(programFilesX86, \"Microsoft\", \"Edge\", \"Application\", \"msedge.exe\")\n );\n }\n for (const p of paths) {\n try {\n if (existsSync(p)) return p;\n } catch {\n // 跳过\n }\n }\n return null;\n}\n\n\n/** CDP 连接配置 */\nexport interface CDPConfig {\n /** Chrome 可执行文件路径,不提供则自动查找 */\n executablePath?: string;\n /** CDP 端口,默认 9222 */\n port?: number;\n /** 是否使用已启动的 Chrome(不自动启动) */\n useExisting?: boolean;\n /** userDataDir,用于持久化 cookies 等 */\n userDataDir?: string;\n /** 是否无头模式 */\n headless?: boolean;\n /** 代理配置 */\n proxy?: string;\n /** 额外的 Chrome 启动参数 */\n args?: string[];\n}\n\n\n/** 启动 Chrome 进程并返回 CDP WebSocket URL */\nasync function launchChromeWithCDP(config: CDPConfig): Promise<{ process: ChildProcess; wsEndpoint: string }> {\n const port = config.port ?? 9222;\n const executablePath = config.executablePath || findChromeExecutable();\n if (!executablePath) {\n throw new Error(\"未找到 Chrome 可执行文件,请设置 CHROME_PATH 环境变量或提供 executablePath\");\n }\n const args: string[] = [\n `--remote-debugging-port=${port}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-web-security\",\n \"--disable-features=IsolateOrigins,site-per-process\",\n \"--disable-site-isolation-trials\",\n \"--disable-infobars\",\n ];\n if (config.headless !== false) {\n args.push(\"--headless=new\");\n }\n if (config.userDataDir) {\n args.push(`--user-data-dir=${config.userDataDir}`);\n }\n if (config.proxy) {\n const u = new URL(config.proxy);\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\n args.push(`--proxy-server=${serverUrl}`);\n }\n if (config.args) {\n args.push(...config.args);\n }\n const process = spawn(executablePath, args, {\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n detached: false,\n });\n // 等待 CDP 端点可用\n const wsEndpoint = `ws://127.0.0.1:${port}/devtools/browser`;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(`Chrome 启动超时,无法连接到 CDP 端口 ${port}`));\n }, 30000);\n const checkInterval = setInterval(async () => {\n try {\n const http = await import(\"node:http\");\n const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {\n if (res.statusCode === 200) {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n resolve();\n }\n });\n req.on(\"error\", () => {\n // 继续等待\n });\n req.end();\n } catch {\n // 继续等待\n }\n }, 500);\n process.on(\"error\", (err) => {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n reject(err);\n });\n });\n return { process, wsEndpoint };\n}\n\n\n/** 连接到已启动的 Chrome(通过 CDP 端口) */\nasync function connectToExistingChrome(port: number = 9222): Promise<string> {\n const wsEndpoint = `ws://127.0.0.1:${port}/devtools/browser`;\n try {\n const http = await import(\"node:http\");\n await new Promise<void>((resolve, reject) => {\n const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {\n if (res.statusCode === 200) {\n resolve();\n } else {\n reject(new Error(`无法连接到 Chrome CDP 端口 ${port}`));\n }\n });\n req.on(\"error\", (err) => {\n reject(new Error(`无法连接到 Chrome CDP 端口 ${port},请确保 Chrome 已启动并开启 --remote-debugging-port=${port}: ${err.message}`));\n });\n req.end();\n });\n return wsEndpoint;\n } catch (err) {\n throw err instanceof Error ? err : new Error(`无法连接到 Chrome CDP 端口 ${port},请确保 Chrome 已启动并开启 --remote-debugging-port=${port}`);\n }\n}\n\n\n/** 通过 CDP 连接到 Chrome 并返回 Browser 对象 */\nexport async function connectBrowser(config: CDPConfig): Promise<{ browser: Browser; cleanup?: () => Promise<void> }> {\n let wsEndpoint: string;\n let chromeProcess: ChildProcess | undefined;\n if (config.useExisting) {\n wsEndpoint = await connectToExistingChrome(config.port);\n } else {\n const result = await launchChromeWithCDP(config);\n wsEndpoint = result.wsEndpoint;\n chromeProcess = result.process;\n }\n const connectOptions: ConnectOptions = {\n browserWSEndpoint: wsEndpoint,\n };\n const browser = await puppeteerCore.connect(connectOptions);\n const cleanup = chromeProcess\n ? async () => {\n await browser.disconnect();\n chromeProcess?.kill();\n }\n : async () => {\n await browser.disconnect();\n };\n return { browser, cleanup };\n}\n\n\n/** 通过 CDP 启动 Chrome 并返回 Browser 对象(使用 executablePath) */\nexport async function launchBrowser(config: CDPConfig & { executablePath: string }): Promise<{ browser: Browser; cleanup: () => Promise<void> }> {\n const port = config.port ?? 9222;\n const args: string[] = [\n `--remote-debugging-port=${port}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-web-security\",\n \"--disable-features=IsolateOrigins,site-per-process\",\n \"--disable-site-isolation-trials\",\n \"--disable-infobars\",\n ];\n if (config.headless !== false) {\n args.push(\"--headless=new\");\n }\n if (config.userDataDir) {\n args.push(`--user-data-dir=${config.userDataDir}`);\n }\n if (config.proxy) {\n const u = new URL(config.proxy);\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\n args.push(`--proxy-server=${serverUrl}`);\n }\n if (config.args) {\n args.push(...config.args);\n }\n const launchOptions: LaunchOptions = {\n executablePath: config.executablePath,\n headless: config.headless !== false,\n args,\n userDataDir: config.userDataDir,\n ignoreDefaultArgs: [\"--enable-automation\"],\n };\n const browser = await puppeteerCore.launch(launchOptions);\n const cleanup = async () => {\n await browser.close();\n };\n return { browser, cleanup };\n}\n\n\n/** 导出类型供外部使用 */\nexport type { Browser, Page } from \"puppeteer-core\";\n","/**\r\n * 系统内部统一的 Feed Item 定义\r\n * 插件 → Normalizer → RSS Generator\r\n * 自包含:携带 sourceRef 后,入库 / Signal 投递等无需再单独传 ref。\r\n */\r\n\r\n/** 单语种译文字段(key 为 BCP 47,如 zh-CN、en) */\r\nexport interface ItemTranslationFields {\r\n title?: string;\r\n summary?: string;\r\n content?: string;\r\n}\r\n\r\n/** 带可选 translations 的条目视图(FeedItem 或 DB 行 + translations 等) */\r\nexport interface ItemWithOptionalTranslations {\r\n title: string;\r\n summary?: string;\r\n content?: string;\r\n translations?: Record<string, ItemTranslationFields>;\r\n}\r\n\r\n/**\r\n * 根据 lng 取条目的「有效」标题/摘要/正文:有 translations[lng] 则优先用译文,否则用原文。\r\n * 路由层传 lng 时用此结果生成 RSS 或 API 响应。\r\n */\r\nexport function getEffectiveItemFields(\r\n item: ItemWithOptionalTranslations,\r\n lng?: string | null,\r\n): { title: string; summary: string; content: string } {\r\n const raw = lng && lng !== \"\" ? item.translations?.[lng] : undefined;\r\n const t = raw && typeof raw === \"object\" ? raw : undefined;\r\n return {\r\n title: (t?.title != null && t.title !== \"\" ? t.title : item.title) ?? \"\",\r\n summary: (t?.summary != null && t.summary !== \"\" ? t.summary : item.summary) ?? \"\",\r\n content: (t?.content != null && t.content !== \"\" ? t.content : item.content) ?? \"\",\r\n };\r\n}\r\n\r\n/** 将发布时间转为 ISO 字符串供入库/序列化;Invalid Date 返回 null(避免 toISOString 抛 RangeError) */\r\nexport function pubDateToIsoOrNull(pubDate: unknown): string | null {\r\n if (pubDate == null) return null;\r\n if (pubDate instanceof Date) {\r\n const ms = pubDate.getTime();\r\n return Number.isNaN(ms) ? null : pubDate.toISOString();\r\n }\r\n if (typeof pubDate === \"string\") {\r\n const s = pubDate.trim();\r\n return s || null;\r\n }\r\n return null;\r\n}\r\n\r\n/** 将 author 规范为 string[],兼容 string 输入(插件等) */\r\nexport function normalizeAuthor(author: string | string[] | null | undefined): string[] | undefined {\r\n if (author == null) return undefined;\r\n if (Array.isArray(author)) return author.filter((s) => typeof s === \"string\" && s.trim()).map((s) => s.trim());\r\n const s = String(author).trim();\r\n return s ? [s] : undefined;\r\n}\r\n\r\n/** 将 author 转为显示用字符串(逗号分隔) */\r\nexport function authorToDisplay(author: string | string[] | null | undefined): string {\r\n const arr = normalizeAuthor(author);\r\n return arr?.join(\", \") ?? \"\";\r\n}\r\n\r\nexport interface FeedItem {\r\n /** 全局唯一标识,link 或稳定 hash */\r\n guid: string;\r\n /** 标题 */\r\n title: string;\r\n /** 原文链接 */\r\n link: string;\r\n /** 发布时间 */\r\n pubDate: Date;\r\n /** 作者列表 */\r\n author?: string[];\r\n /** 简要描述(纯文本,适合 RSS description) */\r\n summary?: string;\r\n /** 详情正文(输出到 RSS description) */\r\n content?: string;\r\n /** 条目配图 URL,输出为 RSS 2.0 <enclosure type=\"image/...\"> */\r\n imageUrl?: string;\r\n /** Frontend-compatible cover field; equivalent to imageUrl / cover_img. */\n coverImg?: string;\n /**\r\n * 封面图:支持 http(s) URL、data URL,或裸 base64(可规范为 data:image/jpeg;base64,...)。\r\n * Gateway JSON 字段名 `cover_img`。\r\n */\r\n cover_img?: string;\r\n /** RSS 来源分类(直接来自 feed 的 <category> 字段) */\r\n categories?: string[];\r\n /** 系统 / pipeline 生成的标签(从用户管理的标签库中匹配) */\r\n tags?: string[];\r\n /** 信源标识(列表页 URL 或 imap 等),入库与按 ref 筛选用;设后则 upsertItems 等无需再传 ref */\r\n sourceRef?: string;\r\n /**\r\n * 多语种译文。key 为 BCP 47(如 zh-CN、en),路由支持 lng 参数时可据此返回对应译文。\r\n * 由 pipeline(如 translator)写入。\r\n */\r\n translations?: Record<string, ItemTranslationFields>;\r\n /**\r\n * 扩展字段,给插件留后门。\r\n * 框架保留键:`_rssanyPipelineDrop` 为 true 表示 pipeline 质量过滤丢弃,feeder 会删库并移出 RSS。\r\n */\r\n extra?: Record<string, unknown>;\r\n }\r\n\r\n/** Pipeline 质量过滤等步骤标记的丢弃键(写入 item.extra) */\r\nexport const PIPELINE_DROP_EXTRA_KEY = \"_rssanyPipelineDrop\";\r\n\r\nexport function markPipelineDrop(item: FeedItem): FeedItem {\r\n item.extra = { ...item.extra, [PIPELINE_DROP_EXTRA_KEY]: true };\r\n return item;\r\n}\r\n\r\nexport function isPipelineDroppedItem(item: FeedItem): boolean {\r\n return item.extra?.[PIPELINE_DROP_EXTRA_KEY] === true;\r\n}","/**\r\n * 信源 ref 入库与查询统一键(规范化存储):\r\n * - http(s):scheme、host(含端口)小写;pathname 小写并去掉非根路径末尾 `/`;query、hash 保持原样(避免破坏带大小写的查询参数)。\r\n * - 非 http(s):trim 后全串小写。\r\n */\r\nexport function canonicalHttpSourceRef(ref: string): string {\r\n const t = ref.trim();\r\n if (!t) return t;\r\n if (!/^https?:\\/\\//i.test(t)) return t.toLowerCase();\r\n try {\r\n const u = new URL(t);\r\n const protocol = u.protocol.toLowerCase();\r\n const host = u.host.toLowerCase();\r\n let path = u.pathname;\r\n if (path.length > 1 && path.endsWith(\"/\")) {\r\n path = path.slice(0, -1);\r\n }\r\n path = path.toLowerCase();\r\n return `${protocol}//${host}${path}${u.search}${u.hash}`;\r\n } catch {\r\n return t.toLowerCase();\r\n }\r\n}\r\n\r\nfunction maxIso(a: string | null, b: string | null): string | null {\r\n if (!a) return b;\r\n if (!b) return a;\r\n return a >= b ? a : b;\r\n}\r\n\r\n/**\r\n * 将 GROUP BY source_url 的统计按规范化键合并(兼容迁移前旧数据或异常重复写法)。\r\n */\r\nexport function mergeSourceStatsRows(\r\n rows: { source_url: string; count: number; count_7d: number; latest_at: string | null }[],\r\n): { source_url: string; count: number; count_7d: number; latest_at: string | null }[] {\r\n const map = new Map<string, { count: number; count_7d: number; latest_at: string | null }>();\r\n for (const row of rows) {\r\n const k = canonicalHttpSourceRef(row.source_url);\r\n const prev = map.get(k);\r\n const count7 = row.count_7d ?? 0;\r\n if (!prev) {\r\n map.set(k, { count: row.count, count_7d: count7, latest_at: row.latest_at });\r\n } else {\r\n map.set(k, {\r\n count: prev.count + row.count,\r\n count_7d: prev.count_7d + count7,\r\n latest_at: maxIso(prev.latest_at, row.latest_at),\r\n });\r\n }\r\n }\r\n return [...map.entries()]\r\n .map(([source_url, v]) => ({ source_url, count: v.count, count_7d: v.count_7d, latest_at: v.latest_at }))\r\n .sort((a, b) => b.count - a.count);\r\n}\r\n","// 解析 npm 包根或仓库根(含 app/plugins/、app/statics/、app/webui/build/);开发时来自 app/,打包后为 dist/ 的上一级\r\n\r\nimport { basename, dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\nconst __dir = dirname(fileURLToPath(import.meta.url));\r\nconst base = basename(__dir);\r\n\r\nexport const PACKAGE_ROOT =\r\n base === \"app\" || base === \"dist\" ? join(__dir, \"..\") : __dir;\r\n","// 路径配置:集中管理所有运行时路径,区分项目文件与用户数据\r\n\r\nimport { mkdir, rename, access, copyFile, writeFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\nimport { logger } from \"../core/logger/index.js\";\r\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\r\n\r\nconst envUserDir = process.env.RSSANY_USER_DIR?.trim();\r\n\r\n/** 用户数据根目录:~/.rssany/(或 RSSANY_USER_DIR);不纳入版本管理 */\r\nexport const USER_DIR = envUserDir && envUserDir.length > 0 ? envUserDir : join(homedir(), \".rssany\");\r\n\r\n/** SQLite 数据库目录:.rssany/data/ */\r\nexport const DATA_DIR = join(USER_DIR, \"data\");\r\n\r\n/** 缓存目录:.rssany/cache/(fetched、extracted、feeds、domains、browser_data 等子目录);环境变量 CACHE_DIR 可覆盖 */\r\nexport const CACHE_DIR = process.env.CACHE_DIR ?? join(USER_DIR, \"cache\");\r\n\r\n/** 站点配置文件:.rssany/sites.json */\r\nexport const SITES_CONFIG_PATH = join(USER_DIR, \"sites.json\");\r\n\r\n/** 爬虫配置:.rssany/sources.json(扁平信源列表,供 scheduler 使用) */\r\nexport const SOURCES_CONFIG_PATH = join(USER_DIR, \"sources.json\");\r\n\r\n/** 系统标签配置:.rssany/tags.json(供 pipeline tagger 使用) */\r\nexport const TAGS_CONFIG_PATH = join(USER_DIR, \"tags.json\");\r\n\r\n/** 全局配置:.rssany/config.json(pipeline 等) */\r\nexport const CONFIG_PATH = join(USER_DIR, \"config.json\");\r\n\r\n/** @deprecated 仅用于迁移:若存在 .rssany/subscriptions.json 且无 sources.json 则迁移为 sources.json */\r\nconst LEGACY_SUBSCRIPTIONS_PATH = join(USER_DIR, \"subscriptions.json\");\r\n\r\n/** 内置信源插件目录:app/plugins/builtin/(随包发布 *.rssany.js) */\r\nexport const BUILTIN_PLUGINS_DIR = join(PACKAGE_ROOT, \"app/plugins/builtin\");\r\n\r\n/** 用户插件目录:.rssany/plugins/(扁平 *.rssany.js / *.rssany.ts) */\r\nexport const USER_PLUGINS_DIR = join(USER_DIR, \"plugins\");\r\n\r\n/** 限定 .rssany 下动态 import 的模块类型,避免 Node 一直向上解析到用户主目录的 package.json 并触发 MODULE_TYPELESS_PACKAGE_JSON */\r\nconst USER_DIR_PACKAGE_JSON = join(USER_DIR, \"package.json\");\r\nconst USER_DIR_PACKAGE_JSON_MINIMAL = `${JSON.stringify({ type: \"module\", private: true, description: \"RssAny user data root; marks plugins as ESM for Node\" })}\\n`;\r\n\r\n/** 管理页「添加插件」所用模板(非 Site,不参与加载) */\r\nexport const PLUGIN_SITE_TEMPLATE_PATH = join(PACKAGE_ROOT, \"app/plugins/site.rssany.js\");\r\n\r\nasync function pathExists(p: string): Promise<boolean> {\r\n try {\r\n await access(p);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nasync function migrateFile(from: string, to: string): Promise<void> {\r\n if (!(await pathExists(from))) return;\r\n if (await pathExists(to)) return;\r\n try {\r\n await rename(from, to);\r\n logger.info(\"config\", \"配置已迁移\", { from, to });\r\n } catch (err) {\r\n logger.warn(\"config\", \"配置迁移失败\", { from, to, err: err instanceof Error ? err.message : String(err) });\r\n }\r\n}\r\n\r\n/** 包内首次初始化用的默认数据(`app/init/`;发布包需列入 package.json `files`) */\r\nconst INIT_DATA_DIR = join(PACKAGE_ROOT, \"app/init\");\r\nconst EXAMPLE_SOURCES = join(INIT_DATA_DIR, \"sources.json\");\r\nconst EXAMPLE_CONFIG = join(INIT_DATA_DIR, \"config.json\");\r\n\r\n/**\r\n * 若用户目录尚无 `sources.json` / `config.json`,则从包内 `app/init/sources.json`、`app/init/config.json` 复制(不覆盖已有文件)。\r\n */\r\nasync function seedExampleConfigsIfMissing(): Promise<void> {\r\n if (!(await pathExists(SOURCES_CONFIG_PATH)) && (await pathExists(EXAMPLE_SOURCES))) {\r\n try {\r\n await copyFile(EXAMPLE_SOURCES, SOURCES_CONFIG_PATH);\r\n logger.info(\"config\", \"已写入默认信源示例\", { path: SOURCES_CONFIG_PATH });\r\n } catch (err) {\r\n logger.warn(\"config\", \"写入 sources 示例失败\", {\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n if (!(await pathExists(CONFIG_PATH)) && (await pathExists(EXAMPLE_CONFIG))) {\r\n try {\r\n await copyFile(EXAMPLE_CONFIG, CONFIG_PATH);\r\n logger.info(\"config\", \"已写入默认配置示例\", { path: CONFIG_PATH });\r\n } catch (err) {\r\n logger.warn(\"config\", \"写入 config 示例失败\", {\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n}\r\n\r\n/** 若尚无文件则写入最小 package.json,使用户插件目录下的 *.rssany.js 被明确视为 ESM */\r\nasync function ensureUserDirPackageJsonForPlugins(): Promise<void> {\r\n if (await pathExists(USER_DIR_PACKAGE_JSON)) return;\r\n try {\r\n await writeFile(USER_DIR_PACKAGE_JSON, USER_DIR_PACKAGE_JSON_MINIMAL, \"utf-8\");\r\n logger.info(\"config\", \"已写入 .rssany/package.json(type: module,消除插件 ESM 歧义)\", { path: USER_DIR_PACKAGE_JSON });\r\n } catch (err) {\r\n logger.warn(\"config\", \"写入 .rssany/package.json 失败\", {\r\n path: USER_DIR_PACKAGE_JSON,\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n}\r\n\r\n/** 初始化用户数据目录;若缺少 sources.json / config.json 则从包内示例复制 */\r\nexport async function initUserDir(): Promise<void> {\r\n await mkdir(USER_DIR, { recursive: true });\r\n await mkdir(DATA_DIR, { recursive: true });\r\n await mkdir(CACHE_DIR, { recursive: true });\r\n await mkdir(USER_PLUGINS_DIR, { recursive: true });\r\n await ensureUserDirPackageJsonForPlugins();\r\n await seedExampleConfigsIfMissing();\r\n if (!(await pathExists(SOURCES_CONFIG_PATH)) && (await pathExists(LEGACY_SUBSCRIPTIONS_PATH))) {\r\n await migrateFile(LEGACY_SUBSCRIPTIONS_PATH, SOURCES_CONFIG_PATH);\r\n }\r\n}\r\n","// SQLite 主库:建表 schema、FTS、FeedItem CRUD 与运行日志库\n// 使用 Node.js 20+ 内置 node:sqlite 模块 (DatabaseSync - 同步API)\n\nimport { DatabaseSync } from \"node:sqlite\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync, openSync, closeSync, writeSync, unlinkSync, readFileSync } from \"node:fs\";\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { normalizeAuthor, pubDateToIsoOrNull } from \"../types/feedItem.js\";\nimport { canonicalHttpSourceRef } from \"../utils/httpSourceRef.js\";\nimport type { LogEntry } from \"../core/logger/types.js\";\nimport { DATA_DIR, TAGS_CONFIG_PATH } from \"../config/paths.js\";\n\n/** 主库日志模式:默认 WAL;环境变量 RSSANY_DB_JOURNAL=delete 时使用 DELETE */\nconst MAIN_DB_JOURNAL = (process.env.RSSANY_DB_JOURNAL ?? \"wal\").toLowerCase() === \"delete\" ? \"DELETE\" : \"WAL\";\n\nlet _db: DatabaseSync | null = null;\n\n/** 单写者锁:串行化 updateItemContent/upsertItems 等写库 */\nlet _writeLock: Promise<void> = Promise.resolve();\n\n/** 单进程锁文件路径:与 rssany.db 同目录,内容为 PID */\nconst MAIN_DB_LOCK_PATH = join(DATA_DIR, \"rssany.db.lock\");\n\n/** 将损坏类错误写到 stderr,便于排查 */\nfunction logCorruptDiagnostic(operation: string, err: unknown): void {\n const msg = err instanceof Error ? err.message : String(err);\n const lines = [\n \"[rssany db] 数据库可能损坏或并发冲突\",\n ` 操作: ${operation}`,\n ` 错误: ${msg}`,\n \" 常见原因:\",\n \" 1. 多进程同时打开同一库(例如 tsx --watch 与另一实例同时写)\",\n \" 2. 异常退出后 WAL 未正常 checkpoint\",\n \" 3. 磁盘/杀毒/同步盘导致文件不完整\",\n \" 建议:\",\n \" - 避免多实例同时写库;开发时慎用 --watch 与后台任务并行\",\n \" - 可尝试 RSSANY_DB_JOURNAL=delete 使用 DELETE 模式降低多文件依赖\",\n \" - 备份后删除 .rssany/data/rssany.db 及同目录 -wal、-shm、rssany.db.lock 再启动\",\n ];\n process.stderr.write(lines.join(\"\\n\") + \"\\n\");\n}\n\n/** 独占创建锁文件 */\nfunction acquireDbLock(dbDir: string): void {\n const lockPath = join(dbDir, \"rssany.db.lock\");\n const pid = process.pid;\n const tryCreate = (): void => {\n try {\n const fd = openSync(lockPath, \"wx\");\n writeSync(fd, String(pid), 0, \"utf8\");\n closeSync(fd);\n return;\n } catch (e: unknown) {\n const code = (e as NodeJS.ErrnoException)?.code;\n if (code !== \"EEXIST\") throw e;\n }\n if (!existsSync(lockPath)) {\n tryCreate();\n return;\n }\n let oldPid: number | null = null;\n try {\n const buf = readFileSync(lockPath, \"utf8\");\n const n = parseInt(buf.trim(), 10);\n if (!Number.isNaN(n)) oldPid = n;\n } catch {\n /* 读锁文件失败则重试创建 */\n }\n if (oldPid !== null && oldPid !== pid) {\n const stillRunning = ((): boolean => {\n try {\n process.kill(oldPid!, 0);\n return true;\n } catch {\n return false;\n }\n })();\n if (stillRunning) {\n throw new Error(\n `数据库已被其他进程占用(PID ${oldPid})。请勿多开实例;若确认无其他进程,可删除 ${lockPath} 后重试(常见于 tsx --watch 未退出)`,\n );\n }\n }\n try {\n unlinkSync(lockPath);\n } catch {\n /* ignore */\n }\n tryCreate();\n };\n tryCreate();\n}\n\n/** 进程退出时删除锁文件 */\nfunction releaseDbLock(): void {\n if (!existsSync(MAIN_DB_LOCK_PATH)) return;\n try {\n unlinkSync(MAIN_DB_LOCK_PATH);\n } catch {\n /* ignore */\n }\n}\n\nexport function withWriteLock<T>(fn: () => Promise<T>): Promise<T> {\n const prev = _writeLock;\n let resolveOut!: (v: T) => void;\n let rejectOut!: (e: unknown) => void;\n const out = new Promise<T>((res, rej) => {\n resolveOut = res;\n rejectOut = rej;\n });\n _writeLock = prev\n .then(() => fn())\n .then(\n (v) => {\n resolveOut(v);\n },\n (e: unknown) => {\n if (isCorruptError(e)) {\n logCorruptDiagnostic(\"withWriteLock 内 updateItemContent/upsertItems 等\", e);\n }\n rejectOut(e);\n throw e;\n },\n );\n return out;\n}\n\n/** 仅英文「日期当标题」的粗判 */\nconst DATE_ONLY_TITLE_RE =\n /^(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)\\b[\\s\\d,./-]*(?:st|nd|rd|th)?[\\s\\d,./-]*$/i;\n\nfunction normalizeText(text: string | null | undefined): string {\n return (text ?? \"\").replace(/\\s+/g, \" \").trim();\n}\n\nfunction isDateOnlyTitle(title: string | null | undefined): boolean {\n const normalized = normalizeText(title);\n if (!normalized) return false;\n return DATE_ONLY_TITLE_RE.test(normalized);\n}\n\nfunction toMs(input: string | null | undefined): number | null {\n if (!input) return null;\n const ms = Date.parse(input);\n return Number.isNaN(ms) ? null : ms;\n}\n\n/** 将 DB 中 author 列解析为 string[] */\nexport function parseAuthorFromDb(raw: string | null | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n try {\n const p = JSON.parse(raw) as unknown;\n if (Array.isArray(p)) return p.filter((s) => typeof s === \"string\").map((s) => String(s).trim()).filter(Boolean);\n return [String(p).trim()];\n } catch {\n return [raw.trim()];\n }\n}\n\n/** 将原始行转为 DbItem */\nfunction toDbItem(row: Record<string, unknown>): DbItem {\n const author = parseAuthorFromDb(row.author as string) ?? null;\n const parseJsonArr = (v: unknown): string[] | null => {\n try {\n return v ? (JSON.parse(v as string) as string[]) : null;\n } catch {\n return null;\n }\n };\n const tags = parseJsonArr(row.tags);\n let translations: Record<string, { title?: string; summary?: string; content?: string }> | null = null;\n try {\n if (row.translations && typeof row.translations === \"string\") {\n const p = JSON.parse(row.translations) as unknown;\n if (p && typeof p === \"object\") translations = p as Record<string, { title?: string; summary?: string; content?: string }>;\n }\n } catch {\n /* ignore */\n }\n return { ...row, author, tags, translations } as DbItem;\n}\n\nfunction mapRowsToDbItems(rows: Record<string, unknown>[]): DbItem[] {\n return rows.map(toDbItem);\n}\n\n/** 判断是否 SQLite 库损坏 */\nfunction isCorruptError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes(\"SQLITE_CORRUPT\") || msg.includes(\"database disk image is malformed\");\n}\n\n/** 获取主库连接 */\nexport async function getDb(): Promise<DatabaseSync> {\n if (_db) return _db;\n const dbPath = join(DATA_DIR, \"rssany.db\");\n await mkdir(DATA_DIR, { recursive: true });\n acquireDbLock(DATA_DIR);\n try {\n _db = new DatabaseSync(dbPath);\n _db.exec(`PRAGMA journal_mode = ${MAIN_DB_JOURNAL}`);\n _db.exec(\"PRAGMA synchronous = NORMAL\");\n initSchema(_db);\n return _db;\n } catch (err: unknown) {\n releaseDbLock();\n if (_db) {\n try {\n _db.close();\n } catch {\n /* ignore */\n }\n _db = null;\n }\n if (isCorruptError(err)) {\n logCorruptDiagnostic(\"打开/初始化主库 (getDb)\", err);\n }\n throw err;\n }\n}\n\n/** 执行 PRAGMA integrity_check */\nexport async function runIntegrityCheck(): Promise<string> {\n const db = await getDb();\n try {\n const result = db.prepare(\"PRAGMA integrity_check\").get() as { integrity_check: string } | undefined;\n return result?.integrity_check ?? \"unknown\";\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return `integrity_check 执行失败: ${msg}`;\n }\n}\n\n/** 运行日志库路径 */\nconst LOGS_DB_PATH = join(DATA_DIR, \"logs.db\");\n\nlet _logsDb: DatabaseSync | null = null;\n\nfunction initLogsSchema(db: DatabaseSync): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n level TEXT NOT NULL,\n category TEXT NOT NULL,\n message TEXT NOT NULL,\n payload TEXT,\n source_url TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_logs_level_created ON logs(level, created_at);\n CREATE INDEX IF NOT EXISTS idx_logs_source_created ON logs(source_url, created_at);\n `);\n}\n\n/** 获取运行日志库 */\nexport async function getLogsDb(): Promise<DatabaseSync> {\n if (_logsDb) return _logsDb;\n await mkdir(DATA_DIR, { recursive: true });\n _logsDb = new DatabaseSync(LOGS_DB_PATH);\n _logsDb.exec(\"PRAGMA journal_mode = WAL\");\n _logsDb.exec(\"PRAGMA synchronous = NORMAL\");\n initLogsSchema(_logsDb);\n return _logsDb;\n}\n\n/** 初始化主库 schema */\nfunction initSchema(db: DatabaseSync): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS items (\n id TEXT PRIMARY KEY,\n url TEXT UNIQUE NOT NULL,\n source_url TEXT NOT NULL,\n title TEXT,\n author TEXT,\n summary TEXT,\n content TEXT,\n image_url TEXT,\n tags TEXT,\n translations TEXT,\n pub_date TEXT,\n fetched_at TEXT NOT NULL,\n pushed_at TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_items_source ON items(source_url);\n CREATE INDEX IF NOT EXISTS idx_items_fetched ON items(fetched_at);\n CREATE INDEX IF NOT EXISTS idx_items_pushed ON items(pushed_at);\n `);\n\n db.exec(`\n CREATE VIEW IF NOT EXISTS items_fts_src AS\n SELECT rowid, title, summary, content,\n json_extract(translations, '$.\"zh-CN\".title') AS title_zh,\n json_extract(translations, '$.\"zh-CN\".summary') AS summary_zh,\n json_extract(translations, '$.\"zh-CN\".content') AS content_zh\n FROM items;\n CREATE VIRTUAL TABLE IF NOT EXISTS items_fts USING fts5(\n title, summary, content, title_zh, summary_zh, content_zh,\n content='items_fts_src',\n content_rowid='rowid'\n );\n CREATE TRIGGER IF NOT EXISTS items_fts_after_insert AFTER INSERT ON items\n BEGIN\n INSERT INTO items_fts(rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n NEW.rowid, NEW.title, NEW.summary, NEW.content,\n json_extract(NEW.translations, '$.\"zh-CN\".title'),\n json_extract(NEW.translations, '$.\"zh-CN\".summary'),\n json_extract(NEW.translations, '$.\"zh-CN\".content')\n );\n END;\n CREATE TRIGGER IF NOT EXISTS items_fts_after_update AFTER UPDATE ON items\n BEGIN\n INSERT INTO items_fts(items_fts, rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n 'delete', OLD.rowid, OLD.title, OLD.summary, OLD.content,\n json_extract(OLD.translations, '$.\"zh-CN\".title'),\n json_extract(OLD.translations, '$.\"zh-CN\".summary'),\n json_extract(OLD.translations, '$.\"zh-CN\".content')\n );\n INSERT INTO items_fts(rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n NEW.rowid, NEW.title, NEW.summary, NEW.content,\n json_extract(NEW.translations, '$.\"zh-CN\".title'),\n json_extract(NEW.translations, '$.\"zh-CN\".summary'),\n json_extract(NEW.translations, '$.\"zh-CN\".content')\n );\n END;\n CREATE TRIGGER IF NOT EXISTS items_fts_after_delete AFTER DELETE ON items\n BEGIN\n INSERT INTO items_fts(items_fts, rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n 'delete', OLD.rowid, OLD.title, OLD.summary, OLD.content,\n json_extract(OLD.translations, '$.\"zh-CN\".title'),\n json_extract(OLD.translations, '$.\"zh-CN\".summary'),\n json_extract(OLD.translations, '$.\"zh-CN\".content')\n );\n END;\n `);\n\n // 旧库迁移:若无 image_url 列则追加\n try {\n const cols = db.prepare(\"PRAGMA table_info(items)\").all().map((r: Record<string, unknown>) => r.name as string);\n if (!cols.includes(\"image_url\")) {\n db.exec(\"ALTER TABLE items ADD COLUMN image_url TEXT\");\n }\n } catch {\n /* ignore */\n }\n\n migrateItemsSourceUrlIfNeeded(db);\n}\n\n/** 规范化 source_url */\nfunction migrateItemsSourceUrlIfNeeded(db: DatabaseSync): void {\n const pragmaResult = db.exec(\"PRAGMA user_version\") as unknown as { values?: unknown[][] } | undefined;\n const v = (pragmaResult?.values?.[0]?.[0] as number) ?? 0;\n if (v >= 2) return;\n const rows = db.prepare(\"SELECT rowid, source_url FROM items\").all() as { rowid: number; source_url: string }[];\n const updateStmt = db.prepare(\"UPDATE items SET source_url = @next WHERE rowid = @rowid\");\n db.exec(\"BEGIN TRANSACTION\");\n try {\n for (const r of rows) {\n const next = canonicalHttpSourceRef(r.source_url);\n if (next !== r.source_url) {\n updateStmt.run({ next, rowid: r.rowid });\n }\n }\n db.exec(\"PRAGMA user_version = 2\");\n db.exec(\"COMMIT\");\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n}\n\n/** 批量插入或忽略重复 */\nexport async function upsertItems(items: FeedItem[], sourceUrlOverride?: string): Promise<{ newCount: number; newIds: Set<string> }> {\n if (items.length === 0) return { newCount: 0, newIds: new Set() };\n const raw = (sourceUrlOverride ?? items[0].sourceRef)?.trim();\n if (!raw) {\n throw new Error(\"upsertItems: 每条 item 须有 sourceRef,或传入 sourceUrlOverride\");\n }\n const sourceUrl = canonicalHttpSourceRef(raw);\n return withWriteLock(async () => {\n const db = await getDb();\n const now = new Date().toISOString();\n let newCount = 0;\n const newIds = new Set<string>();\n\n const insertStmt = db.prepare(`\n INSERT OR IGNORE INTO items (id, url, source_url, title, author, summary, image_url, tags, pub_date, fetched_at)\n VALUES (@id, @url, @sourceUrl, @title, @author, @summary, @imageUrl, @tags, @pubDate, @fetchedAt)\n `);\n const selectExistingStmt = db.prepare(`\n SELECT title, author, summary, image_url, pub_date, fetched_at\n FROM items WHERE id = @id\n `);\n const updateStmt = db.prepare(`\n UPDATE items SET title = @title, author = @author, summary = @summary,\n image_url = @imageUrl, pub_date = @pubDate, fetched_at = @fetchedAt\n WHERE id = @id\n `);\n\n for (const item of items) {\n const nextTitle = normalizeText(item.title) || null;\n const nextSummary = normalizeText(item.summary) || null;\n const nextAuthorArr = normalizeAuthor(item.author);\n const nextAuthor = nextAuthorArr?.length ? JSON.stringify(nextAuthorArr) : null;\n const nextPubDate = pubDateToIsoOrNull(item.pubDate);\n const nextTags = item.tags?.length ? JSON.stringify(item.tags) : null;\n const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;\n const nextImageUrl = typeof rawImageUrl === \"string\" && rawImageUrl.trim() ? rawImageUrl.trim() : null;\n\n const info = insertStmt.run({\n id: item.guid,\n url: item.link,\n sourceUrl,\n title: nextTitle,\n author: nextAuthor,\n summary: nextSummary,\n imageUrl: nextImageUrl,\n tags: nextTags,\n pubDate: nextPubDate,\n fetchedAt: now,\n });\n newCount += Number(info.changes);\n if (info.changes > 0) {\n newIds.add(item.guid);\n continue;\n }\n\n const existing = selectExistingStmt.get({ id: item.guid }) as {\n title: string | null;\n author: string | null;\n summary: string | null;\n image_url: string | null;\n pub_date: string | null;\n fetched_at: string | null;\n } | undefined;\n if (!existing) continue;\n\n const shouldRepairTitle =\n !!nextTitle &&\n !isDateOnlyTitle(nextTitle) &&\n (isDateOnlyTitle(existing.title) || !normalizeText(existing.title));\n const existingSummaryText = normalizeText(existing.summary ?? \"\");\n const shouldClearDuplicatedSummary =\n nextSummary == null && !!nextTitle && existingSummaryText === nextTitle;\n const shouldRepairSummary =\n (!!nextSummary &&\n (existingSummaryText.length < nextSummary.length ||\n /!\\[[^\\]]*\\]\\([^)]*\\)/.test(existingSummaryText))) ||\n shouldClearDuplicatedSummary;\n const shouldRepairImageUrl = !!nextImageUrl && !existing.image_url?.trim();\n const existingAuthorArr = parseAuthorFromDb(existing.author);\n const shouldRepairAuthor = !!nextAuthorArr?.length && !existingAuthorArr?.length;\n\n const existingPubDateMs = toMs(existing.pub_date);\n const existingFetchedAtMs = toMs(existing.fetched_at);\n const nextPubDateMs = toMs(nextPubDate);\n const existingPubDateLooksFallback =\n existingPubDateMs != null &&\n existingFetchedAtMs != null &&\n Math.abs(existingPubDateMs - existingFetchedAtMs) <= 5 * 60 * 1000;\n const shouldRepairPubDate =\n nextPubDateMs != null &&\n (existingPubDateMs == null ||\n (existingPubDateLooksFallback && nextPubDateMs < existingPubDateMs - 24 * 60 * 60 * 1000));\n\n if (!(shouldRepairTitle || shouldRepairSummary || shouldRepairImageUrl || shouldRepairAuthor || shouldRepairPubDate)) {\n continue;\n }\n\n updateStmt.run({\n id: item.guid,\n title: shouldRepairTitle ? nextTitle : existing.title,\n author: shouldRepairAuthor ? nextAuthor : (existing.author ?? null),\n summary: shouldClearDuplicatedSummary ? null : (shouldRepairSummary ? nextSummary : existing.summary),\n imageUrl: shouldRepairImageUrl ? nextImageUrl : (existing.image_url ?? null),\n pubDate: shouldRepairPubDate ? nextPubDate : existing.pub_date,\n fetchedAt: now,\n });\n }\n return { newCount, newIds };\n });\n}\n\n/** 查询已存在的 guid 集合 */\nexport async function getExistingIds(guids: string[]): Promise<Set<string>> {\n if (guids.length === 0) return new Set();\n const db = await getDb();\n const placeholders = guids.map(() => \"?\").join(\",\");\n const rows = db.prepare(`SELECT id FROM items WHERE id IN (${placeholders})`).all(...guids) as { id: string }[];\n return new Set(rows.map((r) => r.id));\n}\n\n/** 按 guid 更新正文 */\nexport async function updateItemContent(item: FeedItem): Promise<void> {\n return withWriteLock(async () => {\n const db = await getDb();\n const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;\n const nextImageUrl = typeof rawImageUrl === \"string\" && rawImageUrl.trim() ? rawImageUrl.trim() : null;\n db.prepare(`\n UPDATE items SET\n content = COALESCE(content, @content),\n image_url = COALESCE(@imageUrl, image_url),\n author = COALESCE(@author, author),\n pub_date = COALESCE(@pubDate, pub_date),\n tags = @tags,\n translations = COALESCE(@translations, translations)\n WHERE id = @id\n `).run({\n id: item.guid,\n content: item.content ?? null,\n imageUrl: nextImageUrl,\n author: (() => {\n const arr = normalizeAuthor(item.author);\n return arr?.length ? JSON.stringify(arr) : null;\n })(),\n pubDate: pubDateToIsoOrNull(item.pubDate),\n tags: item.tags?.length ? JSON.stringify(item.tags) : null,\n translations: item.translations && Object.keys(item.translations).length > 0 ? JSON.stringify(item.translations) : null,\n });\n });\n}\n\n/** 按 id(guid)取单条 */\nexport async function getItemById(id: string): Promise<DbItem | null> {\n const db = await getDb();\n const row = db.prepare(\"SELECT * FROM items WHERE id = @id\").get({ id });\n if (!row) return null;\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(row)) obj[k] = v;\n return toDbItem(obj);\n}\n\n/** 单信源最近条目 */\nexport async function queryItemsBySource(sourceUrl: string, limit = 50, since?: Date): Promise<DbItem[]> {\n const key = canonicalHttpSourceRef(sourceUrl);\n if (!key) return [];\n const db = await getDb();\n const sinceClause = since ? \"AND COALESCE(pub_date, fetched_at) >= @since\" : \"\";\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE source_url = @sourceUrl ${sinceClause}\n ORDER BY COALESCE(pub_date, fetched_at) DESC\n LIMIT ${limit}\n `)\n .all({ sourceUrl: key, since: since?.toISOString() ?? null });\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 多条件查询 */\nexport async function queryItems(opts: {\n sourceUrl?: string;\n sourceUrls?: string[];\n author?: string;\n q?: string;\n tags?: string[];\n limit?: number;\n offset?: number;\n since?: Date;\n until?: Date;\n}): Promise<{ items: DbItem[]; total: number }> {\n const db = await getDb();\n const { sourceUrl, sourceUrls, author, q, tags: tagsFilter, limit = 20, offset = 0, since, until } = opts;\n const conditions: string[] = [];\n const params: Record<string, unknown> = {};\n if (sourceUrl) {\n const key = canonicalHttpSourceRef(sourceUrl);\n if (!key) return { items: [], total: 0 };\n conditions.push(\"i.source_url = @sourceUrl\");\n params.sourceUrl = key;\n } else if (sourceUrls && sourceUrls.length > 0) {\n const expanded = [...new Set(sourceUrls.map((s) => canonicalHttpSourceRef(s)).filter(Boolean))];\n if (expanded.length === 0) return { items: [], total: 0 };\n const placeholders = expanded.map((_, i) => `@src${i}`).join(\", \");\n conditions.push(`i.source_url IN (${placeholders})`);\n expanded.forEach((s, i) => { (params as Record<string, unknown>)[`src${i}`] = s; });\n }\n if (author && author.trim().length >= 2) {\n conditions.push(\"instr(i.author, @author) > 0\");\n params.author = author.trim();\n }\n if (q) {\n conditions.push(\"i.rowid IN (SELECT rowid FROM items_fts WHERE items_fts MATCH @q)\");\n params.q = q;\n }\n if (tagsFilter && tagsFilter.length > 0) {\n const trimmed = tagsFilter\n .filter((t): t is string => typeof t === \"string\" && t.trim().length > 0)\n .map((t) => t.trim());\n if (trimmed.length > 0) {\n const tagConds = trimmed.map((_, idx: number) => `LOWER(TRIM(json_each.value)) = LOWER(@tag${idx})`).join(\" OR \");\n conditions.push(`i.tags IS NOT NULL AND EXISTS (SELECT 1 FROM json_each(i.tags) WHERE ${tagConds})`);\n trimmed.forEach((t: string, i: number) => { (params as Record<string, unknown>)[`tag${i}`] = t; });\n }\n }\n if (since) {\n conditions.push(\"COALESCE(i.pub_date, i.fetched_at) >= @since\");\n params.since = since.toISOString();\n }\n if (until) {\n conditions.push(\"COALESCE(i.pub_date, i.fetched_at) < @until\");\n params.until = until.toISOString();\n }\n const where = conditions.length ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const sqlParams = params as unknown as Record<string, string | number | null>;\n const rows = db\n .prepare(`\n SELECT i.id, i.url, i.source_url, i.title, i.author, i.summary, i.content, i.image_url, i.tags, i.translations, i.pub_date, i.fetched_at, i.pushed_at\n FROM items i ${where}\n ORDER BY COALESCE(i.pub_date, i.fetched_at) DESC\n LIMIT ${limit} OFFSET ${offset}\n `)\n .all(sqlParams);\n const { count } = db.prepare(`SELECT COUNT(*) as count FROM items i ${where}`).get(sqlParams) as { count: number };\n return { items: mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n })), total: count };\n}\n\n/** 从所有条目移除指定标签 */\nexport async function removeTagFromAllItems(tag: string): Promise<number> {\n const trimmed = String(tag ?? \"\").trim();\n if (!trimmed) return 0;\n const targetLower = trimmed.toLowerCase();\n\n return withWriteLock(async () => {\n const db = await getDb();\n const rows = db.prepare(\"SELECT id, tags FROM items WHERE tags IS NOT NULL AND tags != ''\").all() as { id: string; tags: string }[];\n const updateStmt = db.prepare(\"UPDATE items SET tags = @tags WHERE id = @id\");\n let count = 0;\n\n for (const row of rows) {\n let itemTags: string[];\n try {\n itemTags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const filtered = itemTags.filter((t) => String(t).trim().toLowerCase() !== targetLower);\n if (filtered.length === itemTags.length) continue;\n const nextTags = filtered.length > 0 ? JSON.stringify(filtered) : null;\n updateStmt.run({ id: row.id, tags: nextTags });\n count += 1;\n }\n return count;\n });\n}\n\n/** 标记已投递 */\nexport async function markPushed(ids: string[]): Promise<void> {\n if (ids.length === 0) return;\n return withWriteLock(async () => {\n const db = await getDb();\n const now = new Date().toISOString();\n const placeholders = ids.map(() => \"?\").join(\",\");\n db.prepare(`UPDATE items SET pushed_at = ? WHERE id IN (${placeholders})`).run(now, ...ids);\n });\n}\n\n/** 按 id 删除条目 */\nexport async function deleteItem(id: string): Promise<boolean> {\n if (!id?.trim()) return false;\n return withWriteLock(async () => {\n const db = await getDb();\n const row = db.prepare(\"SELECT rowid FROM items WHERE id = @id\").get({ id: id.trim() }) as { rowid: number } | undefined;\n if (!row) return false;\n db.prepare(\"DELETE FROM items_fts WHERE rowid = @rowid\").run({ rowid: row.rowid });\n const info = db.prepare(\"DELETE FROM items WHERE id = @id\").run({ id: id.trim() });\n return Number(info.changes) > 0;\n });\n}\n\n/** 按 source_url 删除条目 */\nexport async function deleteItemsBySourceUrl(sourceUrl: string): Promise<number> {\n if (!sourceUrl?.trim()) return 0;\n const key = canonicalHttpSourceRef(sourceUrl.trim());\n if (!key) return 0;\n return withWriteLock(async () => {\n const db = await getDb();\n const info = db.prepare(\"DELETE FROM items WHERE source_url = @sourceUrl\").run({ sourceUrl: key });\n return Number(info.changes);\n });\n}\n\n/** 待投递条目 */\nexport async function getPendingPushItems(limit = 100): Promise<DbItem[]> {\n const db = await getDb();\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE pushed_at IS NULL AND content IS NOT NULL\n ORDER BY fetched_at ASC\n LIMIT ${limit}\n `)\n .all();\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 按本地日取条目 */\nexport async function getItemsForDate(date: string): Promise<DbItem[]> {\n const db = await getDb();\n const start = new Date(`${date}T00:00:00`).toISOString();\n const end = new Date(`${date}T23:59:59.999`).toISOString();\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE fetched_at >= @start AND fetched_at <= @end\n ORDER BY fetched_at DESC\n LIMIT 300\n `)\n .all({ start, end });\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 信源统计 */\nexport async function getSourceStats(): Promise<\n { source_url: string; count: number; count_7d: number; latest_at: string | null }[]\n> {\n const { mergeSourceStatsRows } = await import(\"../utils/httpSourceRef.js\");\n const db = await getDb();\n const rows = db\n .prepare(`\n SELECT source_url,\n COUNT(*) as count,\n SUM(CASE WHEN julianday(fetched_at) >= julianday('now', '-7 days') THEN 1 ELSE 0 END) as count_7d,\n MAX(COALESCE(pub_date, fetched_at)) as latest_at\n FROM items GROUP BY source_url ORDER BY count DESC\n `)\n .all() as { source_url: string; count: number; count_7d: number; latest_at: string | null }[];\n return mergeSourceStatsRows(rows);\n}\n\n/** 写入运行日志 */\nexport async function insertLog(entry: LogEntry): Promise<void> {\n const db = await getLogsDb();\n db.prepare(`\n INSERT INTO logs (level, category, message, payload, source_url, created_at)\n VALUES (@level, @category, @message, @payload, NULL, @created_at)\n `).run({\n level: entry.level,\n category: entry.category,\n message: entry.message,\n payload: entry.payload != null ? JSON.stringify(entry.payload) : null,\n created_at: entry.created_at,\n });\n}\n\n/** 分页查询运行日志 */\nexport async function queryLogs(opts: {\n level?: LogEntry[\"level\"];\n category?: LogEntry[\"category\"];\n limit?: number;\n offset?: number;\n since?: Date;\n}): Promise<{ items: DbLog[]; total: number }> {\n const db = await getLogsDb();\n const { level, category, limit = 50, offset = 0, since } = opts;\n const conditions: string[] = [];\n const params: Record<string, unknown> = {};\n if (level) {\n conditions.push(\"level = @level\");\n params.level = level;\n }\n if (category) {\n conditions.push(\"INSTR(LOWER(category), LOWER(@categoryPattern)) > 0\");\n params.categoryPattern = category;\n }\n if (since) {\n conditions.push(\"created_at >= @since\");\n params.since = since.toISOString();\n }\n const where = conditions.length ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const sqlParams = params as unknown as Record<string, string | number | null>;\n const rows = db\n .prepare(`\n SELECT id, level, category, message, payload, created_at\n FROM logs ${where}\n ORDER BY created_at DESC\n LIMIT ${limit} OFFSET ${offset}\n `)\n .all(sqlParams);\n const { count } = db.prepare(`SELECT COUNT(*) as count FROM logs ${where}`).get(sqlParams) as { count: number };\n return {\n items: rows.map((r) => ({\n id: Number(r.id),\n level: String(r.level),\n category: String(r.category),\n message: String(r.message),\n payload: r.payload as string | null,\n created_at: String(r.created_at),\n })),\n total: Number(count)\n };\n}\n\n/** 清空日志 */\nexport async function clearAllLogs(): Promise<number> {\n const db = await getLogsDb();\n const r = db.prepare(\"DELETE FROM logs\").run();\n return Number(r.changes);\n}\n\n/** 读取系统标签 */\nexport async function getSystemTags(): Promise<string[]> {\n try {\n const raw = await readFile(TAGS_CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw) as { tags?: unknown[] };\n if (!Array.isArray(parsed?.tags)) return [];\n return parsed.tags\n .filter((t): t is string => typeof t === \"string\" && t.trim().length > 0)\n .map((t) => t.trim());\n } catch {\n return [];\n }\n}\n\n/** 保存系统标签 */\nexport async function saveSystemTagsToFile(tags: string[]): Promise<void> {\n const list = tags\n .filter((t) => typeof t === \"string\" && t.trim())\n .map((t) => t.trim());\n await writeFile(TAGS_CONFIG_PATH, JSON.stringify({ tags: list }, null, 2), \"utf-8\");\n}\n\n/** 标签使用统计 */\nexport async function getSystemTagStats(): Promise<TagStat[]> {\n const systemTags = await getSystemTags();\n if (systemTags.length === 0) return [];\n\n const db = await getDb();\n const rows = db\n .prepare(\"SELECT tags, pub_date, fetched_at FROM items WHERE tags IS NOT NULL AND tags != ''\")\n .all() as { tags: string; pub_date: string | null; fetched_at: string }[];\n\n const now = Date.now();\n const tagMap = new Map<string, { count: number; hotness: number }>();\n for (const name of systemTags) {\n tagMap.set(name.toLowerCase(), { count: 0, hotness: 0 });\n }\n\n for (const row of rows) {\n let itemTags: string[];\n try {\n itemTags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const pubMs = row.pub_date ? Date.parse(row.pub_date) : null;\n const fetchedMs = Date.parse(row.fetched_at);\n const factor = recencyFactor(pubMs, fetchedMs, now);\n\n for (const t of itemTags) {\n const key = String(t).trim().toLowerCase();\n const entry = tagMap.get(key);\n if (entry) {\n entry.count += 1;\n entry.hotness += factor;\n }\n }\n }\n\n return systemTags.map((name) => {\n const entry = tagMap.get(name.toLowerCase()) ?? { count: 0, hotness: 0 };\n return {\n name,\n count: entry.count,\n hotness: Math.round(entry.hotness * 100) / 100,\n };\n });\n}\n\nexport interface TagStat {\n name: string;\n count: number;\n hotness: number;\n period?: number;\n}\n\nfunction recencyFactor(pubDateMs: number | null, fetchedAtMs: number, nowMs: number): number {\n const ref = pubDateMs ?? fetchedAtMs;\n const daysAgo = (nowMs - ref) / (24 * 60 * 60 * 1000);\n return 1 / (1 + Math.max(0, daysAgo) / 7);\n}\n\n/** 建议标签 */\nexport async function getSuggestedTags(): Promise<TagStat[]> {\n const systemTags = await getSystemTags();\n const systemLower = new Set(systemTags.map((t) => t.toLowerCase().trim()));\n\n const db = await getDb();\n const rows = db\n .prepare(\"SELECT tags, pub_date, fetched_at FROM items WHERE tags IS NOT NULL AND tags != ''\")\n .all() as { tags: string; pub_date: string | null; fetched_at: string }[];\n\n const tagMap = new Map<string, { name: string; count: number; hotness: number }>();\n const now = Date.now();\n\n for (const row of rows) {\n let tags: string[];\n try {\n tags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const pubMs = row.pub_date ? Date.parse(row.pub_date) : null;\n const fetchedMs = Date.parse(row.fetched_at);\n const factor = recencyFactor(pubMs, fetchedMs, now);\n\n for (const t of tags) {\n const trimmed = String(t).trim();\n if (!trimmed) continue;\n const key = trimmed.toLowerCase();\n if (systemLower.has(key)) continue;\n\n const existing = tagMap.get(key);\n if (existing) {\n existing.count += 1;\n existing.hotness += factor;\n } else {\n tagMap.set(key, { name: trimmed, count: 1, hotness: factor });\n }\n }\n }\n\n return Array.from(tagMap.values())\n .filter((s) => s.hotness > 20)\n .sort((a, b) => b.hotness - a.hotness)\n .slice(0, 5)\n .map((s) => ({ name: s.name, count: s.count, hotness: Math.round(s.hotness * 100) / 100 }));\n}\n\n/** 库中行结构 */\nexport interface DbItem {\n id: string;\n url: string;\n source_url: string;\n title: string | null;\n author: string[] | null;\n summary: string | null;\n content: string | null;\n image_url: string | null;\n tags: string[] | null;\n translations: Record<string, { title?: string; summary?: string; content?: string }> | null;\n pub_date: string | null;\n fetched_at: string;\n pushed_at: string | null;\n}\n\n/** 运行日志表行 */\nexport interface DbLog {\n id: number;\n level: string;\n category: string;\n message: string;\n payload: string | null;\n created_at: string;\n}\n","// 日志配置:从环境变量读取,不依赖 config.json 以尽早可用\n\nimport type { LogLevel } from \"./types.js\";\n\nconst LEVEL_ORDER: LogLevel[] = [\"debug\", \"info\", \"warn\", \"error\"];\n\nfunction parseLevel(s: string | undefined, fallback: LogLevel): LogLevel {\n if (!s) return fallback;\n const v = s.toLowerCase() as LogLevel;\n if (LEVEL_ORDER.includes(v)) return v;\n return fallback;\n}\n\n/** 当前控制台最低输出级别(默认 info,生产可设为 warn) */\nexport function getConsoleLevel(): LogLevel {\n return parseLevel(process.env.LOG_LEVEL, \"info\");\n}\n\n/** 是否将 error/warn 写入数据库(默认 true) */\nexport function getLogToDb(): boolean {\n const v = process.env.LOG_TO_DB;\n if (v === \"0\" || v === \"false\") return false;\n if (v === \"1\" || v === \"true\") return true;\n return true;\n}\n\n/** 落库的最低级别(默认 warn) */\nexport function getDbLevel(): LogLevel {\n return parseLevel(process.env.LOG_DB_LEVEL, \"warn\");\n}\n\nexport function levelOrder(l: LogLevel): number {\n return LEVEL_ORDER.indexOf(l);\n}\n\n/** 是否应输出到控制台 */\nexport function shouldLogToConsole(consoleLevel: LogLevel, entryLevel: LogLevel): boolean {\n return levelOrder(entryLevel) >= levelOrder(consoleLevel);\n}\n\n/** 是否应写入 DB */\nexport function shouldLogToDb(logToDb: boolean, dbLevel: LogLevel, entryLevel: LogLevel): boolean {\n return logToDb && levelOrder(entryLevel) >= levelOrder(dbLevel);\n}\n","// 统一日志:仅落库,不打印控制台;全体级别落库,由日志页/API 查看\n\nimport { insertLog } from \"../../db/index.js\";\nimport { getLogToDb } from \"./config.js\";\nimport type { LogCategory, LogEntry, LogLevel } from \"./types.js\";\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction writeDb(entry: LogEntry): void {\n insertLog(entry).catch((err) => {\n // 落库失败只打一次 stderr,避免循环\n process.stderr.write(`[logger] 写入日志表失败: ${err instanceof Error ? err.message : String(err)}\\n`);\n });\n}\n\nfunction emit(level: LogLevel, category: LogCategory, message: string, meta?: Record<string, unknown>): void {\n const payload = meta && Object.keys(meta).length > 0 ? { ...meta } : undefined;\n const entry: LogEntry = {\n level,\n category,\n message,\n payload: payload && Object.keys(payload).length > 0 ? payload : undefined,\n created_at: now(),\n };\n\n if (getLogToDb()) {\n writeDb(entry);\n }\n}\n\n/** 统一 logger:仅落库,不输出控制台;全体级别落库 */\nexport const logger = {\n error(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"error\", category, message, meta);\n },\n warn(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"warn\", category, message, meta);\n },\n info(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"info\", category, message, meta);\n },\n debug(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"debug\", category, message, meta);\n },\n};\n","// 使用无头浏览器(Puppeteer)拉取页面,缓存逻辑在 cacher 中\r\n\r\nimport { exec } from \"node:child_process\";\r\nimport { platform } from \"node:os\";\r\nimport { join, resolve } from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport puppeteerCore, { type Browser, type Page } from \"puppeteer-core\";\r\nimport { applyPurify } from \"./purify.js\";\r\nimport { findChromeExecutable } from \"./cdp.js\";\r\nimport type { AuthFlow } from \"../../../auth/index.js\";\r\nimport type { RequestConfig, StructuredHtmlResult } from \"./types.js\";\r\nimport { logger } from \"../../../../core/logger/index.js\";\r\n\r\nconst execAsync = promisify(exec);\r\n\r\n/** 与 launchArgs / setViewport 一致;无头拉高便于长页与懒加载内容 */\r\nconst VIEWPORT_WIDTH = 1366;\r\nconst VIEWPORT_HEIGHT_HEADLESS = 5000;\r\nconst VIEWPORT_HEIGHT_HEADFUL = 1200;\r\n\r\n/** 解析代理:显式传入的 proxy,否则 HTTP_PROXY / HTTPS_PROXY */\r\nexport function resolveProxy(config?: { proxy?: string }): string | undefined {\r\n return config?.proxy ?? process.env.HTTP_PROXY ?? process.env.HTTPS_PROXY;\r\n}\r\n\r\n/** 从代理字符串解析出 serverUrl 和可选账号密码;支持 http://user:pass@host:port */\r\nfunction parseProxy(proxy: string): { serverUrl: string; username?: string; password?: string } {\r\n const u = new URL(proxy);\r\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\r\n const username = u.username || undefined;\r\n const password = u.password || undefined;\r\n return { serverUrl, username, password };\r\n}\r\n\r\n/** 在 Page 上设置代理认证(与 preCheckAuth / fetchHtml 一致;需与 launchBrowser 的 proxy 同时使用) */\r\nexport async function applyProxyAuthToPage(page: Page, opts?: { proxy?: string }): Promise<void> {\r\n const proxy = resolveProxy(opts);\r\n if (!proxy) return;\r\n const { username, password } = parseProxy(proxy);\r\n if (username !== undefined || password !== undefined) {\r\n await page.authenticate({ username: username ?? \"\", password: password ?? \"\" });\r\n }\r\n}\r\n\r\n\r\n/** 构建 Puppeteer launch args */\r\nfunction launchArgs(config?: { proxy?: string; headless?: boolean }): string[] {\r\n const base = [\r\n \"--disable-blink-features=AutomationControlled\",\r\n \"--no-sandbox\",\r\n \"--disable-setuid-sandbox\",\r\n \"--disable-dev-shm-usage\",\r\n \"--disable-web-security\",\r\n \"--disable-features=IsolateOrigins,site-per-process\",\r\n \"--disable-site-isolation-trials\",\r\n \"--disable-infobars\",\r\n ];\r\n const height = config?.headless !== false ? VIEWPORT_HEIGHT_HEADLESS : VIEWPORT_HEIGHT_HEADFUL;\r\n base.push(`--window-size=${VIEWPORT_WIDTH},${height}`);\r\n const proxy = resolveProxy(config);\r\n if (proxy) {\r\n const { serverUrl } = parseProxy(proxy);\r\n base.push(`--proxy-server=${serverUrl}`);\r\n }\r\n return base;\r\n}\r\n\r\n\r\n/** 获取 userDataDir:统一使用 main 目录,所有站点共享同一浏览器 profile */\r\nfunction getUserDataDir(cacheDir?: string): string | undefined {\r\n if (!cacheDir) return undefined;\r\n return join(cacheDir, \"browser_data\", \"main\");\r\n}\r\n\r\n\r\n/** 是否为「userDataDir 已被占用」的报错(上次进程未正常退出或并发启动) */\r\nfunction isAlreadyRunningError(e: unknown): boolean {\r\n const msg = e instanceof Error ? e.message : String(e);\r\n return /already running/i.test(msg) && /userDataDir|user-data-dir|user data dir/i.test(msg);\r\n}\r\n\r\n\r\n/**\r\n * 强制结束占用指定 userDataDir 的 Chrome/Chromium 进程(上次未正常退出时残留)。\r\n * 仅在 darwin/linux 下执行;启动前调用以释放 profile 锁。\r\n */\r\nasync function killStaleChromeProcesses(absUserDataDir: string): Promise<void> {\r\n const plat = platform();\r\n if (plat !== \"darwin\" && plat !== \"linux\") {\r\n return;\r\n }\r\n try {\r\n // 匹配命令行中包含该 userDataDir 的进程(Chrome 使用 --user-data-dir=/path)\r\n const psCmd = plat === \"darwin\"\r\n ? `ps -eww -o pid= -o args= 2>/dev/null`\r\n : `ps -eo pid,args --no-headers 2>/dev/null`;\r\n const { stdout } = await execAsync(psCmd, { maxBuffer: 4 * 1024 * 1024 });\r\n const pids = new Set<number>();\r\n const lineRegex = /^\\s*(\\d+)\\s+/;\r\n for (const line of stdout.split(\"\\n\")) {\r\n if (!line.includes(absUserDataDir)) continue;\r\n const m = line.match(lineRegex);\r\n if (m) pids.add(parseInt(m[1], 10));\r\n }\r\n if (pids.size === 0) return;\r\n logger.info(\"scraper\", \"发现占用 browser_data 的 Chrome 进程,正在结束\", { pids: [...pids], userDataDir: absUserDataDir });\r\n for (const pid of pids) {\r\n try {\r\n process.kill(pid, \"SIGTERM\");\r\n } catch {\r\n // 进程可能已退出\r\n }\r\n }\r\n await new Promise((r) => setTimeout(r, 800));\r\n for (const pid of pids) {\r\n try {\r\n process.kill(pid, \"SIGKILL\");\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n await new Promise((r) => setTimeout(r, 300));\r\n } catch (err) {\r\n logger.warn(\"scraper\", \"结束残留 Chrome 进程时出错\", { err: err instanceof Error ? err.message : String(err) });\r\n }\r\n}\r\n\r\n\r\n// 注入脚本隐藏自动化特征\r\nasync function stealthPage(page: Page): Promise<void> {\r\n await page.evaluateOnNewDocument(() => {\r\n /* global navigator, window */\r\n Object.defineProperty(navigator, \"webdriver\", { get: () => false });\r\n Object.defineProperty(navigator, \"plugins\", { get: () => [1, 2, 3, 4, 5] });\r\n Object.defineProperty(navigator, \"languages\", { get: () => [\"zh-CN\", \"zh\", \"en\"] });\r\n const originalQuery = window.navigator.permissions.query;\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n window.navigator.permissions.query = (parameters: any) =>\r\n parameters.name === \"notifications\"\r\n ? Promise.resolve({ state: Notification.permission } as PermissionStatus)\r\n : originalQuery(parameters);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (window as any).chrome = { runtime: {} };\r\n Object.defineProperty(Notification, \"permission\", { get: () => \"default\" });\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const nav = navigator as any;\r\n if (nav.getBattery) {\r\n nav.getBattery = () => Promise.resolve({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 });\r\n }\r\n });\r\n await page.setExtraHTTPHeaders({\r\n \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\r\n \"Accept-Encoding\": \"gzip, deflate, br\",\r\n \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8\",\r\n \"Cache-Control\": \"max-age=0\",\r\n \"Sec-Ch-Ua\": '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\r\n \"Sec-Ch-Ua-Mobile\": \"?0\",\r\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\r\n \"Sec-Fetch-Dest\": \"document\",\r\n \"Sec-Fetch-Mode\": \"navigate\",\r\n \"Sec-Fetch-Site\": \"none\",\r\n \"Sec-Fetch-User\": \"?1\",\r\n \"Upgrade-Insecure-Requests\": \"1\",\r\n });\r\n}\r\n\r\n\r\nfunction headersToRecord(headers: Record<string, unknown>): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n for (const [k, v] of Object.entries(headers)) {\r\n out[k.toLowerCase()] = String(v);\r\n }\r\n return out;\r\n}\r\n\r\n\r\n/** 对新 Page 做通用初始化:UA、Viewport、stealth 脚本 */\r\nasync function setupPage(page: Page, headless = true): Promise<void> {\r\n const realUserAgent =\r\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\r\n await page.setUserAgent(realUserAgent);\r\n await page.setViewport({\r\n width: VIEWPORT_WIDTH,\r\n height: headless ? VIEWPORT_HEIGHT_HEADLESS : VIEWPORT_HEIGHT_HEADFUL,\r\n });\r\n await stealthPage(page);\r\n}\r\n\r\n\r\n// ─── 浏览器:单发模式 ─────────────────────────────────────────────────────────\r\n// 每次任务独立启动 Chrome,用毕在调用方 `browser.close()`;不保留全局单例,不跨请求复用 Tab。\r\n\r\n/** 是否为「frame 已分离」类错误(页面发生客户端导航/重定向导致主 frame 失效) */\r\nfunction isFrameDetachedError(e: unknown): boolean {\r\n const msg = e instanceof Error ? e.message : String(e);\r\n return /detached|Navigating frame was detached|Session closed/i.test(msg);\r\n}\r\n\r\n\r\ntype BrowserLaunchConfig = {\r\n headless?: boolean;\r\n cacheDir?: string;\r\n proxy?: string;\r\n chromeExecutablePath?: string;\r\n};\r\n\r\ntype SharedBrowserSlot = {\r\n browser?: Browser;\r\n promise?: Promise<Browser>;\r\n};\r\n\r\nconst sharedBrowsers = new Map<string, SharedBrowserSlot>();\r\n\r\nfunction browserKey(config: BrowserLaunchConfig): string {\r\n const wantHeadless = config.headless !== false;\r\n const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable() ?? \"\";\r\n const userDataDir = getUserDataDir(config.cacheDir);\r\n const proxy = resolveProxy(config) ?? \"\";\r\n return JSON.stringify({\r\n headless: wantHeadless,\r\n userDataDir: userDataDir ? resolve(userDataDir) : \"\",\r\n proxy,\r\n executablePath,\r\n });\r\n}\r\n\r\nfunction isBrowserConnected(browser: Browser | undefined): browser is Browser {\r\n return !!browser && browser.connected !== false;\r\n}\r\n\r\n/**\r\n * 启动新的 Chrome 实例(不缓存、不复用)。调用方须在 `finally` 中 `await browser.close()`。\r\n */\r\nexport async function launchBrowser(config: BrowserLaunchConfig): Promise<Browser> {\r\n const wantHeadless = config.headless !== false;\r\n const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable();\r\n if (!executablePath) {\r\n throw new Error(\"未找到 Chrome 可执行文件,请安装 Google Chrome 或设置 CHROME_PATH 环境变量\");\r\n }\r\n const userDataDir = getUserDataDir(config.cacheDir);\r\n const maxRetries = 2;\r\n let lastErr: unknown;\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n if (attempt === 0 && userDataDir) {\r\n const absUserDataDir = resolve(userDataDir);\r\n await killStaleChromeProcesses(absUserDataDir);\r\n }\r\n if (attempt > 0) {\r\n const waitMs = attempt * 2000;\r\n logger.info(\"scraper\", \"userDataDir 曾被占用,等待后重试\", { waitMs, attempt });\r\n await new Promise((r) => setTimeout(r, waitMs));\r\n }\r\n return await puppeteerCore.launch({\r\n headless: wantHeadless,\r\n args: launchArgs({ proxy: config.proxy, headless: wantHeadless }),\r\n userDataDir,\r\n executablePath,\r\n ignoreDefaultArgs: [\"--enable-automation\"],\r\n });\r\n } catch (e) {\r\n lastErr = e;\r\n if (attempt < maxRetries && isAlreadyRunningError(e)) {\r\n continue;\r\n }\r\n if (isAlreadyRunningError(e)) {\r\n const dir = userDataDir ?? \"browser_data/main\";\r\n throw new Error(\r\n `Chrome 的 profile 目录已被占用(${dir})。通常是因为上次未正常退出或同时运行了多个本服务实例。请关闭占用该目录的 Chrome 进程后重试,或设置环境变量 CACHE_DIR 使用不同缓存目录。`\r\n );\r\n }\r\n throw e;\r\n }\r\n }\r\n throw lastErr;\r\n}\r\n\r\n\r\n/**\r\n * Get a reusable browser for crawler requests.\r\n *\r\n * Proxy is a Chrome launch option, so reuse is bucketed by headless/userDataDir/proxy/executablePath.\r\n * Requests with the same launch config share one browser and only create/close their own pages.\r\n */\r\nexport async function getOrCreateBrowser(config: BrowserLaunchConfig): Promise<Browser> {\r\n const key = browserKey(config);\r\n const current = sharedBrowsers.get(key);\r\n if (isBrowserConnected(current?.browser)) {\r\n return current.browser;\r\n }\r\n if (current?.promise) {\r\n return current.promise;\r\n }\r\n\r\n const slot: SharedBrowserSlot = {};\r\n const promise = launchBrowser({ ...config, proxy: resolveProxy(config) }).then((browser) => {\r\n slot.browser = browser;\r\n slot.promise = undefined;\r\n browser.once(\"disconnected\", () => {\r\n if (sharedBrowsers.get(key)?.browser === browser) {\r\n sharedBrowsers.delete(key);\r\n }\r\n });\r\n return browser;\r\n }).catch((err) => {\r\n if (sharedBrowsers.get(key) === slot) {\r\n sharedBrowsers.delete(key);\r\n }\r\n throw err;\r\n });\r\n\r\n slot.promise = promise;\r\n sharedBrowsers.set(key, slot);\r\n return promise;\r\n}\r\n\r\n\r\n// ─── 对外 API ─────────────────────────────────────────────────────────────────\r\n\r\n/** 预检认证:单发浏览器(新开 Tab)检查是否已登录;opts 与 fetchHtml 一致(代理、有头/无头) */\r\nexport async function preCheckAuth(\r\n authFlow: AuthFlow,\r\n cacheDir: string,\r\n opts?: { proxy?: string; headless?: boolean }\r\n): Promise<boolean> {\r\n const { checkAuth, loginUrl, domain } = authFlow;\r\n if (domain == null || !cacheDir) return true;\r\n const isHeadless = opts?.headless !== false;\r\n const browser = await getOrCreateBrowser({\r\n headless: isHeadless,\r\n cacheDir,\r\n proxy: resolveProxy(opts),\r\n });\r\n const page = await browser.newPage();\r\n try {\r\n await setupPage(page, isHeadless);\r\n await applyProxyAuthToPage(page, opts);\r\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 60000 });\r\n await new Promise((resolve) => setTimeout(resolve, 3000));\r\n return await checkAuth(page, page.url());\r\n } finally {\r\n await page.close().catch(() => {});\r\n }\r\n}\r\n\r\n\r\n// 执行认证流程:单发有头浏览器打开登录页,等待用户完成登录后关闭进程\r\nexport async function ensureAuth(\r\n authFlow: AuthFlow,\r\n cacheDir: string,\r\n opts?: { proxy?: string }\r\n): Promise<void> {\r\n const { checkAuth, loginUrl, loginTimeoutMs = 60 * 1000, pollIntervalMs = 2000 } = authFlow;\r\n const browser = await launchBrowser({ headless: false, cacheDir, proxy: resolveProxy(opts) });\r\n try {\r\n const page = await browser.newPage();\r\n try {\r\n await setupPage(page, false);\r\n await applyProxyAuthToPage(page, opts);\r\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 60000 });\r\n await new Promise((resolve) => setTimeout(resolve, 3000));\r\n const authenticated = await checkAuth(page, page.url());\r\n if (authenticated) return;\r\n const startTime = Date.now();\r\n while (Date.now() - startTime < loginTimeoutMs) {\r\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\r\n const authenticated = await checkAuth(page, page.url());\r\n if (authenticated) return;\r\n }\r\n throw new Error(`登录超时(${loginTimeoutMs}ms)`);\r\n } finally {\r\n await page.close().catch(() => {});\r\n }\r\n } finally {\r\n await browser.close().catch(() => {});\r\n }\r\n}\r\n\r\n\r\n// 单发浏览器:本次任务内最多开两个 Tab(frame 分离时重试一次),结束后关闭 Chrome\r\n// 若发生「Navigating frame was detached」等 frame 分离错误(常见于 SPA 客户端跳转),会换新 Tab 重试一次,并用 domcontentloaded 尽快取 HTML\r\nexport async function fetchHtml(url: string, config: RequestConfig = {}): Promise<StructuredHtmlResult> {\r\n const {\r\n timeoutMs,\r\n headers,\r\n cookies,\r\n cacheDir,\r\n checkAuth,\r\n authFlow,\r\n purify,\r\n headless,\r\n waitAfterLoadMs,\r\n waitForSelector,\r\n waitForSelectorTimeoutMs,\r\n scrollBeforeSnapshot,\r\n useHttpResponseBody,\r\n } = config;\r\n const isHeadless = headless !== false;\r\n const browser = await getOrCreateBrowser({\r\n headless: isHeadless,\r\n cacheDir,\r\n proxy: resolveProxy(config),\r\n chromeExecutablePath: config.chromeExecutablePath,\r\n });\r\n const navigationTimeout = timeoutMs ?? 60000;\r\n const maxAttempts = 2;\r\n let lastError: unknown;\r\n\r\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\r\n const page = await browser.newPage();\r\n const isRetry = attempt === 1;\r\n // 重试时用 domcontentloaded 尽快取 HTML,减少 SPA 客户端跳转在取 content 前发生的概率\r\n const waitUntil = isRetry ? \"domcontentloaded\" : \"load\";\r\n const extraWaitMs = isRetry ? Math.min(500, Math.max(0, waitAfterLoadMs ?? 2000)) : Math.max(0, waitAfterLoadMs ?? 2000);\r\n try {\r\n if (config.browserContext) {\r\n await config.browserContext(page.browserContext());\r\n }\r\n await setupPage(page, isHeadless);\r\n const extraHeaders: Record<string, string> = { \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8\", ...(headers ?? {}) };\r\n if (cookies != null && cookies !== \"\") {\r\n extraHeaders.cookie = cookies;\r\n }\r\n await page.setExtraHTTPHeaders(extraHeaders);\r\n const proxy = resolveProxy(config);\r\n if (proxy) {\r\n const { username, password } = parseProxy(proxy);\r\n if (username !== undefined || password !== undefined) {\r\n await page.authenticate({ username: username ?? \"\", password: password ?? \"\" });\r\n }\r\n }\r\n if (timeoutMs != null) {\r\n await page.setDefaultNavigationTimeout(timeoutMs);\r\n }\r\n const response = await page.goto(url, { waitUntil, timeout: navigationTimeout });\r\n if (extraWaitMs > 0) {\r\n await new Promise((resolve) => setTimeout(resolve, extraWaitMs));\r\n }\r\n if (waitForSelector != null && waitForSelector !== \"\" && !isRetry) {\r\n const selectorTimeout = waitForSelectorTimeoutMs ?? 20000;\r\n await page.waitForSelector(waitForSelector, { timeout: selectorTimeout });\r\n }\r\n if (scrollBeforeSnapshot && !isRetry) {\r\n const scrollSelector = scrollBeforeSnapshot.selector ?? null;\r\n const rounds = scrollBeforeSnapshot.rounds ?? 6;\r\n const pauseMs = scrollBeforeSnapshot.pauseMs ?? 800;\r\n for (let i = 0; i < rounds; i++) {\r\n const before = await page.evaluate((sel) => {\r\n const target = sel ? document.querySelector(sel) : null;\r\n const el = target ?? document.scrollingElement ?? document.documentElement;\r\n return el?.scrollHeight ?? 0;\r\n }, scrollSelector);\r\n await page.evaluate((sel) => {\r\n const target = sel ? document.querySelector(sel) : null;\r\n const el = target ?? document.scrollingElement ?? document.documentElement;\r\n if (!el) return;\r\n el.scrollTop = el.scrollHeight;\r\n window.scrollBy(0, window.innerHeight);\r\n }, scrollSelector);\r\n await new Promise((resolve) => setTimeout(resolve, pauseMs));\r\n const after = await page.evaluate((sel) => {\r\n const target = sel ? document.querySelector(sel) : null;\r\n const el = target ?? document.scrollingElement ?? document.documentElement;\r\n return el?.scrollHeight ?? 0;\r\n }, scrollSelector);\r\n if (after <= before && i >= 2) break;\r\n }\r\n }\r\n if (checkAuth != null || authFlow != null) {\r\n const authCheck = checkAuth ?? authFlow?.checkAuth;\r\n if (authCheck != null) {\r\n const ok = await authCheck(page, url);\r\n if (!ok) {\r\n throw new Error(\"checkAuth failed: 未通过认证检查,请先调用 ensureAuth 进行预处理登录\");\r\n }\r\n }\r\n }\r\n let rawBody: string;\r\n if (useHttpResponseBody === true && response != null) {\r\n try {\r\n rawBody = await response.text();\r\n } catch {\r\n rawBody = await page.content();\r\n }\r\n } else {\r\n rawBody = await page.content();\r\n }\r\n const finalUrl = response?.url() ?? page.url() ?? String(url);\r\n const status = response?.status() ?? 0;\r\n const statusText = response?.statusText() ?? \"\";\r\n const rawHeaders = response?.headers() ?? {};\r\n const normalizedHeaders = headersToRecord(rawHeaders);\r\n const body = applyPurify(rawBody, purify);\r\n await page.close().catch(() => {});\r\n return { finalUrl, status, statusText, headers: normalizedHeaders, body };\r\n } catch (e) {\r\n lastError = e;\r\n await page.close().catch(() => {});\r\n if (isRetry || !isFrameDetachedError(e)) {\r\n throw e;\r\n }\r\n logger.warn(\"scraper\", \"fetchHtml 因 frame 分离重试\", { url, attempt: attempt + 1, err: e instanceof Error ? e.message : String(e) });\r\n await new Promise((r) => setTimeout(r, 800));\r\n }\r\n }\r\n throw lastError;\r\n}\r\n","// RefreshInterval:类型定义 + interval ↔ cron ↔ 缓存 key 转换,供全项目共用\n// 调度与缓存 key 对齐:refresh 自动转 cron,cron 转 strategy 与 cacher.timeBucket 一致\n// - refreshIntervalToCron(interval) → 调度器使用的 cron\n// - cronToRefreshInterval(cron) → cacher.cacheKey 使用的 strategy\n// - cacher.timeBucket(strategy) → 缓存 key 的时间窗口前缀\n// 三者逻辑一致,保证调度触发时刻与缓存 key 时间窗口边界对齐\n\nimport type { CacheKeyStrategy } from \"../scraper/sources/web/fetcher/types.js\";\n\n\n/** 刷新间隔类型(有时间语义的缓存策略,排除 forever) */\nexport type RefreshInterval = Exclude<CacheKeyStrategy, \"forever\">;\n\n\n/** 合法的刷新间隔值列表(用于运行时校验) */\nexport const VALID_INTERVALS: RefreshInterval[] = [\"1min\", \"5min\", \"10min\", \"30min\", \"1h\", \"6h\", \"12h\", \"1day\", \"3day\", \"7day\"];\n\n\n/**\n * 从 cron 表达式推断缓存策略(RefreshInterval),供 cacheKey 使用\n * 与 refreshIntervalToCron 互为逆映射,保证调度时间与缓存 key 时间窗口一致\n * 格式:minute hour day month weekday(5 字段)或 second minute hour day month weekday(6 字段)\n */\nexport function cronToRefreshInterval(cronExpr: string): RefreshInterval {\n const parts = cronExpr.trim().split(/\\s+/);\n const is6Field = parts.length >= 6;\n const minute = parts[is6Field ? 1 : 0];\n const hour = parts[is6Field ? 2 : 1];\n const day = parts[is6Field ? 3 : 2];\n const weekday = parts[is6Field ? 5 : 4];\n\n // 分钟级:*/N 或 *\n const minMatch = minute.match(/^\\*\\/(\\d+)$/);\n if (minute === \"*\" || (minMatch && parseInt(minMatch[1], 10) <= 1)) return \"1min\";\n if (minMatch) {\n const n = parseInt(minMatch[1], 10);\n if (n <= 5) return \"5min\";\n if (n <= 10) return \"10min\";\n if (n <= 30) return \"30min\";\n }\n\n // 小时级:minute=0, hour=*/N 或 *\n if (minute === \"0\" || minute === \"00\") {\n const hourMatch = hour.match(/^\\*\\/(\\d+)$/);\n if (hour === \"*\" || (hourMatch && parseInt(hourMatch[1], 10) <= 1)) return \"1h\";\n if (hourMatch) {\n const n = parseInt(hourMatch[1], 10);\n if (n <= 2) return \"1h\";\n if (n <= 6) return \"6h\";\n if (n <= 12) return \"12h\";\n }\n // 每天固定时刻(如 0 9 * * *)\n if (day === \"*\" || day === \"?\") return \"1day\";\n // 每周固定时刻(如 0 0 * * 0)\n if (weekday !== \"*\" && weekday !== \"?\") return \"7day\";\n }\n\n return \"1h\";\n}\n\n\n/**\n * 将 RefreshInterval 转为 cron 表达式\n * 调度触发时刻与 cacher.timeBucket 的时间窗口边界一致,保证缓存 key 与执行频率对齐\n */\nexport function refreshIntervalToCron(interval: RefreshInterval): string {\n const map: Record<RefreshInterval, string> = {\n \"1min\": \"* * * * *\",\n \"5min\": \"*/5 * * * *\",\n \"10min\": \"*/10 * * * *\",\n \"30min\": \"*/30 * * * *\",\n \"1h\": \"0 * * * *\",\n \"6h\": \"0 */6 * * *\",\n \"12h\": \"0 0,12 * * *\",\n \"1day\": \"0 0 * * *\",\n \"3day\": \"0 0 */3 * *\",\n \"7day\": \"0 0 * * 0\",\n };\n return map[interval];\n}\n\n\n/** 将 RefreshInterval 转换为对应的毫秒数 */\nexport function refreshIntervalToMs(interval: RefreshInterval): number {\n const map: Record<RefreshInterval, number> = {\n \"1min\": 1 * 60 * 1000,\n \"5min\": 5 * 60 * 1000,\n \"10min\": 10 * 60 * 1000,\n \"30min\": 30 * 60 * 1000,\n \"1h\": 60 * 60 * 1000,\n \"6h\": 6 * 60 * 60 * 1000,\n \"12h\": 12 * 60 * 60 * 1000,\n \"1day\": 24 * 60 * 60 * 1000,\n \"3day\": 3 * 24 * 60 * 60 * 1000,\n \"7day\": 7 * 24 * 60 * 60 * 1000,\n };\n return map[interval];\n}\n","// 缓存管理:认证 profile 缓存\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CacheKeyStrategy } from \"../../scraper/sources/web/fetcher/types.js\";\nimport { cronToRefreshInterval } from \"../../utils/refreshInterval.js\";\n\n\nconst DOMAINS_SUBDIR = \"domains\";\n\n\n/** 认证配置档:可存 cookies、localStorage、sessionStorage 等,由 cacher 写入 cacheDir/domains/ */\nexport interface AuthProfile {\n cookies?: string;\n localStorage?: string;\n sessionStorage?: string;\n [key: string]: unknown;\n}\n\n\nfunction profileFileName(domain: string): string {\n return `${domain.replace(/[/\\\\]/g, \"_\")}.json`;\n}\n\n\nfunction urlHash(url: string): string {\n return createHash(\"sha256\").update(url).digest(\"hex\");\n}\n\n\n// 将时间映射到对应策略的时间窗口前缀(UTC)\n// 与 refreshIntervalToCron 的时间边界一致:如 1h 策略,窗口为整点,cron 为 \"0 * * * *\"\nfunction timeBucket(strategy: CacheKeyStrategy, now: Date): string {\n const y = now.getUTCFullYear();\n const mo = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(now.getUTCDate()).padStart(2, \"0\");\n const h = now.getUTCHours();\n const hs = String(h).padStart(2, \"0\");\n const min = now.getUTCMinutes();\n if (strategy === \"1min\") return `${y}-${mo}-${d}T${hs}:${String(min).padStart(2, \"0\")}`;\n if (strategy === \"5min\") return `${y}-${mo}-${d}T${hs}:${String(Math.floor(min / 5) * 5).padStart(2, \"0\")}`;\n if (strategy === \"10min\") return `${y}-${mo}-${d}T${hs}:${String(Math.floor(min / 10) * 10).padStart(2, \"0\")}`;\n if (strategy === \"30min\") return `${y}-${mo}-${d}T${hs}:${min < 30 ? \"00\" : \"30\"}`;\n if (strategy === \"1h\") return `${y}-${mo}-${d}T${hs}`;\n if (strategy === \"6h\") return `${y}-${mo}-${d}T${String(Math.floor(h / 6) * 6).padStart(2, \"0\")}`;\n if (strategy === \"12h\") return `${y}-${mo}-${d}T${h < 12 ? \"00\" : \"12\"}`;\n if (strategy === \"1day\") return `${y}-${mo}-${d}`;\n if (strategy === \"3day\") {\n const epochDay = Math.floor(now.getTime() / 86400000);\n return `d${Math.floor(epochDay / 3) * 3}`;\n }\n if (strategy === \"7day\") {\n const adjusted = new Date(now);\n adjusted.setUTCHours(0, 0, 0, 0);\n adjusted.setUTCDate(adjusted.getUTCDate() + 3 - ((adjusted.getUTCDay() + 6) % 7));\n const week1 = new Date(Date.UTC(adjusted.getUTCFullYear(), 0, 4));\n const isoYear = adjusted.getUTCFullYear();\n const isoWeek = 1 + Math.round(((adjusted.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getUTCDay() + 6) % 7) / 7);\n return `${isoYear}-W${String(isoWeek).padStart(2, \"0\")}`;\n }\n return \"\";\n}\n\n\n// 根据 URL 与策略生成缓存 key:forever=仅 sha256(url);其余=时间窗口前缀-sha256(url)\nexport function cacheKey(url: string, strategy: CacheKeyStrategy = \"forever\", now: Date = new Date()): string {\n const hash = urlHash(url);\n if (strategy === \"forever\") return hash;\n return `${timeBucket(strategy, now)}-${hash}`;\n}\n\n\n/** 基于 cron 表达式生成缓存 key:策略由 cronToRefreshInterval 派生,与调度触发时刻对齐 */\nexport function cacheKeyFromCron(url: string, cron: string, now: Date = new Date()): string {\n return cacheKey(url, cronToRefreshInterval(cron), now);\n}\n\n\n// 从 cacheDir/domains/{domain}.json 读取认证配置(未来可扩展为 domains/{domain}/{profileName}.json)\nexport async function readProfile(cacheDir: string, domain: string): Promise<AuthProfile | null> {\n const dir = join(cacheDir, DOMAINS_SUBDIR);\n const filePath = join(dir, profileFileName(domain));\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as AuthProfile;\n } catch {\n return null;\n }\n}\n\n\n// 将认证配置写入 cacheDir/domains/{domain}.json\nexport async function writeProfile(cacheDir: string, domain: string, data: AuthProfile): Promise<void> {\n const dir = join(cacheDir, DOMAINS_SUBDIR);\n await mkdir(dir, { recursive: true });\n const filePath = join(dir, profileFileName(domain));\n await writeFile(filePath, JSON.stringify(data, null, 2), \"utf-8\");\n}\n","// 正文提取器:从详情页 HTML 提取单条正文(规则 / Readability),支持缓存\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { JSDOM } from \"jsdom\";\nimport { Readability } from \"@mozilla/readability\";\nimport { cacheKey as cacherCacheKey } from \"../../../../core/cacher/index.js\";\nimport { fetchHtml } from \"../fetcher/index.js\";\nimport type { RequestConfig } from \"../fetcher/types.js\";\nimport type { FeedItem } from \"../../../../types/feedItem.js\";\nimport { normalizeAuthor } from \"../../../../types/feedItem.js\";\nimport type { ExtractedResult, ExtractorConfig } from \"./types.js\";\nimport { logger } from \"../../../../core/logger/index.js\";\n\n\nconst EXTRACTED_SUBDIR = \"extracted\";\n\n\n/** 从缓存读取已提取结果 */\nasync function readCachedExtracted(cacheDir: string, key: string): Promise<ExtractedResult | null> {\n const filePath = join(cacheDir, EXTRACTED_SUBDIR, `${key}.json`);\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as ExtractedResult & { cachedAt?: string };\n return {\n author: parsed.author,\n title: parsed.title,\n summary: parsed.summary,\n content: parsed.content ?? (parsed as { contentMarkdown?: string }).contentMarkdown ?? (parsed as { contentHtml?: string }).contentHtml,\n pubDate: parsed.pubDate,\n };\n } catch {\n return null;\n }\n}\n\n\n/** 将提取结果写入缓存 */\nasync function writeCachedExtracted(cacheDir: string, key: string, result: ExtractedResult): Promise<void> {\n const dir = join(cacheDir, EXTRACTED_SUBDIR);\n await mkdir(dir, { recursive: true });\n const filePath = join(dir, `${key}.json`);\n const cache = { ...result, cachedAt: new Date().toISOString() };\n await writeFile(filePath, JSON.stringify(cache, null, 2), \"utf-8\");\n}\n\n\n/** 解析 key:与 fetched 一致,使用 sha256(url) */\nfunction extractedCacheKey(url: string, config: ExtractorConfig): string {\n if (config.cacheKey != null && config.cacheKey !== \"\") return config.cacheKey;\n if (url) return cacherCacheKey(url, \"forever\");\n return \"\";\n}\n\n\n/** 使用 Readability 从详情页 HTML 提取正文 */\nfunction extractWithReadability(html: string, url: string): ExtractedResult {\n const dom = new JSDOM(html, { url });\n const reader = new Readability(dom.window.document);\n const article = reader.parse();\n if (!article) return {};\n const summary = article.excerpt && article.excerpt.length > 200 ? article.excerpt.slice(0, 200) + \"…\" : article.excerpt;\n return {\n author: article.byline || undefined,\n title: article.title || undefined,\n summary: summary || undefined,\n content: article.content || undefined,\n };\n}\n\n\n/** 从 HTML 提取正文(规则 / Readability),支持缓存 */\nexport async function extractHtml(html: string, config: ExtractorConfig = {}): Promise<ExtractedResult> {\n const { url = \"\", mode, customExtractor, cacheDir, useCache = true } = config;\n const key = extractedCacheKey(url, config);\n if (useCache !== false && cacheDir != null && cacheDir !== \"\" && key) {\n const cached = await readCachedExtracted(cacheDir, key);\n if (cached != null) return cached;\n }\n if (customExtractor != null) {\n const result = await Promise.resolve(customExtractor(html, url));\n if (cacheDir != null && cacheDir !== \"\" && key) {\n await writeCachedExtracted(cacheDir, key, result);\n }\n return result;\n }\n if (mode === \"readability\") {\n const result = extractWithReadability(html, url);\n if (cacheDir != null && cacheDir !== \"\" && key) {\n await writeCachedExtracted(cacheDir, key, result);\n }\n return result;\n }\n return {};\n}\n\n\n/** 根据 link 拉取 HTML 并提取正文(提取过程不缓存 fetch) */\nexport async function extractFromLink(\n link: string,\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<ExtractedResult> {\n const { cacheDir } = extractorConfig;\n const fetchOpts: RequestConfig = {\n cacheDir: fetchConfig.cacheDir ?? cacheDir,\n useCache: false,\n timeoutMs: fetchConfig.timeoutMs ?? 15_000,\n ...fetchConfig,\n };\n const res = await fetchHtml(link, fetchOpts);\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`fetch 失败: ${res.status} ${res.statusText} for ${link}`);\n }\n const finalUrl = res.finalUrl ?? link;\n return extractHtml(res.body, {\n ...extractorConfig,\n url: finalUrl,\n cacheKey: extractorConfig.cacheKey ?? (cacheDir ? cacherCacheKey(link, \"forever\") : undefined),\n });\n}\n\n\n/** 对单个 FeedItem 根据 link 拉取并提取正文,合并回 item(补充 author/title/summary/content/pubDate) */\nexport async function extractItem(\n item: FeedItem,\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<FeedItem> {\n const extracted = await extractFromLink(item.link, extractorConfig, fetchConfig);\n const pubDate =\n extracted.pubDate != null\n ? typeof extracted.pubDate === \"string\"\n ? new Date(extracted.pubDate)\n : extracted.pubDate\n : item.pubDate;\n return {\n ...item,\n author: normalizeAuthor(extracted.author ?? item.author),\n title: extracted.title ?? item.title,\n summary: extracted.summary ?? item.summary,\n content: extracted.content ?? item.content,\n pubDate,\n };\n}\n\n\n/** 对多个 FeedItem 依次提取正文并合并(可复用 fetcher 缓存) */\nexport async function extractItems(\n items: FeedItem[],\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<FeedItem[]> {\n const results: FeedItem[] = [];\n const onProgress = extractorConfig.onProgress;\n for (let i = 0; i < items.length; i++) {\n try {\n results.push(await extractItem(items[i], extractorConfig, fetchConfig));\n } catch (err) {\n logger.warn(\"scraper\", \"正文提取失败\", { item_url: items[i].link, err: err instanceof Error ? err.message : String(err) });\n results.push(items[i]);\n }\n onProgress?.(i + 1, items.length);\n }\n return results;\n}\n","// LLM 配置:config.json 的 llm 块优先,其次环境变量\r\n\r\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\r\nimport \"dotenv/config\";\r\nimport { CONFIG_PATH } from \"../config/paths.js\";\r\n\r\n/** LLM 配置(文件、环境变量或调用方传入) */\r\nexport interface LLMConfig {\r\n apiKey?: string;\r\n baseUrl?: string;\r\n model?: string;\r\n}\r\n\r\nconst DEFAULT_BASE_URL = \"https://api.openai.com/v1\";\r\nconst DEFAULT_MODEL = \"gpt-4o-mini\";\r\n\r\ninterface FileLlmCache {\r\n mtimeMs: number;\r\n llm: Partial<LLMConfig>;\r\n}\r\n\r\nlet fileCache: FileLlmCache | null = null;\r\n\r\n/** 配置或外部写入后调用,使下次 getLLMConfig 重新读盘 */\r\nexport function invalidateLLMConfigCache(): void {\r\n fileCache = null;\r\n}\r\n\r\nfunction readLlmFromFileSync(): Partial<LLMConfig> {\r\n if (!existsSync(CONFIG_PATH)) return {};\r\n try {\r\n const st = statSync(CONFIG_PATH);\r\n if (fileCache && fileCache.mtimeMs === st.mtimeMs) return fileCache.llm;\r\n const raw = readFileSync(CONFIG_PATH, \"utf-8\");\r\n const j = JSON.parse(raw) as { llm?: unknown };\r\n const llmRaw = j?.llm;\r\n const llm: Partial<LLMConfig> = {};\r\n if (llmRaw && typeof llmRaw === \"object\") {\r\n const o = llmRaw as Record<string, unknown>;\r\n if (typeof o.apiKey === \"string\" && o.apiKey.length > 0) llm.apiKey = o.apiKey;\r\n if (typeof o.baseUrl === \"string\" && o.baseUrl.trim()) llm.baseUrl = o.baseUrl.trim();\r\n if (typeof o.model === \"string\" && o.model.trim()) llm.model = o.model.trim();\r\n }\r\n fileCache = { mtimeMs: st.mtimeMs, llm };\r\n return llm;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\n/** 合并文件与环境变量:文件中的非空字段优先 */\r\nexport function getLLMConfig(): Required<Pick<LLMConfig, \"baseUrl\" | \"model\">> & Pick<LLMConfig, \"apiKey\"> {\r\n const file = readLlmFromFileSync();\r\n const apiKey = file.apiKey ?? process.env.OPENAI_API_KEY;\r\n const baseUrl = file.baseUrl ?? process.env.OPENAI_BASE_URL ?? DEFAULT_BASE_URL;\r\n const model = file.model ?? process.env.OPENAI_MODEL ?? DEFAULT_MODEL;\r\n return { apiKey, baseUrl, model };\r\n}\r\n","// LLM 统一调用:封装 OpenAI chat completion,供 parser/extractor / pipeline 复用\r\n\r\nimport OpenAI from \"openai\";\r\nimport type { ChatCompletion } from \"openai/resources/chat/completions\";\r\nimport { getLLMConfig } from \"./llmConfig.js\";\r\nimport type { LLMConfig } from \"./llmConfig.js\";\r\n\r\n/** 从非流式 chat completion 取出助手文本;content 为空时检查 refusal、finish_reason */\r\nfunction extractAssistantText(completion: ChatCompletion): string {\r\n const choice = completion.choices[0];\r\n if (!choice) throw new Error(\"LLM 返回无 choices\");\r\n const msg = choice.message;\r\n const raw = msg.content;\r\n if (typeof raw === \"string\") {\r\n const t = raw.trim();\r\n if (t.length > 0) return t;\r\n }\r\n // DeepSeek deepseek-reasoner:推理在 reasoning_content,答复在 content;总输出受 max_tokens 限制,可能仅有 reasoning_content\r\n const extra = msg as unknown as Record<string, unknown>;\r\n const rc = extra.reasoning_content;\r\n if (typeof rc === \"string\" && rc.trim().length > 0) {\r\n return rc.trim();\r\n }\r\n const refusal = msg.refusal;\r\n if (typeof refusal === \"string\" && refusal.trim()) {\r\n throw new Error(`模型拒绝: ${refusal.trim()}`);\r\n }\r\n const fr = choice.finish_reason;\r\n if (fr === \"tool_calls\") {\r\n throw new Error(\"LLM 返回了工具调用而非文本,请换一个模型或关闭工具调用\");\r\n }\r\n if (fr === \"content_filter\") {\r\n throw new Error(\"内容被内容策略过滤\");\r\n }\r\n if (fr === \"length\") {\r\n throw new Error(\r\n \"LLM 输出在 content / reasoning_content 均为空前已用尽\",\r\n );\r\n }\r\n throw new Error(`LLM 返回空内容 (finish_reason=${String(fr)})`);\r\n}\r\n\r\n/** 合并调用方配置与环境变量配置 */\r\nfunction mergeConfig(override?: Partial<LLMConfig> & { apiUrl?: string }): { apiKey: string; baseUrl: string; model: string } {\r\n const env = getLLMConfig();\r\n const apiKey = override?.apiKey ?? env.apiKey;\r\n const baseUrl = override?.apiUrl ?? override?.baseUrl ?? env.baseUrl;\r\n const model = override?.model ?? env.model;\r\n if (!apiKey) throw new Error(\"LLM API Key 未配置:请在管理后台「设置 → LLM」或环境变量 OPENAI_API_KEY 中设置\");\r\n return { apiKey, baseUrl, model };\r\n}\r\n\r\n/** 调用 LLM 获取 JSON 响应,供 parser/extractor 复用 */\r\nexport async function chatJson(\r\n prompt: string,\r\n config?: Partial<LLMConfig> & { apiUrl?: string },\r\n options?: { maxTokens?: number; debugLabel?: string },\r\n): Promise<Record<string, unknown>> {\r\n const { apiKey, baseUrl, model } = mergeConfig(config);\r\n const openai = new OpenAI({ apiKey, baseURL: baseUrl });\r\n const completion = await openai.chat.completions.create({\r\n model,\r\n messages: [{ role: \"user\", content: prompt }],\r\n max_tokens: options?.maxTokens ?? 8192,\r\n response_format: { type: \"json_object\" },\r\n });\r\n const content = extractAssistantText(completion);\r\n return JSON.parse(content) as Record<string, unknown>;\r\n}\r\n\r\n/** 调用 LLM 获取纯文本响应 */\r\nexport async function chatText(\r\n prompt: string,\r\n config?: Partial<LLMConfig> & { apiUrl?: string },\r\n options?: { maxTokens?: number; debugLabel?: string },\r\n): Promise<string> {\r\n const { apiKey, baseUrl, model } = mergeConfig(config);\r\n const openai = new OpenAI({ apiKey, baseURL: baseUrl });\r\n const completion = await openai.chat.completions.create({\r\n model,\r\n messages: [{ role: \"user\", content: prompt }],\r\n max_tokens: options?.maxTokens ?? 8192,\r\n });\r\n return extractAssistantText(completion);\r\n}\r\n\r\nexport { getLLMConfig } from \"./llmConfig.js\";\r\nexport type { LLMConfig } from \"./llmConfig.js\";\r\n","// HTML 列表解析器:专注于从列表页提取多个条目(支持自定义函数、LLM 两种模式)\r\n\r\nimport { createHash } from \"node:crypto\";\r\nimport { applyPurify } from \"../fetcher/purify.js\";\r\nimport { chatJson } from \"../../../../core/llm.js\";\r\nimport { getLLMConfig } from \"../../../../core/llmConfig.js\";\r\nimport type { FeedItem } from \"../../../../types/feedItem.js\";\r\nimport type { ParsedEntry } from \"./types.js\";\r\n\r\n\r\n// 使用 sha256 生成 guid\r\nfunction generateGuid(link: string): string {\r\n return createHash(\"sha256\").update(link).digest(\"hex\");\r\n}\r\n\r\n\r\n/** 解析结果结构(类似 RSS feed 结构) */\r\nexport interface ParsedListResult {\r\n /** 解析出的条目列表 */\r\n items: FeedItem[];\r\n /** 源 URL */\r\n url?: string;\r\n /** 解析模式 */\r\n mode?: ParserMode;\r\n}\r\n\r\n\r\n/** 自定义解析函数:输入 HTML 和 URL,返回 ParsedEntry 数组 */\r\nexport type CustomParserFn = (html: string, url: string) => Promise<ParsedEntry[]> | ParsedEntry[];\r\n\r\n\r\n/** LLM 解析配置 */\r\nexport interface LLMParserConfig {\r\n /** LLM API 端点,不传则从环境变量 OPENAI_BASE_URL 读取 */\r\n apiUrl?: string;\r\n /** API Key,不传则从环境变量 OPENAI_API_KEY 读取 */\r\n apiKey?: string;\r\n /** 模型名称,不传则从环境变量 OPENAI_MODEL 读取 */\r\n model?: string;\r\n /** 提示词模板,{html} 会被替换为 HTML 内容 */\r\n prompt?: string;\r\n}\r\n\r\n\r\n/** 解析模式 */\r\nexport type ParserMode = \"custom\" | \"llm\";\r\n\r\n\r\n/** Parser 配置 */\r\nexport interface ParserConfig {\r\n /** 源 URL,用于提取相对链接和发布日期 */\r\n url?: string;\r\n /** 解析模式:custom(自定义函数)、llm(LLM API),默认根据 customParser 或 llmConfig 自动判断 */\r\n mode?: ParserMode;\r\n /** 自定义解析函数,mode 为 \"custom\" 时必需 */\r\n customParser?: CustomParserFn;\r\n /** LLM 解析配置,mode 为 \"llm\" 时使用(可从环境变量读取) */\r\n llmConfig?: LLMParserConfig;\r\n /** 是否包含详细内容(content),默认 false(仅摘要) */\r\n includeContent?: boolean;\r\n /** 解析前是否净化 HTML(移除 script/style/nav 等无关标签),llm 模式默认 true,custom 模式不适用 */\r\n purify?: boolean;\r\n}\r\n\r\n\r\n// 使用 LLM 解析 HTML,返回 ParsedEntry 数组\r\nasync function parseWithLLM(html: string, url: string, config: LLMParserConfig): Promise<ParsedEntry[]> {\r\n const prompt =\r\n config.prompt ??\r\n `你是一个专业的 HTML 内容提取助手。请仔细分析以下 HTML 页面,提取所有可点击的内容条目(如文章、笔记、帖子、视频等)。\r\n\r\n要求:\r\n1. 返回 JSON 对象,格式为 {\"items\": [...]}\r\n2. 每个条目必须包含以下字段:\r\n - title: 标题(必填)\r\n - link: 完整链接(必填)。**必须从 HTML 的 href 原样提取**:若为绝对路径(以 / 开头)则直接使用;若为相对路径则补全为完整 URL。当前页: ${url}\r\n - description: 摘要或描述(200字内,必填)\r\n - author: 作者(可选)\r\n - published: 发布日期 ISO 字符串(可选)\r\n3. 如果页面是列表页,提取所有列表项\r\n4. 如果页面是详情页,将整个页面作为一个条目提取\r\n5. 如果页面是用户主页/个人资料页:\r\n - 优先尝试提取该用户发布的内容列表\r\n - 如果无法提取列表,至少提取用户基本信息作为一个条目(title 为用户名称或页面标题,description 为用户简介或页面描述,link 为当前 URL)\r\n6. 如果页面是单页应用(SPA),尝试从以下位置提取数据:\r\n - <title> 标签中的页面标题\r\n - <meta name=\"description\"> 或 <meta property=\"og:description\"> 中的描述\r\n - <meta property=\"og:title\"> 中的标题\r\n - <link rel=\"canonical\" href=\"...\"> 中的规范链接(优先使用)\r\n - <script> 标签中的 JSON 数据(搜索包含 \"title\", \"description\", \"user\", \"author\", \"items\", \"list\" 等关键词的 JSON)\r\n - 任何包含 href 属性的 <a> 标签,**link 必须使用 href 的原始值**(以 / 开头的路径表示从站根算起)\r\n - 任何包含文本内容的元素\r\n7. **重要**:即使页面看起来是空的 SPA,也必须至少返回一个条目:\r\n - 从 <title> 提取标题(如果没有则使用 URL)\r\n - 从 <meta> 标签提取描述(如果没有则使用 \"页面内容通过 JavaScript 动态加载\")\r\n - link 使用当前 URL: ${url}\r\n - 如果页面 URL 包含 \"user\" 或 \"profile\",可以推断这是一个用户主页,title 可以是 \"用户主页\",description 可以是页面 URL\r\n\r\n**强制要求**:绝对不能返回空数组 {\"items\": []},至少要返回一个包含页面基本信息的条目。\r\n\r\nHTML:\r\n${html}`;\r\n const parsed = await chatJson(\r\n prompt,\r\n { apiKey: config.apiKey, apiUrl: config.apiUrl, model: config.model },\r\n { debugLabel: \"parseWithLLM\" }\r\n );\r\n let entries: ParsedEntry[] = [];\r\n if (parsed.items && Array.isArray(parsed.items)) {\r\n entries = parsed.items as ParsedEntry[];\r\n } else if (parsed.entries && Array.isArray(parsed.entries)) {\r\n entries = parsed.entries as ParsedEntry[];\r\n } else if (Array.isArray(parsed)) {\r\n entries = parsed as ParsedEntry[];\r\n } else if (parsed && typeof parsed === \"object\") {\r\n entries = [parsed as unknown as ParsedEntry];\r\n }\r\n return entries.map((e) => {\r\n const raw = e.link || url;\r\n if (raw.startsWith(\"http\")) return { ...e, link: raw };\r\n const path = raw.startsWith(\"/\") ? raw : `/${raw}`;\r\n try {\r\n const base = new URL(url);\r\n return { ...e, link: new URL(path, base.origin).href };\r\n } catch {\r\n return { ...e, link: new URL(raw, url).href };\r\n }\r\n });\r\n}\r\n\r\n\r\n// 将 ParsedEntry 转为 FeedItem(不包含详细内容)\r\nfunction toFeedItem(entry: ParsedEntry, includeContent: boolean): FeedItem {\r\n return {\r\n guid: entry.guid ?? generateGuid(entry.link),\r\n title: entry.title,\r\n link: entry.link,\r\n pubDate: entry.published ? new Date(entry.published) : new Date(),\r\n author: entry.author ? [entry.author] : undefined,\r\n summary: entry.description,\r\n content: includeContent ? entry.content : undefined,\r\n imageUrl: entry.imageUrl,\r\n };\r\n}\r\n\r\n\r\n/** 解析 HTML 列表页,返回包含 items 的对象(类似 RSS feed 结构) */\r\nexport async function parseHtml(html: string, config: ParserConfig = {}): Promise<ParsedListResult> {\r\n const { url = \"\", mode, customParser, llmConfig, includeContent = false, purify } = config;\r\n let entries: ParsedEntry[] = [];\r\n const actualMode = mode ?? (llmConfig != null ? \"llm\" : customParser != null ? \"custom\" : \"llm\");\r\n if (actualMode === \"llm\") {\r\n if (llmConfig == null && !getLLMConfig().apiKey) {\r\n throw new Error('mode 为 \"llm\" 时必须提供 llmConfig,或在后台「设置 → LLM」/ OPENAI_API_KEY 中配置 Key');\r\n }\r\n const htmlForLLM = applyPurify(html, purify !== false);\r\n entries = await parseWithLLM(htmlForLLM, url, llmConfig ?? {});\r\n } else if (actualMode === \"custom\") {\r\n if (customParser == null) {\r\n throw new Error('mode 为 \"custom\" 时必须提供 customParser');\r\n }\r\n entries = await customParser(html, url);\r\n if (!Array.isArray(entries)) {\r\n entries = [entries];\r\n }\r\n } else {\r\n throw new Error(`不支持的解析模式: ${actualMode}`);\r\n }\r\n const items = entries.map((e) => toFeedItem(e, includeContent));\r\n return {\r\n items,\r\n url,\r\n mode: actualMode,\r\n };\r\n}\r\n","// 站点抽象接口:声明 URL 模式及 fetchItems 能力(WebSource 专用)\n\nimport type { PluginHostDeps } from \"../../../plugins/hostDeps.js\";\nimport type { AuthFlow, CheckAuthFn } from \"../../auth/index.js\";\nimport type { RefreshInterval } from \"../../../utils/refreshInterval.js\";\nimport type { FeedItem } from \"../../../types/feedItem.js\";\n\n\n/** 框架注入给插件的调用上下文,提供浏览器抓取工具与默认正文提取 */\nexport interface SiteContext {\n /** 缓存目录 */\n cacheDir?: string;\n /** 是否无头浏览器 */\n headless?: boolean;\n /** 代理地址 */\n proxy?: string;\n /** 与 SourceContext.deps 相同,宿主注入依赖,用户插件勿从 npm 直接 import */\n deps: PluginHostDeps;\n /**\n * 用浏览器抓取指定 URL,返回渲染后的 HTML。\n * 插件需要访问网页时调用此方法,框架负责浏览器管理与 cookie 注入。\n */\n fetchHtml(\n url: string,\n opts?: {\n waitMs?: number;\n purify?: boolean;\n waitForSelector?: string;\n waitForSelectorTimeoutMs?: number;\n scrollBeforeSnapshot?: {\n selector?: string;\n rounds?: number;\n pauseMs?: number;\n };\n /** 使用导航 HTTP 响应原文(适合 RSS/XML),见 RequestConfig.useHttpResponseBody */\n useHttpResponseBody?: boolean;\n }\n ): Promise<{ html: string; finalUrl: string; status: number }>;\n /**\n * 默认正文提取:拉取 item.link 的 HTML 后用 Readability 提取正文,合并回条目并返回。\n * 可在 fetchItems 内按需 await ctx.extractItem(单条) 使用。\n */\n extractItem(\n item: FeedItem,\n opts?: { cacheKey?: string }\n ): Promise<FeedItem>;\n}\n\n\n/** 站点抽象接口:声明该站点支持的 URL 模式及数据获取能力 */\nexport interface Site {\n /** 站点标识,如 \"xiaohongshu\"、\"lingowhale\" */\n readonly id: string;\n readonly name?: string;\n /** 列表页 URL 匹配模式,{placeholder} 匹配路径段 */\n readonly listUrlPattern: string | RegExp;\n /** 条目有效时间窗口;不填默认 1day */\n readonly refreshInterval?: RefreshInterval;\n /** 代理地址;不填则使用 env HTTP_PROXY */\n readonly proxy?: string;\n /**\n * 核心能力:给定 sourceId,返回条目列表。\n * 插件自行决定如何获取数据(调用 ctx.fetchHtml、直接 fetch API 等均可)。\n */\n fetchItems(sourceId: string, ctx: SiteContext): Promise<FeedItem[]>;\n /** 认证:检查是否已登录 */\n checkAuth?: CheckAuthFn | null;\n /** 认证:登录页 URL */\n loginUrl?: string | null;\n /** 认证:域名,cookies 保存在 domains/{domain}.json */\n domain?: string | null;\n /** 认证:等待登录超时毫秒,默认 300000 */\n loginTimeoutMs?: number | null;\n /** 认证:轮询 checkAuth 间隔毫秒,默认 2000 */\n pollIntervalMs?: number | null;\n}\n\n\n/** 将字符串 URL 模式转为正则:{xxx} → [^/]+,末尾允许 ?query */\nfunction patternToRegex(pattern: string | RegExp): RegExp {\n if (pattern instanceof RegExp) return pattern;\n const pathOnly = pattern.split(\"?\")[0];\n const pl = \"<<<__PL__>>>\";\n const s = pathOnly\n .replace(/\\{[^}]*\\}/g, pl)\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => \"\\\\\" + c)\n .replace(new RegExp(pl.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\"), \"[^/]+\");\n return new RegExp(\"^\" + s + \"(\\\\?.*)?$\");\n}\n\n\n/** 从 Site 扁平字段构建 AuthFlow,无需登录时返回 undefined */\nexport function toAuthFlow(site: Site): AuthFlow | undefined {\n if (!site.checkAuth || !site.loginUrl) return undefined;\n return {\n checkAuth: site.checkAuth,\n loginUrl: site.loginUrl,\n domain: site.domain ?? undefined,\n loginTimeoutMs: site.loginTimeoutMs ?? undefined,\n pollIntervalMs: site.pollIntervalMs ?? undefined,\n };\n}\n\n\n/** 判断 URL 是否匹配站点的 listUrlPattern */\nexport function matchesListUrl(site: Site, url: string): boolean {\n try {\n return patternToRegex(site.listUrlPattern).test(url);\n } catch {\n return false;\n }\n}\n\n\n/** 根据 listUrlPattern 自动计算 URL 匹配具体度(不匹配返回 -1) */\nexport function computeSpecificity(site: Site, url: string): number {\n if (!matchesListUrl(site, url)) return -1;\n const p = site.listUrlPattern;\n if (typeof p === \"string\") {\n const pathOnly = p.split(\"?\")[0];\n return 1000 + pathOnly.split(\"/\").filter(Boolean).length;\n }\n return p.source.length;\n}\n\n\n/** 根据 URL 查找匹配的站点实例,返回具体度最高的站点 */\nexport function getSiteByUrl(url: string, sites: Site[]): Site | undefined {\n const matched = sites\n .map((s) => ({ site: s, score: computeSpecificity(s, url) }))\n .filter((x) => x.score >= 0)\n .sort((a, b) => b.score - a.score);\n return matched[0]?.site;\n}\n","// HTTP 错误:router 捕获后返回对应状态码页面\n\nexport class AuthRequiredError extends Error {\n constructor(message = \"需要登录\") {\n super(message);\n this.name = \"AuthRequiredError\";\n }\n}\n\n\nexport class NotFoundError extends Error {\n constructor(message = \"未找到\") {\n super(message);\n this.name = \"NotFoundError\";\n }\n}\n","// 插件加载器:从 app/plugins/builtin/ 与 .rssany/plugins/ 加载 Site / Source 插件\r\n\r\nimport { readdir } from \"node:fs/promises\";\r\nimport { pathToFileURL } from \"node:url\";\r\nimport { join } from \"node:path\";\r\nimport type { Site } from \"../scraper/sources/web/site.js\";\r\nimport type { Source } from \"../scraper/sources/types.js\";\r\nimport { BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR } from \"../config/paths.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\n\r\n\r\n/** LLM 帮助函数,由 feeder 注入到插件上下文 */\r\nexport interface PluginLlm {\r\n chatJson: (prompt: string, config?: Record<string, unknown>, options?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>>;\r\n chatText: (prompt: string, config?: Record<string, unknown>, options?: { maxTokens?: number; debugLabel?: string }) => Promise<string>;\r\n}\r\n\r\n/** DB 帮助函数,由 feeder 注入到插件上下文 */\r\nexport interface PluginDb {\r\n getSystemTags: () => Promise<string[]>;\r\n}\r\n\r\n/** 插件统一上下文,由 feeder 在执行前注入 llm / db */\r\nexport interface PluginContext {\r\n sourceUrl?: string;\r\n llm?: PluginLlm;\r\n db?: PluginDb;\r\n [key: string]: unknown;\r\n}\r\n\r\n\r\nconst PLUGIN_EXTENSIONS = [\".rssany.js\", \".rssany.ts\"];\r\n\r\n\r\n/** 判断对象是否为有效的 Site 实现 */\r\nfunction isValidSite(obj: unknown): obj is Site {\r\n if (obj == null || typeof obj !== \"object\") return false;\r\n const s = obj as Record<string, unknown>;\r\n return (\r\n typeof s.id === \"string\" &&\r\n (typeof s.listUrlPattern === \"string\" || s.listUrlPattern instanceof RegExp) &&\r\n typeof s.fetchItems === \"function\"\r\n );\r\n}\r\n\r\n/** 判断对象是否为有效的 Source 实现 */\r\nfunction isValidSource(obj: unknown): obj is Source {\r\n if (obj == null || typeof obj !== \"object\") return false;\r\n const s = obj as Record<string, unknown>;\r\n return (\r\n typeof s.id === \"string\" &&\r\n (typeof s.pattern === \"string\" || s.pattern instanceof RegExp) &&\r\n typeof s.fetchItems === \"function\" &&\r\n s.listUrlPattern === undefined\r\n );\r\n}\r\n\r\n/** 从单个目录加载 Site / Source 插件,并记录每个 Site / Source 的文件路径 */\r\nasync function loadSourcePluginsFromDir(\r\n dir: string,\r\n label: string,\r\n): Promise<{\r\n siteEntries: Array<{ site: Site; filePath: string }>;\r\n sources: Array<{ source: Source; filePath: string }>;\r\n}> {\r\n const siteEntries: Array<{ site: Site; filePath: string }> = [];\r\n const sources: Array<{ source: Source; filePath: string }> = [];\r\n let entries: { name: string; isFile: () => boolean }[];\r\n try {\r\n const raw = await readdir(dir, { withFileTypes: true, encoding: \"utf-8\" });\r\n entries = raw as { name: string; isFile: () => boolean }[];\r\n } catch {\r\n return { siteEntries, sources };\r\n }\r\n for (const e of entries) {\r\n const name = String(e.name);\r\n if (!e.isFile()) continue;\r\n if (!PLUGIN_EXTENSIONS.some((ext) => name.endsWith(ext))) continue;\r\n const filePath = join(dir, name);\r\n try {\r\n const mod = await import(pathToFileURL(filePath).href);\r\n const plugin = mod.default ?? mod;\r\n if (isValidSite(plugin)) {\r\n siteEntries.push({ site: plugin, filePath });\r\n } else if (isValidSource(plugin)) {\r\n sources.push({ source: plugin, filePath });\r\n } else {\r\n logger.warn(\"plugin\", \"插件未实现 Site 或 Source 接口,已跳过\", { label, name });\r\n }\r\n } catch (err) {\r\n logger.warn(\"plugin\", \"插件加载失败\", { label, name, err: err instanceof Error ? err.message : String(err) });\r\n }\r\n }\r\n return { siteEntries, sources };\r\n}\r\n\r\n\r\nasync function loadBuiltinAndUser(): Promise<{\r\n builtin: {\r\n siteEntries: Array<{ site: Site; filePath: string }>;\r\n sources: Array<{ source: Source; filePath: string }>;\r\n };\r\n user: {\r\n siteEntries: Array<{ site: Site; filePath: string }>;\r\n sources: Array<{ source: Source; filePath: string }>;\r\n };\r\n}> {\r\n const [builtin, user] = await Promise.all([\r\n loadSourcePluginsFromDir(BUILTIN_PLUGINS_DIR, \"builtin\"),\r\n loadSourcePluginsFromDir(USER_PLUGINS_DIR, \"user\"),\r\n ]);\r\n return { builtin, user };\r\n}\r\n\r\n\r\n/** Site / Source 插件 id → 当前生效的文件路径(用户覆盖内置后的路径) */\r\nconst pluginSitePaths = new Map<string, string>();\r\n\r\n/** 将 Source 插件路径写入 pathMap(不与 Site id 冲突;用户覆盖内置 Source) */\r\nfunction mergeSourcePluginPaths(\r\n siteIds: Set<string>,\r\n pathMap: Map<string, string>,\r\n builtinSources: Array<{ source: Source; filePath: string }>,\r\n userSources: Array<{ source: Source; filePath: string }>,\r\n): void {\r\n for (const { source, filePath } of builtinSources) {\r\n if (siteIds.has(source.id)) {\r\n logger.warn(\"plugin\", \"Source 插件 id 与 Site 插件冲突,已忽略 Source 路径\", { sourceId: source.id });\r\n continue;\r\n }\r\n pathMap.set(source.id, filePath);\r\n }\r\n for (const { source, filePath } of userSources) {\r\n if (siteIds.has(source.id)) {\r\n logger.warn(\"plugin\", \"Source 插件 id 与 Site 插件冲突,已忽略 Source 路径\", { sourceId: source.id });\r\n continue;\r\n }\r\n if (pathMap.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\r\n pathMap.set(source.id, filePath);\r\n }\r\n}\r\n\r\n/** 根据插件 id 获取其源文件路径(用于详情页编辑,仅当前生效的插件有路径) */\r\nexport function getPluginFilePath(id: string): string | undefined {\r\n return pluginSitePaths.get(id);\r\n}\r\n\r\n/** 加载所有 Site 插件;用户插件可覆盖同 id 内置 */\r\nexport async function loadPlugins(): Promise<Site[]> {\r\n const { builtin, user } = await loadBuiltinAndUser();\r\n const merged = new Map<string, Site>();\r\n const pathMap = new Map<string, string>();\r\n for (const { site, filePath } of builtin.siteEntries) {\r\n merged.set(site.id, site);\r\n pathMap.set(site.id, filePath);\r\n }\r\n for (const { site, filePath } of user.siteEntries) {\r\n if (merged.has(site.id)) logger.info(\"plugin\", \"用户插件覆盖同名内置插件\", { pluginId: site.id });\r\n merged.set(site.id, site);\r\n pathMap.set(site.id, filePath);\r\n }\r\n mergeSourcePluginPaths(new Set(merged.keys()), pathMap, builtin.sources, user.sources);\r\n pluginSitePaths.clear();\r\n pathMap.forEach((path, id) => pluginSitePaths.set(id, path));\r\n return Array.from(merged.values());\r\n}\r\n\r\n\r\n/** 加载所有 Source 插件;用户插件可覆盖同 id */\r\nexport async function loadSourcePlugins(): Promise<Source[]> {\r\n const { builtin, user } = await loadBuiltinAndUser();\r\n const merged = new Map<string, Source>();\r\n for (const { source } of builtin.sources) merged.set(source.id, source);\r\n for (const { source } of user.sources) {\r\n if (merged.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\r\n merged.set(source.id, source);\r\n }\r\n return Array.from(merged.values());\r\n}\r\n\r\n\r\n/** 加载 Site 与 Source:合并去重,供 initSources 使用;同时更新 pluginSitePaths */\r\nexport async function loadSiteAndSourcePlugins(): Promise<{ sites: Site[]; sources: Source[] }> {\r\n const { builtin, user } = await loadBuiltinAndUser();\r\n const siteMap = new Map<string, Site>();\r\n const pathMap = new Map<string, string>();\r\n for (const { site: s, filePath } of builtin.siteEntries) {\r\n siteMap.set(s.id, s);\r\n pathMap.set(s.id, filePath);\r\n }\r\n for (const { site: s, filePath } of user.siteEntries) {\r\n if (siteMap.has(s.id)) logger.info(\"plugin\", \"用户插件覆盖同名内置\", { pluginId: s.id });\r\n siteMap.set(s.id, s);\r\n pathMap.set(s.id, filePath);\r\n }\r\n mergeSourcePluginPaths(new Set(siteMap.keys()), pathMap, builtin.sources, user.sources);\r\n const sourceMap = new Map<string, Source>();\r\n for (const { source } of builtin.sources) sourceMap.set(source.id, source);\r\n for (const { source } of user.sources) {\r\n if (sourceMap.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\r\n sourceMap.set(source.id, source);\r\n }\r\n pluginSitePaths.clear();\r\n pathMap.forEach((path, id) => pluginSitePaths.set(id, path));\r\n return { sites: Array.from(siteMap.values()), sources: Array.from(sourceMap.values()) };\r\n}\r\n","// 插件加载与包装:将 Site 插件包装为 Source 接口,注入 SiteContext 工具\n\nimport { fetchHtml as fetchHtmlFn, preCheckAuth } from \"./fetcher/index.js\";\nimport { extractHtml } from \"./extractor/index.js\";\nimport { parseHtml } from \"./parser/index.js\";\nimport { toAuthFlow, getSiteByUrl } from \"./site.js\";\nimport { AuthRequiredError } from \"../../auth/index.js\";\nimport type { Site, SiteContext } from \"./site.js\";\nimport type { Source, SourceContext } from \"../types.js\";\nimport type { FeedItem } from \"../../../types/feedItem.js\";\nimport { normalizeAuthor } from \"../../../types/feedItem.js\";\n\n\n/** 从 SourceContext + Site 构建注入了工具的 SiteContext */\nexport function buildSiteContext(site: Site, ctx: SourceContext): SiteContext {\n const proxy = ctx.proxy ?? site.proxy;\n const authFlow = toAuthFlow(site);\n return {\n cacheDir: ctx.cacheDir,\n headless: ctx.headless,\n proxy,\n deps: ctx.deps,\n async fetchHtml(url, opts) {\n const res = await fetchHtmlFn(url, {\n cacheDir: ctx.cacheDir,\n useCache: false,\n authFlow,\n headless: ctx.headless,\n proxy,\n waitAfterLoadMs: opts?.waitMs,\n purify: opts?.purify,\n waitForSelector: opts?.waitForSelector,\n waitForSelectorTimeoutMs: opts?.waitForSelectorTimeoutMs,\n scrollBeforeSnapshot: opts?.scrollBeforeSnapshot,\n useHttpResponseBody: opts?.useHttpResponseBody,\n });\n return { html: res.body, finalUrl: res.finalUrl ?? url, status: res.status };\n },\n async extractItem(item, opts) {\n const res = await fetchHtmlFn(item.link, {\n cacheDir: ctx.cacheDir,\n useCache: false,\n authFlow,\n headless: ctx.headless,\n proxy,\n });\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`默认正文提取失败: HTTP ${res.status} ${res.statusText} for ${item.link}`);\n }\n const extracted = await extractHtml(res.body, {\n url: res.finalUrl ?? item.link,\n cacheDir: ctx.cacheDir ?? undefined,\n mode: \"readability\",\n useCache: true,\n cacheKey: opts?.cacheKey,\n });\n const pubDate =\n extracted.pubDate != null\n ? typeof extracted.pubDate === \"string\"\n ? new Date(extracted.pubDate)\n : extracted.pubDate\n : item.pubDate;\n return {\n ...item,\n author: normalizeAuthor(extracted.author ?? item.author),\n title: extracted.title ?? item.title,\n summary: extracted.summary ?? item.summary,\n content: extracted.content ?? item.content,\n pubDate,\n };\n },\n };\n}\n\n\n/** 将 Site 插件包装为统一 Source 接口 */\nexport function createWebSource(site: Site): Source {\n const authFlow = toAuthFlow(site);\n return {\n id: site.id,\n name: site.name,\n pattern: site.listUrlPattern,\n priority: 50,\n refreshInterval: site.refreshInterval ?? undefined,\n proxy: site.proxy ?? undefined,\n preCheck: authFlow\n ? async (ctx: SourceContext) => {\n if (!ctx.cacheDir) return;\n const passed = await preCheckAuth(authFlow, ctx.cacheDir, {\n proxy: ctx.proxy,\n headless: ctx.headless,\n });\n if (!passed) throw new AuthRequiredError(`站点 ${site.id} 需要登录,请先执行 ensureAuth`);\n }\n : undefined,\n async fetchItems(sourceId: string, ctx: SourceContext): Promise<FeedItem[]> {\n return site.fetchItems(sourceId, buildSiteContext(site, ctx));\n },\n };\n}\n\n\n/** 通用 WebSource:兜底匹配所有 http/https URL,使用浏览器抓取 + LLM 解析 */\nexport const genericWebSource: Source = {\n id: \"generic\",\n pattern: /^https?:\\/\\//,\n priority: 200,\n async fetchItems(sourceId: string, ctx: SourceContext): Promise<FeedItem[]> {\n const res = await fetchHtmlFn(sourceId, {\n cacheDir: ctx.cacheDir,\n useCache: true,\n headless: ctx.headless,\n proxy: ctx.proxy,\n });\n // 304 Not Modified:浏览器用缓存,page.content() 仍会返回完整 HTML,视为成功\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`抓取失败: HTTP ${res.status} ${res.statusText}`);\n }\n const parsed = await parseHtml(res.body, {\n url: res.finalUrl ?? sourceId,\n });\n return parsed.items;\n },\n};\n\n\n/** 保存已加载的 Site 对象(供 auth 路由、调试路由直接访问) */\nconst loadedSites: Site[] = [];\n\n\n/** 更新已加载站点列表(由 sources/index.ts 调用) */\nexport function setLoadedSites(sites: Site[]): void {\n loadedSites.length = 0;\n loadedSites.push(...sites);\n}\n\n\n/** 根据 id 获取底层 Site(用于 auth 路由) */\nexport function getWebSite(id: string): Site | undefined {\n return loadedSites.find((s) => s.id === id);\n}\n\n\n/** 获取所有已加载的 Site(用于插件列表 API) */\nexport function getPluginSites(): Site[] {\n return loadedSites.filter((s) => s.id !== \"generic\");\n}\n\n\n/** 根据 URL 获取最具体匹配的 Site(用于调试路由) */\nexport function getBestSite(url: string): Site | undefined {\n return getSiteByUrl(url, loadedSites);\n}\n\n\nexport type { Site, SiteContext } from \"./site.js\";\nexport { toAuthFlow, computeSpecificity } from \"./site.js\";\nexport { loadPlugins } from \"../../../plugins/loader.js\";\n","// 宿主应用解析的常用依赖,注入到 SourceContext / SiteContext.deps\r\n// 用户目录下的 .rssany/plugins 内脚本无法解析应用 node_modules,须通过 ctx.deps 使用\r\n\r\nimport { parse, NodeType } from \"node-html-parser\";\r\nimport { createHash } from \"node:crypto\";\r\nimport RssParser from \"rss-parser\";\r\nimport { HttpsProxyAgent } from \"https-proxy-agent\";\r\nimport { ImapFlow } from \"imapflow\";\r\nimport { simpleParser } from \"mailparser\";\r\nimport { logger } from \"../core/logger/index.js\";\r\n\r\nexport const PLUGIN_HOST_DEPS = {\r\n parseHtml: parse,\r\n NodeType,\r\n createHash,\r\n RssParser,\r\n HttpsProxyAgent,\r\n ImapFlow,\r\n simpleParser,\r\n logger,\r\n} as const;\r\n\r\nexport type PluginHostDeps = typeof PLUGIN_HOST_DEPS;\r\n","import { PLUGIN_HOST_DEPS } from \"../../plugins/hostDeps.js\";\r\nimport type { SourceContext } from \"./types.js\";\r\nimport { fetchHtml as fetchHtmlFn } from \"./web/fetcher/index.js\";\r\n\r\n/** 构造带 deps 的信源上下文(抓取、preCheck、插件 fetchItems 均须使用) */\r\nexport function buildSourceContext(partial: {\r\n cacheDir?: string;\r\n headless?: boolean;\r\n proxy?: string;\r\n}): SourceContext {\r\n const { cacheDir, headless, proxy } = partial;\r\n return {\r\n ...partial,\r\n deps: PLUGIN_HOST_DEPS,\r\n async fetchHtml(url, opts) {\r\n const res = await fetchHtmlFn(url, {\r\n cacheDir,\r\n useCache: false,\r\n headless,\r\n proxy,\r\n waitAfterLoadMs: opts?.waitMs,\r\n purify: opts?.purify,\r\n waitForSelector: opts?.waitForSelector,\r\n waitForSelectorTimeoutMs: opts?.waitForSelectorTimeoutMs,\r\n useHttpResponseBody: opts?.useHttpResponseBody,\r\n });\r\n return { html: res.body, finalUrl: res.finalUrl ?? url, status: res.status };\r\n },\r\n };\r\n}\r\n","// 统一信源注册表:汇聚 WebSource 插件、Source 插件(RSS/Email)、genericWebSource,提供 getSource 查找入口\n\nimport { createWebSource, genericWebSource, setLoadedSites } from \"./web/index.js\";\nimport { loadSiteAndSourcePlugins } from \"../../plugins/loader.js\";\nimport type { Source } from \"./types.js\";\nimport { logger } from \"../../core/logger/index.js\";\nexport { buildSourceContext } from \"./context.js\";\n\n/** 所有已注册的信源(按 priority 排序后迭代匹配) */\nexport const registeredSources: Source[] = [];\n\n/** 将字符串 URL 模式转为正则:{placeholder} 匹配单个路径段,末尾允许 query */\nfunction sourcePatternToRegex(pattern: string | RegExp): RegExp {\n if (pattern instanceof RegExp) return pattern;\n const pathOnly = pattern.split(\"?\")[0];\n const pl = \"<<<__PL__>>>\";\n const escaped = pathOnly\n .replace(/\\{[^}]*\\}/g, pl)\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => \"\\\\\" + c)\n .replace(new RegExp(pl.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\"), \"[^/]+\");\n return new RegExp(\"^\" + escaped + \"(\\\\?.*)?$\");\n}\n\n\n/** 根据 sourceId 查找匹配度最高的 Source;按 priority 升序,优先用 match 否则用 pattern */\nexport function getSource(sourceId: string): Source {\n for (const source of registeredSources) {\n const matches = source.match ? source.match(sourceId) : (() => {\n try {\n return sourcePatternToRegex(source.pattern).test(sourceId);\n } catch {\n return false;\n }\n })();\n if (matches) return source;\n }\n return genericWebSource;\n}\n\n\n/** 根据 id 精确查找 Source(用于内部调试) */\nexport function getSourceById(id: string): Source | undefined {\n return registeredSources.find((s) => s.id === id);\n}\n\n\n/** 初始化所有信源:加载 sources 插件、构建注册表(pipeline 为固定流程,见 app/pipeline/) */\nexport async function initSources(): Promise<void> {\n const siteResult = await loadSiteAndSourcePlugins();\n const { sites, sources: sourcePlugins } = siteResult;\n setLoadedSites(sites);\n registeredSources.length = 0;\n const webSources = sites.map((s) => createWebSource(s));\n const all: Source[] = [\n ...sourcePlugins,\n ...webSources,\n genericWebSource,\n ];\n all.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));\n registeredSources.push(...all);\n logger.info(\"scheduler\", \"信源已注册\", {\n total: registeredSources.length,\n siteCount: sites.length,\n sourcePluginCount: sourcePlugins.length,\n });\n}\n","// 订阅配置类型:一个订阅由多个信源组成,支持网页、RSS、邮件、API 等多种类型\n\nimport type { RefreshInterval } from \"../../utils/refreshInterval.js\";\n\n\n/** 信源类型枚举 */\nexport type SourceType = \"web\" | \"rss\" | \"email\";\n\n\n/**\n * 单个信源配置\n *\n * ref 格式示例:\n * web / rss → https://sspai.com/feed\n * → https://xiaohongshu.com/user/profile/xxx\n * email → imaps://user:password@imap.gmail.com:993/INBOX\n * → imap://user:password@imap.qq.com:143/INBOX\n * → imaps://me%40gmail.com:app-password@imap.gmail.com/INBOX\n * (用户名含 @ 时用 %40 编码;Gmail 需使用「应用专用密码」)\n */\nexport interface SubscriptionSource {\n /** 信源标识符:HTTP(S) URL、imaps?:// 连接串、或插件自定义协议(如 lingowhale://) */\n ref: string;\n /** 信源类型(省略时由 getSource 自动识别) */\n type?: SourceType;\n /** 显示名称,用于界面展示和报错信息(省略则显示 ref) */\n label?: string;\n /** 简短描述,用于界面展示信源用途或内容说明 */\n description?: string;\n /** 单源有效时间窗口覆盖:优先级高于 Source 声明;不填则使用 Source 声明 */\n refresh?: RefreshInterval;\n /** 单源 cron 表达式(如 \"0 9 * * *\" 每天 9:00);有值时优先于 refresh */\n cron?: string;\n /** 单源代理覆盖:优先级高于 Source.proxy;不填则使用 Source.proxy 或 env HTTP_PROXY */\n proxy?: string;\n /** 信源权重,用于排序与优先级控制;默认 0,值越大优先级越高 */\n weight?: number;\n}\n\n\n/** sources.json 标准格式:扁平信源列表,无频道/订阅层级 */\nexport interface SourcesFile {\n /** 所有要抓取的信源 */\n sources: SubscriptionSource[];\n}\n\n\n/** 从旧格式({ url })或新格式({ ref })中提取信源标识符,确保向后兼容 */\nexport function resolveRef(src: SubscriptionSource | { url?: string; ref?: string }): string {\n return (src as SubscriptionSource).ref ?? (src as { url?: string }).url ?? \"\";\n}\n","// 全局 HTTP(S) 代理:读写 .rssany/config.json 的 globalProxy;与单源代理合并见 subscription/getEffectiveProxyForListUrl\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { CONFIG_PATH } from \"./paths.js\";\r\n\r\nexport async function readGlobalProxyFromConfig(): Promise<string | undefined> {\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n const j = JSON.parse(raw) as { globalProxy?: unknown };\r\n if (typeof j.globalProxy === \"string\") {\r\n const t = j.globalProxy.trim();\r\n return t.length > 0 ? t : undefined;\r\n }\r\n } catch {\r\n /* 无文件或解析失败 */\r\n }\r\n return undefined;\r\n}\r\n\r\n/** 写入或清空(空串则删除键) */\r\nexport async function saveGlobalProxyToConfig(proxy: string): Promise<void> {\r\n let root: Record<string, unknown> = {};\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n root = JSON.parse(raw) as Record<string, unknown>;\r\n } catch {\r\n //\r\n }\r\n const t = proxy.trim();\r\n if (t.length === 0) {\r\n delete root.globalProxy;\r\n } else {\r\n root.globalProxy = t;\r\n }\r\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\r\n}\r\n\r\n/** 插件 Site 声明的 proxy 优先于 config 全局代理 */\r\nexport async function resolveProxyForSite(site: { proxy?: string }): Promise<string | undefined> {\r\n const s = site.proxy?.trim();\r\n if (s) return s;\r\n return readGlobalProxyFromConfig();\r\n}\r\n","// 信源配置模块:读取 .rssany/sources.json(扁平信源列表,供 scheduler 使用)\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { SOURCES_CONFIG_PATH } from \"../../config/paths.js\";\r\nimport type { SubscriptionSource, SourcesFile } from \"./types.js\";\r\nimport { resolveRef } from \"./types.js\";\r\nimport type { Source } from \"../sources/types.js\";\r\nimport { readGlobalProxyFromConfig } from \"../../config/globalProxy.js\";\r\n\r\nexport type { SubscriptionSource, SourcesFile } from \"./types.js\";\r\nexport { resolveRef } from \"./types.js\";\r\n\r\n\r\n/** 从 .rssany/sources.json 加载;支持新格式 { sources: [] } 与旧格式 { [id]: { sources: [] } },统一扁平为 SubscriptionSource[] */\r\nasync function loadSourcesFile(): Promise<SubscriptionSource[]> {\r\n try {\r\n const raw = await readFile(SOURCES_CONFIG_PATH, \"utf-8\");\r\n const parsed = JSON.parse(raw) as unknown;\r\n if (!parsed || typeof parsed !== \"object\") return [];\r\n if (Array.isArray((parsed as SourcesFile).sources)) {\r\n return (parsed as SourcesFile).sources.filter((s) => resolveRef(s));\r\n }\r\n const entries = Object.values(parsed as Record<string, { sources?: SubscriptionSource[] }>);\r\n const list: SubscriptionSource[] = [];\r\n for (const entry of entries) {\r\n if (entry && Array.isArray(entry.sources)) {\r\n for (const s of entry.sources) {\r\n if (resolveRef(s)) list.push(s);\r\n }\r\n }\r\n }\r\n return list;\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n\r\n/** 获取所有信源(扁平列表),供 scheduler 使用 */\r\nexport async function getAllSources(): Promise<SubscriptionSource[]> {\r\n return loadSourcesFile();\r\n}\r\n\r\n/** 去重后的 ref 列表(与 sources.json 一致),供 Feed / 聚合查询使用 */\r\nexport async function getAllSubscriptionRefs(): Promise<string[]> {\r\n const list = await loadSourcesFile();\r\n const seen = new Set<string>();\r\n const out: string[] = [];\r\n for (const s of list) {\r\n const r = resolveRef(s);\r\n if (r && !seen.has(r)) {\r\n seen.add(r);\r\n out.push(r);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n\r\n/** 将扁平列表写回 sources.json(新格式);供 raw API 写入 */\r\nexport async function saveSourcesFile(sources: SubscriptionSource[]): Promise<void> {\r\n await writeFile(\r\n SOURCES_CONFIG_PATH,\r\n JSON.stringify({ sources }, null, 2) + \"\\n\",\r\n \"utf-8\"\r\n );\r\n}\r\n\r\n\r\n/**\r\n * 代理优先级:sources.json 单源 → 插件 Source.proxy → config.json globalProxy →(未写入则由 fetcher resolveProxy 使用 HTTP_PROXY)\r\n */\r\nexport async function getEffectiveProxyForListUrl(listUrl: string, source: Source): Promise<string | undefined> {\r\n const list = await getAllSources();\r\n const sub = list.find((s) => resolveRef(s) === listUrl);\r\n const fromSub = sub?.proxy?.trim();\r\n if (fromSub) return fromSub;\r\n const fromPlugin = source.proxy?.trim();\r\n if (fromPlugin) return fromPlugin;\r\n return readGlobalProxyFromConfig();\r\n}\r\n\r\n/** 读取 sources.json 原始内容(用于 GET /api/sources/raw);旧格式会转为新格式返回 */\r\nexport async function getSourcesRaw(): Promise<string> {\r\n try {\r\n const raw = await readFile(SOURCES_CONFIG_PATH, \"utf-8\");\r\n const parsed = JSON.parse(raw) as unknown;\r\n if (!parsed || typeof parsed !== \"object\") return JSON.stringify({ sources: [] }, null, 2);\r\n if (Array.isArray((parsed as SourcesFile).sources)) {\r\n return raw;\r\n }\r\n const list = await loadSourcesFile();\r\n return JSON.stringify({ sources: list }, null, 2);\r\n } catch {\r\n return JSON.stringify({ sources: [] }, null, 2);\r\n }\r\n}\r\n","/**\r\n * Pipeline 步骤:LLM 内容质量过滤\r\n *\r\n * 判定为低质量(无实质信息、垃圾占位、纯广告等)时标记条目为丢弃;\r\n * feeder 会从数据库删除该条并不再输出到 RSS。\r\n *\r\n * 需在 .rssany/config.json 的 pipeline.steps 中启用,且配置 OPENAI_API_KEY 等 LLM 环境变量。\r\n * 建议放在 tagger / translator 之前以省 token。\r\n */\r\n\r\nimport type { FeedItem } from \"../types/feedItem.js\";\r\nimport { markPipelineDrop } from \"../types/feedItem.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\n\r\nconst MAX_BODY_CHARS = 4000;\r\n\r\nconst SYSTEM = `你是信息质量评估助手。根据标题、摘要与正文片段,判断该条是否值得保留在订阅信息流中。\r\n- 保留(keep: true):有实质信息、观点、技术内容、新闻价值,或对读者有参考意义。\r\n- 过滤(keep: false):明显垃圾广告、纯日期/站务占位、无意义一句话、乱码或空白、纯导航无内容、重复无信息量的模板句。\r\n- 只输出 JSON,格式:{\"keep\": true 或 false, \"reason\": \"一句话中文理由\"}`;\r\n\r\nexport interface QualityFilterContext {\r\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\r\n}\r\n\r\nfunction combinedForJudge(item: FeedItem): string {\r\n const title = (item.title ?? \"\").trim();\r\n const summary = (item.summary ?? \"\").trim();\r\n let body = (item.content ?? \"\").trim();\r\n if (body.length > MAX_BODY_CHARS) body = body.slice(0, MAX_BODY_CHARS) + \"\\n\\n[... 正文已截断 ...]\";\r\n const parts: string[] = [];\r\n if (title) parts.push(`标题:\\n${title}`);\r\n if (summary) parts.push(`摘要:\\n${summary}`);\r\n if (body) parts.push(`正文:\\n${body}`);\r\n return parts.join(\"\\n\\n---\\n\\n\");\r\n}\r\n\r\n/** 无 LLM 时跳过 */\r\nexport function qualityFilterMatch(_item: FeedItem, ctx: QualityFilterContext): boolean {\r\n return !!ctx.llm;\r\n}\r\n\r\nexport async function runQualityFilter(item: FeedItem, ctx: QualityFilterContext): Promise<FeedItem> {\r\n if (!ctx.llm) return item;\r\n\r\n const text = combinedForJudge(item);\r\n if (!text.trim()) return item;\r\n\r\n const prompt = `${SYSTEM}\\n\\n请评估以下条目:\\n\\n${text}`;\r\n\r\n let res: Record<string, unknown>;\r\n try {\r\n res = await ctx.llm.chatJson(prompt, undefined, { maxTokens: 256, debugLabel: \"qualityFilter\" });\r\n } catch {\r\n return item;\r\n }\r\n\r\n const isKeep = res.keep === true || res.keep === \"true\";\r\n const isDrop = res.keep === false || res.keep === \"false\";\r\n if (isKeep) return item;\r\n if (!isDrop) return item;\r\n\r\n const reason = typeof res.reason === \"string\" ? res.reason.trim() : \"\";\r\n logger.info(\"pipeline\", \"质量过滤移除条目\", { link: item.link, reason: reason || undefined });\r\n return markPipelineDrop(item);\r\n}\r\n","/**\n * Pipeline 步骤:LLM 自动打标签\n *\n * 从系统标签库(话题 tags 并集)中选取匹配的标签写入 item.tags。\n * 模型建议的 tag 若不在系统标签库中则忽略。\n *\n * 需要配置 OPENAI_API_KEY(或等效 LLM 环境变量)。\n */\n\nimport type { FeedItem } from \"../types/feedItem.js\";\n\nconst SYSTEM = `你是一个信息分类助手。根据文章标题和摘要,从候选标签库中选出最匹配的标签,最多 5 个。\n- 只能使用候选标签库中已有的标签,不要输出库中不存在的标签。\n- 如果没有合适的标签,输出空数组 {\"tags\": []},不要硬选不相关的标签。\n- 只输出 JSON,格式:{\"tags\": [\"tag1\", \"tag2\", ...]}`;\n\nexport interface TaggerContext {\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\n db?: { getSystemTags: () => Promise<string[]> };\n}\n\n/** 跳过已有 tags 的条目;无 LLM 则跳过 */\nexport function taggerMatch(item: FeedItem, ctx: TaggerContext): boolean {\n return !item.tags?.length && !!ctx.llm;\n}\n\nexport async function runTagger(item: FeedItem, ctx: TaggerContext): Promise<FeedItem> {\n if (!ctx.db) return item;\n const systemTags = await ctx.db.getSystemTags();\n if (!systemTags.length || !ctx.llm) return item;\n\n const candidateList = `候选标签库(只能从中选取):\\n${systemTags.join(\", \")}\\n\\n`;\n const prompt = `${SYSTEM}\\n\\n${candidateList}文章标题:${item.title ?? \"\"}\\n文章摘要:${item.summary ?? item.content?.slice(0, 300) ?? \"(无摘要)\"}`;\n\n let suggested: string[];\n try {\n const res = await ctx.llm.chatJson(prompt, undefined, { maxTokens: 256, debugLabel: \"tagger\" });\n suggested = Array.isArray(res.tags) ? res.tags.filter((t): t is string => typeof t === \"string\") : [];\n } catch {\n return item;\n }\n\n if (!suggested.length) return item;\n\n const set = new Set(systemTags.map((t) => t.toLowerCase()));\n const confirmed = suggested.filter((t) => set.has(t.toLowerCase()));\n\n if (confirmed.length) {\n item.tags = [...new Set([...(item.tags ?? []), ...confirmed])];\n }\n\n return item;\n}\n","/**\r\n * Pipeline 步骤:LLM 翻译为中文\r\n *\r\n * 将条目的 title、summary、content 翻译为中文,写入 item.translations[\"zh-CN\"]。\r\n * 路由支持 lng=zh-CN 时可据此返回译文。\r\n *\r\n * 需要配置 OPENAI_API_KEY(或等效 LLM 环境变量)。\r\n *\r\n * 粗判已为中文的条目不调 LLM(省 token);展示时 lng=zh-CN 无译文则回退原文。\r\n */\r\n\r\nimport type { FeedItem } from \"../types/feedItem.js\";\r\n\r\nconst ZH_CN = \"zh-CN\";\r\nconst MAX_CONTENT_CHARS = 6000;\r\n/** 用于语言检测的正文上限(与 runTranslator 截断策略大致一致) */\r\nconst DETECT_CONTENT_PREFIX = 2000;\r\n\r\nconst SYSTEM = `你是一个专业翻译助手。将用户提供的英文(或其他语言)内容翻译为简体中文。\r\n- 保持专业、准确、流畅。\r\n- 若原文已是中文,则保持原样或轻微润色。\r\n- 只输出 JSON,格式:{\"title\": \"译文标题\", \"summary\": \"译文摘要\", \"content\": \"译文正文\"}\r\n- 若某字段为空或用户未提供,对应输出空字符串 \"\"。`;\r\n\r\nexport interface TranslatorContext {\r\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\r\n}\r\n\r\nfunction combinedTextForDetection(item: FeedItem): string {\r\n const title = (item.title ?? \"\").trim();\r\n const summary = (item.summary ?? item.content?.slice(0, 500) ?? \"\").trim();\r\n const content = (item.content ?? \"\").trim().slice(0, DETECT_CONTENT_PREFIX);\r\n return `${title}\\n${summary}\\n${content}`;\r\n}\r\n\r\n/**\r\n * 粗判主体是否已是中文(汉字占比高),用于跳过 LLM。\r\n * 含日文假名 / 韩文谚文时不视为「已是中文」,避免误跳日文/韩文条目。\r\n */\r\nexport function isLikelyChineseContent(text: string): boolean {\r\n const t = text.trim();\r\n if (t.length < 8) return false;\r\n if (/[\\u3040-\\u309f\\u30a0-\\u30ff]/.test(t)) return false;\r\n if (/[\\uac00-\\ud7af]/.test(t)) return false;\r\n\r\n const cjk = (t.match(/[\\u4e00-\\u9fff\\u3400-\\u4dbf]/g) ?? []).length;\r\n const latin = (t.match(/[A-Za-z]/g) ?? []).length;\r\n const letterish = cjk + latin;\r\n if (letterish < 12) return false;\r\n return cjk / letterish >= 0.55;\r\n}\r\n\r\n/** 跳过已有 zh-CN 译文、无 LLM、或粗判已为中文的条目 */\r\nexport function translatorMatch(item: FeedItem, ctx: TranslatorContext): boolean {\r\n const hasZh = item.translations?.[ZH_CN];\r\n if (hasZh || !ctx.llm) return false;\r\n if (isLikelyChineseContent(combinedTextForDetection(item))) return false;\r\n return true;\r\n}\r\n\r\nexport async function runTranslator(item: FeedItem, ctx: TranslatorContext): Promise<FeedItem> {\r\n if (!ctx.llm) return item;\r\n\r\n const title = (item.title ?? \"\").trim();\r\n const summary = (item.summary ?? item.content?.slice(0, 500) ?? \"\").trim();\r\n const content = (item.content ?? \"\").trim();\r\n const contentTruncated =\r\n content.length > MAX_CONTENT_CHARS ? content.slice(0, MAX_CONTENT_CHARS) + \"\\n\\n[... 内容已截断 ...]\" : content;\r\n\r\n if (!title && !summary && !content) return item;\r\n\r\n const parts: string[] = [];\r\n if (title) parts.push(`标题:\\n${title}`);\r\n if (summary) parts.push(`摘要:\\n${summary}`);\r\n if (contentTruncated) parts.push(`正文:\\n${contentTruncated}`);\r\n\r\n const prompt = `${SYSTEM}\\n\\n请翻译以下内容:\\n\\n${parts.join(\"\\n\\n---\\n\\n\")}`;\r\n\r\n let res: Record<string, unknown>;\r\n try {\r\n res = await ctx.llm.chatJson(prompt, undefined, {\r\n maxTokens: Math.min(8192, Math.ceil((title.length + summary.length + contentTruncated.length) * 1.5)),\r\n debugLabel: \"translator\",\r\n });\r\n } catch {\r\n return item;\r\n }\r\n\r\n const tTitle = typeof res.title === \"string\" ? res.title.trim() : \"\";\r\n const tSummary = typeof res.summary === \"string\" ? res.summary.trim() : \"\";\r\n const tContent = typeof res.content === \"string\" ? res.content.trim() : \"\";\r\n\r\n if (!tTitle && !tSummary && !tContent) return item;\r\n\r\n item.translations = item.translations ?? {};\r\n item.translations[ZH_CN] = {\r\n title: tTitle || undefined,\r\n summary: tSummary || undefined,\r\n content: tContent || undefined,\r\n };\r\n\r\n return item;\r\n}\r\n","/**\r\n * Pipeline 配置:从 .rssany/config.json 的 pipeline 块读取\r\n *\r\n * 格式:{ \"pipeline\": { \"steps\": [{ \"id\": \"qualityFilter\", \"enabled\": false }, ...] } }\r\n * - steps 数组顺序即执行顺序,enabled: false 的步骤跳过\r\n */\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { CONFIG_PATH } from \"../config/paths.js\";\r\n\r\nexport interface PipelineStepConfig {\r\n id: string;\r\n enabled: boolean;\r\n}\r\n\r\nexport interface PipelineConfig {\r\n steps: PipelineStepConfig[];\r\n}\r\n\r\n/** 默认配置(入库前) */\r\nexport const DEFAULT_PIPELINE_STEPS: PipelineStepConfig[] = [\r\n { id: \"qualityFilter\", enabled: false },\r\n { id: \"tagger\", enabled: false },\r\n { id: \"translator\", enabled: false },\r\n];\r\n\r\n/** 可用步骤 id */\r\nexport const PIPELINE_STEP_IDS = [\"qualityFilter\", \"tagger\", \"translator\"] as const;\r\n\r\nfunction parseSteps(rawSteps: unknown[]): PipelineStepConfig[] {\r\n const steps: PipelineStepConfig[] = [];\r\n const seen = new Set<string>();\r\n for (const s of rawSteps) {\r\n if (s && typeof s === \"object\" && typeof (s as { id?: unknown }).id === \"string\") {\r\n const obj = s as { id: string; enabled?: unknown };\r\n const id = obj.id.trim();\r\n if (!id || seen.has(id)) continue;\r\n seen.add(id);\r\n const enabled = obj.enabled;\r\n steps.push({\r\n id,\r\n enabled: enabled !== false && enabled !== 0,\r\n });\r\n }\r\n }\r\n return steps;\r\n}\r\n\r\n/** 与默认步骤表对齐:保证 JSON 里出现的步骤 id 齐全、顺序与默认一致,已有项保留 enabled */\r\nfunction mergeWithDefaultSteps(userSteps: PipelineStepConfig[]): PipelineStepConfig[] {\r\n const map = new Map(userSteps.map((s) => [s.id, s]));\r\n return DEFAULT_PIPELINE_STEPS.map((def) => {\r\n const u = map.get(def.id);\r\n return { id: def.id, enabled: u ? u.enabled : def.enabled };\r\n });\r\n}\r\n\r\n/** 读取 pipeline 配置,缺失时返回默认;已存在的 config 会与默认步骤合并,使新步骤(如 qualityFilter)始终出现在列表中 */\r\nexport async function loadPipelineConfig(): Promise<PipelineConfig> {\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n const parsed = JSON.parse(raw) as { pipeline?: { steps?: unknown[] } };\r\n const rawSteps = Array.isArray(parsed?.pipeline?.steps) ? parsed.pipeline.steps : [];\r\n const steps = mergeWithDefaultSteps(parseSteps(rawSteps));\r\n if (steps.length > 0) return { steps };\r\n } catch {\r\n // 文件不存在或解析失败\r\n }\r\n return { steps: [...DEFAULT_PIPELINE_STEPS] };\r\n}\r\n\r\n/** 保存 pipeline 配置到 config.json(合并其他块,不覆盖) */\r\nexport async function savePipelineConfig(config: PipelineConfig): Promise<void> {\r\n let root: Record<string, unknown> = {};\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n root = JSON.parse(raw) as Record<string, unknown>;\r\n } catch {\r\n // 文件不存在或解析失败,使用空对象\r\n }\r\n root.pipeline = { steps: config.steps };\r\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2), \"utf-8\");\r\n}\r\n","/**\r\n * Pipeline:入库前固定处理链(翻译、打标签等)\r\n *\r\n * 与 plugins 同级别,作为固定流程而非插件系统。\r\n * 步骤开关与排序由 .rssany/config.json 的 pipeline.steps 配置。\r\n */\r\n\r\nimport type { FeedItem } from \"../types/feedItem.js\";\r\nimport { qualityFilterMatch, runQualityFilter } from \"./qualityFilter.js\";\r\nimport { taggerMatch, runTagger } from \"./tagger.js\";\r\nimport { translatorMatch, runTranslator } from \"./translator.js\";\r\nimport { loadPipelineConfig } from \"./config.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\n\r\nexport interface PipelineContext {\r\n sourceUrl?: string;\r\n llm?: {\r\n chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>>;\r\n chatText: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<string>;\r\n };\r\n db?: { getSystemTags: () => Promise<string[]> };\r\n}\r\n\r\n/** Pipeline 步骤注册表 */\r\nconst STEP_REGISTRY: Record<string, { match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }> = {\r\n qualityFilter: { match: qualityFilterMatch, run: runQualityFilter },\r\n tagger: { match: taggerMatch, run: runTagger },\r\n translator: { match: translatorMatch, run: runTranslator },\r\n};\r\n\r\n/** 根据配置解析出要执行的步骤(按配置顺序,仅启用且存在的) */\r\nasync function getResolvedSteps(): Promise<Array<{ id: string; match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }>> {\r\n const config = await loadPipelineConfig();\r\n const out: Array<{ id: string; match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }> = [];\r\n for (const { id, enabled } of config.steps) {\r\n if (!enabled) continue;\r\n const step = STEP_REGISTRY[id];\r\n if (!step) {\r\n logger.debug(\"pipeline\", \"未知步骤已跳过\", { id });\r\n continue;\r\n }\r\n out.push({ id, ...step });\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * 对单条条目执行 pipeline\r\n */\r\nexport async function runPipeline(\r\n item: FeedItem,\r\n ctx: PipelineContext\r\n): Promise<FeedItem> {\r\n const steps = await getResolvedSteps();\r\n let current = item;\r\n for (const step of steps) {\r\n if (!step.match(current, ctx)) continue;\r\n try {\r\n current = await step.run(current, ctx);\r\n } catch (err) {\r\n logger.warn(\"pipeline\", \"步骤执行失败\", {\r\n stepId: step.id,\r\n item_url: item.link,\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n return current;\r\n}\r\n\r\n/**\r\n * 对多条条目执行 pipeline\r\n */\r\nexport async function runPipelineBatch(\r\n items: FeedItem[],\r\n ctx: PipelineContext\r\n): Promise<FeedItem[]> {\r\n const out: FeedItem[] = [];\r\n for (let i = 0; i < items.length; i++) {\r\n out.push(await runPipeline(items[i], ctx));\r\n }\r\n return out;\r\n}\r\n","// 将频道 + 条目构建为 RSS 2.0 XML\n\nimport type { RssChannel, RssEntry } from \"./types.js\";\n\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n\nfunction isoToRfc822(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toUTCString();\n}\n\n\nfunction buildItem(entry: RssEntry): string {\n const title = escapeXml(entry.title ?? \"\");\n const link = escapeXml(entry.link ?? \"\");\n const descRaw = entry.description ?? \"\";\n const desc = descRaw.includes(\"<\") || descRaw.includes(\">\")\n ? `<![CDATA[${descRaw.replace(/\\]\\]>/g, \"]]]]><![CDATA[>\")}]]>`\n : escapeXml(descRaw);\n const pubDate = entry.published ? escapeXml(isoToRfc822(entry.published)) : \"\";\n const guid = entry.guid ?? entry.link ?? \"\";\n const guidEscaped = escapeXml(guid);\n let buf = ` <item>\\n <title>${title}</title>\\n <link>${link}</link>\\n <description>${desc}</description>\\n`;\n if (pubDate) buf += ` <pubDate>${pubDate}</pubDate>\\n`;\n if (entry.imageUrl?.trim()) {\n const encUrl = escapeXml(entry.imageUrl.trim());\n const encType = escapeXml(entry.imageType?.trim() || \"image/jpeg\");\n buf += ` <enclosure url=\"${encUrl}\" length=\"0\" type=\"${encType}\"/>\\n`;\n }\n buf += ` <guid isPermaLink=\"true\">${guidEscaped}</guid>\\n`;\n buf += ` </item>\\n`;\n return buf;\n}\n\n\nexport function buildRssXml(channel: RssChannel, entries: RssEntry[]): string {\n const title = escapeXml(channel.title);\n const link = escapeXml(channel.link);\n const desc = escapeXml(channel.description ?? \"\");\n const lang = escapeXml(channel.language ?? \"zh-CN\");\n const now = new Date().toUTCString();\n const items = entries.map(buildItem).join(\"\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\">\n <channel>\n <title>${title}</title>\n <link>${link}</link>\n <description>${desc}</description>\n <language>${lang}</language>\n <lastBuildDate>${now}</lastBuildDate>\n\n${items} </channel>\n</rss>`;\n}\n","// 事件总线:进程内单例 EventEmitter,供 db 层 emit、HTTP 层 subscribe\n\nimport { EventEmitter } from \"node:events\";\n\n\n/** feed:updated 事件的载荷 */\nexport interface FeedUpdatedEvent {\n sourceUrl: string;\n newCount: number;\n}\n\n\n/** 全局单例事件总线,setMaxListeners 避免 SSE 多连接时的警告 */\nexport const eventBus = new EventEmitter();\neventBus.setMaxListeners(200);\n\n\n/** 向事件总线广播新条目事件 */\nexport function emitFeedUpdated(payload: FeedUpdatedEvent): void {\n eventBus.emit(\"feed:updated\", payload);\n}\n\n\n/** 订阅 feed:updated 事件,返回取消订阅函数 */\nexport function onFeedUpdated(fn: (e: FeedUpdatedEvent) => void): () => void {\n eventBus.on(\"feed:updated\", fn);\n return () => eventBus.off(\"feed:updated\", fn);\n}\n","// 投递目标:.rssany/config.json 的 deliver.gateway / deliver.token\r\n// Gateway 基址示例:https://agidaily.cc/api/gateway\r\n// 出站固定为:POST {gateway}/items、POST {gateway}/sources、连通性测试 POST {gateway}/test\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { CONFIG_PATH } from \"./paths.js\";\r\n\r\nexport interface DeliverConfig {\r\n /** 基址,不含 /items;如 https://agidaily.cc/api/gateway */\r\n gateway: string;\r\n /** 与下游 Gateway(如 agidaily `data/token.txt`)一致:非空时请求头带 `Authorization: Bearer <token>` */\r\n token: string;\r\n}\r\n\r\ntype DeliverFileShape = {\r\n deliver?: { gateway?: string; url?: string; sourcesUrl?: string; token?: string };\r\n};\r\n\r\n/** 从旧版 deliver.url(…/items)或 deliver.sourcesUrl 推断 gateway 基址 */\r\nfunction migrateGatewayFromFile(j: DeliverFileShape): string {\r\n const g = j?.deliver?.gateway?.trim();\r\n if (g) return g;\r\n const u = j?.deliver?.url?.trim() ?? \"\";\r\n if (u) {\r\n return u\r\n .replace(/\\/items\\/?$/i, \"\")\r\n .replace(/\\/+$/, \"\")\r\n .trim();\r\n }\r\n const s = j?.deliver?.sourcesUrl?.trim() ?? \"\";\r\n if (s) {\r\n return s\r\n .replace(/\\/sources\\/?$/i, \"\")\r\n .replace(/\\/+$/, \"\")\r\n .trim();\r\n }\r\n return \"\";\r\n}\r\n\r\nexport async function getDeliverConfig(): Promise<DeliverConfig> {\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n const j = JSON.parse(raw) as DeliverFileShape;\r\n const t = j?.deliver?.token;\r\n return {\r\n gateway: migrateGatewayFromFile(j),\r\n token: typeof t === \"string\" ? t.trim() : \"\",\r\n };\r\n } catch {\r\n return { gateway: \"\", token: \"\" };\r\n }\r\n}\r\n\r\n/** 非空 gateway 表示启用条目投递(不影响是否写库) */\r\nexport async function getDeliverUrl(): Promise<string> {\r\n const { gateway } = await getDeliverConfig();\r\n const base = gateway.trim().replace(/\\/+$/, \"\");\r\n return base ? `${base}/items` : \"\";\r\n}\r\n\r\nexport async function saveDeliverConfig(config: DeliverConfig): Promise<void> {\r\n let root: Record<string, unknown> = {};\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n root = JSON.parse(raw) as Record<string, unknown>;\r\n } catch {\r\n // 无文件则新建\r\n }\r\n const gateway = config.gateway.trim();\r\n const token = config.token.trim();\r\n const next: Record<string, unknown> = {};\r\n if (gateway) next.gateway = gateway;\r\n if (token) next.token = token;\r\n root.deliver = next;\r\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\r\n}\r\n","// 将条目 / 信源 POST 到下游 Gateway:路径相对基址固定为 /items、/sources、/test\r\n\r\nimport type { FeedItem } from \"../types/feedItem.js\";\r\nimport { pubDateToIsoOrNull } from \"../types/feedItem.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\n\r\nexport function feedItemsToPayload(items: FeedItem[]): unknown[] {\r\n return items.map((i) => ({\r\n guid: i.guid,\r\n title: i.title,\r\n link: i.link,\r\n pubDate: pubDateToIsoOrNull(i.pubDate) ?? new Date().toISOString(),\r\n author: i.author,\r\n summary: i.summary,\r\n content: i.content,\r\n tags: i.tags,\r\n sourceRef: i.sourceRef,\r\n translations: i.translations,\r\n }));\r\n}\r\n\r\nexport interface PostDeliverOptions {\r\n /** 非空时设置 `Authorization: Bearer <token>`(与 agidaily Gateway 一致) */\r\n bearerToken?: string;\r\n}\r\n\r\n/** Gateway 基址(如 https://agidaily.cc/api/gateway)与路径 items | sources | test 拼接 */\r\nexport function joinGatewayPath(gatewayBase: string, segment: \"items\" | \"sources\" | \"test\"): string {\r\n const base = gatewayBase.trim().replace(/\\/+$/, \"\");\r\n if (!base) return \"\";\r\n return `${base}/${segment}`;\r\n}\r\n\r\n/** POST { sourceRef, items } 到 {gateway}/items */\r\nexport async function postDeliverItems(\r\n url: string,\r\n sourceRef: string,\r\n items: FeedItem[],\r\n options?: PostDeliverOptions,\r\n): Promise<void> {\r\n if (!url.trim() || items.length === 0) return;\r\n const body = JSON.stringify({ sourceRef, items: feedItemsToPayload(items) });\r\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\r\n const t = options?.bearerToken?.trim();\r\n if (t) headers.Authorization = `Bearer ${t}`;\r\n const res = await fetch(url.trim(), {\r\n method: \"POST\",\r\n headers,\r\n body,\r\n signal: AbortSignal.timeout(120_000),\r\n });\r\n if (!res.ok) {\r\n const text = await res.text().catch(() => \"\");\r\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\r\n }\r\n}\r\n\r\nexport async function postDeliverItemsSafe(\r\n url: string,\r\n sourceRef: string,\r\n items: FeedItem[],\r\n options?: PostDeliverOptions,\r\n): Promise<void> {\r\n try {\r\n await postDeliverItems(url, sourceRef, items, options);\r\n } catch (err) {\r\n logger.warn(\"deliver\", \"投递失败\", {\r\n sourceRef,\r\n count: items.length,\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n}\r\n\r\n/** POST 当前 sources.json 正文到 {gateway}/sources */\r\nexport async function postDeliverSources(\r\n url: string,\r\n sourcesJson: string,\r\n options?: PostDeliverOptions,\r\n): Promise<void> {\r\n if (!url.trim() || !sourcesJson.trim()) return;\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json; charset=utf-8\",\r\n };\r\n const t = options?.bearerToken?.trim();\r\n if (t) headers.Authorization = `Bearer ${t}`;\r\n const res = await fetch(url.trim(), {\r\n method: \"POST\",\r\n headers,\r\n body: sourcesJson,\r\n signal: AbortSignal.timeout(120_000),\r\n });\r\n if (!res.ok) {\r\n const text = await res.text().catch(() => \"\");\r\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\r\n }\r\n}\r\n\r\nexport async function postDeliverSourcesSafe(\r\n url: string,\r\n sourcesJson: string,\r\n options?: PostDeliverOptions,\r\n): Promise<void> {\r\n try {\r\n await postDeliverSources(url, sourcesJson, options);\r\n } catch (err) {\r\n logger.warn(\"deliver\", \"信源配置投递失败\", {\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n}\r\n\r\n/** POST 连通性测试体到 {gateway}/test(由路由组装的 JSON) */\r\nexport async function postDeliverGatewayTest(\r\n gateway: string,\r\n body: unknown,\r\n options?: PostDeliverOptions,\r\n): Promise<void> {\r\n const url = joinGatewayPath(gateway, \"test\");\r\n if (!url) throw new Error(\"gateway 不能为空\");\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json; charset=utf-8\",\r\n };\r\n const t = options?.bearerToken?.trim();\r\n if (t) headers.Authorization = `Bearer ${t}`;\r\n const res = await fetch(url, {\r\n method: \"POST\",\r\n headers,\r\n body: JSON.stringify(body),\r\n signal: AbortSignal.timeout(120_000),\r\n });\r\n if (!res.ok) {\r\n const text = await res.text().catch(() => \"\");\r\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\r\n }\r\n}\r\n","// Feeder:根据 URL 生成 RSS,直接通过 Source 接口驱动,与具体信源解耦\r\n\r\nimport { cacheKey, cacheKeyFromCron } from \"../core/cacher/index.js\";\r\nimport { getSource } from \"../scraper/sources/index.js\";\r\nimport { runPipeline } from \"../pipeline/index.js\";\r\nimport { AuthRequiredError } from \"../scraper/auth/index.js\";\r\nimport { buildRssXml } from \"./rss.js\";\r\nimport type { RssChannel, RssEntry } from \"./types.js\";\r\nimport type { FeedItem } from \"../types/feedItem.js\";\r\nimport { normalizeAuthor, getEffectiveItemFields, isPipelineDroppedItem, pubDateToIsoOrNull } from \"../types/feedItem.js\";\r\nimport type { FeederConfig } from \"./types.js\";\r\nimport { buildSourceContext } from \"../scraper/sources/context.js\";\r\nimport { upsertItems, updateItemContent, getSystemTags, deleteItem } from \"../db/index.js\";\r\nimport { emitFeedUpdated } from \"../core/events/index.js\";\r\nimport { chatJson, chatText } from \"../core/llm.js\";\r\nimport type { PipelineContext } from \"../pipeline/index.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\nimport { getDeliverConfig } from \"../config/deliver.js\";\r\nimport { joinGatewayPath, postDeliverItemsSafe } from \"../deliver/post.js\";\r\nimport { getEffectiveProxyForListUrl } from \"../scraper/subscription/index.js\";\r\nimport { canonicalHttpSourceRef } from \"../utils/httpSourceRef.js\";\r\n\r\n/** 主动拉取默认有头;仅显式 headless:true 时用无头;其余场景沿用 config.headless */\r\nfunction resolveHeadlessForFeeder(config: FeederConfig): boolean | undefined {\r\n if (config.force === true) {\r\n return config.headless === true ? true : false;\r\n }\r\n return config.headless;\r\n}\r\n\r\n/** 根据 listUrl + items 构建 RssChannel(与 generateAndCache 一致,用于缓存命中时实时生成 XML);lng 存在时设置 channel.language */\r\nfunction buildChannelFromItems(listUrl: string, items: FeedItem[], lng?: string | null): RssChannel {\r\n const channel: RssChannel = {\r\n title: items[0]?.author?.length ? `${items[0].author[0]} 的订阅` : \"RSS 订阅\",\r\n link: listUrl,\r\n description: `来自 ${listUrl} 的订阅`,\r\n };\r\n if (lng) channel.language = lng;\r\n return channel;\r\n}\r\n\r\n\r\n/** 根据条目生成 RssEntry:有 lng 且存在译文则用译文,否则用原文;有正文用 content,否则用 summary */\r\nfunction toRssEntry(item: FeedItem, lng?: string | null): RssEntry {\r\n const eff = getEffectiveItemFields(item, lng);\r\n const hasContent = eff.content != null && eff.content !== \"\";\r\n const desc = hasContent ? eff.content : eff.summary;\r\n return {\r\n title: eff.title,\r\n link: item.link,\r\n description: desc,\r\n guid: item.guid,\r\n published: pubDateToIsoOrNull(item.pubDate) ?? undefined,\r\n imageUrl: item.imageUrl,\r\n };\r\n}\r\n\r\n\r\n/** 同一 URL 的首次生成任务去重(仅在初始 fetch+parse 阶段有效) */\r\nconst generatingKeys = new Map<string, Promise<{ items: FeedItem[] }>>();\r\n\r\n\r\n/** Pipeline 上下文 */\r\nconst pipelineCtx: PipelineContext = {\r\n llm: { chatJson, chatText } as PipelineContext[\"llm\"],\r\n db: { getSystemTags },\r\n};\r\n\r\n/** 单条 pipeline */\r\nasync function runPipelineOnItem(item: FeedItem, ctx: { sourceUrl: string }): Promise<FeedItem> {\r\n return runPipeline(item, { ...pipelineCtx, ...ctx });\r\n}\r\n\r\n\r\n/** 执行生成流程:抓取 → 入库 → 对新条目跑 pipeline → 更新库 */\r\nasync function generateAndCache(\r\n listUrl: string,\r\n key: string,\r\n config: FeederConfig,\r\n proxy: string | undefined,\r\n): Promise<{ items: FeedItem[] }> {\r\n const { cacheDir = \"cache\" } = config;\r\n const headless = resolveHeadlessForFeeder(config);\r\n const source = getSource(listUrl);\r\n const ctx = buildSourceContext({ cacheDir, headless, proxy });\r\n let items: FeedItem[];\r\n try {\r\n items = await source.fetchItems(listUrl, ctx);\r\n } catch (err) {\r\n generatingKeys.delete(key);\r\n const message = err instanceof Error ? err.message : String(err);\r\n logger.error(\"scraper\", \"抓取失败\", { source_url: listUrl, err: message });\r\n throw err;\r\n }\r\n const sourceRefStored = canonicalHttpSourceRef(listUrl);\r\n items.forEach((i) => {\r\n i.sourceRef = sourceRefStored;\r\n i.author = normalizeAuthor(i.author);\r\n });\r\n generatingKeys.delete(key);\r\n logger.info(\"scraper\", \"抓取成功\", { source_url: listUrl, count: items.length });\r\n\r\n const { gateway: deliverGateway, token: deliverToken } = await getDeliverConfig();\r\n\r\n let newCount = 0;\r\n let newIds = new Set<string>();\r\n const upsertResult = await upsertItems(items).catch((err) => {\r\n logger.warn(\"db\", \"upsertItems 失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) });\r\n return { newCount: 0, newIds: new Set<string>() };\r\n });\r\n newCount = upsertResult.newCount;\r\n newIds = upsertResult.newIds;\r\n\r\n let pipelineDroppedNew = 0;\r\n const shouldRunPipelineRow = (guid: string) => newIds.has(guid);\r\n\r\n for (let i = 0; i < items.length; i++) {\r\n if (!shouldRunPipelineRow(items[i].guid)) continue;\r\n const processed = await runPipelineOnItem(items[i], { sourceUrl: sourceRefStored });\r\n items[i] = processed;\r\n if (isPipelineDroppedItem(processed)) {\r\n await deleteItem(processed.guid).catch((err) =>\r\n logger.warn(\"db\", \"质量过滤后删除条目失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) })\r\n );\r\n pipelineDroppedNew++;\r\n } else {\r\n updateItemContent(processed).catch((err) =>\r\n logger.warn(\"db\", \"updateItemContent 失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) })\r\n );\r\n }\r\n }\r\n if (newCount > 0) {\r\n emitFeedUpdated({ sourceUrl: sourceRefStored, newCount: newCount - pipelineDroppedNew });\r\n }\r\n const out = items.filter((i) => !isPipelineDroppedItem(i));\r\n if (deliverGateway.trim() && out.length > 0) {\r\n await postDeliverItemsSafe(joinGatewayPath(deliverGateway, \"items\"), sourceRefStored, out, {\r\n bearerToken: deliverToken || undefined,\r\n });\r\n }\r\n return { items: out };\r\n}\r\n\r\n\r\n/** 根据 list URL 获取条目列表:按 cron 或 refresh 策略生成时间窗口 key 用于去重,每次均重新抓取 */\r\nexport async function crawlSource(listUrl: string, config: FeederConfig = {}): Promise<{ items: FeedItem[] }> {\n const source = getSource(listUrl);\n const proxy = await getEffectiveProxyForListUrl(listUrl, source);\n const headless = resolveHeadlessForFeeder(config);\n const key = config.cron\r\n ? cacheKeyFromCron(listUrl, config.cron)\r\n : cacheKey(listUrl, config.refreshInterval ?? source.refreshInterval ?? \"1day\");\r\n if (source.preCheck != null) {\r\n try {\r\n await source.preCheck(\r\n buildSourceContext({\r\n cacheDir: config.cacheDir ?? \"cache\",\r\n headless,\r\n proxy,\r\n }),\r\n );\r\n } catch (err) {\r\n if (err instanceof AuthRequiredError) throw err;\r\n throw err;\r\n }\r\n }\r\n let task = config.force ? undefined : generatingKeys.get(key);\r\n if (!task) {\r\n task = generateAndCache(listUrl, key, config, proxy);\r\n if (!config.force) generatingKeys.set(key, task);\r\n }\n const { items } = await task;\n return { items };\n}\n\nexport async function getItems(listUrl: string, config: FeederConfig = {}): Promise<{ items: FeedItem[]; fromCache: boolean }> {\n const { items } = await crawlSource(listUrl, config);\n return { items, fromCache: false };\n}\n\r\n\r\n/** 将 FeedItem[] 转为 RSS 2.0 XML 字符串;可选 channelTitle/channelDesc 覆盖默认 */\r\nexport function feedItemsToRssXml(\r\n items: FeedItem[],\r\n listUrl: string,\r\n lng?: string | null,\r\n opts?: { channelTitle?: string; channelDesc?: string }\r\n): string {\r\n const channel = buildChannelFromItems(listUrl, items, lng);\r\n if (opts?.channelTitle) channel.title = opts.channelTitle;\r\n if (opts?.channelDesc) channel.description = opts.channelDesc;\r\n return buildRssXml(channel, items.map((it) => toRssEntry(it, lng)));\r\n}\r\n","// 通用调度器:cron 定时任务、重试与分组并发\r\n\r\nimport { CronExpressionParser } from \"cron-parser\";\r\nimport { schedule as cronSchedule, validate as cronValidate } from \"node-cron\";\r\n\r\n/** 校验 cron 表达式是否合法 */\r\nexport const validateCron = cronValidate;\r\n\r\n/** 调度任务:返回 Promise,失败时由调度器负责重试 */\r\nexport type ScheduledTask = () => Promise<void>;\r\n\r\n\r\n/** 调度选项 */\r\nexport interface ScheduleOptions {\r\n /** cron 表达式;不填或空表示一次性任务 */\r\n cron?: string;\r\n /** 失败时重试次数,默认 0 */\r\n retries?: number;\r\n /** 重试间隔(毫秒),默认 5000 */\r\n retryDelayMs?: number;\r\n /** 分组并发数,首次使用该分组时生效,默认 10 */\r\n concurrency?: number;\r\n /** 定时任务:注册后是否立即执行一次,默认 false */\r\n runNow?: boolean;\r\n /** 一次性任务:是否插队到队首,默认 false */\r\n priority?: boolean;\r\n /** 分组名(内部使用,由 schedule 注入) */\r\n group?: string;\r\n}\r\n\r\n\r\n/** 分组配置 */\r\nexport interface GroupConfig {\r\n /** 该组最大并发数 */\r\n concurrency: number;\r\n}\r\n\r\n\r\n/** 已注册任务 */\r\ninterface RegisteredTask {\r\n id: string;\r\n cronExpr: string;\r\n task: ScheduledTask;\r\n options: ScheduleOptions;\r\n stop: () => void;\r\n /** 上次触发时间(用于计算下次执行) */\r\n lastRunTime: number;\r\n}\r\n\r\n\r\n/** 分组队列项 */\r\ninterface QueuedItem {\r\n id: string;\r\n task: ScheduledTask | (() => Promise<unknown>);\r\n options: ScheduleOptions;\r\n resolve?: () => void;\r\n resolveValue?: (value: unknown) => void;\r\n rejectValue?: (err: unknown) => void;\r\n}\r\n\r\n\r\nconst tasks = new Map<string, RegisteredTask>();\r\nconst groups = new Map<string, { config: GroupConfig; running: number; queue: QueuedItem[]; completedCount: number }>();\r\nconst DEFAULT_RETRY_DELAY_MS = 5000;\r\nconst DEFAULT_GROUP_CONCURRENCY = 10;\r\n\r\n\r\nasync function runWithRetry(\r\n task: ScheduledTask,\r\n options: ScheduleOptions\r\n): Promise<void> {\r\n const retries = options.retries ?? 0;\r\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\r\n let lastErr: unknown;\r\n for (let attempt = 0; attempt <= retries; attempt++) {\r\n try {\r\n await task();\r\n return;\r\n } catch (err) {\r\n lastErr = err;\r\n if (attempt < retries) {\r\n await new Promise((r) => setTimeout(r, retryDelayMs));\r\n }\r\n }\r\n }\r\n throw lastErr;\r\n}\r\n\r\n\r\nasync function runWithRetryAndResult<T>(\r\n task: () => Promise<T>,\r\n options: ScheduleOptions\r\n): Promise<T> {\r\n const retries = options.retries ?? 0;\r\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\r\n let lastErr: unknown;\r\n for (let attempt = 0; attempt <= retries; attempt++) {\r\n try {\r\n return await task();\r\n } catch (err) {\r\n lastErr = err;\r\n if (attempt < retries) {\r\n await new Promise((r) => setTimeout(r, retryDelayMs));\r\n }\r\n }\r\n }\r\n throw lastErr;\r\n}\r\n\r\n\r\nfunction ensureGroup(group: string, concurrency: number): void {\r\n if (!groups.has(group)) {\r\n groups.set(group, { config: { concurrency }, running: 0, queue: [], completedCount: 0 });\r\n } else {\r\n const g = groups.get(group)!;\r\n g.config.concurrency = concurrency;\r\n if (g.completedCount === undefined) g.completedCount = 0;\r\n }\r\n}\r\n\r\n\r\nfunction enqueueAndProcess(\r\n group: string,\r\n id: string,\r\n task: ScheduledTask | (() => Promise<unknown>),\r\n options: ScheduleOptions,\r\n resolve?: () => void,\r\n priority?: boolean,\r\n resolveValue?: (value: unknown) => void,\r\n rejectValue?: (err: unknown) => void\r\n): void {\r\n const g = groups.get(group);\r\n if (!g) return;\r\n g.queue = g.queue.filter((it) => it.id !== id);\r\n const item: QueuedItem = { id, task, options, resolve, resolveValue, rejectValue };\r\n if (priority) {\r\n g.queue.unshift(item);\r\n } else {\r\n g.queue.push(item);\r\n }\r\n processGroupQueue(group);\r\n}\r\n\r\n\r\nfunction processGroupQueue(group: string): void {\r\n const g = groups.get(group);\r\n if (!g || g.running >= g.config.concurrency || g.queue.length === 0) return;\r\n const item = g.queue.shift()!;\r\n g.running += 1;\r\n const done = () => {\r\n g.running -= 1;\r\n g.completedCount = (g.completedCount ?? 0) + 1;\r\n processGroupQueue(group);\r\n };\r\n if (item.resolveValue != null || item.rejectValue != null) {\r\n runWithRetryAndResult(item.task as () => Promise<unknown>, item.options)\r\n .then((result) => {\r\n item.resolveValue?.(result);\r\n })\r\n .catch((err) => {\r\n item.rejectValue?.(err);\r\n })\r\n .finally(done);\r\n } else {\r\n runWithRetry(item.task as ScheduledTask, item.options)\r\n .catch(() => {})\r\n .finally(() => {\r\n item.resolve?.();\r\n done();\r\n });\r\n }\r\n}\r\n\r\n\r\n/**\r\n * 调度任务:options.cron 有值时注册定时任务,否则一次性入队\r\n * @param group 分组名\r\n * @param id 任务唯一标识\r\n * @param task 异步任务函数\r\n * @param options cron 有值=定时任务,无值=一次性任务;可选 retries、retryDelayMs、concurrency、runNow、priority\r\n */\r\nexport function schedule(group: string, id: string, task: ScheduledTask, options: ScheduleOptions & { cron: string }): boolean;\r\nexport function schedule<T>(group: string, id: string, task: () => Promise<T>, options?: ScheduleOptions): Promise<T>;\r\nexport function schedule<T>(\r\n group: string,\r\n id: string,\r\n task: ScheduledTask | (() => Promise<T>),\r\n options: ScheduleOptions = {}\r\n): boolean | Promise<T> {\r\n const cronExpr = options.cron?.trim();\r\n ensureGroup(group, options.concurrency ?? groups.get(group)?.config.concurrency ?? DEFAULT_GROUP_CONCURRENCY);\r\n\r\n if (cronExpr && cronValidate(cronExpr)) {\r\n unschedule(id);\r\n const optsWithGroup = { ...options, group };\r\n const job = cronSchedule(cronExpr, () => {\r\n const reg = tasks.get(id);\r\n if (reg) reg.lastRunTime = Date.now();\r\n enqueueAndProcess(group, id, task as ScheduledTask, optsWithGroup);\r\n });\r\n tasks.set(id, {\r\n id,\r\n cronExpr,\r\n task: task as ScheduledTask,\r\n options: optsWithGroup,\r\n stop: () => job.stop(),\r\n lastRunTime: 0,\r\n });\r\n if (options.runNow) {\r\n runNow(id, true).catch(() => {});\r\n }\r\n return true;\r\n }\r\n\r\n return new Promise<T>((resolve, reject) => {\r\n enqueueAndProcess(\r\n group,\r\n id,\r\n task as () => Promise<unknown>,\r\n { ...options, group },\r\n undefined,\r\n options.priority ?? false,\r\n resolve as (v: unknown) => void,\r\n reject\r\n );\r\n });\r\n}\r\n\r\n\r\n/**\r\n * 取消任务\r\n */\r\nexport function unschedule(id: string): void {\r\n const reg = tasks.get(id);\r\n if (reg) {\r\n reg.stop();\r\n tasks.delete(id);\r\n }\r\n}\r\n\r\n\r\n/**\r\n * 取消指定分组下的所有定时任务(不清理队列,用于 reschedule 前仅移除本组任务)\r\n */\r\nexport function unscheduleGroup(group: string): void {\r\n const ids = [...tasks.entries()].filter(([, reg]) => reg.options.group === group).map(([id]) => id);\r\n for (const id of ids) unschedule(id);\r\n}\r\n\r\n\r\n/**\r\n * 立即执行一次任务(不等待下次定时)\r\n * @param id 任务 id\r\n * @param priority 有分组时,true 表示插入队首优先执行,默认 false 插入队尾\r\n */\r\nexport function runNow(id: string, priority = false): Promise<void> {\r\n const reg = tasks.get(id);\r\n if (!reg) return Promise.resolve();\r\n reg.lastRunTime = Date.now();\r\n const group = reg.options.group;\r\n if (group) {\r\n return new Promise<void>((resolve) => {\r\n enqueueAndProcess(group, id, reg.task, reg.options, resolve, priority);\r\n });\r\n }\r\n return runWithRetry(reg.task, reg.options);\r\n}\r\n\r\n\r\n/**\r\n * 清空所有任务(含各分组队列中的待执行项)\r\n */\r\nexport function clearAll(): void {\r\n for (const [, reg] of tasks) {\r\n reg.stop();\r\n }\r\n tasks.clear();\r\n for (const g of groups.values()) {\r\n g.queue.length = 0;\r\n }\r\n}\r\n\r\n\r\n/**\r\n * 获取已注册任务 id 列表\r\n */\r\nexport function getTaskIds(): string[] {\r\n return [...tasks.keys()];\r\n}\r\n\r\n\r\n/** 分组统计 */\r\nexport interface GroupStats {\r\n /** 正在执行的任务数 */\r\n running: number;\r\n /** 队列中等待的任务数 */\r\n queued: number;\r\n /** 该组最大并发数 */\r\n concurrency: number;\r\n /** 该组下已注册的定时任务数量 */\r\n scheduledCount: number;\r\n /** 已完成任务数(含定时任务每次执行 + 单次任务,进程启动后累计) */\r\n completedCount: number;\r\n /**\r\n * 下次任务时间(毫秒时间戳)\r\n * - 0:正在执行\r\n * - -1:无定时任务\r\n * - 其他:下次预计执行时间戳\r\n */\r\n nextRunTime: number;\r\n}\r\n\r\n\r\nfunction getNextRunForTask(reg: RegisteredTask): number {\r\n try {\r\n const base = reg.lastRunTime > 0 ? new Date(reg.lastRunTime) : new Date();\r\n const expr = CronExpressionParser.parse(reg.cronExpr, { currentDate: base });\r\n let next = expr.next().getTime();\r\n if (next <= Date.now()) {\r\n const retry = CronExpressionParser.parse(reg.cronExpr, { currentDate: new Date() });\r\n next = retry.next().getTime();\r\n }\r\n return next;\r\n } catch {\r\n return -1;\r\n }\r\n}\r\n\r\n\r\n/**\r\n * 获取各分组的执行统计,用于管理页进度条等\r\n */\r\nexport function getGroupStats(): Record<string, GroupStats> {\r\n const result: Record<string, GroupStats> = {};\r\n for (const [name, g] of groups) {\r\n const groupTasks = [...tasks.values()].filter((t) => t.options.group === name);\r\n const scheduledCount = groupTasks.length;\r\n let nextRunTime: number;\r\n if (g.running > 0) {\r\n nextRunTime = 0;\r\n } else if (scheduledCount === 0) {\r\n nextRunTime = -1;\r\n } else {\r\n const times = groupTasks.map(getNextRunForTask).filter((t) => t > 0);\r\n nextRunTime = times.length > 0 ? Math.min(...times) : -1;\r\n }\r\n result[name] = {\r\n running: g.running,\r\n queued: g.queue.length,\r\n concurrency: g.config.concurrency,\r\n scheduledCount,\r\n completedCount: g.completedCount ?? 0,\r\n nextRunTime,\r\n };\r\n }\r\n return result;\r\n}\r\n\r\n\r\n","// 信源调度:根据 sources.json 中的信源 refresh 定时触发 getItems,使用通用调度器\r\n\r\nimport { watch } from \"node:fs\";\r\nimport { getAllSources, getSourcesRaw } from \"../subscription/index.js\";\r\nimport { resolveRef } from \"../subscription/types.js\";\r\nimport { crawlSource } from \"../../feeder/index.js\";\nimport { SOURCES_CONFIG_PATH } from \"../../config/paths.js\";\r\nimport { getDeliverConfig } from \"../../config/deliver.js\";\r\nimport { joinGatewayPath, postDeliverSourcesSafe } from \"../../deliver/post.js\";\r\nimport type { RefreshInterval } from \"../../utils/refreshInterval.js\";\r\nimport { refreshIntervalToCron } from \"../../utils/refreshInterval.js\";\r\nimport * as scheduler from \"../../scheduler/index.js\";\r\n\r\nconst DEFAULT_REFRESH: RefreshInterval = \"1day\";\r\nconst SOURCES_CONCURRENCY = 1;\r\n\r\nfunction createPullTask(ref: string, cacheDir: string, cronExpr: string): scheduler.ScheduledTask {\n return async () => {\n await crawlSource(ref, {\n cacheDir,\n cron: cronExpr,\n });\n };\r\n}\r\n\r\nexport const SOURCES_GROUP = \"sources\";\r\n\r\n/** sources.json 变更且 config.deliver.gateway 非空时,向 {gateway}/sources POST 当前信源 JSON */\r\nasync function deliverSourcesConfigIfConfigured(): Promise<void> {\r\n const { gateway, token } = await getDeliverConfig();\r\n if (!gateway.trim()) return;\r\n let raw: string;\r\n try {\r\n raw = await getSourcesRaw();\r\n } catch {\r\n return;\r\n }\r\n await postDeliverSourcesSafe(joinGatewayPath(gateway, \"sources\"), raw, { bearerToken: token || undefined });\r\n}\r\n\r\nasync function rescheduleSources(cacheDir: string, runNow: boolean): Promise<void> {\r\n scheduler.unscheduleGroup(SOURCES_GROUP);\r\n let sources: Awaited<ReturnType<typeof getAllSources>>;\r\n try {\r\n sources = await getAllSources();\r\n } catch {\r\n sources = [];\r\n }\r\n\r\n for (const src of sources) {\r\n const ref = resolveRef(src);\r\n if (!ref) continue;\r\n const cronExpr: string = src.cron\r\n ? src.cron\r\n : refreshIntervalToCron(src.refresh ?? DEFAULT_REFRESH);\r\n if (!scheduler.validateCron(cronExpr)) continue;\r\n scheduler.schedule(SOURCES_GROUP, ref, createPullTask(ref, cacheDir, cronExpr), {\r\n cron: cronExpr,\r\n retries: 2,\r\n retryDelayMs: 5000,\r\n concurrency: SOURCES_CONCURRENCY,\r\n runNow,\r\n });\r\n }\r\n}\r\n\r\nexport async function initScheduler(cacheDir: string): Promise<void> {\r\n await rescheduleSources(cacheDir, false);\r\n let debounceTimer: NodeJS.Timeout | null = null;\r\n try {\r\n const watcher = watch(SOURCES_CONFIG_PATH, () => {\r\n if (debounceTimer) clearTimeout(debounceTimer);\r\n debounceTimer = setTimeout(() => {\r\n void rescheduleSources(cacheDir, false)\r\n .then(() => deliverSourcesConfigIfConfigured())\r\n .catch(() => {});\r\n }, 500);\r\n });\r\n watcher.on(\"error\", () => {});\r\n } catch {\r\n /* sources.json 尚不存在 */\r\n }\r\n}\r\n","// 本地运行:管理 API 不做令牌校验(原 admin-token 逻辑已移除)\r\n\r\nimport type { Context, Next } from \"hono\";\r\n\r\n/** 占位中间件,与既有路由注册兼容,始终放行 */\r\nexport function requireAdmin() {\r\n return async (_c: Context, next: Next): Promise<Response | void> => {\r\n return next();\r\n };\r\n}\r\n","// /api/server-info\r\n\r\nimport { networkInterfaces } from \"node:os\";\r\nimport type { Hono } from \"hono\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nconst PORT = Number(process.env.PORT) || 18473;\r\n\r\nexport function registerServerRoutes(app: Hono): void {\r\n app.get(\"/api/server-info\", requireAdmin(), (c) => {\r\n const lanIp = Object.values(networkInterfaces())\r\n .flat()\r\n .find((iface) => iface?.family === \"IPv4\" && !iface.internal)?.address;\r\n const lanUrl = lanIp ? `http://${lanIp}:${PORT}` : null;\r\n return c.json({ port: PORT, lanUrl });\r\n });\r\n}\r\n","// /api/rss(JSON,按 URL 抓取返回条目列表)\n\nimport { createHash } from \"node:crypto\";\nimport type { Hono } from \"hono\";\nimport { getItems } from \"../../../feeder/index.js\";\nimport { SOURCES_GROUP } from \"../../../scraper/scheduler/index.js\";\nimport * as scheduler from \"../../../scheduler/index.js\";\nimport { CACHE_DIR } from \"../../../config/paths.js\";\nimport { AuthRequiredError, NotFoundError } from \"../../../scraper/auth/index.js\";\nimport { getEffectiveItemFields, pubDateToIsoOrNull } from \"../../../types/feedItem.js\";\n\nexport function registerRssApiRoutes(app: Hono): void {\n app.get(\"/api/rss\", async (c) => {\n const url = c.req.query(\"url\");\n if (!url) return c.json({ error: \"url 参数缺失\" }, 400);\n const headlessParam = c.req.query(\"headless\");\n const headless = headlessParam === \"false\" || headlessParam === \"0\" ? false : undefined;\n const lng = c.req.query(\"lng\") ?? undefined;\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 20), 200);\n const offset = Number(c.req.query(\"offset\") ?? 0);\n try {\n const httpId = \"http-\" + createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\n const { items: allItems, fromCache } = await scheduler.schedule(\n SOURCES_GROUP,\n httpId,\n () => getItems(url, { cacheDir: CACHE_DIR, headless, lng })\n );\n const total = allItems.length;\n const pageItems = allItems.slice(offset, offset + limit);\n return c.json({\n fromCache,\n total,\n hasMore: offset + pageItems.length < total,\n items: pageItems.map((item) => {\n const { title, summary } = lng ? getEffectiveItemFields(item, lng) : { title: item.title, summary: item.summary ?? \"\" };\n return {\n guid: item.guid,\n title,\n link: item.link,\n summary,\n author: item.author,\n pubDate: pubDateToIsoOrNull(item.pubDate),\n };\n }),\n });\n } catch (err) {\n if (err instanceof AuthRequiredError) return c.json({ error: \"需要登录\", code: \"AUTH_REQUIRED\" }, 401);\n if (err instanceof NotFoundError) return c.json({ error: err.message, code: \"NOT_FOUND\" }, 404);\n return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);\n }\n });\n}\n","// /api/scheduler/stats\r\n\r\nimport type { Hono } from \"hono\";\r\nimport * as scheduler from \"../../../scheduler/index.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nexport function registerSchedulerRoutes(app: Hono): void {\r\n app.get(\"/api/scheduler/stats\", requireAdmin(), (c) => {\r\n const stats = scheduler.getGroupStats();\r\n return c.json(stats);\r\n });\r\n}\r\n","// /api/plugins、POST /api/plugins(从模板新建)、/api/plugins/:id(读写字节码插件源文件)\r\n\r\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\r\nimport { join, resolve, sep } from \"node:path\";\r\nimport type { Hono } from \"hono\";\r\nimport { getPluginSites } from \"../../../scraper/sources/web/index.js\";\r\nimport { registeredSources } from \"../../../scraper/sources/index.js\";\r\nimport { getPluginFilePath } from \"../../../plugins/loader.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport { initSources } from \"../../../scraper/sources/index.js\";\r\nimport { BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR, PLUGIN_SITE_TEMPLATE_PATH } from \"../../../config/paths.js\";\r\n\r\nconst SITE_TEMPLATE_FALLBACK = `/**\n * Site plugin template created from the /plugins page.\n * Plugin protocol: named exports. No export default is required.\n * Parse HTML with ctx.deps.parseHtml; do not import app dependencies directly.\n */\n\n// Predefined fields stay together at the top.\nexport const id = \"__PLUGIN_ID__\";\nexport const name = \"__PLUGIN_ID__\";\nexport const listUrlPattern = __LIST_URL_PATTERN__;\nexport const refreshInterval = \"1day\";\n\nexport async function fetchItems(sourceId, ctx) {\n const { html, finalUrl } = await ctx.fetchHtml(sourceId, {\n waitMs: 2000,\n purify: true,\n });\n void ctx.deps.parseHtml(html);\n void finalUrl;\n return [];\n}\n`;\r\n\r\nfunction isValidNewPluginId(id: string): boolean {\r\n return /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(id) && id !== \"generic\" && id !== \"new\";\r\n}\r\n\r\n/** 与模板中 `listUrlPattern: __LIST_URL_PATTERN__` 注入一致:非空、无换行、长度上限 */\r\nfunction isValidNewListUrlPattern(pattern: string): boolean {\r\n if (pattern.length === 0 || pattern.length > 2048) return false;\r\n if (/[\\r\\n]/.test(pattern)) return false;\r\n return true;\r\n}\r\n\r\nasync function fileExists(p: string): Promise<boolean> {\r\n try {\r\n await access(p);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nfunction isAllowedPluginPath(absPath: string): boolean {\r\n const f = resolve(absPath);\r\n for (const root of [BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR]) {\r\n const r = resolve(root);\r\n if (f === r || f.startsWith(r + sep)) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport function registerPluginsRoutes(app: Hono): void {\r\n /** 从模板在 .rssany/plugins/{id}.rssany.js 新建 Site 插件 */\r\n app.post(\"/api/plugins\", requireAdmin(), async (c) => {\r\n let body: { id?: string; listUrlPattern?: string };\r\n try {\r\n body = await c.req.json();\r\n } catch {\r\n return c.json({ error: \"无效 JSON\" }, 400);\r\n }\r\n const id = typeof body.id === \"string\" ? body.id.trim() : \"\";\r\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\r\n if (!isValidNewPluginId(id)) {\r\n return c.json({ error: \"id 须为字母开头,仅含字母数字、下划线、连字符;不能为 generic 或 new\" }, 400);\r\n }\r\n const listUrlPatternRaw = typeof body.listUrlPattern === \"string\" ? body.listUrlPattern.trim() : \"\";\r\n if (!listUrlPatternRaw) {\r\n return c.json({ error: \"缺少支持的站点(listUrlPattern),例如 https://example.com/*\" }, 400);\r\n }\r\n if (!isValidNewListUrlPattern(listUrlPatternRaw)) {\r\n return c.json({ error: \"支持的站点须为非空字符串,不超过 2048 字符,且不能含换行\" }, 400);\r\n }\r\n await mkdir(USER_PLUGINS_DIR, { recursive: true });\r\n const outPath = join(USER_PLUGINS_DIR, `${id}.rssany.js`);\r\n if (await fileExists(outPath)) return c.json({ error: \"该 id 已存在同名文件\" }, 409);\r\n let tpl = SITE_TEMPLATE_FALLBACK;\r\n try {\r\n tpl = await readFile(PLUGIN_SITE_TEMPLATE_PATH, \"utf-8\");\r\n } catch {\r\n // 使用内置模板\r\n }\r\n const patternLiteral = JSON.stringify(listUrlPatternRaw);\r\n const content = tpl.replace(/__PLUGIN_ID__/g, id).replace(/__LIST_URL_PATTERN__/g, patternLiteral);\r\n if (!isAllowedPluginPath(outPath)) return c.json({ error: \"路径不允许\" }, 403);\r\n try {\r\n await writeFile(outPath, content, \"utf-8\");\r\n await initSources();\r\n return c.json({ ok: true, filePath: outPath, id });\r\n } catch (e) {\r\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\r\n }\r\n });\r\n\r\n app.get(\"/api/plugins\", requireAdmin(), (c) => {\r\n const sites = getPluginSites().map((s) => ({\r\n kind: \"site\" as const,\n id: s.id,\n name: s.name ?? s.id,\n listUrlPattern: typeof s.listUrlPattern === \"string\" ? s.listUrlPattern : String(s.listUrlPattern),\n hasAuth: !!(s.checkAuth && s.loginUrl),\r\n }));\r\n const siteIds = new Set(sites.map((p) => p.id));\r\n const sources = registeredSources\r\n .filter((src) => src.id !== \"generic\" && !siteIds.has(src.id))\r\n .map((src) => ({\r\n kind: \"source\" as const,\n id: src.id,\n name: src.name ?? src.id,\n listUrlPattern: typeof src.pattern === \"string\" ? src.pattern : String(src.pattern),\n hasAuth: false,\r\n }));\r\n return c.json([...sites, ...sources]);\r\n });\r\n\r\n app.get(\"/api/plugins/:id\", requireAdmin(), async (c) => {\r\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\r\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\r\n const filePath = getPluginFilePath(id);\r\n if (!filePath) return c.json({ error: \"未找到该插件或无可编辑文件\" }, 404);\r\n if (!isAllowedPluginPath(filePath)) return c.json({ error: \"路径不允许\" }, 403);\r\n try {\r\n const content = await readFile(filePath, \"utf-8\");\r\n return c.json({ id, filePath, content });\r\n } catch (e) {\r\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\r\n }\r\n });\r\n\r\n app.put(\"/api/plugins/:id\", requireAdmin(), async (c) => {\r\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\r\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\r\n let body: { content?: string };\r\n try {\r\n body = await c.req.json();\r\n } catch {\r\n return c.json({ error: \"无效 JSON\" }, 400);\r\n }\r\n if (typeof body.content !== \"string\") return c.json({ error: \"需要 content 字符串\" }, 400);\r\n const filePath = getPluginFilePath(id);\r\n if (!filePath) return c.json({ error: \"未找到该插件\" }, 404);\r\n if (!isAllowedPluginPath(filePath)) return c.json({ error: \"路径不允许\" }, 403);\r\n try {\r\n await writeFile(filePath, body.content, \"utf-8\");\r\n await initSources();\r\n return c.json({ ok: true });\r\n } catch (e) {\r\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\r\n }\r\n });\r\n}\r\n","// /api/pipeline(步骤开关与排序)\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport {\r\n loadPipelineConfig,\r\n savePipelineConfig,\r\n DEFAULT_PIPELINE_STEPS,\r\n PIPELINE_STEP_IDS,\r\n} from \"../../../pipeline/config.js\";\r\n\r\ntype StepInput = { id: string; enabled?: boolean };\r\n\r\nfunction parseSteps(rawSteps: unknown[]): Array<{ id: string; enabled: boolean }> {\r\n return rawSteps\r\n .filter((s) => s && typeof s === \"object\" && typeof (s as StepInput).id === \"string\")\r\n .map((s) => {\r\n const obj = s as StepInput;\r\n const e: unknown = obj.enabled;\r\n return {\r\n id: String(obj.id).trim(),\r\n enabled: e !== false && e !== 0,\r\n };\r\n })\r\n .filter((s) => s.id.length > 0);\r\n}\r\n\r\nfunction dedupeSteps<T extends { id: string }>(steps: T[]): T[] {\r\n const seen = new Set<string>();\r\n const out: T[] = [];\r\n for (const s of steps) {\r\n if (seen.has(s.id)) continue;\r\n seen.add(s.id);\r\n out.push(s);\r\n }\r\n return out;\r\n}\r\n\r\nexport function registerPipelineRoutes(app: Hono): void {\r\n app.get(\"/api/pipeline\", requireAdmin(), async (c) => {\r\n const config = await loadPipelineConfig();\r\n return c.json({\r\n steps: config.steps,\r\n availableIds: [...PIPELINE_STEP_IDS],\r\n defaults: DEFAULT_PIPELINE_STEPS,\r\n });\r\n });\r\n\r\n app.put(\"/api/pipeline\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ steps?: unknown[] }>();\r\n const rawSteps = Array.isArray(body?.steps) ? body.steps : [];\r\n const steps = dedupeSteps(parseSteps(rawSteps));\r\n await savePipelineConfig({ steps });\r\n return c.json({ ok: true, steps });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n}\r\n","// /api/feed、/api/events\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { streamSSE } from \"hono/streaming\";\r\nimport { onFeedUpdated } from \"../../../core/events/index.js\";\r\nimport { getAllSources, getAllSubscriptionRefs, resolveRef } from \"../../../scraper/subscription/index.js\";\r\nimport { getEffectiveItemFields, type ItemTranslationFields } from \"../../../types/feedItem.js\";\r\nimport { queryItems } from \"../../../db/index.js\";\r\n\r\nexport function registerFeedRoutes(app: Hono): void {\r\n app.get(\"/api/feed\", async (c) => {\r\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 50), 200);\r\n const offset = Number(c.req.query(\"offset\") ?? 0);\r\n const refFilter = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? undefined;\r\n const lng = c.req.query(\"lng\") ?? undefined;\r\n const since = c.req.query(\"since\");\r\n const until = c.req.query(\"until\");\r\n\r\n const sources = await getAllSources();\r\n const refSet = new Set(sources.map((s) => resolveRef(s)).filter(Boolean));\r\n let sourceRefs: string[];\r\n if (refFilter) {\r\n sourceRefs = refSet.has(refFilter) ? [refFilter] : [];\r\n } else {\r\n sourceRefs = await getAllSubscriptionRefs();\r\n }\r\n\r\n const labelByRef = new Map(sources.map((s) => [resolveRef(s), s.label ?? resolveRef(s)] as const));\r\n const sourcesMeta = sources.map((s) => ({\r\n ref: resolveRef(s),\r\n label: s.label ?? resolveRef(s),\r\n }));\r\n\r\n const parseDateBound = (value: string | undefined, endExclusive: boolean): Date | undefined => {\n if (!value) return undefined;\n if (value.length === 10) {\n const d = new Date(endExclusive ? `${value}T12:00:00Z` : `${value}T00:00:00.000Z`);\n if (endExclusive) d.setUTCDate(d.getUTCDate() + 1);\n return d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? undefined : d;\n };\n const result = sourceRefs.length > 0\n ? await queryItems({\n sourceUrls: sourceRefs,\n limit: limit + 1,\n offset,\n since: parseDateBound(since ?? undefined, false),\n until: parseDateBound(until ?? undefined, true),\n })\n : { items: [], total: 0 };\n const hasMore = result.items.length > limit;\n const dbItems = hasMore ? result.items.slice(0, limit) : result.items;\r\n const items = dbItems.map((item) => {\r\n const refKey = item.source_url ?? \"\";\r\n const base = {\r\n ...item,\r\n sub_id: refKey,\r\n sub_title: labelByRef.get(refKey) ?? \"\",\r\n };\r\n if (!lng) return base;\r\n const view = {\r\n title: item.title ?? \"\",\r\n summary: item.summary ?? \"\",\r\n content: item.content ?? \"\",\r\n translations: (item as { translations?: Record<string, ItemTranslationFields> }).translations,\r\n };\r\n const eff = getEffectiveItemFields(view, lng);\r\n return { ...base, title: eff.title, summary: eff.summary, content: eff.content };\r\n });\r\n return c.json({ sources: sourcesMeta, items, hasMore });\r\n });\r\n\r\n app.get(\"/api/events\", (c) => {\r\n return streamSSE(c, async (stream) => {\r\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\r\n const off = onFeedUpdated((e) => {\r\n stream.writeSSE({ data: JSON.stringify({ type: \"feed:updated\", sourceUrl: e.sourceUrl, newCount: e.newCount }) }).catch(() => {});\r\n });\r\n const heartbeat = setInterval(() => {\r\n stream.writeSSE({ data: \"\", event: \"ping\" }).catch(() => {});\r\n }, 25000);\r\n stream.onAbort(() => {\r\n off();\r\n clearInterval(heartbeat);\r\n });\r\n await new Promise<void>((resolve) => stream.onAbort(resolve));\r\n });\r\n });\r\n}\r\n","// /api/items、/api/items/pending-push、/api/items/mark-pushed、/api/items/by-source(须在 :id 之前注册)、/api/items/:id\r\n\r\n// /api/user/items(JWT 认证用户)\r\n\r\n\r\n\r\nimport type { Hono } from \"hono\";\r\n\r\nimport { getAllSubscriptionRefs } from \"../../../scraper/subscription/index.js\";\r\n\r\nimport { getEffectiveItemFields, type ItemTranslationFields } from \"../../../types/feedItem.js\";\r\n\r\nimport { queryItems, getPendingPushItems, markPushed, deleteItem, deleteItemsBySourceUrl } from \"../../../db/index.js\";\r\n\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nimport { canonicalHttpSourceRef } from \"../../../utils/httpSourceRef.js\";\r\n\r\n\r\n\r\nfunction parseSubscribedFlag(v: string | undefined): boolean {\r\n\r\n return v === \"1\" || v === \"true\" || v === \"yes\";\r\n\r\n}\r\n\r\n\r\n\r\nexport function registerItemsRoutes(app: Hono): void {\r\n\r\n app.get(\"/api/items/pending-push\", async (c) => {\r\n\r\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 100), 500);\r\n\r\n const items = await getPendingPushItems(limit);\r\n\r\n return c.json({ items, count: items.length });\r\n\r\n });\r\n\r\n\r\n\r\n app.post(\"/api/items/mark-pushed\", async (c) => {\r\n\r\n try {\r\n\r\n const { ids } = await c.req.json<{ ids?: string[] }>();\r\n\r\n if (!Array.isArray(ids) || ids.length === 0) return c.json({ ok: false, message: \"ids 不能为空\" }, 400);\r\n\r\n await markPushed(ids);\r\n\r\n return c.json({ ok: true, count: ids.length });\r\n\r\n } catch (err) {\r\n\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n\r\n }\r\n\r\n });\r\n\r\n\r\n\r\n /** 清空指定信源(source_url)下所有已入库条目 — 必须早于 /api/items/:id,否则 \"by-source\" 会被当成 id */\r\n\r\n app.delete(\"/api/items/by-source\", requireAdmin(), async (c) => {\r\n\r\n const sourceUrl = (c.req.query(\"source_url\") ?? \"\").trim();\r\n\r\n if (!sourceUrl) return c.json({ ok: false, message: \"source_url 不能为空\" }, 400);\r\n\r\n const deleted = await deleteItemsBySourceUrl(sourceUrl);\r\n\r\n return c.json({ ok: true, deleted });\r\n\r\n });\r\n\r\n\r\n\r\n app.delete(\"/api/items/:id\", async (c) => {\r\n\r\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\r\n\r\n if (!id) return c.json({ ok: false, message: \"id 不能为空\" }, 400);\r\n\r\n const deleted = await deleteItem(id);\r\n\r\n if (!deleted) return c.json({ ok: false, message: \"条目不存在或已删除\" }, 404);\r\n\r\n return c.json({ ok: true });\r\n\r\n });\r\n\r\n\r\n\r\n app.get(\"/api/items\", async (c) => {\r\n\r\n const ref = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? undefined;\r\n\r\n const subscribed = parseSubscribedFlag(c.req.query(\"subscribed\"));\r\n\r\n const author = c.req.query(\"author\") ?? undefined;\r\n\r\n const q = c.req.query(\"q\") ?? undefined;\r\n\r\n const tagsParam = c.req.query(\"tags\") ?? undefined;\r\n\r\n const tags = tagsParam ? tagsParam.split(\",\").map((t) => t.trim()).filter(Boolean) : undefined;\r\n\r\n // days=0 或不传 days 表示不按天数筛选;days=N 表示最近 N 天;since/until 仍可单独传\r\n\r\n const daysParam = c.req.query(\"days\");\r\n\r\n const sinceParam = c.req.query(\"since\") ?? undefined;\r\n\r\n const untilParam = c.req.query(\"until\") ?? undefined;\r\n\r\n let since: Date | undefined;\r\n\r\n let until: Date | undefined;\r\n\r\n const daysNum = daysParam !== undefined && daysParam !== \"\" ? Number(daysParam) : NaN;\r\n\r\n if (Number.isFinite(daysNum) && daysNum > 0) {\r\n\r\n const n = Math.max(1, Math.min(365, Math.floor(daysNum)));\r\n\r\n const now = new Date();\r\n\r\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\r\n\r\n const todayEnd = new Date(todayStart);\r\n\r\n todayEnd.setDate(todayEnd.getDate() + 1);\r\n\r\n since = new Date(todayStart);\r\n\r\n since.setDate(since.getDate() - (n - 1));\r\n\r\n until = todayEnd;\r\n\r\n } else {\r\n\r\n since = sinceParam ? new Date(sinceParam) : undefined;\r\n\r\n if (untilParam) {\r\n\r\n if (untilParam.length === 10) {\r\n\r\n const d = new Date(untilParam + \"T12:00:00Z\");\r\n\r\n d.setUTCDate(d.getUTCDate() + 1);\r\n\r\n until = d;\r\n\r\n } else {\r\n\r\n until = new Date(untilParam);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 200), 500);\r\n\r\n const offset = Number(c.req.query(\"offset\") ?? 0);\r\n\r\n const lng = c.req.query(\"lng\") ?? undefined;\r\n\r\n\r\n\r\n let sourceUrls: string[] | undefined;\r\n\r\n let effectiveSourceUrl: string | undefined;\r\n\r\n if (ref) {\r\n\r\n effectiveSourceUrl = ref;\r\n\r\n sourceUrls = undefined;\r\n\r\n } else if (subscribed) {\r\n\r\n const refs = await getAllSubscriptionRefs();\r\n\r\n sourceUrls = refs.length > 0 ? refs : [];\r\n\r\n }\r\n\r\n\r\n\r\n if (!effectiveSourceUrl && sourceUrls?.length === 0) {\r\n\r\n return c.json({ items: [], total: 0, hasMore: false });\r\n\r\n }\r\n\r\n\r\n const result = await queryItems({\r\n\r\n sourceUrl: sourceUrls ? undefined : (effectiveSourceUrl ? canonicalHttpSourceRef(effectiveSourceUrl) : undefined),\r\n\r\n sourceUrls,\r\n\r\n author,\r\n\r\n q,\r\n\r\n tags,\r\n\r\n since,\r\n\r\n until,\r\n\r\n limit,\r\n\r\n offset,\r\n\r\n });\r\n\r\n const items =\r\n\r\n lng && result.items.length > 0\r\n\r\n ? result.items.map((it) => {\r\n\r\n const view = {\r\n\r\n title: it.title ?? \"\",\r\n\r\n summary: it.summary ?? \"\",\r\n\r\n content: it.content ?? \"\",\r\n\r\n translations: (it as { translations?: Record<string, ItemTranslationFields> }).translations,\r\n\r\n };\r\n\r\n const eff = getEffectiveItemFields(view, lng);\r\n\r\n return { ...it, title: eff.title, summary: eff.summary, content: eff.content };\r\n\r\n })\r\n\r\n : result.items;\r\n\r\n const hasMore = offset + items.length < result.total;\r\n\r\n return c.json({ items, total: result.total, hasMore });\r\n\r\n });\r\n\r\n}\r\n\r\n","// /api/logs\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { clearAllLogs, queryLogs } from \"../../../db/index.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nexport function registerLogsRoutes(app: Hono): void {\r\n app.delete(\"/api/logs\", requireAdmin(), async (c) => {\r\n const deleted = await clearAllLogs();\r\n return c.json({ ok: true, deleted });\r\n });\r\n\r\n app.get(\"/api/logs\", requireAdmin(), async (c) => {\r\n const levelParam = c.req.query(\"level\");\r\n const level = levelParam === \"error\" || levelParam === \"warn\" || levelParam === \"info\" || levelParam === \"debug\" ? levelParam : undefined;\r\n const categoryRaw = c.req.query(\"category\");\r\n const category = typeof categoryRaw === \"string\" && categoryRaw.trim() ? categoryRaw.trim() : undefined;\r\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 100), 200);\r\n const offset = Number(c.req.query(\"offset\") ?? 0);\r\n const sinceParam = c.req.query(\"since\");\r\n const since = sinceParam ? new Date(sinceParam) : undefined;\r\n const result = await queryLogs({ level, category, limit, offset, since });\r\n return c.json(result);\r\n });\r\n}\r\n","// /api/admin/verify, /api/admin/integrity-check\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { runIntegrityCheck } from \"../../../db/index.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nexport function registerAdminApiRoutes(app: Hono): void {\r\n app.get(\"/api/admin/verify\", requireAdmin(), async (c) => {\r\n return c.json({ ok: true });\r\n });\r\n\r\n /** SQLite 主库 PRAGMA integrity_check */\r\n app.get(\"/api/admin/integrity-check\", requireAdmin(), async (c) => {\r\n try {\r\n const result = await runIntegrityCheck();\r\n const ok = result === \"ok\";\r\n return c.json({ ok, result });\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err);\r\n return c.json({ ok: false, result: `error: ${message}` }, 500);\r\n }\r\n });\r\n}\r\n","// /api/sources/stats、/api/sources/raw、/api/sources/plugin-match(admin)\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { getSourceStats } from \"../../../db/index.js\";\r\nimport { getSource } from \"../../../scraper/sources/index.js\";\r\nimport { getPluginSites } from \"../../../scraper/sources/web/index.js\";\r\nimport { getSourcesRaw, saveSourcesFile, getEffectiveProxyForListUrl } from \"../../../scraper/subscription/index.js\";\r\nimport { launchBrowser, applyProxyAuthToPage, resolveProxy } from \"../../../scraper/sources/web/fetcher/index.js\";\r\nimport { CACHE_DIR } from \"../../../config/paths.js\";\r\nimport type { SourceType } from \"../../../scraper/subscription/types.js\";\r\nimport type { RefreshInterval } from \"../../../utils/refreshInterval.js\";\r\nimport { VALID_INTERVALS } from \"../../../utils/refreshInterval.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport { canonicalHttpSourceRef } from \"../../../utils/httpSourceRef.js\";\r\n\r\nexport function registerSourcesRoutes(app: Hono): void {\r\n app.get(\"/api/sources/stats\", requireAdmin(), async (c) => {\r\n const stats = await getSourceStats();\r\n return c.json(stats);\r\n });\r\n\r\n app.post(\"/api/sources/plugin-match\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ refs?: string[] }>();\r\n const refs = Array.isArray(body?.refs) ? body.refs : [];\r\n const pluginIds = new Set(getPluginSites().map((s) => s.id));\r\n const result: Record<string, string | null> = {};\r\n for (const ref of refs) {\r\n const source = getSource(ref);\r\n result[ref] = pluginIds.has(source.id) ? source.id : null;\r\n }\r\n return c.json(result);\r\n } catch {\r\n return c.json({});\r\n }\r\n });\r\n\r\n /**\r\n * 在有头 Chrome 中打开 URL:与抓取共用 CACHE_DIR/browser_data、代理优先级与 /auth/open 一致。\r\n * 浏览器在本机服务端弹出,非用户默认浏览器。\r\n */\r\n app.post(\"/api/sources/open-browser\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ url?: string }>();\r\n const raw = typeof body?.url === \"string\" ? body.url.trim() : \"\";\r\n if (!raw) return c.json({ ok: false, message: \"缺少 url\" }, 400);\r\n const lower = raw.toLowerCase();\r\n if (!lower.startsWith(\"http://\") && !lower.startsWith(\"https://\")) {\r\n return c.json({ ok: false, message: \"仅支持 http(s) URL\" }, 400);\r\n }\r\n const url = raw;\r\n const source = getSource(url);\r\n const merged = await getEffectiveProxyForListUrl(url, source);\r\n const proxy = resolveProxy({ proxy: merged });\r\n void launchBrowser({ headless: false, cacheDir: CACHE_DIR, proxy })\r\n .then(async (browser) => {\r\n try {\r\n const page = await browser.newPage();\r\n await applyProxyAuthToPage(page, { proxy: merged });\r\n const realUserAgent =\r\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\r\n await page.setUserAgent(realUserAgent);\r\n await page.setViewport({ width: 1366, height: 960 });\r\n await page.goto(url, { waitUntil: \"domcontentloaded\", timeout: 60_000 });\r\n page.once(\"close\", () => {\r\n void browser.close().catch(() => {});\r\n });\r\n } catch {\r\n await browser.close().catch(() => {});\r\n }\r\n })\r\n .catch(() => {});\r\n return c.json({ ok: true, message: \"已在爬虫浏览器中打开\" });\r\n } catch {\r\n return c.json({ ok: false, message: \"请求体无效\" }, 400);\r\n }\r\n });\r\n\r\n app.get(\"/api/sources/raw\", requireAdmin(), async (c) => {\r\n try {\r\n const raw = await getSourcesRaw();\r\n return c.text(raw, 200, { \"Content-Type\": \"application/json; charset=utf-8\" });\r\n } catch {\r\n return c.text(JSON.stringify({ sources: [] }, null, 2), 200, { \"Content-Type\": \"application/json; charset=utf-8\" });\r\n }\r\n });\r\n\r\n app.put(\"/api/sources/raw\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ sources?: unknown[] }>();\r\n const list = Array.isArray(body?.sources) ? body.sources : [];\r\n const sources: { ref: string; type?: SourceType; label?: string; description?: string; refresh?: RefreshInterval; proxy?: string; weight?: number }[] = list\r\n .filter((s): s is Record<string, unknown> => s != null && typeof s === \"object\" && typeof (s as { ref?: unknown }).ref === \"string\")\r\n .map((s) => {\r\n const t = (s as { type?: string }).type;\r\n const type: SourceType | undefined =\r\n t === \"web\" || t === \"rss\" || t === \"email\" ? t : undefined;\r\n const r = (s as { refresh?: string }).refresh;\r\n const refresh: RefreshInterval | undefined =\r\n r && VALID_INTERVALS.includes(r as RefreshInterval) ? (r as RefreshInterval) : undefined;\r\n const w = (s as { weight?: unknown }).weight;\r\n const weight: number | undefined = typeof w === \"number\" ? w : undefined;\r\n return {\r\n ref: canonicalHttpSourceRef(String((s as { ref: string }).ref)),\r\n type,\r\n label: (s as { label?: string }).label,\r\n description: (s as { description?: string }).description,\r\n refresh,\r\n proxy: (s as { proxy?: string }).proxy,\r\n weight,\r\n };\r\n });\r\n await saveSourcesFile(sources);\r\n return c.json({ ok: true });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n}\r\n","// /api/tags — 系统标签(pipeline tagger);原 /api/topics 任务接口已迁至 /api/agent-tasks\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport {\r\n getSuggestedTags,\r\n getSystemTags,\r\n getSystemTagStats,\r\n saveSystemTagsToFile,\r\n removeTagFromAllItems,\r\n} from \"../../../db/index.js\";\r\n\r\nexport function registerTopicsRoutes(app: Hono): void {\r\n /** 系统标签:来自 .rssany/tags.json,供 pipeline tagger 使用 */\r\n app.get(\"/api/tags\", async (c) => {\r\n const [tags, stats, suggested] = await Promise.all([\r\n getSystemTags(),\r\n getSystemTagStats(),\r\n getSuggestedTags(),\r\n ]);\r\n return c.json({\r\n tags,\r\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n });\r\n });\r\n\r\n app.put(\"/api/tags\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ tags?: string[] }>();\r\n const list = Array.isArray(body?.tags) ? body.tags : [];\r\n await saveSystemTagsToFile(list);\r\n const [tags, stats, suggested] = await Promise.all([\r\n getSystemTags(),\r\n getSystemTagStats(),\r\n getSuggestedTags(),\r\n ]);\r\n return c.json({\r\n ok: true,\r\n tags,\r\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n\r\n /** 从所有条目的 tags 中移除指定标签 */\r\n app.post(\"/api/tags/remove-from-items\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{ tag?: string }>();\r\n const tag = typeof body?.tag === \"string\" ? body.tag.trim() : \"\";\r\n if (!tag) return c.json({ ok: false, message: \"tag 参数缺失\" }, 400);\r\n const count = await removeTagFromAllItems(tag);\r\n const [tags, stats, suggested] = await Promise.all([\r\n getSystemTags(),\r\n getSystemTagStats(),\r\n getSuggestedTags(),\r\n ]);\r\n return c.json({\r\n ok: true,\r\n removedCount: count,\r\n tags,\r\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\r\n });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n}\r\n","// /api/deliver、/api/deliver/test — Gateway 基址;固定 POST {gateway}/items、{gateway}/sources,测试 POST {gateway}/test\r\n\r\nimport type { Hono } from \"hono\";\r\nimport type { FeedItem } from \"../../../types/feedItem.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport { getDeliverConfig, saveDeliverConfig } from \"../../../config/deliver.js\";\r\nimport { getSourcesRaw } from \"../../../scraper/subscription/index.js\";\r\nimport { feedItemsToPayload, postDeliverGatewayTest } from \"../../../deliver/post.js\";\r\n\r\nexport function registerDeliverRoutes(app: Hono): void {\r\n app.get(\"/api/deliver\", requireAdmin(), async (c) => {\r\n const { gateway, token } = await getDeliverConfig();\r\n return c.json({ gateway, token });\r\n });\r\n\r\n app.put(\"/api/deliver\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{\r\n gateway?: string;\r\n token?: string;\r\n /** 旧版:完整 …/items URL,将迁移为 gateway 基址 */\r\n url?: string;\r\n }>();\r\n const prev = await getDeliverConfig();\r\n const explicitGateway = body != null && \"gateway\" in body;\r\n const explicitUrl = body != null && \"url\" in body;\r\n const explicitToken = body != null && \"token\" in body;\r\n let gateway = typeof body?.gateway === \"string\" ? body.gateway.trim() : \"\";\r\n if (!gateway && typeof body?.url === \"string\") {\r\n gateway = body.url\r\n .trim()\r\n .replace(/\\/items\\/?$/i, \"\")\r\n .replace(/\\/+$/, \"\");\r\n }\r\n if (!explicitGateway && !explicitUrl) {\r\n gateway = prev.gateway;\r\n }\r\n let token = typeof body?.token === \"string\" ? body.token.trim() : \"\";\r\n if (!explicitToken) {\r\n token = prev.token;\r\n }\r\n await saveDeliverConfig({ gateway, token });\r\n return c.json({ ok: true, gateway, token });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n\r\n /** 合并测试:仅 POST 到 {gateway}/test,体含示例 items 批次与当前 sources 文档 */\r\n app.post(\"/api/deliver/test\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{\r\n gateway?: string;\r\n token?: string;\r\n url?: string;\r\n }>();\r\n const prev = await getDeliverConfig();\r\n let gateway = typeof body?.gateway === \"string\" ? body.gateway.trim() : \"\";\r\n if (!gateway && typeof body?.url === \"string\") {\r\n gateway = body.url\r\n .trim()\r\n .replace(/\\/items\\/?$/i, \"\")\r\n .replace(/\\/+$/, \"\");\r\n }\r\n if (!gateway) gateway = prev.gateway;\r\n const token =\r\n typeof body?.token === \"string\" ? body.token.trim() : prev.token;\r\n if (!gateway.trim()) return c.json({ ok: false, message: \"gateway 不能为空\" }, 400);\r\n\r\n const now = Date.now();\r\n const sample: FeedItem = {\r\n guid: \"deliver-test-\" + now,\r\n title: \"投递连通性测试\",\r\n link: \"https://example.com/rssany-deliver-test\",\r\n pubDate: new Date(),\r\n summary: \"若下游 /test 收到此条,说明 Gateway 可用。\",\r\n sourceRef: \"rssany-deliver-test\",\r\n };\r\n const raw = await getSourcesRaw();\r\n let sourcesDoc: unknown;\r\n try {\r\n sourcesDoc = JSON.parse(raw) as unknown;\r\n } catch {\r\n sourcesDoc = { sources: [] };\r\n }\r\n\r\n const payload = {\r\n rssanyConnectivityTest: true,\r\n items: {\r\n sourceRef: \"rssany-deliver-test\",\r\n items: feedItemsToPayload([sample]),\r\n },\r\n sources: sourcesDoc,\r\n };\r\n await postDeliverGatewayTest(gateway.trim(), payload, { bearerToken: token || undefined });\r\n return c.json({ ok: true });\r\n } catch (err) {\r\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\r\n }\r\n });\r\n}\r\n","// LLM:读写 .rssany/config.json 的 llm 块;与 OPENAI_* 环境变量合并见 core/llmConfig.ts\r\n\r\nimport { readFile, writeFile } from \"node:fs/promises\";\r\nimport { CONFIG_PATH } from \"./paths.js\";\r\nimport { invalidateLLMConfigCache } from \"../core/llmConfig.js\";\r\n\r\nexport interface LlmFileConfig {\r\n apiKey?: string;\r\n baseUrl?: string;\r\n model?: string;\r\n}\r\n\r\nfunction trimOrUndef(s: unknown): string | undefined {\r\n if (typeof s !== \"string\") return undefined;\r\n const t = s.trim();\r\n return t.length > 0 ? t : undefined;\r\n}\r\n\r\n/** 读取 config.json 中的 llm 块(无则空) */\r\nexport async function readLlmFileConfig(): Promise<LlmFileConfig> {\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n const j = JSON.parse(raw) as { llm?: unknown };\r\n const llm = j?.llm;\r\n if (!llm || typeof llm !== \"object\") return {};\r\n const o = llm as Record<string, unknown>;\r\n return {\r\n apiKey: typeof o.apiKey === \"string\" ? o.apiKey : undefined,\r\n baseUrl: trimOrUndef(o.baseUrl),\r\n model: trimOrUndef(o.model),\r\n };\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nexport interface SaveLlmSettingsInput {\r\n baseUrl: string;\r\n model: string;\r\n apiKey?: string;\r\n}\r\n\r\n/** 合并写入 config.json 的 llm 块,不覆盖其它顶层字段 */\r\nexport async function saveLlmSettings(input: SaveLlmSettingsInput): Promise<void> {\r\n let root: Record<string, unknown> = {};\r\n try {\r\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\r\n root = JSON.parse(raw) as Record<string, unknown>;\r\n } catch {\r\n // 新建\r\n }\r\n const prev = await readLlmFileConfig();\r\n const next: Record<string, unknown> = {\r\n baseUrl: input.baseUrl.trim(),\r\n model: input.model.trim(),\r\n };\r\n\r\n const newKey = typeof input.apiKey === \"string\" && input.apiKey.length > 0 ? input.apiKey : undefined;\r\n if (newKey) {\r\n next.apiKey = newKey;\r\n } else if (prev.apiKey) {\r\n next.apiKey = prev.apiKey;\r\n }\r\n\r\n root.llm = next;\r\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\r\n invalidateLLMConfigCache();\r\n}\r\n","// /api/llm — 读写 LLM 配置(config.json.llm);GET 不返回完整 Key\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport { getLLMConfig } from \"../../../core/llmConfig.js\";\r\nimport { chatText } from \"../../../core/llm.js\";\r\nimport {readLlmFileConfig,saveLlmSettings} from \"../../../config/llmSettings.js\";\r\n\r\n\r\nexport function registerLlmRoutes(app: Hono): void {\r\n app.get(\"/api/llm\", requireAdmin(), async (c) => {\r\n const resolved = getLLMConfig();\r\n const file = await readLlmFileConfig();\r\n const hasApiKey = !!resolved.apiKey;\r\n const apiKeyInFile = !!(file.apiKey && file.apiKey.length > 0);\r\n return c.json({\r\n baseUrl: resolved.baseUrl,\r\n model: resolved.model,\r\n hasApiKey,\r\n apiKeyInFile,\r\n });\r\n });\r\n\r\n \r\n app.put(\"/api/llm\", requireAdmin(), async (c) => {\r\n try {\r\n const body = await c.req.json<{\r\n baseUrl?: unknown;\r\n model?: unknown;\r\n apiKey?: unknown;\r\n }>();\r\n const baseUrl = typeof body.baseUrl === \"string\" ? body.baseUrl : \"\";\r\n const model = typeof body.model === \"string\" ? body.model : \"\";\r\n const apiKey = typeof body.apiKey === \"string\" ? body.apiKey : undefined;\r\n await saveLlmSettings({\r\n baseUrl,\r\n model,\r\n ...(apiKey !== undefined ? { apiKey } : {}),\r\n });\r\n const resolved = getLLMConfig();\r\n const file = await readLlmFileConfig();\r\n return c.json({\r\n ok: true,\r\n baseUrl: resolved.baseUrl,\r\n model: resolved.model,\r\n hasApiKey: !!resolved.apiKey,\r\n apiKeyInFile: !!(file.apiKey && file.apiKey.length > 0),\r\n });\r\n } catch (err) {\r\n return c.json(\r\n { ok: false, message: err instanceof Error ? err.message : String(err) },\r\n 400,\r\n );\r\n }\r\n });\r\n\r\n app.post(\"/api/llm/test\", requireAdmin(), async (c) => {\r\n const t0 = Date.now();\r\n try {\r\n const cfg = getLLMConfig();\r\n if (!cfg.apiKey) {\r\n return c.json({ ok: false, message: \"未配置 API Key(请在界面或 OPENAI_API_KEY 中设置)\" }, 400);\r\n }\r\n const reply = await chatText(\"Reply with exactly the single word: ok\", undefined, {\r\n maxTokens: 32768,\r\n debugLabel: \"llmSettingsTest\",\r\n });\r\n return c.json({ ok: true, reply });\r\n } catch (err) {\r\n const ms = Date.now() - t0;\r\n const message = err instanceof Error ? err.message : String(err);\r\n console.error(\"[llm/test] fail\", { ms, message });\r\n return c.json({ ok: false, message }, 400);\r\n }\r\n });\r\n}\r\n","// GET/PUT /api/proxy — config.json 全局代理(admin)\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\nimport { readGlobalProxyFromConfig, saveGlobalProxyToConfig } from \"../../../config/globalProxy.js\";\r\n\r\nexport function registerProxySettingsRoutes(app: Hono): void {\r\n app.get(\"/api/proxy\", requireAdmin(), async (c) => {\r\n const globalProxy = (await readGlobalProxyFromConfig()) ?? \"\";\r\n return c.json({ globalProxy });\r\n });\r\n\r\n app.put(\"/api/proxy\", requireAdmin(), async (c) => {\r\n try {\r\n const body = (await c.req.json().catch(() => ({}))) as { globalProxy?: unknown };\r\n const globalProxy = typeof body.globalProxy === \"string\" ? body.globalProxy : \"\";\r\n await saveGlobalProxyToConfig(globalProxy);\r\n const saved = (await readGlobalProxyFromConfig()) ?? \"\";\r\n return c.json({ ok: true, globalProxy: saved });\r\n } catch (err) {\r\n return c.json(\r\n { ok: false, message: err instanceof Error ? err.message : String(err) },\r\n 400,\r\n );\r\n }\r\n });\r\n}\r\n","// 后端任务:提交后立即返回 taskId,任务在后台执行,前端轮询状态\n\nconst tasks = new Map<string, { id: string; status: string; result?: unknown; error?: string; createdAt: number; updatedAt: number }>();\nlet idCounter = 0;\n\nfunction nextId(): string {\n idCounter += 1;\n return `t_${Date.now().toString(36)}_${idCounter}`;\n}\n\nexport function createTask(): string {\n const id = nextId();\n const now = Date.now();\n tasks.set(id, { id, status: \"pending\", createdAt: now, updatedAt: now });\n return id;\n}\n\nexport function getTask(id: string) {\n return tasks.get(id) ?? null;\n}\n\nexport function setTaskRunning(id: string): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"running\";\n t.updatedAt = Date.now();\n }\n}\n\nexport function setTaskDone<T>(id: string, result: T): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"done\";\n t.result = result as unknown;\n t.updatedAt = Date.now();\n }\n}\n\nexport function setTaskError(id: string, error: string): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"error\";\n t.error = error;\n t.updatedAt = Date.now();\n }\n}\n\n/** 清理 1 小时前的已完成/失败任务 */\nexport function pruneTasks(): void {\n const cutoff = Date.now() - 60 * 60 * 1000;\n for (const [id, t] of tasks) {\n if ((t.status === \"done\" || t.status === \"error\") && t.updatedAt < cutoff) {\n tasks.delete(id);\n }\n }\n}\n","// 任务 API:POST /api/tasks 提交拉取信源,GET /api/tasks/:id 轮询\r\n\r\nimport type { Hono } from \"hono\";\r\nimport * as taskStore from \"../../../tasks/index.js\";\r\nimport * as scheduler from \"../../../scheduler/index.js\";\r\nimport { CACHE_DIR } from \"../../../config/paths.js\";\r\nimport { crawlSource } from \"../../../feeder/index.js\";\nimport { SOURCES_GROUP } from \"../../../scraper/scheduler/index.js\";\r\nimport { requireAdmin } from \"../../../auth/middleware.js\";\r\n\r\nexport function registerTasksRoutes(app: Hono): void {\r\n app.get(\"/api/tasks/:id\", (c) => {\r\n const id = c.req.param(\"id\") ?? \"\";\r\n const task = taskStore.getTask(id);\r\n if (!task) return c.json({ error: \"任务不存在\" }, 404);\r\n return c.json(task);\r\n });\r\n\r\n app.post(\"/api/tasks\", requireAdmin(), async (c) => {\r\n try {\r\n const body = (await c.req.json().catch(() => ({}))) as { type?: string; ref?: string };\r\n const type = body.type ?? \"\";\r\n if (type === \"source-pull\") {\r\n const ref = typeof body.ref === \"string\" ? body.ref.trim() : \"\";\r\n if (!ref) return c.json({ error: \"ref 不能为空\" }, 400);\r\n const taskId = taskStore.createTask();\r\n scheduler.schedule(SOURCES_GROUP, taskId, async () => {\r\n taskStore.setTaskRunning(taskId);\r\n try {\r\n await crawlSource(ref, { cacheDir: CACHE_DIR, force: true });\n taskStore.setTaskDone(taskId, { ok: true });\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n taskStore.setTaskError(taskId, msg);\r\n throw err;\r\n }\r\n }, { priority: true }).catch(() => {});\r\n return c.json({ taskId });\r\n }\r\n return c.json({ error: `未知任务类型: ${type}` }, 400);\r\n } catch (err) {\r\n return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);\r\n }\r\n });\r\n}\r\n","// GET /api/feed-favicon?domain=example.com — 信源列表站点图标;磁盘缓存在 CACHE_DIR/feed-favicons。\r\n//\r\n// 合法 domain 时始终返回 200 + 可用图片:上游全部失败则回退为字母 SVG(按域名稳定着色)。\r\n// 磁盘与 HTTP 缓存均为 3 天(mtime / max-age),过期后重新抓取。\r\n//\r\n// 多路上游并行竞速(Promise.any)。默认不含 Google s2(国内访问差):设 FAVICON_INCLUDE_GOOGLE=1 可启用。\r\n// 境外聚合源(DDG / icon.horse / unavatar)在国内网络可能慢或失败;若仅需直连站点图标,设 FAVICON_THIRD_PARTY=0。\r\n// 解析首页 HTML 中 link[rel~=icon] 等(尊重 <base href>);设 FAVICON_SKIP_HTML=1 可关闭。\r\n// 同域名 in-flight 合并。\r\n\r\nimport { createHash } from \"node:crypto\";\r\nimport { readFile, writeFile, mkdir, unlink, stat } from \"node:fs/promises\";\r\nimport { join } from \"node:path\";\r\nimport { parse } from \"node-html-parser\";\r\nimport type { Hono } from \"hono\";\r\nimport { CACHE_DIR } from \"../../../config/paths.js\";\r\n\r\nconst CACHE_SUBDIR = \"feed-favicons\";\r\nconst CACHE_KEY_PREFIX = \"feed-favicon:v1:\";\r\n/** 磁盘与 CDN/浏览器缓存:3 天 */\r\nconst CACHE_MAX_AGE_SEC = 3 * 24 * 60 * 60;\r\nconst CACHE_MAX_AGE_MS = CACHE_MAX_AGE_SEC * 1000;\r\nconst CACHE_CONTROL = `public, max-age=${CACHE_MAX_AGE_SEC}`;\r\n\r\nconst FETCH_TIMEOUT_MS = 6_000;\r\nconst MAX_ICON_BYTES = 2 * 1024 * 1024;\r\nconst MAX_HTML_BYTES = 512 * 1024;\r\n\r\nconst inflightByDomain = new Map<string, Promise<{ buf: Buffer; mime: string }>>();\r\n\r\nconst MAX_DOMAIN_LEN = 253;\r\n\r\nfunction isPlausibleHostname(s: string): boolean {\r\n if (s.length === 0 || s.length > MAX_DOMAIN_LEN) return false;\r\n return /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i.test(s);\r\n}\r\n\r\nfunction cacheFilePath(domainKey: string): string {\r\n const h = createHash(\"sha256\").update(CACHE_KEY_PREFIX + domainKey.toLowerCase()).digest(\"hex\");\r\n return join(CACHE_DIR, CACHE_SUBDIR, h);\r\n}\r\n\r\n/** 直连目标站常见图标路径(不依赖境外 CDN,适合国内部署优先尝试) */\r\nfunction originFaviconUrls(domain: string): string[] {\r\n const d = domain.toLowerCase();\r\n const hosts: string[] = [`https://${d}`];\r\n if (d.startsWith(\"www.\")) {\r\n const bare = d.slice(4);\r\n if (bare) hosts.push(`https://${bare}`);\r\n } else {\r\n hosts.push(`https://www.${d}`);\r\n }\r\n const paths = [\"/favicon.ico\", \"/favicon.png\", \"/apple-touch-icon.png\"];\r\n const urls: string[] = [];\r\n for (const base of [...new Set(hosts)]) {\r\n for (const p of paths) {\r\n urls.push(`${base}${p}`);\r\n }\r\n }\r\n return urls;\r\n}\r\n\r\nfunction homepageUrlsForDomain(domain: string): string[] {\r\n const d = domain.toLowerCase();\r\n const urls = [`https://${d}/`];\r\n if (d.startsWith(\"www.\")) {\r\n const bare = d.slice(4);\r\n if (bare) urls.push(`https://${bare}/`);\r\n } else {\r\n urls.push(`https://www.${d}/`);\r\n }\r\n return [...new Set(urls)];\r\n}\r\n\r\nfunction isIconLinkRel(rel: string): boolean {\r\n const tokens = rel\r\n .toLowerCase()\r\n .trim()\r\n .split(/\\s+/)\r\n .filter(Boolean);\r\n if (tokens.some((x) => x === \"mask-icon\")) return true;\r\n if (tokens.some((x) => x === \"apple-touch-icon\" || x === \"apple-touch-icon-precomposed\")) return true;\r\n if (tokens.includes(\"shortcut\") && tokens.includes(\"icon\")) return true;\r\n return tokens.includes(\"icon\");\r\n}\r\n\r\n/** 从 HTML 提取 <link rel=\"icon\" …> 等 href,按文档顺序去重 */\r\nfunction parseLinkIconHrefs(html: string, pageUrl: string): string[] {\r\n const root = parse(html, { lowerCaseTagName: true });\r\n let base = pageUrl;\r\n const baseEl = root.querySelector(\"base[href]\");\r\n if (baseEl) {\r\n const bh = baseEl.getAttribute(\"href\")?.trim();\r\n if (bh) {\r\n try {\r\n base = new URL(bh, pageUrl).href;\r\n } catch {\r\n /* keep pageUrl */\r\n }\r\n }\r\n }\r\n const out: string[] = [];\r\n const seen = new Set<string>();\r\n for (const el of root.querySelectorAll(\"link[href]\")) {\r\n const rel = el.getAttribute(\"rel\") ?? \"\";\r\n if (!isIconLinkRel(rel)) continue;\r\n const href = el.getAttribute(\"href\")?.trim();\r\n if (!href || href.startsWith(\"data:\") || href.startsWith(\"blob:\")) continue;\r\n try {\r\n const abs = new URL(href, base).href;\r\n if ((abs.startsWith(\"http:\") || abs.startsWith(\"https:\")) && !seen.has(abs)) {\r\n seen.add(abs);\r\n out.push(abs);\r\n }\r\n } catch {\r\n /* skip */\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nasync function fetchHtmlPage(url: string): Promise<string | null> {\r\n try {\r\n const upstream = await fetch(url, {\r\n redirect: \"follow\",\r\n headers: {\r\n Accept: \"text/html,application/xhtml+xml;q=0.9,*/*;q=0.1\",\r\n \"User-Agent\": \"Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon\",\r\n },\r\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\r\n });\r\n if (!upstream.ok) return null;\r\n const ab = await upstream.arrayBuffer();\r\n const buf = Buffer.from(ab);\r\n const slice = buf.subarray(0, Math.min(buf.length, MAX_HTML_BYTES));\r\n return slice.toString(\"utf-8\");\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** 抓取首页 HTML,解析 link 图标;按候选首页顺序直到得到至少一条 href */\r\nasync function discoverIconUrlsFromHomepage(domain: string): Promise<string[]> {\r\n if (process.env.FAVICON_SKIP_HTML === \"1\" || process.env.FAVICON_SKIP_HTML === \"true\") {\r\n return [];\r\n }\r\n for (const pageUrl of homepageUrlsForDomain(domain)) {\r\n const html = await fetchHtmlPage(pageUrl);\r\n if (!html) continue;\r\n const hrefs = parseLinkIconHrefs(html, pageUrl);\r\n if (hrefs.length > 0) return hrefs;\r\n }\r\n return [];\r\n}\r\n\r\nfunction duckduckgoFaviconUrl(domain: string): string {\r\n return `https://icons.duckduckgo.com/ip3/${domain}.ico`;\r\n}\r\n\r\nfunction iconHorseUrl(domain: string): string {\r\n return `https://icon.horse/icon/${encodeURIComponent(domain)}`;\r\n}\r\n\r\nfunction unavatarUrl(domain: string): string {\r\n return `https://unavatar.io/${encodeURIComponent(domain)}`;\r\n}\r\n\r\nfunction googleFaviconUrl(domain: string): string {\r\n return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=64`;\r\n}\r\n\r\nfunction letterCharFromDomain(domain: string): string {\r\n const d = domain.toLowerCase().replace(/^www\\./, \"\");\r\n const m = d.match(/[a-z0-9]/);\r\n return m ? m[0]!.toUpperCase() : \"?\";\r\n}\r\n\r\nfunction hueFromDomain(domain: string): number {\r\n const h = createHash(\"sha256\").update(domain.toLowerCase()).digest();\r\n return ((h[0]! << 8) | h[1]!) % 360;\r\n}\r\n\r\nfunction escapeXmlText(s: string): string {\r\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\r\n}\r\n\r\n/** 上游全部失败时的稳定回退:圆角方块 + 首字母 */\r\nfunction letterAvatarSvg(domain: string): Buffer {\r\n const letter = escapeXmlText(letterCharFromDomain(domain));\r\n const hue = hueFromDomain(domain);\r\n const bg = `hsl(${hue} 42% 44%)`;\r\n const svg = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\">\r\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"${bg}\"/>\r\n <text x=\"32\" y=\"32\" dominant-baseline=\"central\" text-anchor=\"middle\" fill=\"#ffffff\" font-family=\"system-ui,Segoe UI,Helvetica,sans-serif\" font-size=\"28\" font-weight=\"600\">${letter}</text>\r\n</svg>`;\r\n return Buffer.from(svg.trim(), \"utf-8\");\r\n}\r\n\r\nfunction letterAvatarForDomain(domain: string): { buf: Buffer; mime: string } {\r\n return { buf: letterAvatarSvg(domain), mime: \"image/svg+xml\" };\r\n}\r\n\r\nfunction isEnoent(e: unknown): boolean {\r\n return typeof e === \"object\" && e !== null && (e as NodeJS.ErrnoException).code === \"ENOENT\";\r\n}\r\n\r\nfunction sniffImageMime(buf: Buffer): string | null {\r\n if (buf.length < 4) return null;\r\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return \"image/png\";\r\n if (buf.length >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) return \"image/gif\";\r\n if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return \"image/jpeg\";\r\n if (\r\n buf.length >= 12 &&\r\n buf.subarray(0, 4).toString(\"ascii\") === \"RIFF\" &&\r\n buf.subarray(8, 12).toString(\"ascii\") === \"WEBP\"\r\n ) {\r\n return \"image/webp\";\r\n }\r\n if (buf.length >= 6 && buf.readUInt16LE(0) === 0 && (buf[2] === 1 || buf[2] === 2) && buf[3] === 0) {\r\n return \"image/x-icon\";\r\n }\r\n const head = buf.subarray(0, Math.min(256, buf.length)).toString(\"utf-8\").trimStart();\r\n if (head.startsWith(\"<svg\") || head.startsWith(\"<?xml\")) return \"image/svg+xml\";\r\n return null;\r\n}\r\n\r\nconst IMAGE_CT_PREFIX = \"image/\";\r\n\r\nfunction mimeFromFetch(ct: string | null): string | null {\r\n if (!ct) return null;\r\n const base = ct.split(\";\")[0].trim().toLowerCase();\r\n return base.startsWith(IMAGE_CT_PREFIX) ? base : null;\r\n}\r\n\r\nfunction resolveImageMime(buf: Buffer, ct: string | null): string | null {\r\n return sniffImageMime(buf) ?? mimeFromFetch(ct);\r\n}\r\n\r\nasync function fetchIconCandidate(url: string): Promise<{ buf: Buffer; ct: string | null } | null> {\r\n let upstream: Response;\r\n try {\r\n upstream = await fetch(url, {\r\n redirect: \"follow\",\r\n headers: {\r\n Accept: \"image/avif,image/webp,image/apng,image/*,*/*;q=0.8\",\r\n \"User-Agent\": \"Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon\",\r\n },\r\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\r\n });\r\n } catch {\r\n return null;\r\n }\r\n if (!upstream.ok) return null;\r\n const ab = await upstream.arrayBuffer();\r\n const buf = Buffer.from(ab);\r\n if (buf.length === 0 || buf.length > MAX_ICON_BYTES) return null;\r\n return { buf, ct: upstream.headers.get(\"content-type\") };\r\n}\r\n\r\nfunction isValidIcon(got: { buf: Buffer; ct: string | null } | null): got is { buf: Buffer; ct: string | null } {\r\n if (!got) return false;\r\n const mime = resolveImageMime(got.buf, got.ct);\r\n return !!(mime && mime.startsWith(IMAGE_CT_PREFIX));\r\n}\r\n\r\nfunction upstreamFaviconUrls(domain: string, htmlIconUrls: string[]): string[] {\r\n const urls: string[] = [...originFaviconUrls(domain), ...htmlIconUrls];\r\n const thirdPartyOff =\r\n process.env.FAVICON_THIRD_PARTY === \"0\" || process.env.FAVICON_THIRD_PARTY === \"false\";\r\n if (!thirdPartyOff) {\r\n urls.push(duckduckgoFaviconUrl(domain), iconHorseUrl(domain), unavatarUrl(domain));\r\n }\r\n const includeGoogle =\r\n process.env.FAVICON_INCLUDE_GOOGLE === \"1\" || process.env.FAVICON_INCLUDE_GOOGLE === \"true\";\r\n if (includeGoogle) urls.push(googleFaviconUrl(domain));\r\n return urls;\r\n}\r\n\r\nasync function fetchFaviconFromNetwork(domain: string): Promise<{ buf: Buffer; mime: string }> {\r\n const htmlIconUrls = await discoverIconUrlsFromHomepage(domain);\r\n const urls = upstreamFaviconUrls(domain, htmlIconUrls);\r\n const tasks = urls.map(async (url) => {\r\n const got = await fetchIconCandidate(url);\r\n if (!isValidIcon(got)) {\r\n throw new Error(\"not-an-icon\");\r\n }\r\n const mime = resolveImageMime(got.buf, got.ct)!;\r\n return { buf: got.buf, mime };\r\n });\r\n try {\r\n return await Promise.any(tasks);\r\n } catch {\r\n return letterAvatarForDomain(domain);\r\n }\r\n}\r\n\r\nfunction fetchFaviconDeduped(domain: string): Promise<{ buf: Buffer; mime: string }> {\r\n let p = inflightByDomain.get(domain);\r\n if (p) return p;\r\n p = fetchFaviconFromNetwork(domain).finally(() => {\r\n if (inflightByDomain.get(domain) === p) inflightByDomain.delete(domain);\r\n });\r\n inflightByDomain.set(domain, p);\r\n return p;\r\n}\r\n\r\nexport function registerFeedFaviconRoutes(app: Hono): void {\r\n app.get(\"/api/feed-favicon\", async (c) => {\r\n const raw = (c.req.query(\"domain\") ?? \"\").trim();\r\n if (!raw || !isPlausibleHostname(raw)) {\r\n return new Response(null, { status: 400 });\r\n }\r\n const domain = raw.toLowerCase();\r\n const path = cacheFilePath(domain);\r\n\r\n let diskStale = false;\r\n try {\r\n const st = await stat(path);\r\n if (Date.now() - st.mtimeMs >= CACHE_MAX_AGE_MS) {\r\n diskStale = true;\r\n await unlink(path).catch(() => {});\r\n }\r\n } catch (e) {\r\n if (!isEnoent(e)) {\r\n return new Response(null, { status: 500 });\r\n }\r\n }\r\n\r\n if (!diskStale) {\r\n try {\r\n const cached = await readFile(path);\r\n const mime = resolveImageMime(cached, null);\r\n if (mime) {\r\n return new Response(new Uint8Array(cached), {\r\n status: 200,\r\n headers: {\r\n \"Content-Type\": mime,\r\n \"Cache-Control\": CACHE_CONTROL,\r\n },\r\n });\r\n }\r\n await unlink(path).catch(() => {});\r\n } catch (e) {\r\n if (!isEnoent(e)) {\r\n return new Response(null, { status: 500 });\r\n }\r\n }\r\n }\r\n\r\n const resolved = await fetchFaviconDeduped(domain);\r\n const { buf, mime } = resolved;\r\n\r\n try {\r\n await mkdir(join(CACHE_DIR, CACHE_SUBDIR), { recursive: true });\r\n await writeFile(path, buf);\r\n } catch {\r\n return new Response(null, { status: 500 });\r\n }\r\n\r\n return new Response(new Uint8Array(buf), {\r\n status: 200,\r\n headers: {\r\n \"Content-Type\": mime,\r\n \"Cache-Control\": CACHE_CONTROL,\r\n },\r\n });\r\n });\r\n}\r\n","// API 路由汇总:server、rss、items、feed、sources、scheduler、plugins、logs、admin、tags、tasks\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { registerServerRoutes } from \"./server.js\";\r\nimport { registerRssApiRoutes } from \"./rss.js\";\r\nimport { registerSchedulerRoutes } from \"./scheduler.js\";\r\nimport { registerPluginsRoutes } from \"./plugins.js\";\r\nimport { registerPipelineRoutes } from \"./pipeline.js\";\r\nimport { registerFeedRoutes } from \"./feed.js\";\r\nimport { registerItemsRoutes } from \"./items.js\";\r\nimport { registerLogsRoutes } from \"./logs.js\";\r\nimport { registerAdminApiRoutes } from \"./admin.js\";\r\nimport { registerSourcesRoutes } from \"./sources.js\";\r\nimport { registerTopicsRoutes } from \"./topics.js\";\r\nimport { registerDeliverRoutes } from \"./deliver.js\";\r\nimport { registerLlmRoutes } from \"./llm.js\";\r\nimport { registerProxySettingsRoutes } from \"./proxy.js\";\r\nimport { registerTasksRoutes } from \"./tasks.js\";\r\nimport { registerFeedFaviconRoutes } from \"./feed-favicon.js\";\r\n\r\nexport function registerApiRoutes(app: Hono): void {\r\n registerServerRoutes(app);\r\n registerFeedFaviconRoutes(app);\r\n registerRssApiRoutes(app);\r\n registerSchedulerRoutes(app);\r\n registerPluginsRoutes(app);\r\n registerPipelineRoutes(app);\r\n registerFeedRoutes(app);\r\n registerItemsRoutes(app);\r\n registerLogsRoutes(app);\r\n registerAdminApiRoutes(app);\r\n registerSourcesRoutes(app);\r\n registerTopicsRoutes(app);\r\n registerDeliverRoutes(app);\r\n registerLlmRoutes(app);\r\n registerProxySettingsRoutes(app);\r\n registerTasksRoutes(app);\r\n}\r\n","// 认证路由:登录检查、打开登录页、ensureAuth\n\nimport type { Hono } from \"hono\";\nimport { getWebSite, getBestSite, toAuthFlow } from \"../../scraper/sources/web/index.js\";\nimport { applyProxyAuthToPage, ensureAuth, preCheckAuth, launchBrowser, resolveProxy } from \"../../scraper/sources/web/fetcher/index.js\";\nimport { CACHE_DIR } from \"../../config/paths.js\";\nimport { resolveProxyForSite } from \"../../config/globalProxy.js\";\n\nexport function registerAuthRoutes(app: Hono): void {\n app.get(\"/auth/check\", async (c) => {\n const siteIdParam = c.req.query(\"siteId\");\n if (!siteIdParam) {\n return c.json({ ok: false, message: \"请提供 siteId\" }, 400);\n }\n const site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n try {\n const authenticated = await preCheckAuth(authFlow, CACHE_DIR, { proxy: await resolveProxyForSite(site) });\n return c.json({ ok: true, authenticated });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, message: `检查失败: ${msg}` }, 500);\n }\n });\n\n app.post(\"/auth/open\", async (c) => {\n const siteIdParam = c.req.query(\"siteId\");\n if (!siteIdParam) {\n return c.json({ ok: false, message: \"请提供 siteId\" }, 400);\n }\n const site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n const { loginUrl } = authFlow;\n const proxy = await resolveProxyForSite(site);\n void launchBrowser({ headless: false, cacheDir: CACHE_DIR, proxy: resolveProxy({ proxy }) }).then(async (browser) => {\n try {\n const page = await browser.newPage();\n await applyProxyAuthToPage(page, { proxy });\n const realUserAgent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\n await page.setUserAgent(realUserAgent);\n await page.setViewport({ width: 1366, height: 960 });\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 60000 });\n page.once(\"close\", () => {\n void browser.close().catch(() => {});\n });\n } catch {\n await browser.close().catch(() => {});\n }\n }).catch(() => {});\n return c.json({ ok: true, message: \"已打开登录页面\" });\n });\n\n app.post(\"/auth/ensure\", async (c) => {\n const urlParam = c.req.query(\"url\");\n const siteIdParam = c.req.query(\"siteId\");\n let site;\n if (urlParam) {\n const decoded = decodeURIComponent(urlParam);\n site = getBestSite(decoded);\n if (!site) return c.json({ ok: false, message: \"无匹配站点\" }, 404);\n } else if (siteIdParam) {\n site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n } else {\n return c.json({ ok: false, message: \"请提供 url 或 siteId\" }, 400);\n }\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n ensureAuth(authFlow, CACHE_DIR, { proxy: await resolveProxyForSite(site) }).then(() => {}).catch(() => {});\n return c.json({ ok: true, message: \"已打开登录窗口,请在弹出的浏览器中完成登录,完成后刷新订阅页面即可。\" });\n });\n}\n","// 路由层共享工具函数\r\n\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { join } from \"node:path\";\r\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\r\n\r\nexport const STATICS_DIR = join(PACKAGE_ROOT, \"app/statics\");\r\n\r\n/** 从路径提取 URL(与 /rss/* 一致) */\r\nexport function parseUrlFromPath(path: string, prefix: string): string | null {\r\n const raw = path.slice(prefix.length) || \"\";\r\n const decoded = decodeURIComponent(raw.startsWith(\"/\") ? raw.slice(1) : raw);\r\n if (!decoded) return null;\r\n return decoded.startsWith(\"http\") ? decoded : `https://${decoded}`;\r\n}\r\n\r\n/** 读取静态 HTML(app/statics/ 目录,用于 401/404 错误页) */\r\nexport async function readStaticHtml(name: string, fallback: string): Promise<string> {\r\n try {\r\n return await readFile(join(STATICS_DIR, `${name}.html`), \"utf-8\");\r\n } catch {\r\n return fallback;\r\n }\r\n}\r\n\r\n/** HTML 转义,用于注入到页面中的不可信内容 */\r\nexport function escapeHtml(s: string): string {\r\n return s\r\n .replace(/&/g, \"&\")\r\n .replace(/</g, \"<\")\r\n .replace(/>/g, \">\")\r\n .replace(/\"/g, \""\")\r\n .replace(/'/g, \"'\");\r\n}\r\n","// Admin 路由:解析调试、正文提取调试\r\n\r\nimport type { Hono } from \"hono\";\r\nimport { getSource } from \"../../scraper/sources/index.js\";\r\nimport { buildSourceContext } from \"../../scraper/sources/context.js\";\r\nimport { extractFromLink } from \"../../scraper/sources/web/extractor/index.js\";\r\nimport { CACHE_DIR } from \"../../config/paths.js\";\r\nimport { AuthRequiredError } from \"../../scraper/auth/index.js\";\r\nimport { parseUrlFromPath, readStaticHtml, escapeHtml } from \"../utils.js\";\r\nimport { requireAdmin } from \"../../auth/middleware.js\";\r\nimport { getEffectiveProxyForListUrl } from \"../../scraper/subscription/index.js\";\r\n\r\n/** 与 fetcher `resolveProxy` 一致:调试 query 优先 → sources.json / Source.proxy → 环境变量 */\r\nfunction effectiveProxyUsed(override: string | undefined, mergedFromSource: string | undefined): string | undefined {\r\n const o = override?.trim();\r\n if (o) return o;\r\n const s = mergedFromSource?.trim();\r\n if (s) return s;\r\n return process.env.HTTP_PROXY?.trim() || process.env.HTTPS_PROXY?.trim();\r\n}\r\n\r\nfunction redactProxyForLog(p: string | undefined): string | null {\r\n if (!p) return null;\r\n try {\r\n const u = new URL(p);\r\n if (u.username) u.username = \"***\";\r\n if (u.password) u.password = \"***\";\r\n return u.toString();\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function registerAdminRoutes(app: Hono): void {\r\n async function render401(listUrl: string): Promise<string> {\r\n const raw = await readStaticHtml(\"401\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>401</title></head><body><h1>401 需要登录</h1></body></html>\");\r\n return raw.replace(/\\{\\{listUrl\\}\\}/g, escapeHtml(listUrl));\r\n }\r\n\r\n /** Parse 与插件解耦:始终通过 getSource(url) 解析,无匹配插件时自动走 generic(浏览器抓取 + LLM 解析),与 /rss/* 行为一致 */\r\n app.get(\"/admin/parse/*\", requireAdmin(), async (c) => {\r\n const url = parseUrlFromPath(c.req.path, \"/admin/parse\");\r\n if (!url) return c.text(\"无效 URL,格式: /admin/parse/https://... 或 /admin/parse/example.com/...\", 400);\r\n try {\r\n // 调试默认有头浏览器;仅 headless=true|1 时使用无头\r\n const headlessParam = c.req.query(\"headless\");\r\n const headless = headlessParam === \"true\" || headlessParam === \"1\";\r\n const proxyOverride = c.req.query(\"proxy\")?.trim();\r\n const source = getSource(url);\r\n const fromSource = await getEffectiveProxyForListUrl(url, source);\r\n const ctx = buildSourceContext({\r\n cacheDir: CACHE_DIR,\r\n headless,\r\n proxy: proxyOverride || fromSource,\r\n });\r\n const items = await source.fetchItems(url, ctx);\r\n const mode = source.id === \"generic\" ? \"generic\" : \"plugin\";\r\n const effective = effectiveProxyUsed(proxyOverride, fromSource);\r\n return c.json({\r\n items,\r\n url,\r\n mode,\r\n pluginId: source.id,\r\n effectiveProxy: redactProxyForLog(effective),\r\n });\r\n } catch (err) {\r\n if (err instanceof AuthRequiredError) {\r\n const html = await render401(url);\r\n return c.html(html, 401);\r\n }\r\n const msg = err instanceof Error ? err.message : String(err);\r\n return c.text(`解析失败: ${msg}`, 500);\r\n }\r\n });\r\n\r\n app.get(\"/admin/extractor/*\", requireAdmin(), async (c) => {\r\n const url = parseUrlFromPath(c.req.path, \"/admin/extractor\");\r\n if (!url) return c.text(\"无效 URL,格式: /admin/extractor/https://... 或 /admin/extractor/example.com/...\", 400);\r\n try {\r\n const headlessParam = c.req.query(\"headless\");\r\n const headless = headlessParam === \"true\" || headlessParam === \"1\";\r\n const proxyOverride = c.req.query(\"proxy\")?.trim();\r\n const source = getSource(url);\r\n const fromSource = await getEffectiveProxyForListUrl(url, source);\r\n const proxy = proxyOverride || fromSource;\r\n const result = await extractFromLink(url, {}, { timeoutMs: 60_000, headless, proxy });\r\n const effective = effectiveProxyUsed(proxyOverride, fromSource);\r\n return c.json({\r\n title: result.title ?? null,\r\n author: result.author ?? null,\r\n pubDate: result.pubDate ?? null,\r\n content: result.content ?? null,\r\n _extractor: \"readability\",\r\n effectiveProxy: redactProxyForLog(effective),\r\n });\r\n } catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n return c.text(`提取失败: ${msg}`, 500);\r\n }\r\n });\r\n}\r\n","// RSS 路由:/rss/* 生成 RSS XML\r\n\r\n\r\n\r\nimport { createHash } from \"node:crypto\";\r\n\r\nimport type { Hono } from \"hono\";\r\n\r\nimport { getItems, feedItemsToRssXml } from \"../../feeder/index.js\";\r\n\r\nimport { queryItems } from \"../../db/index.js\";\r\n\r\nimport { getAllSubscriptionRefs } from \"../../scraper/subscription/index.js\";\r\n\r\nimport { SOURCES_GROUP } from \"../../scraper/scheduler/index.js\";\r\n\r\nimport * as scheduler from \"../../scheduler/index.js\";\r\n\r\nimport { CACHE_DIR } from \"../../config/paths.js\";\r\n\r\nimport { AuthRequiredError, NotFoundError } from \"../../scraper/auth/index.js\";\r\n\r\nimport { parseUrlFromPath, readStaticHtml, escapeHtml } from \"../utils.js\";\r\n\r\n\r\n\r\nfunction parseSubscribedFlag(v: string | undefined): boolean {\r\n\r\n return v === \"1\" || v === \"true\" || v === \"yes\";\r\n\r\n}\r\n\r\n\r\n\r\nexport function registerRssRoutes(app: Hono): void {\r\n\r\n async function render401(listUrl: string): Promise<string> {\r\n\r\n const raw = await readStaticHtml(\"401\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>401</title></head><body><h1>401 需要登录</h1></body></html>\");\r\n\r\n return raw.replace(/\\{\\{listUrl\\}\\}/g, escapeHtml(listUrl));\r\n\r\n }\r\n\r\n\r\n\r\n /** 查询式 RSS:按 search、sourceUrl、subscribed、author、tags 过滤,返回匹配条目的 XML */\r\n\r\n app.get(\"/rss\", async (c) => {\r\n\r\n const search = c.req.query(\"search\") ?? c.req.query(\"q\") ?? undefined;\r\n\r\n const ref = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? c.req.query(\"sourceUrl\") ?? undefined;\r\n\r\n const subscribed = parseSubscribedFlag(c.req.query(\"subscribed\"));\r\n\r\n const author = c.req.query(\"author\") ?? undefined;\r\n\r\n const tagsParam = c.req.query(\"tags\") ?? undefined;\r\n\r\n const tags = tagsParam ? tagsParam.split(\",\").map((t) => t.trim()).filter(Boolean) : undefined;\r\n\r\n const lng = c.req.query(\"lng\") ?? undefined;\r\n\r\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 50), 200);\r\n\r\n const offset = Number(c.req.query(\"offset\") ?? 0);\r\n\r\n const title = c.req.query(\"title\") ?? undefined;\r\n\r\n const daysParam = c.req.query(\"days\");\r\n\r\n const sinceParam = c.req.query(\"since\") ?? undefined;\r\n\r\n const untilParam = c.req.query(\"until\") ?? undefined;\r\n\r\n let since: Date | undefined;\r\n\r\n let until: Date | undefined;\r\n\r\n if (daysParam) {\r\n\r\n const n = Math.max(1, Math.min(365, Number(daysParam) || 1));\r\n\r\n const now = new Date();\r\n\r\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\r\n\r\n const todayEnd = new Date(todayStart);\r\n\r\n todayEnd.setDate(todayEnd.getDate() + 1);\r\n\r\n since = new Date(todayStart);\r\n\r\n since.setDate(since.getDate() - (n - 1));\r\n\r\n until = todayEnd;\r\n\r\n } else {\r\n\r\n since = sinceParam ? new Date(sinceParam) : undefined;\r\n\r\n if (untilParam) {\r\n\r\n if (untilParam.length === 10) {\r\n\r\n const d = new Date(untilParam + \"T12:00:00Z\");\r\n\r\n d.setUTCDate(d.getUTCDate() + 1);\r\n\r\n until = d;\r\n\r\n } else {\r\n\r\n until = new Date(untilParam);\r\n\r\n }\r\n\r\n }\r\n\r\n }\r\n\r\n\r\n\r\n let sourceUrls: string[] | undefined;\r\n\r\n if (ref) {\r\n\r\n sourceUrls = undefined;\r\n\r\n } else if (subscribed) {\r\n\r\n sourceUrls = await getAllSubscriptionRefs();\r\n\r\n }\r\n\r\n\r\n\r\n if (sourceUrls?.length === 0) {\r\n\r\n const xml = feedItemsToRssXml([], new URL(c.req.url).href, lng, {\r\n\r\n channelTitle: title ?? \"RSS 订阅\",\r\n\r\n channelDesc: \"无匹配条目\",\r\n\r\n });\r\n\r\n return c.body(xml, 200, { \"Content-Type\": \"application/rss+xml; charset=utf-8\" });\r\n\r\n }\r\n\r\n\r\n\r\n const result = await queryItems({\r\n\r\n sourceUrl: sourceUrls ? undefined : ref,\r\n\r\n sourceUrls,\r\n\r\n author,\r\n\r\n q: search,\r\n\r\n tags,\r\n\r\n since,\r\n\r\n until,\r\n\r\n limit,\r\n\r\n offset,\r\n\r\n });\r\n\r\n const feedItems = result.items.map((dbItem) => ({\r\n\r\n guid: dbItem.id,\r\n\r\n title: dbItem.title ?? \"\",\r\n\r\n link: dbItem.url,\r\n\r\n pubDate: dbItem.pub_date ? new Date(dbItem.pub_date) : new Date(),\r\n\r\n author: dbItem.author ?? undefined,\r\n\r\n summary: dbItem.summary ?? undefined,\r\n\r\n content: dbItem.content ?? undefined,\r\n\r\n imageUrl: dbItem.image_url ?? undefined,\r\n\r\n tags: dbItem.tags ?? undefined,\r\n\r\n sourceRef: dbItem.source_url,\r\n\r\n translations: dbItem.translations ?? undefined,\r\n\r\n }));\r\n\r\n\r\n\r\n const rssUrl = new URL(c.req.url);\r\n\r\n const channelTitle = title ?? \"RSS 订阅\";\r\n\r\n const xml = feedItemsToRssXml(feedItems, rssUrl.href, lng, {\r\n\r\n channelTitle,\r\n\r\n channelDesc: `来自 ${rssUrl.href} 的订阅`,\r\n\r\n });\r\n\r\n return c.body(xml, 200, {\r\n\r\n \"Content-Type\": \"application/rss+xml; charset=utf-8\",\r\n\r\n });\r\n\r\n });\r\n\r\n\r\n\r\n /** 按 URL 抓取式 RSS:/rss/https://... 从信源实时抓取 */\r\n\r\n app.get(\"/rss/*\", async (c) => {\r\n\r\n const url = parseUrlFromPath(c.req.path, \"/rss\");\r\n\r\n if (!url) return c.text(\"无效 URL,格式: /rss/https://... 或 /rss/www.xiaohongshu.com/...\", 400);\r\n\r\n try {\r\n\r\n const headlessParam = c.req.query(\"headless\");\r\n\r\n const headless = headlessParam === \"false\" || headlessParam === \"0\" ? false : undefined;\r\n\r\n const lng = c.req.query(\"lng\") ?? undefined;\r\n\r\n const httpId = \"rss-\" + createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\r\n\r\n const { items } = await scheduler.schedule(\r\n\r\n SOURCES_GROUP,\r\n\r\n httpId,\r\n\r\n () => getItems(url, { cacheDir: CACHE_DIR, headless, lng }),\r\n\r\n );\r\n\r\n const xml = feedItemsToRssXml(items, url, lng);\r\n\r\n return c.body(xml, 200, {\r\n\r\n \"Content-Type\": \"application/rss+xml; charset=utf-8\",\r\n\r\n });\r\n\r\n } catch (err) {\r\n\r\n if (err instanceof AuthRequiredError) {\r\n\r\n const html = await render401(url);\r\n\r\n return c.html(html, 401);\r\n\r\n }\r\n\r\n if (err instanceof NotFoundError) {\r\n\r\n const html = await readStaticHtml(\"404\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>404</title></head><body><h1>404 未找到</h1></body></html>\");\r\n\r\n return c.html(html, 404);\r\n\r\n }\r\n\r\n const msg = err instanceof Error ? err.message : String(err);\r\n\r\n return c.text(`生成 RSS 失败: ${msg}`, 500);\r\n\r\n }\r\n\r\n });\r\n\r\n}\r\n\r\n","// 生产环境:在同一端口托管 SvelteKit 静态构建(adapter-static + 200.html SPA fallback)\r\n\r\nimport { existsSync } from \"node:fs\";\r\nimport { readFile } from \"node:fs/promises\";\r\nimport { join, relative } from \"node:path\";\r\nimport { serveStatic } from \"@hono/node-server/serve-static\";\r\nimport type { Context, Hono } from \"hono\";\r\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\r\n\r\n/** 与 svelte.config.js 中 adapter-static 的 pages/assets 输出目录一致 */\r\nexport function getWebUiBuildDir(): string {\r\n const w = process.env.WEBUI_BUILD_DIR?.trim();\r\n if (w) {\r\n if (w.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(w)) return w;\r\n return join(process.cwd(), w);\r\n }\r\n return join(PACKAGE_ROOT, \"app/webui/build\");\r\n}\r\n\r\n/** 仅后端接口路径,不走静态/SPA;注意 /admin 为前端路由,仅 /admin/parse、/admin/extractor 为后端 */\r\nfunction isBackendOnlyPath(pathname: string): boolean {\r\n if (pathname.startsWith(\"/api\")) return true;\r\n if (pathname.startsWith(\"/rss\")) return true;\r\n if (pathname.startsWith(\"/auth\")) return true;\r\n if (pathname.startsWith(\"/admin/parse\") || pathname.startsWith(\"/admin/extractor\")) return true;\r\n return false;\r\n}\r\n\r\n/** 明显是静态资源路径但磁盘上无对应文件时,不应返回 200.html */\r\nfunction looksLikeStaticAsset(pathname: string): boolean {\r\n return /\\.[a-zA-Z0-9]{1,12}$/.test(pathname);\r\n}\r\n\r\n/**\r\n * 在已注册全部 API 路由之后调用。\r\n * `serveStatic` 的 root 需为相对 cwd 的路径(见 @hono/node-server/serve-static)。\r\n */\r\nexport function registerWebUiRoutes(app: Hono): void {\r\n const absRoot = getWebUiBuildDir();\r\n if (!existsSync(absRoot)) {\r\n console.warn(\r\n \"未找到 WebUI 构建目录,静态路由已注册,等待前端 watch 构建:\",\r\n absRoot,\r\n \"(开发模式:npm run dev;单独构建:npm run webui:build)\",\r\n );\r\n }\r\n\r\n const relRoot = relative(process.cwd(), absRoot).replace(/\\\\/g, \"/\");\r\n const staticRoot =\r\n relRoot === \"\" || relRoot === \".\"\r\n ? \".\"\r\n : relRoot.startsWith(\".\") || relRoot.startsWith(\"/\") || /^[A-Za-z]:/.test(relRoot)\r\n ? relRoot\r\n : `./${relRoot}`;\r\n\r\n const staticMw = serveStatic({\r\n root: staticRoot,\r\n index: \"200.html\",\r\n });\r\n\r\n app.use(\"*\", async (c, next) => {\r\n if (isBackendOnlyPath(c.req.path)) return next();\r\n return staticMw(c, next);\r\n });\r\n\r\n const spaFallback = async (c: Context) => {\r\n const p = c.req.path;\r\n if (isBackendOnlyPath(p)) return c.notFound();\r\n if (looksLikeStaticAsset(p)) return c.notFound();\r\n try {\r\n const html = await readFile(join(absRoot, \"200.html\"), \"utf-8\");\r\n return c.html(html);\r\n } catch {\r\n return c.notFound();\r\n }\r\n };\r\n\r\n app.get(\"*\", spaFallback);\r\n}\r\n","import { readFileSync } from \"node:fs\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { dirname, join } from \"node:path\";\r\n\r\nconst here = dirname(fileURLToPath(import.meta.url));\r\n\r\n/** 运行时读取仓库根目录 package.json(编译后为 dist/version.js → 上一级为根) */\r\nexport function getAppVersion(): string {\r\n try {\r\n const pkgPath = join(here, \"../package.json\");\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\r\n return pkg.version ?? \"unknown\";\r\n } catch {\r\n return \"unknown\";\r\n }\r\n}\r\n","// App 入口:Hono 服务,与 feeder 解耦;可替换为 Express 等\r\n\r\nimport \"dotenv/config\";\r\nimport { watch } from \"node:fs\";\r\nimport { networkInterfaces } from \"node:os\";\r\nimport { serve } from \"@hono/node-server\";\r\nimport { Hono } from \"hono\";\r\nimport { cors } from \"hono/cors\";\r\nimport { initSources as initSites } from \"../scraper/sources/index.js\";\r\nimport { initScheduler } from \"../scraper/scheduler/index.js\";\r\nimport { initUserDir, BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR, CACHE_DIR } from \"../config/paths.js\";\r\nimport { logger } from \"../core/logger/index.js\";\r\nimport { registerApiRoutes } from \"./routes/api/index.js\";\r\nimport { registerAuthRoutes } from \"./routes/auth.js\";\r\nimport { registerAdminRoutes } from \"./routes/admin.js\";\r\nimport { registerRssRoutes } from \"./routes/rss.js\";\r\nimport { registerWebUiRoutes } from \"./webui.js\";\r\nimport { getAppVersion } from \"../version.js\";\r\n\r\nconst PORT = Number(process.env.PORT) || 18473;\r\nconst IS_DEV = process.env.NODE_ENV === \"development\" || process.argv.includes(\"--watch\");\r\nconst PLUGIN_WATCH_EXTS = [\".rssany.js\", \".rssany.ts\"];\r\n\r\nfunction createApp(): Hono {\r\n const app = new Hono();\r\n\r\n app.use(\r\n \"*\",\r\n cors({\r\n origin: \"*\",\r\n allowMethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"],\r\n allowHeaders: [\"Content-Type\", \"Authorization\", \"X-Admin-Token\"],\r\n }),\r\n );\r\n\r\n registerApiRoutes(app);\r\n registerAuthRoutes(app);\r\n registerAdminRoutes(app);\r\n registerRssRoutes(app);\r\n registerWebUiRoutes(app);\r\n\r\n return app;\r\n}\r\n\r\nfunction watchPlugins(): void {\r\n let reloadTimer: NodeJS.Timeout | null = null;\r\n const debouncedReload = async () => {\r\n if (reloadTimer) clearTimeout(reloadTimer);\r\n reloadTimer = setTimeout(async () => {\r\n try {\r\n await initSites();\r\n } catch (err) {\r\n logger.error(\"plugin\", \"插件重新加载失败\", { err: err instanceof Error ? err.message : String(err) });\r\n }\r\n }, 300);\r\n };\r\n for (const dir of [BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR]) {\r\n const watcher = watch(dir, { recursive: true }, (eventType, filename) => {\r\n if (!filename || !PLUGIN_WATCH_EXTS.some((ext) => filename.endsWith(ext))) return;\r\n if (eventType === \"rename\" || eventType === \"change\") debouncedReload();\r\n });\r\n watcher.on(\"error\", (err) => {\r\n logger.warn(\"plugin\", \"插件目录监听错误\", { dir, err: err.message });\r\n });\r\n }\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n await initUserDir();\r\n await initSites();\r\n await initScheduler(CACHE_DIR);\r\n const app = createApp();\r\n const server = serve({ fetch: app.fetch, port: PORT, hostname: \"0.0.0.0\" });\r\n server.setMaxListeners(32);\r\n console.log(\n `RssAny ${getAppVersion()} 服务已启动 http://127.0.0.1:${PORT}/(API + 静态前端单地址)`,\n );\n const lanIp = Object.values(networkInterfaces()).flat().find((iface) => iface?.family === \"IPv4\" && !iface.internal)?.address;\r\n if (lanIp) console.log(`局域网访问 http://${lanIp}:${PORT}/`);\r\n if (IS_DEV) {\r\n watchPlugins();\r\n }\r\n}\r\nmain();\r\n"],"names":["s","now","mergeSourceStatsRows","base","resolve","authenticated","cacherCacheKey","fetchHtmlFn","SYSTEM","parseSteps","cronValidate","tasks","cronSchedule","runNow","scheduler.unscheduleGroup","scheduler.validateCron","scheduler.schedule","PORT","scheduler.getGroupStats","d","parseSubscribedFlag","taskStore.getTask","taskStore.createTask","taskStore.setTaskRunning","taskStore.setTaskDone","taskStore.setTaskError","mime","xml","initSites"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../app/scraper/sources/web/fetcher/purify.ts","../app/scraper/sources/web/fetcher/cdp.ts","../app/types/feedItem.ts","../app/utils/httpSourceRef.ts","../app/packageRoot.ts","../app/config/userDir.ts","../app/config/paths.ts","../app/db/index.ts","../app/core/logger/config.ts","../app/core/logger/index.ts","../app/scraper/sources/web/fetcher/browser.ts","../app/utils/refreshInterval.ts","../app/core/cacher/index.ts","../app/scraper/sources/web/extractor/extractor.ts","../app/core/llmConfig.ts","../app/core/llm.ts","../app/scraper/sources/web/parser/parser.ts","../app/scraper/sources/web/site.ts","../app/scraper/auth/errors.ts","../app/plugins/loader.ts","../app/scraper/sources/web/index.ts","../app/plugins/hostDeps.ts","../app/scraper/sources/context.ts","../app/scraper/sources/index.ts","../app/scraper/subscription/types.ts","../app/config/globalProxy.ts","../app/scraper/subscription/index.ts","../app/pipeline/qualityFilter.ts","../app/pipeline/tagger.ts","../app/pipeline/translator.ts","../app/pipeline/config.ts","../app/pipeline/index.ts","../app/feeder/rss.ts","../app/core/events/index.ts","../app/config/deliver.ts","../app/deliver/post.ts","../app/feeder/feeder.ts","../app/scheduler/index.ts","../app/scraper/scheduler/index.ts","../app/auth/middleware.ts","../app/router/routes/api/server.ts","../app/router/routes/api/rss.ts","../app/router/routes/api/scheduler.ts","../app/router/routes/api/plugins.ts","../app/router/routes/api/pipeline.ts","../app/router/routes/api/feed.ts","../app/router/routes/api/items.ts","../app/router/routes/api/logs.ts","../app/router/routes/api/admin.ts","../app/router/routes/api/sources.ts","../app/router/routes/api/topics.ts","../app/router/routes/api/deliver.ts","../app/config/llmSettings.ts","../app/router/routes/api/llm.ts","../app/router/routes/api/proxy.ts","../app/tasks/index.ts","../app/router/routes/api/tasks.ts","../app/router/routes/api/feed-favicon.ts","../app/router/routes/api/cover-img.ts","../app/router/routes/api/index.ts","../app/router/routes/auth.ts","../app/router/utils.ts","../app/router/routes/admin.ts","../app/router/routes/rss.ts","../app/router/webui.ts","../app/version.ts","../app/router/index.ts"],"sourcesContent":["// 基于 node-html-parser 的 HTML 净化:移除与 RSS 内容无关的标签、属性、注释\n\nimport { parse, NodeType } from \"node-html-parser\";\nimport type { HTMLElement, Node } from \"node-html-parser\";\n\n\nconst TAGS_TO_REMOVE = [\n \"script\", \"style\", \"svg\", \"symbol\", \"link\", \"meta\",\n \"input\", \"embed\", \"button\", \"select\", \"textarea\",\n \"nav\", \"iframe\", \"noscript\", \"template\", \"object\", \"canvas\",\n];\n\n\nconst BASE64_IMG_PATTERN = /^data:image\\/[^;\"'\\s]+;base64,/i;\n\n\nfunction collectCommentNodes(node: Node, out: Node[]): void {\n if (node.nodeType === NodeType.COMMENT_NODE) {\n out.push(node);\n return;\n }\n if (\"childNodes\" in node && Array.isArray(node.childNodes)) {\n for (const child of node.childNodes) {\n collectCommentNodes(child, out);\n }\n }\n}\n\n\nfunction stripRssIrrelevantAttributes(root: HTMLElement): void {\n const toProcess: HTMLElement[] = [root];\n const all = root.querySelectorAll(\"*\");\n for (const el of all) {\n if (el.nodeType === NodeType.ELEMENT_NODE) toProcess.push(el as HTMLElement);\n }\n for (const elem of toProcess) {\n elem.removeAttribute(\"class\");\n elem.removeAttribute(\"style\");\n const src = elem.getAttribute(\"src\");\n if (src && BASE64_IMG_PATTERN.test(src)) {\n elem.removeAttribute(\"src\");\n }\n }\n}\n\n\n/** 使用 node-html-parser 解析并净化 HTML,剥离与 RSS 内容无关的部分(默认开启) */\nexport function applyPurify(html: string, purify: boolean | undefined): string {\n if (purify === false) return html;\n const root = parse(html, { comment: true });\n for (const tag of TAGS_TO_REMOVE) {\n const list = root.querySelectorAll(tag);\n for (const el of list) {\n el.remove();\n }\n }\n const commentNodes: Node[] = [];\n collectCommentNodes(root, commentNodes);\n for (const node of commentNodes) {\n node.remove();\n }\n stripRssIrrelevantAttributes(root);\n return root.toString();\n}\n","// Chrome DevTools Protocol (CDP) 控制模块:支持连接到手动启动的 Chrome,避免安装 Puppeteer 专属 Chrome\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport puppeteerCore, { type Browser, type ConnectOptions, type LaunchOptions } from \"puppeteer-core\";\n\n\n/** 按平台枚举所有可能的 Chrome 路径,优先返回第一个实际存在的;全部不存在则返回 null */\nexport function findChromeExecutable(): string | null {\n const platformName = platform();\n const paths: string[] = [];\n const envChrome = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;\n if (envChrome) paths.push(envChrome);\n if (platformName === \"darwin\") {\n paths.push(\n \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\n \"/Applications/Chromium.app/Contents/MacOS/Chromium\",\n \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"\n );\n } else if (platformName === \"linux\") {\n paths.push(\n \"/usr/bin/google-chrome\",\n \"/usr/bin/google-chrome-stable\",\n \"/usr/bin/chromium\",\n \"/usr/bin/chromium-browser\",\n \"/snap/bin/chromium\"\n );\n } else if (platformName === \"win32\") {\n const programFiles = process.env[\"ProgramFiles\"] || \"C:\\\\Program Files\";\n const programFilesX86 = process.env[\"ProgramFiles(x86)\"] || \"C:\\\\Program Files (x86)\";\n paths.push(\n join(programFiles, \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n join(programFilesX86, \"Google\", \"Chrome\", \"Application\", \"chrome.exe\"),\n join(programFiles, \"Microsoft\", \"Edge\", \"Application\", \"msedge.exe\"),\n join(programFilesX86, \"Microsoft\", \"Edge\", \"Application\", \"msedge.exe\")\n );\n }\n for (const p of paths) {\n try {\n if (existsSync(p)) return p;\n } catch {\n // 跳过\n }\n }\n return null;\n}\n\n\n/** CDP 连接配置 */\nexport interface CDPConfig {\n /** Chrome 可执行文件路径,不提供则自动查找 */\n executablePath?: string;\n /** CDP 端口,默认 9222 */\n port?: number;\n /** 是否使用已启动的 Chrome(不自动启动) */\n useExisting?: boolean;\n /** userDataDir,用于持久化 cookies 等 */\n userDataDir?: string;\n /** 是否无头模式 */\n headless?: boolean;\n /** 代理配置 */\n proxy?: string;\n /** 额外的 Chrome 启动参数 */\n args?: string[];\n}\n\n\n/** 启动 Chrome 进程并返回 CDP WebSocket URL */\nasync function launchChromeWithCDP(config: CDPConfig): Promise<{ process: ChildProcess; wsEndpoint: string }> {\n const port = config.port ?? 9222;\n const executablePath = config.executablePath || findChromeExecutable();\n if (!executablePath) {\n throw new Error(\"未找到 Chrome 可执行文件,请设置 CHROME_PATH 环境变量或提供 executablePath\");\n }\n const args: string[] = [\n `--remote-debugging-port=${port}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-web-security\",\n \"--disable-features=IsolateOrigins,site-per-process\",\n \"--disable-site-isolation-trials\",\n \"--disable-infobars\",\n ];\n if (config.headless !== false) {\n args.push(\"--headless=new\");\n }\n if (config.userDataDir) {\n args.push(`--user-data-dir=${config.userDataDir}`);\n }\n if (config.proxy) {\n const u = new URL(config.proxy);\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\n args.push(`--proxy-server=${serverUrl}`);\n }\n if (config.args) {\n args.push(...config.args);\n }\n const process = spawn(executablePath, args, {\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n detached: false,\n });\n // 等待 CDP 端点可用\n const wsEndpoint = `ws://127.0.0.1:${port}/devtools/browser`;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(`Chrome 启动超时,无法连接到 CDP 端口 ${port}`));\n }, 30000);\n const checkInterval = setInterval(async () => {\n try {\n const http = await import(\"node:http\");\n const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {\n if (res.statusCode === 200) {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n resolve();\n }\n });\n req.on(\"error\", () => {\n // 继续等待\n });\n req.end();\n } catch {\n // 继续等待\n }\n }, 500);\n process.on(\"error\", (err) => {\n clearInterval(checkInterval);\n clearTimeout(timeout);\n reject(err);\n });\n });\n return { process, wsEndpoint };\n}\n\n\n/** 连接到已启动的 Chrome(通过 CDP 端口) */\nasync function connectToExistingChrome(port: number = 9222): Promise<string> {\n const wsEndpoint = `ws://127.0.0.1:${port}/devtools/browser`;\n try {\n const http = await import(\"node:http\");\n await new Promise<void>((resolve, reject) => {\n const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {\n if (res.statusCode === 200) {\n resolve();\n } else {\n reject(new Error(`无法连接到 Chrome CDP 端口 ${port}`));\n }\n });\n req.on(\"error\", (err) => {\n reject(new Error(`无法连接到 Chrome CDP 端口 ${port},请确保 Chrome 已启动并开启 --remote-debugging-port=${port}: ${err.message}`));\n });\n req.end();\n });\n return wsEndpoint;\n } catch (err) {\n throw err instanceof Error ? err : new Error(`无法连接到 Chrome CDP 端口 ${port},请确保 Chrome 已启动并开启 --remote-debugging-port=${port}`);\n }\n}\n\n\n/** 通过 CDP 连接到 Chrome 并返回 Browser 对象 */\nexport async function connectBrowser(config: CDPConfig): Promise<{ browser: Browser; cleanup?: () => Promise<void> }> {\n let wsEndpoint: string;\n let chromeProcess: ChildProcess | undefined;\n if (config.useExisting) {\n wsEndpoint = await connectToExistingChrome(config.port);\n } else {\n const result = await launchChromeWithCDP(config);\n wsEndpoint = result.wsEndpoint;\n chromeProcess = result.process;\n }\n const connectOptions: ConnectOptions = {\n browserWSEndpoint: wsEndpoint,\n };\n const browser = await puppeteerCore.connect(connectOptions);\n const cleanup = chromeProcess\n ? async () => {\n await browser.disconnect();\n chromeProcess?.kill();\n }\n : async () => {\n await browser.disconnect();\n };\n return { browser, cleanup };\n}\n\n\n/** 通过 CDP 启动 Chrome 并返回 Browser 对象(使用 executablePath) */\nexport async function launchBrowser(config: CDPConfig & { executablePath: string }): Promise<{ browser: Browser; cleanup: () => Promise<void> }> {\n const port = config.port ?? 9222;\n const args: string[] = [\n `--remote-debugging-port=${port}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-web-security\",\n \"--disable-features=IsolateOrigins,site-per-process\",\n \"--disable-site-isolation-trials\",\n \"--disable-infobars\",\n ];\n if (config.headless !== false) {\n args.push(\"--headless=new\");\n }\n if (config.userDataDir) {\n args.push(`--user-data-dir=${config.userDataDir}`);\n }\n if (config.proxy) {\n const u = new URL(config.proxy);\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\n args.push(`--proxy-server=${serverUrl}`);\n }\n if (config.args) {\n args.push(...config.args);\n }\n const launchOptions: LaunchOptions = {\n executablePath: config.executablePath,\n headless: config.headless !== false,\n args,\n userDataDir: config.userDataDir,\n ignoreDefaultArgs: [\"--enable-automation\"],\n };\n const browser = await puppeteerCore.launch(launchOptions);\n const cleanup = async () => {\n await browser.close();\n };\n return { browser, cleanup };\n}\n\n\n/** 导出类型供外部使用 */\nexport type { Browser, Page } from \"puppeteer-core\";\n","/**\n * 系统内部统一的 Feed Item 定义\n * 插件 → Normalizer → RSS Generator\n * 自包含:携带 sourceRef 后,入库 / Signal 投递等无需再单独传 ref。\n */\n\n/** 单语种译文字段(key 为 BCP 47,如 zh-CN、en) */\nexport interface ItemTranslationFields {\n title?: string;\n summary?: string;\n content?: string;\n}\n\n/** 带可选 translations 的条目视图(FeedItem 或 DB 行 + translations 等) */\nexport interface ItemWithOptionalTranslations {\n title: string;\n summary?: string;\n content?: string;\n translations?: Record<string, ItemTranslationFields>;\n}\n\n/**\n * 根据 lng 取条目的「有效」标题/摘要/正文:有 translations[lng] 则优先用译文,否则用原文。\n * 路由层传 lng 时用此结果生成 RSS 或 API 响应。\n */\nexport function getEffectiveItemFields(\n item: ItemWithOptionalTranslations,\n lng?: string | null,\n): { title: string; summary: string; content: string } {\n const raw = lng && lng !== \"\" ? item.translations?.[lng] : undefined;\n const t = raw && typeof raw === \"object\" ? raw : undefined;\n return {\n title: (t?.title != null && t.title !== \"\" ? t.title : item.title) ?? \"\",\n summary: (t?.summary != null && t.summary !== \"\" ? t.summary : item.summary) ?? \"\",\n content: (t?.content != null && t.content !== \"\" ? t.content : item.content) ?? \"\",\n };\n}\n\n/** 将发布时间转为 ISO 字符串供入库/序列化;Invalid Date 返回 null(避免 toISOString 抛 RangeError) */\nexport function pubDateToIsoOrNull(pubDate: unknown): string | null {\n if (pubDate == null) return null;\n if (pubDate instanceof Date) {\n const ms = pubDate.getTime();\n return Number.isNaN(ms) ? null : pubDate.toISOString();\n }\n if (typeof pubDate === \"string\") {\n const s = pubDate.trim();\n return s || null;\n }\n return null;\n}\n\n/** 将 author 规范为 string[],兼容 string 输入(插件等) */\nexport function normalizeAuthor(author: string | string[] | null | undefined): string[] | undefined {\n if (author == null) return undefined;\n if (Array.isArray(author)) return author.filter((s) => typeof s === \"string\" && s.trim()).map((s) => s.trim());\n const s = String(author).trim();\n return s ? [s] : undefined;\n}\n\n/** 将 author 转为显示用字符串(逗号分隔) */\nexport function authorToDisplay(author: string | string[] | null | undefined): string {\n const arr = normalizeAuthor(author);\n return arr?.join(\", \") ?? \"\";\n}\n\nexport interface FeedItem {\n /** 全局唯一标识,link 或稳定 hash */\n guid: string;\n /** 标题 */\n title: string;\n /** 原文链接 */\n link: string;\n /** 发布时间 */\n pubDate: Date;\n /** 作者列表 */\n author?: string[];\n /** 简要描述(纯文本,适合 RSS description) */\n summary?: string;\n /** 详情正文(输出到 RSS description) */\n content?: string;\n /** 条目配图 URL,输出为 RSS 2.0 <enclosure type=\"image/...\"> */\n imageUrl?: string;\n /** Frontend-compatible cover field; equivalent to imageUrl / cover_img. */\n coverImg?: string;\n /**\n * 封面图:支持 http(s) URL、data URL,或裸 base64(可规范为 data:image/jpeg;base64,...)。\n * Gateway JSON 字段名 `cover_img`。\n */\n cover_img?: string;\n /** RSS 来源分类(直接来自 feed 的 <category> 字段) */\n categories?: string[];\n /** 系统 / pipeline 生成的标签(从用户管理的标签库中匹配) */\n tags?: string[];\n /** 信源标识(列表页 URL 或 imap 等),入库与按 ref 筛选用;设后则 upsertItems 等无需再传 ref */\n sourceRef?: string;\n /**\n * 多语种译文。key 为 BCP 47(如 zh-CN、en),路由支持 lng 参数时可据此返回对应译文。\n * 由 pipeline(如 translator)写入。\n */\n translations?: Record<string, ItemTranslationFields>;\n /**\n * 扩展字段,给插件留后门。\n * 框架保留键:`_rssanyPipelineDrop` 为 true 表示 pipeline 质量过滤丢弃,feeder 会删库并移出 RSS。\n */\n extra?: Record<string, unknown>;\n }\n\n/** Pipeline 质量过滤等步骤标记的丢弃键(写入 item.extra) */\nexport const PIPELINE_DROP_EXTRA_KEY = \"_rssanyPipelineDrop\";\n\nexport function markPipelineDrop(item: FeedItem): FeedItem {\n item.extra = { ...item.extra, [PIPELINE_DROP_EXTRA_KEY]: true };\n return item;\n}\n\nexport function isPipelineDroppedItem(item: FeedItem): boolean {\n return item.extra?.[PIPELINE_DROP_EXTRA_KEY] === true;\n}","/**\n * 信源 ref 入库与查询统一键(规范化存储):\n * - http(s):scheme、host(含端口)小写;pathname、query、hash 保持原样;可通过 lowerPathCase 兼容旧的 path 小写键。\n * - 非 http(s):trim 后全串小写。\n */\nexport function canonicalHttpSourceRef(ref: string, opts: { lowerPathCase?: boolean } = {}): string {\n const t = ref.trim();\n if (!t) return t;\n if (!/^https?:\\/\\//i.test(t)) return t.toLowerCase();\n try {\n const u = new URL(t);\n const protocol = u.protocol.toLowerCase();\n const host = u.host.toLowerCase();\n let path = u.pathname;\n if (path.length > 1 && path.endsWith(\"/\")) {\n path = path.slice(0, -1);\n }\n if (opts.lowerPathCase) path = path.toLowerCase();\n return `${protocol}//${host}${path}${u.search}${u.hash}`;\n } catch {\n return t.toLowerCase();\n }\n}\n\nfunction maxIso(a: string | null, b: string | null): string | null {\n if (!a) return b;\n if (!b) return a;\n return a >= b ? a : b;\n}\n\n/**\n * 将 GROUP BY source_url 的统计按规范化键合并(兼容迁移前旧数据或异常重复写法)。\n */\nexport function mergeSourceStatsRows(\n rows: { source_url: string; count: number; count_7d: number; latest_at: string | null }[],\n): { source_url: string; count: number; count_7d: number; latest_at: string | null }[] {\n const map = new Map<string, { count: number; count_7d: number; latest_at: string | null }>();\n for (const row of rows) {\n const k = canonicalHttpSourceRef(row.source_url);\n const prev = map.get(k);\n const count7 = row.count_7d ?? 0;\n if (!prev) {\n map.set(k, { count: row.count, count_7d: count7, latest_at: row.latest_at });\n } else {\n map.set(k, {\n count: prev.count + row.count,\n count_7d: prev.count_7d + count7,\n latest_at: maxIso(prev.latest_at, row.latest_at),\n });\n }\n }\n return [...map.entries()]\n .map(([source_url, v]) => ({ source_url, count: v.count, count_7d: v.count_7d, latest_at: v.latest_at }))\n .sort((a, b) => b.count - a.count);\n}\n","// 解析 npm 包根或仓库根(含 app/plugins/、app/statics/、app/webui/build/);开发时来自 app/,打包后为 dist/ 的上一级\n\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dir = dirname(fileURLToPath(import.meta.url));\nconst base = basename(__dir);\n\nexport const PACKAGE_ROOT =\n base === \"app\" || base === \"dist\" ? join(__dir, \"..\") : __dir;\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst LEGACY_HOME_DIR = \".rssany\";\n\n/** 从全局安装路径 .../{prefix}/lib/node_modules/rssany 或 .../{prefix}/node_modules/rssany 反推 npm prefix */\nexport function resolveNpmPrefixFromPackageRoot(packageRoot: string): string | null {\n const normalized = packageRoot.replace(/\\\\/g, \"/\");\n const libSuffix = \"/lib/node_modules/rssany\";\n if (normalized.endsWith(libSuffix)) {\n return packageRoot.slice(0, packageRoot.length - libSuffix.length);\n }\n const flatSuffix = \"/node_modules/rssany\";\n if (normalized.endsWith(flatSuffix)) {\n return packageRoot.slice(0, packageRoot.length - flatSuffix.length);\n }\n return null;\n}\n\n/** 是否为 npm 全局安装(非项目内 node_modules 依赖) */\nexport function isGlobalNpmInstall(packageRoot: string): boolean {\n const normalized = packageRoot.replace(/\\\\/g, \"/\");\n if (normalized.endsWith(\"/lib/node_modules/rssany\")) return true;\n const globalPatterns = [\n /\\/npm\\/node_modules\\/rssany$/,\n /\\/\\.local\\/lib\\/node_modules\\/rssany$/,\n /\\/\\.local\\/node_modules\\/rssany$/,\n /\\/\\.npm-global\\/lib\\/node_modules\\/rssany$/,\n /\\/\\.nvm\\/versions\\/node\\/[^/]+\\/lib\\/node_modules\\/rssany$/,\n /\\/\\.fnm\\/node-versions\\/[^/]+\\/installation\\/lib\\/node_modules\\/rssany$/,\n ];\n return globalPatterns.some((pattern) => pattern.test(normalized));\n}\n\n/**\n * 默认用户数据目录:\n * - 全局安装:{npm prefix}/var/rssany\n * - 源码开发:{repo}/.rssany\n * - 其它:~/.rssany\n */\nexport function resolveDefaultUserDir(packageRoot: string): string {\n const env = process.env.RSSANY_USER_DIR?.trim();\n if (env) return env;\n\n const npmPrefix = resolveNpmPrefixFromPackageRoot(packageRoot);\n if (npmPrefix && isGlobalNpmInstall(packageRoot)) {\n return join(npmPrefix, \"var\", \"rssany\");\n }\n\n if (!packageRoot.replace(/\\\\/g, \"/\").includes(\"/node_modules/\")) {\n return join(packageRoot, \".rssany\");\n }\n\n return join(homedir(), LEGACY_HOME_DIR);\n}\n\nexport function getLegacyHomeUserDir(): string {\n return join(homedir(), LEGACY_HOME_DIR);\n}\n","// 路径配置:集中管理所有运行时路径,区分项目文件与用户数据\n\nimport { mkdir, rename, access, copyFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { logger } from \"../core/logger/index.js\";\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\nimport { getLegacyHomeUserDir, resolveDefaultUserDir } from \"./userDir.js\";\n\n/** 用户数据根目录:全局安装时为 {npm prefix}/var/rssany;开发时为 {repo}/.rssany */\nexport const USER_DIR = resolveDefaultUserDir(PACKAGE_ROOT);\n\n/** SQLite 数据库目录:.rssany/data/ */\nexport const DATA_DIR = join(USER_DIR, \"data\");\n\n/** 缓存目录:.rssany/cache/(fetched、extracted、feeds、domains、browser_data 等子目录);环境变量 CACHE_DIR 可覆盖 */\nexport const CACHE_DIR = process.env.CACHE_DIR ?? join(USER_DIR, \"cache\");\n\n/** 站点配置文件:.rssany/sites.json */\nexport const SITES_CONFIG_PATH = join(USER_DIR, \"sites.json\");\n\n/** 爬虫配置:.rssany/sources.json(扁平信源列表,供 scheduler 使用) */\nexport const SOURCES_CONFIG_PATH = join(USER_DIR, \"sources.json\");\n\n/** 系统标签配置:.rssany/tags.json(供 pipeline tagger 使用) */\nexport const TAGS_CONFIG_PATH = join(USER_DIR, \"tags.json\");\n\n/** 全局配置:.rssany/config.json(pipeline 等) */\nexport const CONFIG_PATH = join(USER_DIR, \"config.json\");\n\n/** @deprecated 仅用于迁移:若存在 .rssany/subscriptions.json 且无 sources.json 则迁移为 sources.json */\nconst LEGACY_SUBSCRIPTIONS_PATH = join(USER_DIR, \"subscriptions.json\");\n\n/** 内置信源插件目录:app/plugins/builtin/(随包发布 *.rssany.js) */\nexport const BUILTIN_PLUGINS_DIR = join(PACKAGE_ROOT, \"app/plugins/builtin\");\n\n/** 用户插件目录:.rssany/plugins/(扁平 *.rssany.js / *.rssany.ts) */\nexport const USER_PLUGINS_DIR = join(USER_DIR, \"plugins\");\n\n/** 限定 .rssany 下动态 import 的模块类型,避免 Node 一直向上解析到用户主目录的 package.json 并触发 MODULE_TYPELESS_PACKAGE_JSON */\nconst USER_DIR_PACKAGE_JSON = join(USER_DIR, \"package.json\");\nconst USER_DIR_PACKAGE_JSON_MINIMAL = `${JSON.stringify({ type: \"module\", private: true, description: \"RssAny user data root; marks plugins as ESM for Node\" })}\\n`;\n\n/** 管理页「添加插件」所用模板(非 Site,不参与加载) */\nexport const PLUGIN_SITE_TEMPLATE_PATH = join(PACKAGE_ROOT, \"app/plugins/site.rssany.js\");\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function migrateFile(from: string, to: string): Promise<void> {\n if (!(await pathExists(from))) return;\n if (await pathExists(to)) return;\n try {\n await rename(from, to);\n logger.info(\"config\", \"配置已迁移\", { from, to });\n } catch (err) {\n logger.warn(\"config\", \"配置迁移失败\", { from, to, err: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** 包内首次初始化用的默认数据(`app/init/`;发布包需列入 package.json `files`) */\nconst INIT_DATA_DIR = join(PACKAGE_ROOT, \"app/init\");\nconst EXAMPLE_SOURCES = join(INIT_DATA_DIR, \"sources.json\");\nconst EXAMPLE_CONFIG = join(INIT_DATA_DIR, \"config.json\");\n\n/**\n * 若用户目录尚无 `sources.json` / `config.json`,则从包内 `app/init/sources.json`、`app/init/config.json` 复制(不覆盖已有文件)。\n */\nasync function seedExampleConfigsIfMissing(): Promise<void> {\n if (!(await pathExists(SOURCES_CONFIG_PATH)) && (await pathExists(EXAMPLE_SOURCES))) {\n try {\n await copyFile(EXAMPLE_SOURCES, SOURCES_CONFIG_PATH);\n logger.info(\"config\", \"已写入默认信源示例\", { path: SOURCES_CONFIG_PATH });\n } catch (err) {\n logger.warn(\"config\", \"写入 sources 示例失败\", {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n if (!(await pathExists(CONFIG_PATH)) && (await pathExists(EXAMPLE_CONFIG))) {\n try {\n await copyFile(EXAMPLE_CONFIG, CONFIG_PATH);\n logger.info(\"config\", \"已写入默认配置示例\", { path: CONFIG_PATH });\n } catch (err) {\n logger.warn(\"config\", \"写入 config 示例失败\", {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n\n/** 若尚无文件则写入最小 package.json,使用户插件目录下的 *.rssany.js 被明确视为 ESM */\nasync function ensureUserDirPackageJsonForPlugins(): Promise<void> {\n if (await pathExists(USER_DIR_PACKAGE_JSON)) return;\n try {\n await writeFile(USER_DIR_PACKAGE_JSON, USER_DIR_PACKAGE_JSON_MINIMAL, \"utf-8\");\n logger.info(\"config\", \"已写入 .rssany/package.json(type: module,消除插件 ESM 歧义)\", { path: USER_DIR_PACKAGE_JSON });\n } catch (err) {\n logger.warn(\"config\", \"写入 .rssany/package.json 失败\", {\n path: USER_DIR_PACKAGE_JSON,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\nasync function migrateLegacyHomeUserDir(): Promise<void> {\n const legacy = getLegacyHomeUserDir();\n if (USER_DIR === legacy) return;\n if (await pathExists(USER_DIR)) return;\n if (!(await pathExists(legacy))) return;\n try {\n await rename(legacy, USER_DIR);\n logger.info(\"config\", \"已从 ~/.rssany 迁移用户数据\", { from: legacy, to: USER_DIR });\n } catch (err) {\n logger.warn(\"config\", \"从 ~/.rssany 迁移失败\", {\n from: legacy,\n to: USER_DIR,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n/** 初始化用户数据目录;若缺少 sources.json / config.json 则从包内示例复制 */\nexport async function initUserDir(): Promise<void> {\n await migrateLegacyHomeUserDir();\n await mkdir(USER_DIR, { recursive: true });\n await mkdir(DATA_DIR, { recursive: true });\n await mkdir(CACHE_DIR, { recursive: true });\n await mkdir(USER_PLUGINS_DIR, { recursive: true });\n await ensureUserDirPackageJsonForPlugins();\n await seedExampleConfigsIfMissing();\n if (!(await pathExists(SOURCES_CONFIG_PATH)) && (await pathExists(LEGACY_SUBSCRIPTIONS_PATH))) {\n await migrateFile(LEGACY_SUBSCRIPTIONS_PATH, SOURCES_CONFIG_PATH);\n }\n}\n","// SQLite 主库:建表 schema、FTS、FeedItem CRUD 与运行日志库\n// 使用 Node.js 20+ 内置 node:sqlite 模块 (DatabaseSync - 同步API)\n\nimport { DatabaseSync } from \"node:sqlite\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync, openSync, closeSync, writeSync, unlinkSync, readFileSync } from \"node:fs\";\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { normalizeAuthor, pubDateToIsoOrNull } from \"../types/feedItem.js\";\nimport { canonicalHttpSourceRef } from \"../utils/httpSourceRef.js\";\nimport type { LogEntry } from \"../core/logger/types.js\";\nimport { DATA_DIR, TAGS_CONFIG_PATH } from \"../config/paths.js\";\n\n/** 主库日志模式:默认 WAL;环境变量 RSSANY_DB_JOURNAL=delete 时使用 DELETE */\nconst MAIN_DB_JOURNAL = (process.env.RSSANY_DB_JOURNAL ?? \"wal\").toLowerCase() === \"delete\" ? \"DELETE\" : \"WAL\";\n\nlet _db: DatabaseSync | null = null;\n\n/** 单写者锁:串行化 updateItemContent/upsertItems 等写库 */\nlet _writeLock: Promise<void> = Promise.resolve();\n\n/** 单进程锁文件路径:与 rssany.db 同目录,内容为 PID */\nconst MAIN_DB_LOCK_PATH = join(DATA_DIR, \"rssany.db.lock\");\n\n/** 将损坏类错误写到 stderr,便于排查 */\nfunction logCorruptDiagnostic(operation: string, err: unknown): void {\n const msg = err instanceof Error ? err.message : String(err);\n const lines = [\n \"[rssany db] 数据库可能损坏或并发冲突\",\n ` 操作: ${operation}`,\n ` 错误: ${msg}`,\n \" 常见原因:\",\n \" 1. 多进程同时打开同一库(例如 tsx --watch 与另一实例同时写)\",\n \" 2. 异常退出后 WAL 未正常 checkpoint\",\n \" 3. 磁盘/杀毒/同步盘导致文件不完整\",\n \" 建议:\",\n \" - 避免多实例同时写库;开发时慎用 --watch 与后台任务并行\",\n \" - 可尝试 RSSANY_DB_JOURNAL=delete 使用 DELETE 模式降低多文件依赖\",\n \" - 备份后删除 .rssany/data/rssany.db 及同目录 -wal、-shm、rssany.db.lock 再启动\",\n ];\n process.stderr.write(lines.join(\"\\n\") + \"\\n\");\n}\n\n/** 独占创建锁文件 */\nfunction acquireDbLock(dbDir: string): void {\n const lockPath = join(dbDir, \"rssany.db.lock\");\n const pid = process.pid;\n const tryCreate = (): void => {\n try {\n const fd = openSync(lockPath, \"wx\");\n writeSync(fd, String(pid), 0, \"utf8\");\n closeSync(fd);\n return;\n } catch (e: unknown) {\n const code = (e as NodeJS.ErrnoException)?.code;\n if (code !== \"EEXIST\") throw e;\n }\n if (!existsSync(lockPath)) {\n tryCreate();\n return;\n }\n let oldPid: number | null = null;\n try {\n const buf = readFileSync(lockPath, \"utf8\");\n const n = parseInt(buf.trim(), 10);\n if (!Number.isNaN(n)) oldPid = n;\n } catch {\n /* 读锁文件失败则重试创建 */\n }\n if (oldPid !== null && oldPid !== pid) {\n const stillRunning = ((): boolean => {\n try {\n process.kill(oldPid!, 0);\n return true;\n } catch {\n return false;\n }\n })();\n if (stillRunning) {\n throw new Error(\n `数据库已被其他进程占用(PID ${oldPid})。请勿多开实例;若确认无其他进程,可删除 ${lockPath} 后重试(常见于 tsx --watch 未退出)`,\n );\n }\n }\n try {\n unlinkSync(lockPath);\n } catch {\n /* ignore */\n }\n tryCreate();\n };\n tryCreate();\n}\n\n/** 进程退出时删除锁文件 */\nfunction releaseDbLock(): void {\n if (!existsSync(MAIN_DB_LOCK_PATH)) return;\n try {\n unlinkSync(MAIN_DB_LOCK_PATH);\n } catch {\n /* ignore */\n }\n}\n\nexport function withWriteLock<T>(fn: () => Promise<T>): Promise<T> {\n const prev = _writeLock;\n let resolveOut!: (v: T) => void;\n let rejectOut!: (e: unknown) => void;\n const out = new Promise<T>((res, rej) => {\n resolveOut = res;\n rejectOut = rej;\n });\n _writeLock = prev\n .then(() => fn())\n .then(\n (v) => {\n resolveOut(v);\n },\n (e: unknown) => {\n if (isCorruptError(e)) {\n logCorruptDiagnostic(\"withWriteLock 内 updateItemContent/upsertItems 等\", e);\n }\n rejectOut(e);\n throw e;\n },\n );\n return out;\n}\n\n/** 仅英文「日期当标题」的粗判 */\nconst DATE_ONLY_TITLE_RE =\n /^(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)\\b[\\s\\d,./-]*(?:st|nd|rd|th)?[\\s\\d,./-]*$/i;\n\nfunction normalizeText(text: string | null | undefined): string {\n return (text ?? \"\").replace(/\\s+/g, \" \").trim();\n}\n\nfunction isDateOnlyTitle(title: string | null | undefined): boolean {\n const normalized = normalizeText(title);\n if (!normalized) return false;\n return DATE_ONLY_TITLE_RE.test(normalized);\n}\n\nfunction toMs(input: string | null | undefined): number | null {\n if (!input) return null;\n const ms = Date.parse(input);\n return Number.isNaN(ms) ? null : ms;\n}\n\n/** 将 DB 中 author 列解析为 string[] */\nexport function parseAuthorFromDb(raw: string | null | undefined): string[] | undefined {\n if (!raw?.trim()) return undefined;\n try {\n const p = JSON.parse(raw) as unknown;\n if (Array.isArray(p)) return p.filter((s) => typeof s === \"string\").map((s) => String(s).trim()).filter(Boolean);\n return [String(p).trim()];\n } catch {\n return [raw.trim()];\n }\n}\n\n/** 将原始行转为 DbItem */\nfunction toDbItem(row: Record<string, unknown>): DbItem {\n const author = parseAuthorFromDb(row.author as string) ?? null;\n const parseJsonArr = (v: unknown): string[] | null => {\n try {\n return v ? (JSON.parse(v as string) as string[]) : null;\n } catch {\n return null;\n }\n };\n const tags = parseJsonArr(row.tags);\n let translations: Record<string, { title?: string; summary?: string; content?: string }> | null = null;\n try {\n if (row.translations && typeof row.translations === \"string\") {\n const p = JSON.parse(row.translations) as unknown;\n if (p && typeof p === \"object\") translations = p as Record<string, { title?: string; summary?: string; content?: string }>;\n }\n } catch {\n /* ignore */\n }\n return { ...row, author, tags, translations } as DbItem;\n}\n\nfunction mapRowsToDbItems(rows: Record<string, unknown>[]): DbItem[] {\n return rows.map(toDbItem);\n}\n\n/** 判断是否 SQLite 库损坏 */\nfunction isCorruptError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes(\"SQLITE_CORRUPT\") || msg.includes(\"database disk image is malformed\");\n}\n\n/** 获取主库连接 */\nexport async function getDb(): Promise<DatabaseSync> {\n if (_db) return _db;\n const dbPath = join(DATA_DIR, \"rssany.db\");\n await mkdir(DATA_DIR, { recursive: true });\n acquireDbLock(DATA_DIR);\n try {\n _db = new DatabaseSync(dbPath);\n _db.exec(`PRAGMA journal_mode = ${MAIN_DB_JOURNAL}`);\n _db.exec(\"PRAGMA synchronous = NORMAL\");\n initSchema(_db);\n return _db;\n } catch (err: unknown) {\n releaseDbLock();\n if (_db) {\n try {\n _db.close();\n } catch {\n /* ignore */\n }\n _db = null;\n }\n if (isCorruptError(err)) {\n logCorruptDiagnostic(\"打开/初始化主库 (getDb)\", err);\n }\n throw err;\n }\n}\n\n/** 执行 PRAGMA integrity_check */\nexport async function runIntegrityCheck(): Promise<string> {\n const db = await getDb();\n try {\n const result = db.prepare(\"PRAGMA integrity_check\").get() as { integrity_check: string } | undefined;\n return result?.integrity_check ?? \"unknown\";\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return `integrity_check 执行失败: ${msg}`;\n }\n}\n\n/** 运行日志库路径 */\nconst LOGS_DB_PATH = join(DATA_DIR, \"logs.db\");\n\nlet _logsDb: DatabaseSync | null = null;\n\nfunction initLogsSchema(db: DatabaseSync): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n level TEXT NOT NULL,\n category TEXT NOT NULL,\n message TEXT NOT NULL,\n payload TEXT,\n source_url TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_logs_level_created ON logs(level, created_at);\n CREATE INDEX IF NOT EXISTS idx_logs_source_created ON logs(source_url, created_at);\n `);\n}\n\n/** 获取运行日志库 */\nexport async function getLogsDb(): Promise<DatabaseSync> {\n if (_logsDb) return _logsDb;\n await mkdir(DATA_DIR, { recursive: true });\n _logsDb = new DatabaseSync(LOGS_DB_PATH);\n _logsDb.exec(\"PRAGMA journal_mode = WAL\");\n _logsDb.exec(\"PRAGMA synchronous = NORMAL\");\n initLogsSchema(_logsDb);\n return _logsDb;\n}\n\n/** 初始化主库 schema */\nfunction initSchema(db: DatabaseSync): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS items (\n id TEXT PRIMARY KEY,\n url TEXT UNIQUE NOT NULL,\n source_url TEXT NOT NULL,\n title TEXT,\n author TEXT,\n summary TEXT,\n content TEXT,\n image_url TEXT,\n tags TEXT,\n translations TEXT,\n pub_date TEXT,\n fetched_at TEXT NOT NULL,\n pushed_at TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_items_source ON items(source_url);\n CREATE INDEX IF NOT EXISTS idx_items_fetched ON items(fetched_at);\n CREATE INDEX IF NOT EXISTS idx_items_pushed ON items(pushed_at);\n `);\n\n db.exec(`\n CREATE VIEW IF NOT EXISTS items_fts_src AS\n SELECT rowid, title, summary, content,\n json_extract(translations, '$.\"zh-CN\".title') AS title_zh,\n json_extract(translations, '$.\"zh-CN\".summary') AS summary_zh,\n json_extract(translations, '$.\"zh-CN\".content') AS content_zh\n FROM items;\n CREATE VIRTUAL TABLE IF NOT EXISTS items_fts USING fts5(\n title, summary, content, title_zh, summary_zh, content_zh,\n content='items_fts_src',\n content_rowid='rowid'\n );\n CREATE TRIGGER IF NOT EXISTS items_fts_after_insert AFTER INSERT ON items\n BEGIN\n INSERT INTO items_fts(rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n NEW.rowid, NEW.title, NEW.summary, NEW.content,\n json_extract(NEW.translations, '$.\"zh-CN\".title'),\n json_extract(NEW.translations, '$.\"zh-CN\".summary'),\n json_extract(NEW.translations, '$.\"zh-CN\".content')\n );\n END;\n CREATE TRIGGER IF NOT EXISTS items_fts_after_update AFTER UPDATE ON items\n BEGIN\n INSERT INTO items_fts(items_fts, rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n 'delete', OLD.rowid, OLD.title, OLD.summary, OLD.content,\n json_extract(OLD.translations, '$.\"zh-CN\".title'),\n json_extract(OLD.translations, '$.\"zh-CN\".summary'),\n json_extract(OLD.translations, '$.\"zh-CN\".content')\n );\n INSERT INTO items_fts(rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n NEW.rowid, NEW.title, NEW.summary, NEW.content,\n json_extract(NEW.translations, '$.\"zh-CN\".title'),\n json_extract(NEW.translations, '$.\"zh-CN\".summary'),\n json_extract(NEW.translations, '$.\"zh-CN\".content')\n );\n END;\n CREATE TRIGGER IF NOT EXISTS items_fts_after_delete AFTER DELETE ON items\n BEGIN\n INSERT INTO items_fts(items_fts, rowid, title, summary, content, title_zh, summary_zh, content_zh)\n VALUES (\n 'delete', OLD.rowid, OLD.title, OLD.summary, OLD.content,\n json_extract(OLD.translations, '$.\"zh-CN\".title'),\n json_extract(OLD.translations, '$.\"zh-CN\".summary'),\n json_extract(OLD.translations, '$.\"zh-CN\".content')\n );\n END;\n `);\n\n // 旧库迁移:若无 image_url 列则追加\n try {\n const cols = db.prepare(\"PRAGMA table_info(items)\").all().map((r: Record<string, unknown>) => r.name as string);\n if (!cols.includes(\"image_url\")) {\n db.exec(\"ALTER TABLE items ADD COLUMN image_url TEXT\");\n }\n } catch {\n /* ignore */\n }\n\n migrateItemsSourceUrlIfNeeded(db);\n}\n\n/** 规范化 source_url */\nfunction migrateItemsSourceUrlIfNeeded(db: DatabaseSync): void {\n const pragmaResult = db.exec(\"PRAGMA user_version\") as unknown as { values?: unknown[][] } | undefined;\n const v = (pragmaResult?.values?.[0]?.[0] as number) ?? 0;\n if (v >= 2) return;\n const rows = db.prepare(\"SELECT rowid, source_url FROM items\").all() as { rowid: number; source_url: string }[];\n const updateStmt = db.prepare(\"UPDATE items SET source_url = @next WHERE rowid = @rowid\");\n db.exec(\"BEGIN TRANSACTION\");\n try {\n for (const r of rows) {\n const next = canonicalHttpSourceRef(r.source_url);\n if (next !== r.source_url) {\n updateStmt.run({ next, rowid: r.rowid });\n }\n }\n db.exec(\"PRAGMA user_version = 2\");\n db.exec(\"COMMIT\");\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n}\n\n/** 批量插入或忽略重复 */\nexport async function upsertItems(items: FeedItem[], sourceUrlOverride?: string): Promise<{ newCount: number; newIds: Set<string> }> {\n if (items.length === 0) return { newCount: 0, newIds: new Set() };\n const raw = (sourceUrlOverride ?? items[0].sourceRef)?.trim();\n if (!raw) {\n throw new Error(\"upsertItems: 每条 item 须有 sourceRef,或传入 sourceUrlOverride\");\n }\n const sourceUrl = canonicalHttpSourceRef(raw);\n return withWriteLock(async () => {\n const db = await getDb();\n const now = new Date().toISOString();\n let newCount = 0;\n const newIds = new Set<string>();\n\n const insertStmt = db.prepare(`\n INSERT OR IGNORE INTO items (id, url, source_url, title, author, summary, image_url, tags, pub_date, fetched_at)\n VALUES (@id, @url, @sourceUrl, @title, @author, @summary, @imageUrl, @tags, @pubDate, @fetchedAt)\n `);\n const selectExistingStmt = db.prepare(`\n SELECT title, author, summary, image_url, pub_date, fetched_at\n FROM items WHERE id = @id\n `);\n const updateStmt = db.prepare(`\n UPDATE items SET title = @title, author = @author, summary = @summary,\n image_url = @imageUrl, pub_date = @pubDate, fetched_at = @fetchedAt\n WHERE id = @id\n `);\n\n for (const item of items) {\n const nextTitle = normalizeText(item.title) || null;\n const nextSummary = normalizeText(item.summary) || null;\n const nextAuthorArr = normalizeAuthor(item.author);\n const nextAuthor = nextAuthorArr?.length ? JSON.stringify(nextAuthorArr) : null;\n const nextPubDate = pubDateToIsoOrNull(item.pubDate);\n const nextTags = item.tags?.length ? JSON.stringify(item.tags) : null;\n const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;\n const nextImageUrl = typeof rawImageUrl === \"string\" && rawImageUrl.trim() ? rawImageUrl.trim() : null;\n\n const info = insertStmt.run({\n id: item.guid,\n url: item.link,\n sourceUrl,\n title: nextTitle,\n author: nextAuthor,\n summary: nextSummary,\n imageUrl: nextImageUrl,\n tags: nextTags,\n pubDate: nextPubDate,\n fetchedAt: now,\n });\n newCount += Number(info.changes);\n if (info.changes > 0) {\n newIds.add(item.guid);\n continue;\n }\n\n const existing = selectExistingStmt.get({ id: item.guid }) as {\n title: string | null;\n author: string | null;\n summary: string | null;\n image_url: string | null;\n pub_date: string | null;\n fetched_at: string | null;\n } | undefined;\n if (!existing) continue;\n\n const shouldRepairTitle =\n !!nextTitle &&\n !isDateOnlyTitle(nextTitle) &&\n (isDateOnlyTitle(existing.title) || !normalizeText(existing.title));\n const existingSummaryText = normalizeText(existing.summary ?? \"\");\n const shouldClearDuplicatedSummary =\n nextSummary == null && !!nextTitle && existingSummaryText === nextTitle;\n const shouldRepairSummary =\n (!!nextSummary &&\n (existingSummaryText.length < nextSummary.length ||\n /!\\[[^\\]]*\\]\\([^)]*\\)/.test(existingSummaryText))) ||\n shouldClearDuplicatedSummary;\n const shouldRepairImageUrl = !!nextImageUrl && !existing.image_url?.trim();\n const existingAuthorArr = parseAuthorFromDb(existing.author);\n const shouldRepairAuthor = !!nextAuthorArr?.length && !existingAuthorArr?.length;\n\n const existingPubDateMs = toMs(existing.pub_date);\n const existingFetchedAtMs = toMs(existing.fetched_at);\n const nextPubDateMs = toMs(nextPubDate);\n const existingPubDateLooksFallback =\n existingPubDateMs != null &&\n existingFetchedAtMs != null &&\n Math.abs(existingPubDateMs - existingFetchedAtMs) <= 5 * 60 * 1000;\n const shouldRepairPubDate =\n nextPubDateMs != null &&\n (existingPubDateMs == null ||\n (existingPubDateLooksFallback && nextPubDateMs < existingPubDateMs - 24 * 60 * 60 * 1000));\n\n if (!(shouldRepairTitle || shouldRepairSummary || shouldRepairImageUrl || shouldRepairAuthor || shouldRepairPubDate)) {\n continue;\n }\n\n updateStmt.run({\n id: item.guid,\n title: shouldRepairTitle ? nextTitle : existing.title,\n author: shouldRepairAuthor ? nextAuthor : (existing.author ?? null),\n summary: shouldClearDuplicatedSummary ? null : (shouldRepairSummary ? nextSummary : existing.summary),\n imageUrl: shouldRepairImageUrl ? nextImageUrl : (existing.image_url ?? null),\n pubDate: shouldRepairPubDate ? nextPubDate : existing.pub_date,\n fetchedAt: now,\n });\n }\n return { newCount, newIds };\n });\n}\n\n/** 查询已存在的 guid 集合 */\nexport async function getExistingIds(guids: string[]): Promise<Set<string>> {\n if (guids.length === 0) return new Set();\n const db = await getDb();\n const placeholders = guids.map(() => \"?\").join(\",\");\n const rows = db.prepare(`SELECT id FROM items WHERE id IN (${placeholders})`).all(...guids) as { id: string }[];\n return new Set(rows.map((r) => r.id));\n}\n\n/** 按 guid 更新正文 */\nexport async function updateItemContent(item: FeedItem): Promise<void> {\n return withWriteLock(async () => {\n const db = await getDb();\n const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;\n const nextImageUrl = typeof rawImageUrl === \"string\" && rawImageUrl.trim() ? rawImageUrl.trim() : null;\n db.prepare(`\n UPDATE items SET\n content = COALESCE(content, @content),\n image_url = COALESCE(@imageUrl, image_url),\n author = COALESCE(@author, author),\n pub_date = COALESCE(@pubDate, pub_date),\n tags = @tags,\n translations = COALESCE(@translations, translations)\n WHERE id = @id\n `).run({\n id: item.guid,\n content: item.content ?? null,\n imageUrl: nextImageUrl,\n author: (() => {\n const arr = normalizeAuthor(item.author);\n return arr?.length ? JSON.stringify(arr) : null;\n })(),\n pubDate: pubDateToIsoOrNull(item.pubDate),\n tags: item.tags?.length ? JSON.stringify(item.tags) : null,\n translations: item.translations && Object.keys(item.translations).length > 0 ? JSON.stringify(item.translations) : null,\n });\n });\n}\n\n/** 按 id(guid)取单条 */\nexport async function getItemById(id: string): Promise<DbItem | null> {\n const db = await getDb();\n const row = db.prepare(\"SELECT * FROM items WHERE id = @id\").get({ id });\n if (!row) return null;\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(row)) obj[k] = v;\n return toDbItem(obj);\n}\n\n/** 单信源最近条目 */\nexport async function queryItemsBySource(sourceUrl: string, limit = 50, since?: Date): Promise<DbItem[]> {\n const key = canonicalHttpSourceRef(sourceUrl);\n if (!key) return [];\n const db = await getDb();\n const sinceClause = since ? \"AND COALESCE(pub_date, fetched_at) >= @since\" : \"\";\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE source_url = @sourceUrl ${sinceClause}\n ORDER BY COALESCE(pub_date, fetched_at) DESC\n LIMIT ${limit}\n `)\n .all({ sourceUrl: key, since: since?.toISOString() ?? null });\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 多条件查询 */\nexport async function queryItems(opts: {\n sourceUrl?: string;\n sourceUrls?: string[];\n author?: string;\n q?: string;\n tags?: string[];\n limit?: number;\n offset?: number;\n since?: Date;\n until?: Date;\n}): Promise<{ items: DbItem[]; total: number }> {\n const db = await getDb();\n const { sourceUrl, sourceUrls, author, q, tags: tagsFilter, limit = 20, offset = 0, since, until } = opts;\n const conditions: string[] = [];\n const params: Record<string, unknown> = {};\n if (sourceUrl) {\n const key = canonicalHttpSourceRef(sourceUrl);\n if (!key) return { items: [], total: 0 };\n conditions.push(\"i.source_url = @sourceUrl\");\n params.sourceUrl = key;\n } else if (sourceUrls && sourceUrls.length > 0) {\n const expanded = [...new Set(sourceUrls.map((s) => canonicalHttpSourceRef(s)).filter(Boolean))];\n if (expanded.length === 0) return { items: [], total: 0 };\n const placeholders = expanded.map((_, i) => `@src${i}`).join(\", \");\n conditions.push(`i.source_url IN (${placeholders})`);\n expanded.forEach((s, i) => { (params as Record<string, unknown>)[`src${i}`] = s; });\n }\n if (author && author.trim().length >= 2) {\n conditions.push(\"instr(i.author, @author) > 0\");\n params.author = author.trim();\n }\n if (q) {\n conditions.push(\"i.rowid IN (SELECT rowid FROM items_fts WHERE items_fts MATCH @q)\");\n params.q = q;\n }\n if (tagsFilter && tagsFilter.length > 0) {\n const trimmed = tagsFilter\n .filter((t): t is string => typeof t === \"string\" && t.trim().length > 0)\n .map((t) => t.trim());\n if (trimmed.length > 0) {\n const tagConds = trimmed.map((_, idx: number) => `LOWER(TRIM(json_each.value)) = LOWER(@tag${idx})`).join(\" OR \");\n conditions.push(`i.tags IS NOT NULL AND EXISTS (SELECT 1 FROM json_each(i.tags) WHERE ${tagConds})`);\n trimmed.forEach((t: string, i: number) => { (params as Record<string, unknown>)[`tag${i}`] = t; });\n }\n }\n if (since) {\n conditions.push(\"COALESCE(i.pub_date, i.fetched_at) >= @since\");\n params.since = since.toISOString();\n }\n if (until) {\n conditions.push(\"COALESCE(i.pub_date, i.fetched_at) < @until\");\n params.until = until.toISOString();\n }\n const where = conditions.length ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const sqlParams = params as unknown as Record<string, string | number | null>;\n const rows = db\n .prepare(`\n SELECT i.id, i.url, i.source_url, i.title, i.author, i.summary, i.content, i.image_url, i.tags, i.translations, i.pub_date, i.fetched_at, i.pushed_at\n FROM items i ${where}\n ORDER BY COALESCE(i.pub_date, i.fetched_at) DESC\n LIMIT ${limit} OFFSET ${offset}\n `)\n .all(sqlParams);\n const { count } = db.prepare(`SELECT COUNT(*) as count FROM items i ${where}`).get(sqlParams) as { count: number };\n return { items: mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n })), total: count };\n}\n\n/** 从所有条目移除指定标签 */\nexport async function removeTagFromAllItems(tag: string): Promise<number> {\n const trimmed = String(tag ?? \"\").trim();\n if (!trimmed) return 0;\n const targetLower = trimmed.toLowerCase();\n\n return withWriteLock(async () => {\n const db = await getDb();\n const rows = db.prepare(\"SELECT id, tags FROM items WHERE tags IS NOT NULL AND tags != ''\").all() as { id: string; tags: string }[];\n const updateStmt = db.prepare(\"UPDATE items SET tags = @tags WHERE id = @id\");\n let count = 0;\n\n for (const row of rows) {\n let itemTags: string[];\n try {\n itemTags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const filtered = itemTags.filter((t) => String(t).trim().toLowerCase() !== targetLower);\n if (filtered.length === itemTags.length) continue;\n const nextTags = filtered.length > 0 ? JSON.stringify(filtered) : null;\n updateStmt.run({ id: row.id, tags: nextTags });\n count += 1;\n }\n return count;\n });\n}\n\n/** 标记已投递 */\nexport async function markPushed(ids: string[]): Promise<void> {\n if (ids.length === 0) return;\n return withWriteLock(async () => {\n const db = await getDb();\n const now = new Date().toISOString();\n const placeholders = ids.map(() => \"?\").join(\",\");\n db.prepare(`UPDATE items SET pushed_at = ? WHERE id IN (${placeholders})`).run(now, ...ids);\n });\n}\n\n/** 按 id 删除条目 */\nexport async function deleteItem(id: string): Promise<boolean> {\n if (!id?.trim()) return false;\n return withWriteLock(async () => {\n const db = await getDb();\n const row = db.prepare(\"SELECT rowid FROM items WHERE id = @id\").get({ id: id.trim() }) as { rowid: number } | undefined;\n if (!row) return false;\n db.prepare(\"DELETE FROM items_fts WHERE rowid = @rowid\").run({ rowid: row.rowid });\n const info = db.prepare(\"DELETE FROM items WHERE id = @id\").run({ id: id.trim() });\n return Number(info.changes) > 0;\n });\n}\n\n/** 按 source_url 删除条目 */\nexport async function deleteItemsBySourceUrl(sourceUrl: string): Promise<number> {\n if (!sourceUrl?.trim()) return 0;\n const key = canonicalHttpSourceRef(sourceUrl.trim());\n if (!key) return 0;\n return withWriteLock(async () => {\n const db = await getDb();\n const info = db.prepare(\"DELETE FROM items WHERE source_url = @sourceUrl\").run({ sourceUrl: key });\n return Number(info.changes);\n });\n}\n\n/** 待投递条目 */\nexport async function getPendingPushItems(limit = 100): Promise<DbItem[]> {\n const db = await getDb();\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE pushed_at IS NULL AND content IS NOT NULL\n ORDER BY fetched_at ASC\n LIMIT ${limit}\n `)\n .all();\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 按本地日取条目 */\nexport async function getItemsForDate(date: string): Promise<DbItem[]> {\n const db = await getDb();\n const start = new Date(`${date}T00:00:00`).toISOString();\n const end = new Date(`${date}T23:59:59.999`).toISOString();\n const rows = db\n .prepare(`\n SELECT * FROM items\n WHERE fetched_at >= @start AND fetched_at <= @end\n ORDER BY fetched_at DESC\n LIMIT 300\n `)\n .all({ start, end });\n return mapRowsToDbItems(rows.map((r) => {\n const obj: Record<string, unknown | null> = {};\n for (const [k, v] of Object.entries(r)) obj[k] = v;\n return obj;\n }));\n}\n\n/** 信源统计 */\nexport async function getSourceStats(): Promise<\n { source_url: string; count: number; count_7d: number; latest_at: string | null }[]\n> {\n const { mergeSourceStatsRows } = await import(\"../utils/httpSourceRef.js\");\n const db = await getDb();\n const rows = db\n .prepare(`\n SELECT source_url,\n COUNT(*) as count,\n SUM(CASE WHEN julianday(fetched_at) >= julianday('now', '-7 days') THEN 1 ELSE 0 END) as count_7d,\n MAX(COALESCE(pub_date, fetched_at)) as latest_at\n FROM items GROUP BY source_url ORDER BY count DESC\n `)\n .all() as { source_url: string; count: number; count_7d: number; latest_at: string | null }[];\n return mergeSourceStatsRows(rows);\n}\n\n/** 写入运行日志 */\nexport async function insertLog(entry: LogEntry): Promise<void> {\n const db = await getLogsDb();\n db.prepare(`\n INSERT INTO logs (level, category, message, payload, source_url, created_at)\n VALUES (@level, @category, @message, @payload, NULL, @created_at)\n `).run({\n level: entry.level,\n category: entry.category,\n message: entry.message,\n payload: entry.payload != null ? JSON.stringify(entry.payload) : null,\n created_at: entry.created_at,\n });\n}\n\n/** 分页查询运行日志 */\nexport async function queryLogs(opts: {\n level?: LogEntry[\"level\"];\n category?: LogEntry[\"category\"];\n limit?: number;\n offset?: number;\n since?: Date;\n}): Promise<{ items: DbLog[]; total: number }> {\n const db = await getLogsDb();\n const { level, category, limit = 50, offset = 0, since } = opts;\n const conditions: string[] = [];\n const params: Record<string, unknown> = {};\n if (level) {\n conditions.push(\"level = @level\");\n params.level = level;\n }\n if (category) {\n conditions.push(\"INSTR(LOWER(category), LOWER(@categoryPattern)) > 0\");\n params.categoryPattern = category;\n }\n if (since) {\n conditions.push(\"created_at >= @since\");\n params.since = since.toISOString();\n }\n const where = conditions.length ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const sqlParams = params as unknown as Record<string, string | number | null>;\n const rows = db\n .prepare(`\n SELECT id, level, category, message, payload, created_at\n FROM logs ${where}\n ORDER BY created_at DESC\n LIMIT ${limit} OFFSET ${offset}\n `)\n .all(sqlParams);\n const { count } = db.prepare(`SELECT COUNT(*) as count FROM logs ${where}`).get(sqlParams) as { count: number };\n return {\n items: rows.map((r) => ({\n id: Number(r.id),\n level: String(r.level),\n category: String(r.category),\n message: String(r.message),\n payload: r.payload as string | null,\n created_at: String(r.created_at),\n })),\n total: Number(count)\n };\n}\n\n/** 清空日志 */\nexport async function clearAllLogs(): Promise<number> {\n const db = await getLogsDb();\n const r = db.prepare(\"DELETE FROM logs\").run();\n return Number(r.changes);\n}\n\n/** 读取系统标签 */\nexport async function getSystemTags(): Promise<string[]> {\n try {\n const raw = await readFile(TAGS_CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw) as { tags?: unknown[] };\n if (!Array.isArray(parsed?.tags)) return [];\n return parsed.tags\n .filter((t): t is string => typeof t === \"string\" && t.trim().length > 0)\n .map((t) => t.trim());\n } catch {\n return [];\n }\n}\n\n/** 保存系统标签 */\nexport async function saveSystemTagsToFile(tags: string[]): Promise<void> {\n const list = tags\n .filter((t) => typeof t === \"string\" && t.trim())\n .map((t) => t.trim());\n await writeFile(TAGS_CONFIG_PATH, JSON.stringify({ tags: list }, null, 2), \"utf-8\");\n}\n\n/** 标签使用统计 */\nexport async function getSystemTagStats(): Promise<TagStat[]> {\n const systemTags = await getSystemTags();\n if (systemTags.length === 0) return [];\n\n const db = await getDb();\n const rows = db\n .prepare(\"SELECT tags, pub_date, fetched_at FROM items WHERE tags IS NOT NULL AND tags != ''\")\n .all() as { tags: string; pub_date: string | null; fetched_at: string }[];\n\n const now = Date.now();\n const tagMap = new Map<string, { count: number; hotness: number }>();\n for (const name of systemTags) {\n tagMap.set(name.toLowerCase(), { count: 0, hotness: 0 });\n }\n\n for (const row of rows) {\n let itemTags: string[];\n try {\n itemTags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const pubMs = row.pub_date ? Date.parse(row.pub_date) : null;\n const fetchedMs = Date.parse(row.fetched_at);\n const factor = recencyFactor(pubMs, fetchedMs, now);\n\n for (const t of itemTags) {\n const key = String(t).trim().toLowerCase();\n const entry = tagMap.get(key);\n if (entry) {\n entry.count += 1;\n entry.hotness += factor;\n }\n }\n }\n\n return systemTags.map((name) => {\n const entry = tagMap.get(name.toLowerCase()) ?? { count: 0, hotness: 0 };\n return {\n name,\n count: entry.count,\n hotness: Math.round(entry.hotness * 100) / 100,\n };\n });\n}\n\nexport interface TagStat {\n name: string;\n count: number;\n hotness: number;\n period?: number;\n}\n\nfunction recencyFactor(pubDateMs: number | null, fetchedAtMs: number, nowMs: number): number {\n const ref = pubDateMs ?? fetchedAtMs;\n const daysAgo = (nowMs - ref) / (24 * 60 * 60 * 1000);\n return 1 / (1 + Math.max(0, daysAgo) / 7);\n}\n\n/** 建议标签 */\nexport async function getSuggestedTags(): Promise<TagStat[]> {\n const systemTags = await getSystemTags();\n const systemLower = new Set(systemTags.map((t) => t.toLowerCase().trim()));\n\n const db = await getDb();\n const rows = db\n .prepare(\"SELECT tags, pub_date, fetched_at FROM items WHERE tags IS NOT NULL AND tags != ''\")\n .all() as { tags: string; pub_date: string | null; fetched_at: string }[];\n\n const tagMap = new Map<string, { name: string; count: number; hotness: number }>();\n const now = Date.now();\n\n for (const row of rows) {\n let tags: string[];\n try {\n tags = JSON.parse(row.tags) as string[];\n } catch {\n continue;\n }\n const pubMs = row.pub_date ? Date.parse(row.pub_date) : null;\n const fetchedMs = Date.parse(row.fetched_at);\n const factor = recencyFactor(pubMs, fetchedMs, now);\n\n for (const t of tags) {\n const trimmed = String(t).trim();\n if (!trimmed) continue;\n const key = trimmed.toLowerCase();\n if (systemLower.has(key)) continue;\n\n const existing = tagMap.get(key);\n if (existing) {\n existing.count += 1;\n existing.hotness += factor;\n } else {\n tagMap.set(key, { name: trimmed, count: 1, hotness: factor });\n }\n }\n }\n\n return Array.from(tagMap.values())\n .filter((s) => s.hotness > 20)\n .sort((a, b) => b.hotness - a.hotness)\n .slice(0, 5)\n .map((s) => ({ name: s.name, count: s.count, hotness: Math.round(s.hotness * 100) / 100 }));\n}\n\n/** 库中行结构 */\nexport interface DbItem {\n id: string;\n url: string;\n source_url: string;\n title: string | null;\n author: string[] | null;\n summary: string | null;\n content: string | null;\n image_url: string | null;\n tags: string[] | null;\n translations: Record<string, { title?: string; summary?: string; content?: string }> | null;\n pub_date: string | null;\n fetched_at: string;\n pushed_at: string | null;\n}\n\n/** 运行日志表行 */\nexport interface DbLog {\n id: number;\n level: string;\n category: string;\n message: string;\n payload: string | null;\n created_at: string;\n}\n","// 日志配置:从环境变量读取,不依赖 config.json 以尽早可用\n\nimport type { LogLevel } from \"./types.js\";\n\nconst LEVEL_ORDER: LogLevel[] = [\"debug\", \"info\", \"warn\", \"error\"];\n\nfunction parseLevel(s: string | undefined, fallback: LogLevel): LogLevel {\n if (!s) return fallback;\n const v = s.toLowerCase() as LogLevel;\n if (LEVEL_ORDER.includes(v)) return v;\n return fallback;\n}\n\n/** 当前控制台最低输出级别(默认 info,生产可设为 warn) */\nexport function getConsoleLevel(): LogLevel {\n return parseLevel(process.env.LOG_LEVEL, \"info\");\n}\n\n/** 是否将 error/warn 写入数据库(默认 true) */\nexport function getLogToDb(): boolean {\n const v = process.env.LOG_TO_DB;\n if (v === \"0\" || v === \"false\") return false;\n if (v === \"1\" || v === \"true\") return true;\n return true;\n}\n\n/** 落库的最低级别(默认 warn) */\nexport function getDbLevel(): LogLevel {\n return parseLevel(process.env.LOG_DB_LEVEL, \"warn\");\n}\n\nexport function levelOrder(l: LogLevel): number {\n return LEVEL_ORDER.indexOf(l);\n}\n\n/** 是否应输出到控制台 */\nexport function shouldLogToConsole(consoleLevel: LogLevel, entryLevel: LogLevel): boolean {\n return levelOrder(entryLevel) >= levelOrder(consoleLevel);\n}\n\n/** 是否应写入 DB */\nexport function shouldLogToDb(logToDb: boolean, dbLevel: LogLevel, entryLevel: LogLevel): boolean {\n return logToDb && levelOrder(entryLevel) >= levelOrder(dbLevel);\n}\n","// 统一日志:仅落库,不打印控制台;全体级别落库,由日志页/API 查看\n\nimport { insertLog } from \"../../db/index.js\";\nimport { getLogToDb } from \"./config.js\";\nimport type { LogCategory, LogEntry, LogLevel } from \"./types.js\";\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction writeDb(entry: LogEntry): void {\n insertLog(entry).catch((err) => {\n // 落库失败只打一次 stderr,避免循环\n process.stderr.write(`[logger] 写入日志表失败: ${err instanceof Error ? err.message : String(err)}\\n`);\n });\n}\n\nfunction emit(level: LogLevel, category: LogCategory, message: string, meta?: Record<string, unknown>): void {\n const payload = meta && Object.keys(meta).length > 0 ? { ...meta } : undefined;\n const entry: LogEntry = {\n level,\n category,\n message,\n payload: payload && Object.keys(payload).length > 0 ? payload : undefined,\n created_at: now(),\n };\n\n if (getLogToDb()) {\n writeDb(entry);\n }\n}\n\n/** 统一 logger:仅落库,不输出控制台;全体级别落库 */\nexport const logger = {\n error(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"error\", category, message, meta);\n },\n warn(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"warn\", category, message, meta);\n },\n info(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"info\", category, message, meta);\n },\n debug(category: LogCategory, message: string, meta?: Record<string, unknown>) {\n emit(\"debug\", category, message, meta);\n },\n};\n","// 使用无头浏览器(Puppeteer)拉取页面,缓存逻辑在 cacher 中\n\nimport { exec } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport puppeteerCore, { type Browser, type Page } from \"puppeteer-core\";\nimport { applyPurify } from \"./purify.js\";\nimport { findChromeExecutable } from \"./cdp.js\";\nimport type { AuthFlow } from \"../../../auth/index.js\";\nimport type { RequestConfig, StructuredHtmlResult } from \"./types.js\";\nimport { logger } from \"../../../../core/logger/index.js\";\n\nconst execAsync = promisify(exec);\n\n/** 与 launchArgs / setViewport 一致;无头拉高便于长页与懒加载内容 */\nconst VIEWPORT_WIDTH = 1366;\nconst VIEWPORT_HEIGHT_HEADLESS = 5000;\nconst VIEWPORT_HEIGHT_HEADFUL = 1200;\n\n/** 解析代理:显式传入的 proxy,否则 HTTP_PROXY / HTTPS_PROXY */\nexport function resolveProxy(config?: { proxy?: string }): string | undefined {\n return config?.proxy ?? process.env.HTTP_PROXY ?? process.env.HTTPS_PROXY;\n}\n\n/** 从代理字符串解析出 serverUrl 和可选账号密码;支持 http://user:pass@host:port */\nfunction parseProxy(proxy: string): { serverUrl: string; username?: string; password?: string } {\n const u = new URL(proxy);\n const serverUrl = u.port ? `${u.protocol}//${u.hostname}:${u.port}` : `${u.protocol}//${u.hostname}`;\n const username = u.username || undefined;\n const password = u.password || undefined;\n return { serverUrl, username, password };\n}\n\n/** 在 Page 上设置代理认证(与 preCheckAuth / fetchHtml 一致;需与 launchBrowser 的 proxy 同时使用) */\nexport async function applyProxyAuthToPage(page: Page, opts?: { proxy?: string }): Promise<void> {\n const proxy = resolveProxy(opts);\n if (!proxy) return;\n const { username, password } = parseProxy(proxy);\n if (username !== undefined || password !== undefined) {\n await page.authenticate({ username: username ?? \"\", password: password ?? \"\" });\n }\n}\n\n\n/** 构建 Puppeteer launch args */\nfunction launchArgs(config?: { proxy?: string; headless?: boolean }): string[] {\n const base = [\n \"--disable-blink-features=AutomationControlled\",\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-web-security\",\n \"--disable-features=IsolateOrigins,site-per-process\",\n \"--disable-site-isolation-trials\",\n \"--disable-infobars\",\n ];\n const height = config?.headless !== false ? VIEWPORT_HEIGHT_HEADLESS : VIEWPORT_HEIGHT_HEADFUL;\n base.push(`--window-size=${VIEWPORT_WIDTH},${height}`);\n const proxy = resolveProxy(config);\n if (proxy) {\n const { serverUrl } = parseProxy(proxy);\n base.push(`--proxy-server=${serverUrl}`);\n }\n return base;\n}\n\n\n/** 获取 userDataDir:默认共享 main;不同代理使用独立 profile,避免 Chrome profile 锁冲突。 */\nfunction proxyProfileName(proxy?: string): string {\n if (!proxy) return \"main\";\n const hash = createHash(\"sha1\").update(proxy).digest(\"hex\").slice(0, 12);\n return `main_proxy_${hash}`;\n}\n\nfunction getUserDataDir(cacheDir?: string, proxy?: string): string | undefined {\n if (!cacheDir) return undefined;\n return join(cacheDir, \"browser_data\", proxyProfileName(proxy));\n}\n\n\n/** 是否为「userDataDir 已被占用」的报错(上次进程未正常退出或并发启动) */\nfunction isAlreadyRunningError(e: unknown): boolean {\n const msg = e instanceof Error ? e.message : String(e);\n return /already running/i.test(msg) && /userDataDir|user-data-dir|user data dir/i.test(msg);\n}\n\n\n/**\n * 强制结束占用指定 userDataDir 的 Chrome/Chromium 进程(上次未正常退出时残留)。\n * 仅在 darwin/linux 下执行;启动前调用以释放 profile 锁。\n */\nasync function killStaleChromeProcesses(absUserDataDir: string): Promise<void> {\n const plat = platform();\n if (plat !== \"darwin\" && plat !== \"linux\") {\n return;\n }\n try {\n // 匹配命令行中包含该 userDataDir 的进程(Chrome 使用 --user-data-dir=/path)\n const psCmd = plat === \"darwin\"\n ? `ps -eww -o pid= -o args= 2>/dev/null`\n : `ps -eo pid,args --no-headers 2>/dev/null`;\n const { stdout } = await execAsync(psCmd, { maxBuffer: 4 * 1024 * 1024 });\n const pids = new Set<number>();\n const lineRegex = /^\\s*(\\d+)\\s+/;\n for (const line of stdout.split(\"\\n\")) {\n if (!line.includes(absUserDataDir)) continue;\n const m = line.match(lineRegex);\n if (m) pids.add(parseInt(m[1], 10));\n }\n if (pids.size === 0) return;\n logger.info(\"scraper\", \"发现占用 browser_data 的 Chrome 进程,正在结束\", { pids: [...pids], userDataDir: absUserDataDir });\n for (const pid of pids) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // 进程可能已退出\n }\n }\n await new Promise((r) => setTimeout(r, 800));\n for (const pid of pids) {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // ignore\n }\n }\n await new Promise((r) => setTimeout(r, 300));\n } catch (err) {\n logger.warn(\"scraper\", \"结束残留 Chrome 进程时出错\", { err: err instanceof Error ? err.message : String(err) });\n }\n}\n\n\n// 注入脚本隐藏自动化特征\nasync function stealthPage(page: Page): Promise<void> {\n await page.evaluateOnNewDocument(() => {\n /* global navigator, window */\n Object.defineProperty(navigator, \"webdriver\", { get: () => false });\n Object.defineProperty(navigator, \"plugins\", { get: () => [1, 2, 3, 4, 5] });\n Object.defineProperty(navigator, \"languages\", { get: () => [\"zh-CN\", \"zh\", \"en\"] });\n const originalQuery = window.navigator.permissions.query;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n window.navigator.permissions.query = (parameters: any) =>\n parameters.name === \"notifications\"\n ? Promise.resolve({ state: Notification.permission } as PermissionStatus)\n : originalQuery(parameters);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).chrome = { runtime: {} };\n Object.defineProperty(Notification, \"permission\", { get: () => \"default\" });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nav = navigator as any;\n if (nav.getBattery) {\n nav.getBattery = () => Promise.resolve({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 });\n }\n });\n await page.setExtraHTTPHeaders({\n \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8\",\n \"Cache-Control\": \"max-age=0\",\n \"Sec-Ch-Ua\": '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n });\n}\n\n\nfunction headersToRecord(headers: Record<string, unknown>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k.toLowerCase()] = String(v);\n }\n return out;\n}\n\n\n/** 对新 Page 做通用初始化:UA、Viewport、stealth 脚本 */\nasync function setupPage(page: Page, headless = true): Promise<void> {\n const realUserAgent =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\";\n await page.setUserAgent(realUserAgent);\n await page.setViewport({\n width: VIEWPORT_WIDTH,\n height: headless ? VIEWPORT_HEIGHT_HEADLESS : VIEWPORT_HEIGHT_HEADFUL,\n });\n await stealthPage(page);\n}\n\n\n// ─── 浏览器:共享实例模式 ─────────────────────────────────────────────────────\n// 同一 launch 桶复用 Chrome 进程;每次请求只创建并关闭自己的 Tab。\n\n/** 是否为「frame 已分离」类错误(页面发生客户端导航/重定向导致主 frame 失效) */\nfunction isFrameDetachedError(e: unknown): boolean {\n const msg = e instanceof Error ? e.message : String(e);\n return /detached|Navigating frame was detached|Session closed/i.test(msg);\n}\n\n\ntype BrowserLaunchConfig = {\n headless?: boolean;\n cacheDir?: string;\n proxy?: string;\n chromeExecutablePath?: string;\n};\n\ntype SharedBrowserSlot = {\n browser?: Browser;\n promise?: Promise<Browser>;\n headless?: boolean;\n};\n\nconst sharedBrowsers = new Map<string, SharedBrowserSlot>();\nconst managedBrowsers = new WeakSet<Browser>();\nconst infoPagePromises = new WeakMap<Browser, Promise<void>>();\n\nconst RSSANY_INFO_PAGE_PREFIX = \"data:text/html;charset=utf-8,\";\n\nfunction rssAnyInfoPageUrl(): string {\n const html = `<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>RssAny 浏览器</title>\n <style>\n :root {\n color-scheme: light dark;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: #f7f7f5;\n color: #202124;\n }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n padding: 32px;\n box-sizing: border-box;\n }\n main {\n max-width: 680px;\n border: 1px solid #d7d7d1;\n border-radius: 8px;\n background: #ffffff;\n padding: 28px 32px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);\n }\n h1 {\n margin: 0 0 12px;\n font-size: 24px;\n line-height: 1.25;\n }\n p {\n margin: 10px 0 0;\n font-size: 15px;\n line-height: 1.7;\n }\n code {\n font-family: ui-monospace, SFMono-Regular, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n background: #f0f0ec;\n padding: 2px 6px;\n border-radius: 4px;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n background: #181a1b;\n color: #f1f1ed;\n }\n main {\n background: #202325;\n border-color: #3a3d3f;\n box-shadow: none;\n }\n code {\n background: #303335;\n }\n }\n </style>\n</head>\n<body>\n <main>\n <h1>这是 RssAny 创建的浏览器</h1>\n <p>RssAny 会复用这个浏览器执行订阅抓取、站点登录、页面解析和调试打开等任务。</p>\n <p>抓取任务会临时打开自己的标签页,完成后自动关闭。请保留此页面,以便区分 RssAny 管理的浏览器窗口。</p>\n <p>用户数据与 cookies 保存在 <code>.rssany/cache/browser_data</code> 对应的浏览器 profile 中。</p>\n </main>\n</body>\n</html>`;\n return `${RSSANY_INFO_PAGE_PREFIX}${encodeURIComponent(html)}`;\n}\n\nfunction isRssAnyInfoPage(page: Page): boolean {\n return page.url().startsWith(RSSANY_INFO_PAGE_PREFIX);\n}\n\nfunction isBlankPage(page: Page): boolean {\n const url = page.url();\n return url === \"about:blank\" || url === \"\" || url.startsWith(\"chrome://newtab\");\n}\n\nasync function cleanupExtraBlankPages(browser: Browser): Promise<void> {\n if (!isBrowserConnected(browser)) return;\n const pages = await browser.pages().catch(() => []);\n for (const page of pages) {\n if (page.isClosed() || isRssAnyInfoPage(page) || !isBlankPage(page)) continue;\n if (pages.length <= 1) continue;\n await page.close().catch(() => {});\n }\n}\n\nasync function ensureRssAnyInfoPage(browser: Browser): Promise<void> {\n if (!isBrowserConnected(browser)) return;\n const current = infoPagePromises.get(browser);\n if (current) return current;\n const promise = (async () => {\n const pages = await browser.pages().catch(() => []);\n if (pages.some((page) => !page.isClosed() && isRssAnyInfoPage(page))) {\n await cleanupExtraBlankPages(browser);\n return;\n }\n const page = await browser.newPage();\n await page.goto(rssAnyInfoPageUrl(), { waitUntil: \"domcontentloaded\", timeout: 10_000 }).catch(() => {});\n await cleanupExtraBlankPages(browser);\n })().finally(() => {\n infoPagePromises.delete(browser);\n });\n infoPagePromises.set(browser, promise);\n return promise;\n}\n\nfunction setupRssAnyBrowserLifecycle(browser: Browser, headless: boolean): void {\n if (headless || managedBrowsers.has(browser)) return;\n managedBrowsers.add(browser);\n browser.on(\"targetcreated\", () => {\n setTimeout(() => {\n cleanupExtraBlankPages(browser).catch(() => {});\n }, 2500);\n });\n browser.on(\"targetdestroyed\", () => {\n setTimeout(() => {\n ensureRssAnyInfoPage(browser).catch(() => {});\n }, 300);\n });\n}\n\nfunction browserKey(config: BrowserLaunchConfig): string {\n const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable() ?? \"\";\n const proxy = resolveProxy(config) ?? \"\";\n const userDataDir = getUserDataDir(config.cacheDir, proxy);\n return JSON.stringify({\n userDataDir: userDataDir ? resolve(userDataDir) : \"\",\n proxy,\n executablePath,\n });\n}\n\nfunction isBrowserConnected(browser: Browser | undefined): browser is Browser {\n return !!browser && browser.connected !== false;\n}\n\n/**\n * 启动新的 Chrome 实例(不缓存、不复用)。调用方须在 `finally` 中 `await browser.close()`。\n */\nexport async function launchBrowser(config: BrowserLaunchConfig): Promise<Browser> {\n const wantHeadless = config.headless !== false;\n const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable();\n if (!executablePath) {\n throw new Error(\"未找到 Chrome 可执行文件,请安装 Google Chrome 或设置 CHROME_PATH 环境变量\");\n }\n const proxy = resolveProxy(config);\n const userDataDir = getUserDataDir(config.cacheDir, proxy);\n const maxRetries = 2;\n let lastErr: unknown;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n if (attempt === 0 && userDataDir) {\n const absUserDataDir = resolve(userDataDir);\n await killStaleChromeProcesses(absUserDataDir);\n }\n if (attempt > 0) {\n const waitMs = attempt * 2000;\n logger.info(\"scraper\", \"userDataDir 曾被占用,等待后重试\", { waitMs, attempt });\n await new Promise((r) => setTimeout(r, waitMs));\n }\n return await puppeteerCore.launch({\n headless: wantHeadless,\n args: launchArgs({ proxy, headless: wantHeadless }),\n userDataDir,\n executablePath,\n ignoreDefaultArgs: [\"--enable-automation\"],\n });\n } catch (e) {\n lastErr = e;\n if (attempt < maxRetries && isAlreadyRunningError(e)) {\n continue;\n }\n if (isAlreadyRunningError(e)) {\n const dir = userDataDir ?? \"browser_data/main\";\n throw new Error(\n `Chrome 的 profile 目录已被占用(${dir})。通常是因为上次未正常退出,或另一个服务实例正在使用同一个缓存目录。请关闭占用该目录的 Chrome 进程后重试,或设置环境变量 CACHE_DIR 使用不同缓存目录。`\n );\n }\n throw e;\n }\n }\n throw lastErr;\n}\n\n\n/**\n * Get a reusable browser for crawler requests.\n *\n * Proxy is a Chrome launch option, so reuse is bucketed by userDataDir/proxy/executablePath.\n * Requests in the same bucket share one browser and only create/close their own pages.\n */\nexport async function getOrCreateBrowser(config: BrowserLaunchConfig): Promise<Browser> {\n const normalizedConfig = { ...config, proxy: resolveProxy(config) };\n const key = browserKey(normalizedConfig);\n const wantHeadless = normalizedConfig.headless !== false;\n const current = sharedBrowsers.get(key);\n if (current?.promise) {\n const browser = await current.promise;\n if (isBrowserConnected(browser) && (wantHeadless || current.headless === false)) {\n if (!wantHeadless) {\n await ensureRssAnyInfoPage(browser);\n }\n return browser;\n }\n if (isBrowserConnected(browser)) {\n await browser.close().catch(() => {});\n }\n if (sharedBrowsers.get(key) === current) {\n sharedBrowsers.delete(key);\n }\n } else if (isBrowserConnected(current?.browser)) {\n if (wantHeadless || current.headless === false) {\n if (!wantHeadless) {\n await ensureRssAnyInfoPage(current.browser);\n }\n return current.browser;\n }\n await current.browser.close().catch(() => {});\n if (sharedBrowsers.get(key) === current) {\n sharedBrowsers.delete(key);\n }\n }\n\n const slot: SharedBrowserSlot = {};\n const promise = launchBrowser(normalizedConfig).then((browser) => {\n slot.browser = browser;\n slot.promise = undefined;\n slot.headless = wantHeadless;\n setupRssAnyBrowserLifecycle(browser, wantHeadless);\n browser.once(\"disconnected\", () => {\n if (sharedBrowsers.get(key)?.browser === browser) {\n sharedBrowsers.delete(key);\n }\n });\n if (wantHeadless) {\n return browser;\n }\n return ensureRssAnyInfoPage(browser).then(() => browser);\n }).catch((err) => {\n if (sharedBrowsers.get(key) === slot) {\n sharedBrowsers.delete(key);\n }\n throw err;\n });\n\n slot.promise = promise;\n sharedBrowsers.set(key, slot);\n return promise;\n}\n\n\n// ─── 对外 API ─────────────────────────────────────────────────────────────────\n\n/** 预检认证:单发浏览器(新开 Tab)检查是否已登录;opts 与 fetchHtml 一致(代理、有头/无头) */\nexport async function preCheckAuth(\n authFlow: AuthFlow,\n cacheDir: string,\n opts?: { proxy?: string; headless?: boolean }\n): Promise<boolean> {\n const { checkAuth, loginUrl, domain } = authFlow;\n if (domain == null || !cacheDir) return true;\n const isHeadless = opts?.headless !== false;\n const browser = await getOrCreateBrowser({\n headless: isHeadless,\n cacheDir,\n proxy: resolveProxy(opts),\n });\n const page = await browser.newPage();\n try {\n await setupPage(page, isHeadless);\n await applyProxyAuthToPage(page, opts);\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 60000 });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n return await checkAuth(page, page.url());\n } finally {\n await page.close().catch(() => {});\n }\n}\n\n\n// 执行认证流程:单发有头浏览器打开登录页,等待用户完成登录后关闭进程\nexport async function ensureAuth(\n authFlow: AuthFlow,\n cacheDir: string,\n opts?: { proxy?: string }\n): Promise<void> {\n const { checkAuth, loginUrl, loginTimeoutMs = 60 * 1000, pollIntervalMs = 2000 } = authFlow;\n const browser = await getOrCreateBrowser({ headless: false, cacheDir, proxy: resolveProxy(opts) });\n const page = await browser.newPage();\n try {\n await setupPage(page, false);\n await applyProxyAuthToPage(page, opts);\n await page.goto(loginUrl, { waitUntil: \"domcontentloaded\", timeout: 60000 });\n await new Promise((resolve) => setTimeout(resolve, 3000));\n const authenticated = await checkAuth(page, page.url());\n if (authenticated) return;\n const startTime = Date.now();\n while (Date.now() - startTime < loginTimeoutMs) {\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n const authenticated = await checkAuth(page, page.url());\n if (authenticated) return;\n }\n throw new Error(`登录超时(${loginTimeoutMs}ms)`);\n } finally {\n await page.close().catch(() => {});\n }\n}\n\n\n/**\n * Open a user-visible page in the same shared browser pool used by crawlers.\n *\n * The page stays open for manual debugging/login. If opening fails, only this\n * page is closed; the shared browser remains available for later crawler work.\n */\nexport async function openBrowserPage(\n url: string,\n cacheDir: string,\n opts?: { proxy?: string }\n): Promise<Page> {\n const browser = await getOrCreateBrowser({ headless: false, cacheDir, proxy: resolveProxy(opts) });\n const page = await browser.newPage();\n try {\n await setupPage(page, false);\n await applyProxyAuthToPage(page, opts);\n await page.goto(url, { waitUntil: \"domcontentloaded\", timeout: 60_000 });\n return page;\n } catch (err) {\n await page.close().catch(() => {});\n throw err;\n }\n}\n\n// 共享浏览器:本次任务内最多开两个 Tab(frame 分离时重试一次),任务结束后关闭自己的 Page。\n// 若发生「Navigating frame was detached」等 frame 分离错误(常见于 SPA 客户端跳转),会换新 Tab 重试一次,并用 domcontentloaded 尽快取 HTML。\nexport async function fetchHtml(url: string, config: RequestConfig = {}): Promise<StructuredHtmlResult> {\n const {\n timeoutMs,\n headers,\n cookies,\n cacheDir,\n checkAuth,\n authFlow,\n purify,\n headless,\n waitAfterLoadMs,\n waitForSelector,\n waitForSelectorTimeoutMs,\n scrollBeforeSnapshot,\n useHttpResponseBody,\n } = config;\n const isHeadless = headless !== false;\n const browser = await getOrCreateBrowser({\n headless: isHeadless,\n cacheDir,\n proxy: resolveProxy(config),\n chromeExecutablePath: config.chromeExecutablePath,\n });\n const navigationTimeout = timeoutMs ?? 60000;\n const maxAttempts = 2;\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const page = await browser.newPage();\n const isRetry = attempt === 1;\n // 重试时用 domcontentloaded 尽快取 HTML,减少 SPA 客户端跳转在取 content 前发生的概率\n const waitUntil = isRetry ? \"domcontentloaded\" : \"load\";\n const extraWaitMs = isRetry ? Math.min(500, Math.max(0, waitAfterLoadMs ?? 2000)) : Math.max(0, waitAfterLoadMs ?? 2000);\n try {\n if (config.browserContext) {\n await config.browserContext(page.browserContext());\n }\n await setupPage(page, isHeadless);\n const extraHeaders: Record<string, string> = { \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.8\", ...(headers ?? {}) };\n if (cookies != null && cookies !== \"\") {\n extraHeaders.cookie = cookies;\n }\n await page.setExtraHTTPHeaders(extraHeaders);\n const proxy = resolveProxy(config);\n if (proxy) {\n const { username, password } = parseProxy(proxy);\n if (username !== undefined || password !== undefined) {\n await page.authenticate({ username: username ?? \"\", password: password ?? \"\" });\n }\n }\n if (timeoutMs != null) {\n await page.setDefaultNavigationTimeout(timeoutMs);\n }\n const response = await page.goto(url, { waitUntil, timeout: navigationTimeout });\n if (extraWaitMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, extraWaitMs));\n }\n if (waitForSelector != null && waitForSelector !== \"\" && !isRetry) {\n const selectorTimeout = waitForSelectorTimeoutMs ?? 20000;\n await page.waitForSelector(waitForSelector, { timeout: selectorTimeout });\n }\n if (scrollBeforeSnapshot && !isRetry) {\n const scrollSelector = scrollBeforeSnapshot.selector ?? null;\n const rounds = scrollBeforeSnapshot.rounds ?? 6;\n const pauseMs = scrollBeforeSnapshot.pauseMs ?? 800;\n for (let i = 0; i < rounds; i++) {\n const before = await page.evaluate((sel) => {\n const target = sel ? document.querySelector(sel) : null;\n const el = target ?? document.scrollingElement ?? document.documentElement;\n return el?.scrollHeight ?? 0;\n }, scrollSelector);\n await page.evaluate((sel) => {\n const target = sel ? document.querySelector(sel) : null;\n const el = target ?? document.scrollingElement ?? document.documentElement;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n window.scrollBy(0, window.innerHeight);\n }, scrollSelector);\n await new Promise((resolve) => setTimeout(resolve, pauseMs));\n const after = await page.evaluate((sel) => {\n const target = sel ? document.querySelector(sel) : null;\n const el = target ?? document.scrollingElement ?? document.documentElement;\n return el?.scrollHeight ?? 0;\n }, scrollSelector);\n if (after <= before && i >= 2) break;\n }\n }\n if (checkAuth != null || authFlow != null) {\n const authCheck = checkAuth ?? authFlow?.checkAuth;\n if (authCheck != null) {\n const ok = await authCheck(page, url);\n if (!ok) {\n throw new Error(\"checkAuth failed: 未通过认证检查,请先调用 ensureAuth 进行预处理登录\");\n }\n }\n }\n let rawBody: string;\n if (useHttpResponseBody === true && response != null) {\n try {\n rawBody = await response.text();\n } catch {\n rawBody = await page.content();\n }\n } else {\n rawBody = await page.content();\n }\n const finalUrl = response?.url() ?? page.url() ?? String(url);\n const status = response?.status() ?? 0;\n const statusText = response?.statusText() ?? \"\";\n const rawHeaders = response?.headers() ?? {};\n const normalizedHeaders = headersToRecord(rawHeaders);\n const body = applyPurify(rawBody, purify);\n await page.close().catch(() => {});\n return { finalUrl, status, statusText, headers: normalizedHeaders, body };\n } catch (e) {\n lastError = e;\n await page.close().catch(() => {});\n if (isRetry || !isFrameDetachedError(e)) {\n throw e;\n }\n logger.warn(\"scraper\", \"fetchHtml 因 frame 分离重试\", { url, attempt: attempt + 1, err: e instanceof Error ? e.message : String(e) });\n await new Promise((r) => setTimeout(r, 800));\n }\n }\n throw lastError;\n}\n","// RefreshInterval:类型定义 + interval ↔ cron ↔ 缓存 key 转换,供全项目共用\n// 调度与缓存 key 对齐:refresh 自动转 cron,cron 转 strategy 与 cacher.timeBucket 一致\n// - refreshIntervalToCron(interval) → 调度器使用的 cron\n// - cronToRefreshInterval(cron) → cacher.cacheKey 使用的 strategy\n// - cacher.timeBucket(strategy) → 缓存 key 的时间窗口前缀\n// 三者逻辑一致,保证调度触发时刻与缓存 key 时间窗口边界对齐\n\nimport type { CacheKeyStrategy } from \"../scraper/sources/web/fetcher/types.js\";\n\n\n/** 刷新间隔类型(有时间语义的缓存策略,排除 forever) */\nexport type RefreshInterval = Exclude<CacheKeyStrategy, \"forever\">;\n\n\n/** 合法的刷新间隔值列表(用于运行时校验) */\nexport const VALID_INTERVALS: RefreshInterval[] = [\"1min\", \"5min\", \"10min\", \"30min\", \"1h\", \"6h\", \"12h\", \"1day\", \"3day\", \"7day\"];\n\n\n/**\n * 从 cron 表达式推断缓存策略(RefreshInterval),供 cacheKey 使用\n * 与 refreshIntervalToCron 互为逆映射,保证调度时间与缓存 key 时间窗口一致\n * 格式:minute hour day month weekday(5 字段)或 second minute hour day month weekday(6 字段)\n */\nexport function cronToRefreshInterval(cronExpr: string): RefreshInterval {\n const parts = cronExpr.trim().split(/\\s+/);\n const is6Field = parts.length >= 6;\n const minute = parts[is6Field ? 1 : 0];\n const hour = parts[is6Field ? 2 : 1];\n const day = parts[is6Field ? 3 : 2];\n const weekday = parts[is6Field ? 5 : 4];\n\n // 分钟级:*/N 或 *\n const minMatch = minute.match(/^\\*\\/(\\d+)$/);\n if (minute === \"*\" || (minMatch && parseInt(minMatch[1], 10) <= 1)) return \"1min\";\n if (minMatch) {\n const n = parseInt(minMatch[1], 10);\n if (n <= 5) return \"5min\";\n if (n <= 10) return \"10min\";\n if (n <= 30) return \"30min\";\n }\n\n // 小时级:minute=0, hour=*/N 或 *\n if (minute === \"0\" || minute === \"00\") {\n const hourMatch = hour.match(/^\\*\\/(\\d+)$/);\n if (hour === \"*\" || (hourMatch && parseInt(hourMatch[1], 10) <= 1)) return \"1h\";\n if (hourMatch) {\n const n = parseInt(hourMatch[1], 10);\n if (n <= 2) return \"1h\";\n if (n <= 6) return \"6h\";\n if (n <= 12) return \"12h\";\n }\n // 每天固定时刻(如 0 9 * * *)\n if (day === \"*\" || day === \"?\") return \"1day\";\n // 每周固定时刻(如 0 0 * * 0)\n if (weekday !== \"*\" && weekday !== \"?\") return \"7day\";\n }\n\n return \"1h\";\n}\n\n\n/**\n * 将 RefreshInterval 转为 cron 表达式\n * 调度触发时刻与 cacher.timeBucket 的时间窗口边界一致,保证缓存 key 与执行频率对齐\n */\nexport function refreshIntervalToCron(interval: RefreshInterval): string {\n const map: Record<RefreshInterval, string> = {\n \"1min\": \"* * * * *\",\n \"5min\": \"*/5 * * * *\",\n \"10min\": \"*/10 * * * *\",\n \"30min\": \"*/30 * * * *\",\n \"1h\": \"0 * * * *\",\n \"6h\": \"0 */6 * * *\",\n \"12h\": \"0 0,12 * * *\",\n \"1day\": \"0 0 * * *\",\n \"3day\": \"0 0 */3 * *\",\n \"7day\": \"0 0 * * 0\",\n };\n return map[interval];\n}\n\n\n/** 将 RefreshInterval 转换为对应的毫秒数 */\nexport function refreshIntervalToMs(interval: RefreshInterval): number {\n const map: Record<RefreshInterval, number> = {\n \"1min\": 1 * 60 * 1000,\n \"5min\": 5 * 60 * 1000,\n \"10min\": 10 * 60 * 1000,\n \"30min\": 30 * 60 * 1000,\n \"1h\": 60 * 60 * 1000,\n \"6h\": 6 * 60 * 60 * 1000,\n \"12h\": 12 * 60 * 60 * 1000,\n \"1day\": 24 * 60 * 60 * 1000,\n \"3day\": 3 * 24 * 60 * 60 * 1000,\n \"7day\": 7 * 24 * 60 * 60 * 1000,\n };\n return map[interval];\n}\n","// 缓存管理:认证 profile 缓存\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CacheKeyStrategy } from \"../../scraper/sources/web/fetcher/types.js\";\nimport { cronToRefreshInterval } from \"../../utils/refreshInterval.js\";\n\n\nconst DOMAINS_SUBDIR = \"domains\";\n\n\n/** 认证配置档:可存 cookies、localStorage、sessionStorage 等,由 cacher 写入 cacheDir/domains/ */\nexport interface AuthProfile {\n cookies?: string;\n localStorage?: string;\n sessionStorage?: string;\n [key: string]: unknown;\n}\n\n\nfunction profileFileName(domain: string): string {\n return `${domain.replace(/[/\\\\]/g, \"_\")}.json`;\n}\n\n\nfunction urlHash(url: string): string {\n return createHash(\"sha256\").update(url).digest(\"hex\");\n}\n\n\n// 将时间映射到对应策略的时间窗口前缀(UTC)\n// 与 refreshIntervalToCron 的时间边界一致:如 1h 策略,窗口为整点,cron 为 \"0 * * * *\"\nfunction timeBucket(strategy: CacheKeyStrategy, now: Date): string {\n const y = now.getUTCFullYear();\n const mo = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(now.getUTCDate()).padStart(2, \"0\");\n const h = now.getUTCHours();\n const hs = String(h).padStart(2, \"0\");\n const min = now.getUTCMinutes();\n if (strategy === \"1min\") return `${y}-${mo}-${d}T${hs}:${String(min).padStart(2, \"0\")}`;\n if (strategy === \"5min\") return `${y}-${mo}-${d}T${hs}:${String(Math.floor(min / 5) * 5).padStart(2, \"0\")}`;\n if (strategy === \"10min\") return `${y}-${mo}-${d}T${hs}:${String(Math.floor(min / 10) * 10).padStart(2, \"0\")}`;\n if (strategy === \"30min\") return `${y}-${mo}-${d}T${hs}:${min < 30 ? \"00\" : \"30\"}`;\n if (strategy === \"1h\") return `${y}-${mo}-${d}T${hs}`;\n if (strategy === \"6h\") return `${y}-${mo}-${d}T${String(Math.floor(h / 6) * 6).padStart(2, \"0\")}`;\n if (strategy === \"12h\") return `${y}-${mo}-${d}T${h < 12 ? \"00\" : \"12\"}`;\n if (strategy === \"1day\") return `${y}-${mo}-${d}`;\n if (strategy === \"3day\") {\n const epochDay = Math.floor(now.getTime() / 86400000);\n return `d${Math.floor(epochDay / 3) * 3}`;\n }\n if (strategy === \"7day\") {\n const adjusted = new Date(now);\n adjusted.setUTCHours(0, 0, 0, 0);\n adjusted.setUTCDate(adjusted.getUTCDate() + 3 - ((adjusted.getUTCDay() + 6) % 7));\n const week1 = new Date(Date.UTC(adjusted.getUTCFullYear(), 0, 4));\n const isoYear = adjusted.getUTCFullYear();\n const isoWeek = 1 + Math.round(((adjusted.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getUTCDay() + 6) % 7) / 7);\n return `${isoYear}-W${String(isoWeek).padStart(2, \"0\")}`;\n }\n return \"\";\n}\n\n\n// 根据 URL 与策略生成缓存 key:forever=仅 sha256(url);其余=时间窗口前缀-sha256(url)\nexport function cacheKey(url: string, strategy: CacheKeyStrategy = \"forever\", now: Date = new Date()): string {\n const hash = urlHash(url);\n if (strategy === \"forever\") return hash;\n return `${timeBucket(strategy, now)}-${hash}`;\n}\n\n\n/** 基于 cron 表达式生成缓存 key:策略由 cronToRefreshInterval 派生,与调度触发时刻对齐 */\nexport function cacheKeyFromCron(url: string, cron: string, now: Date = new Date()): string {\n return cacheKey(url, cronToRefreshInterval(cron), now);\n}\n\n\n// 从 cacheDir/domains/{domain}.json 读取认证配置(未来可扩展为 domains/{domain}/{profileName}.json)\nexport async function readProfile(cacheDir: string, domain: string): Promise<AuthProfile | null> {\n const dir = join(cacheDir, DOMAINS_SUBDIR);\n const filePath = join(dir, profileFileName(domain));\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as AuthProfile;\n } catch {\n return null;\n }\n}\n\n\n// 将认证配置写入 cacheDir/domains/{domain}.json\nexport async function writeProfile(cacheDir: string, domain: string, data: AuthProfile): Promise<void> {\n const dir = join(cacheDir, DOMAINS_SUBDIR);\n await mkdir(dir, { recursive: true });\n const filePath = join(dir, profileFileName(domain));\n await writeFile(filePath, JSON.stringify(data, null, 2), \"utf-8\");\n}\n","// 正文提取器:从详情页 HTML 提取单条正文(规则 / Readability),支持缓存\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { JSDOM } from \"jsdom\";\nimport { Readability } from \"@mozilla/readability\";\nimport { cacheKey as cacherCacheKey } from \"../../../../core/cacher/index.js\";\nimport { fetchHtml } from \"../fetcher/index.js\";\nimport type { RequestConfig } from \"../fetcher/types.js\";\nimport type { FeedItem } from \"../../../../types/feedItem.js\";\nimport { normalizeAuthor } from \"../../../../types/feedItem.js\";\nimport type { ExtractedResult, ExtractorConfig } from \"./types.js\";\nimport { logger } from \"../../../../core/logger/index.js\";\n\n\nconst EXTRACTED_SUBDIR = \"extracted\";\n\n\n/** 从缓存读取已提取结果 */\nasync function readCachedExtracted(cacheDir: string, key: string): Promise<ExtractedResult | null> {\n const filePath = join(cacheDir, EXTRACTED_SUBDIR, `${key}.json`);\n try {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as ExtractedResult & { cachedAt?: string };\n return {\n author: parsed.author,\n title: parsed.title,\n summary: parsed.summary,\n content: parsed.content ?? (parsed as { contentMarkdown?: string }).contentMarkdown ?? (parsed as { contentHtml?: string }).contentHtml,\n pubDate: parsed.pubDate,\n };\n } catch {\n return null;\n }\n}\n\n\n/** 将提取结果写入缓存 */\nasync function writeCachedExtracted(cacheDir: string, key: string, result: ExtractedResult): Promise<void> {\n const dir = join(cacheDir, EXTRACTED_SUBDIR);\n await mkdir(dir, { recursive: true });\n const filePath = join(dir, `${key}.json`);\n const cache = { ...result, cachedAt: new Date().toISOString() };\n await writeFile(filePath, JSON.stringify(cache, null, 2), \"utf-8\");\n}\n\n\n/** 解析 key:与 fetched 一致,使用 sha256(url) */\nfunction extractedCacheKey(url: string, config: ExtractorConfig): string {\n if (config.cacheKey != null && config.cacheKey !== \"\") return config.cacheKey;\n if (url) return cacherCacheKey(url, \"forever\");\n return \"\";\n}\n\n\n/** 使用 Readability 从详情页 HTML 提取正文 */\nfunction extractWithReadability(html: string, url: string): ExtractedResult {\n const dom = new JSDOM(html, { url });\n const reader = new Readability(dom.window.document);\n const article = reader.parse();\n if (!article) return {};\n const summary = article.excerpt && article.excerpt.length > 200 ? article.excerpt.slice(0, 200) + \"…\" : article.excerpt;\n return {\n author: article.byline || undefined,\n title: article.title || undefined,\n summary: summary || undefined,\n content: article.content || undefined,\n };\n}\n\n\n/** 从 HTML 提取正文(规则 / Readability),支持缓存 */\nexport async function extractHtml(html: string, config: ExtractorConfig = {}): Promise<ExtractedResult> {\n const { url = \"\", mode, customExtractor, cacheDir, useCache = true } = config;\n const key = extractedCacheKey(url, config);\n if (useCache !== false && cacheDir != null && cacheDir !== \"\" && key) {\n const cached = await readCachedExtracted(cacheDir, key);\n if (cached != null) return cached;\n }\n if (customExtractor != null) {\n const result = await Promise.resolve(customExtractor(html, url));\n if (cacheDir != null && cacheDir !== \"\" && key) {\n await writeCachedExtracted(cacheDir, key, result);\n }\n return result;\n }\n if (mode === \"readability\") {\n const result = extractWithReadability(html, url);\n if (cacheDir != null && cacheDir !== \"\" && key) {\n await writeCachedExtracted(cacheDir, key, result);\n }\n return result;\n }\n return {};\n}\n\n\n/** 根据 link 拉取 HTML 并提取正文(提取过程不缓存 fetch) */\nexport async function extractFromLink(\n link: string,\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<ExtractedResult> {\n const { cacheDir } = extractorConfig;\n const fetchOpts: RequestConfig = {\n cacheDir: fetchConfig.cacheDir ?? cacheDir,\n useCache: false,\n timeoutMs: fetchConfig.timeoutMs ?? 15_000,\n ...fetchConfig,\n };\n const res = await fetchHtml(link, fetchOpts);\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`fetch 失败: ${res.status} ${res.statusText} for ${link}`);\n }\n const finalUrl = res.finalUrl ?? link;\n return extractHtml(res.body, {\n ...extractorConfig,\n url: finalUrl,\n cacheKey: extractorConfig.cacheKey ?? (cacheDir ? cacherCacheKey(link, \"forever\") : undefined),\n });\n}\n\n\n/** 对单个 FeedItem 根据 link 拉取并提取正文,合并回 item(补充 author/title/summary/content/pubDate) */\nexport async function extractItem(\n item: FeedItem,\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<FeedItem> {\n const extracted = await extractFromLink(item.link, extractorConfig, fetchConfig);\n const pubDate =\n extracted.pubDate != null\n ? typeof extracted.pubDate === \"string\"\n ? new Date(extracted.pubDate)\n : extracted.pubDate\n : item.pubDate;\n return {\n ...item,\n author: normalizeAuthor(extracted.author ?? item.author),\n title: extracted.title ?? item.title,\n summary: extracted.summary ?? item.summary,\n content: extracted.content ?? item.content,\n pubDate,\n };\n}\n\n\n/** 对多个 FeedItem 依次提取正文并合并(可复用 fetcher 缓存) */\nexport async function extractItems(\n items: FeedItem[],\n extractorConfig: ExtractorConfig = {},\n fetchConfig: RequestConfig = {}\n): Promise<FeedItem[]> {\n const results: FeedItem[] = [];\n const onProgress = extractorConfig.onProgress;\n for (let i = 0; i < items.length; i++) {\n try {\n results.push(await extractItem(items[i], extractorConfig, fetchConfig));\n } catch (err) {\n logger.warn(\"scraper\", \"正文提取失败\", { item_url: items[i].link, err: err instanceof Error ? err.message : String(err) });\n results.push(items[i]);\n }\n onProgress?.(i + 1, items.length);\n }\n return results;\n}\n","// LLM 配置:config.json 的 llm 块优先,其次环境变量\n\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport \"dotenv/config\";\nimport { CONFIG_PATH } from \"../config/paths.js\";\n\n/** LLM 配置(文件、环境变量或调用方传入) */\nexport interface LLMConfig {\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.openai.com/v1\";\nconst DEFAULT_MODEL = \"gpt-4o-mini\";\n\ninterface FileLlmCache {\n mtimeMs: number;\n llm: Partial<LLMConfig>;\n}\n\nlet fileCache: FileLlmCache | null = null;\n\n/** 配置或外部写入后调用,使下次 getLLMConfig 重新读盘 */\nexport function invalidateLLMConfigCache(): void {\n fileCache = null;\n}\n\nfunction readLlmFromFileSync(): Partial<LLMConfig> {\n if (!existsSync(CONFIG_PATH)) return {};\n try {\n const st = statSync(CONFIG_PATH);\n if (fileCache && fileCache.mtimeMs === st.mtimeMs) return fileCache.llm;\n const raw = readFileSync(CONFIG_PATH, \"utf-8\");\n const j = JSON.parse(raw) as { llm?: unknown };\n const llmRaw = j?.llm;\n const llm: Partial<LLMConfig> = {};\n if (llmRaw && typeof llmRaw === \"object\") {\n const o = llmRaw as Record<string, unknown>;\n if (typeof o.apiKey === \"string\" && o.apiKey.length > 0) llm.apiKey = o.apiKey;\n if (typeof o.baseUrl === \"string\" && o.baseUrl.trim()) llm.baseUrl = o.baseUrl.trim();\n if (typeof o.model === \"string\" && o.model.trim()) llm.model = o.model.trim();\n }\n fileCache = { mtimeMs: st.mtimeMs, llm };\n return llm;\n } catch {\n return {};\n }\n}\n\n/** 合并文件与环境变量:文件中的非空字段优先 */\nexport function getLLMConfig(): Required<Pick<LLMConfig, \"baseUrl\" | \"model\">> & Pick<LLMConfig, \"apiKey\"> {\n const file = readLlmFromFileSync();\n const apiKey = file.apiKey ?? process.env.OPENAI_API_KEY;\n const baseUrl = file.baseUrl ?? process.env.OPENAI_BASE_URL ?? DEFAULT_BASE_URL;\n const model = file.model ?? process.env.OPENAI_MODEL ?? DEFAULT_MODEL;\n return { apiKey, baseUrl, model };\n}\n","// LLM 统一调用:封装 OpenAI chat completion,供 parser/extractor / pipeline 复用\n\nimport OpenAI from \"openai\";\nimport type { ChatCompletion } from \"openai/resources/chat/completions\";\nimport { getLLMConfig } from \"./llmConfig.js\";\nimport type { LLMConfig } from \"./llmConfig.js\";\n\n/** 从非流式 chat completion 取出助手文本;content 为空时检查 refusal、finish_reason */\nfunction extractAssistantText(completion: ChatCompletion): string {\n const choice = completion.choices[0];\n if (!choice) throw new Error(\"LLM 返回无 choices\");\n const msg = choice.message;\n const raw = msg.content;\n if (typeof raw === \"string\") {\n const t = raw.trim();\n if (t.length > 0) return t;\n }\n // DeepSeek deepseek-reasoner:推理在 reasoning_content,答复在 content;总输出受 max_tokens 限制,可能仅有 reasoning_content\n const extra = msg as unknown as Record<string, unknown>;\n const rc = extra.reasoning_content;\n if (typeof rc === \"string\" && rc.trim().length > 0) {\n return rc.trim();\n }\n const refusal = msg.refusal;\n if (typeof refusal === \"string\" && refusal.trim()) {\n throw new Error(`模型拒绝: ${refusal.trim()}`);\n }\n const fr = choice.finish_reason;\n if (fr === \"tool_calls\") {\n throw new Error(\"LLM 返回了工具调用而非文本,请换一个模型或关闭工具调用\");\n }\n if (fr === \"content_filter\") {\n throw new Error(\"内容被内容策略过滤\");\n }\n if (fr === \"length\") {\n throw new Error(\n \"LLM 输出在 content / reasoning_content 均为空前已用尽\",\n );\n }\n throw new Error(`LLM 返回空内容 (finish_reason=${String(fr)})`);\n}\n\n/** 合并调用方配置与环境变量配置 */\nfunction mergeConfig(override?: Partial<LLMConfig> & { apiUrl?: string }): { apiKey: string; baseUrl: string; model: string } {\n const env = getLLMConfig();\n const apiKey = override?.apiKey ?? env.apiKey;\n const baseUrl = override?.apiUrl ?? override?.baseUrl ?? env.baseUrl;\n const model = override?.model ?? env.model;\n if (!apiKey) throw new Error(\"LLM API Key 未配置:请在管理后台「设置 → LLM」或环境变量 OPENAI_API_KEY 中设置\");\n return { apiKey, baseUrl, model };\n}\n\n/** 调用 LLM 获取 JSON 响应,供 parser/extractor 复用 */\nexport async function chatJson(\n prompt: string,\n config?: Partial<LLMConfig> & { apiUrl?: string },\n options?: { maxTokens?: number; debugLabel?: string },\n): Promise<Record<string, unknown>> {\n const { apiKey, baseUrl, model } = mergeConfig(config);\n const openai = new OpenAI({ apiKey, baseURL: baseUrl });\n const completion = await openai.chat.completions.create({\n model,\n messages: [{ role: \"user\", content: prompt }],\n max_tokens: options?.maxTokens ?? 8192,\n response_format: { type: \"json_object\" },\n });\n const content = extractAssistantText(completion);\n return JSON.parse(content) as Record<string, unknown>;\n}\n\n/** 调用 LLM 获取纯文本响应 */\nexport async function chatText(\n prompt: string,\n config?: Partial<LLMConfig> & { apiUrl?: string },\n options?: { maxTokens?: number; debugLabel?: string },\n): Promise<string> {\n const { apiKey, baseUrl, model } = mergeConfig(config);\n const openai = new OpenAI({ apiKey, baseURL: baseUrl });\n const completion = await openai.chat.completions.create({\n model,\n messages: [{ role: \"user\", content: prompt }],\n max_tokens: options?.maxTokens ?? 8192,\n });\n return extractAssistantText(completion);\n}\n\nexport { getLLMConfig } from \"./llmConfig.js\";\nexport type { LLMConfig } from \"./llmConfig.js\";\n","// HTML 列表解析器:专注于从列表页提取多个条目(支持自定义函数、LLM 两种模式)\n\nimport { createHash } from \"node:crypto\";\nimport { applyPurify } from \"../fetcher/purify.js\";\nimport { chatJson } from \"../../../../core/llm.js\";\nimport { getLLMConfig } from \"../../../../core/llmConfig.js\";\nimport type { FeedItem } from \"../../../../types/feedItem.js\";\nimport type { ParsedEntry } from \"./types.js\";\n\n\n// 使用 sha256 生成 guid\nfunction generateGuid(link: string): string {\n return createHash(\"sha256\").update(link).digest(\"hex\");\n}\n\n\n/** 解析结果结构(类似 RSS feed 结构) */\nexport interface ParsedListResult {\n /** 解析出的条目列表 */\n items: FeedItem[];\n /** 源 URL */\n url?: string;\n /** 解析模式 */\n mode?: ParserMode;\n}\n\n\n/** 自定义解析函数:输入 HTML 和 URL,返回 ParsedEntry 数组 */\nexport type CustomParserFn = (html: string, url: string) => Promise<ParsedEntry[]> | ParsedEntry[];\n\n\n/** LLM 解析配置 */\nexport interface LLMParserConfig {\n /** LLM API 端点,不传则从环境变量 OPENAI_BASE_URL 读取 */\n apiUrl?: string;\n /** API Key,不传则从环境变量 OPENAI_API_KEY 读取 */\n apiKey?: string;\n /** 模型名称,不传则从环境变量 OPENAI_MODEL 读取 */\n model?: string;\n /** 提示词模板,{html} 会被替换为 HTML 内容 */\n prompt?: string;\n}\n\n\n/** 解析模式 */\nexport type ParserMode = \"custom\" | \"llm\";\n\n\n/** Parser 配置 */\nexport interface ParserConfig {\n /** 源 URL,用于提取相对链接和发布日期 */\n url?: string;\n /** 解析模式:custom(自定义函数)、llm(LLM API),默认根据 customParser 或 llmConfig 自动判断 */\n mode?: ParserMode;\n /** 自定义解析函数,mode 为 \"custom\" 时必需 */\n customParser?: CustomParserFn;\n /** LLM 解析配置,mode 为 \"llm\" 时使用(可从环境变量读取) */\n llmConfig?: LLMParserConfig;\n /** 是否包含详细内容(content),默认 false(仅摘要) */\n includeContent?: boolean;\n /** 解析前是否净化 HTML(移除 script/style/nav 等无关标签),llm 模式默认 true,custom 模式不适用 */\n purify?: boolean;\n}\n\n\n// 使用 LLM 解析 HTML,返回 ParsedEntry 数组\nasync function parseWithLLM(html: string, url: string, config: LLMParserConfig): Promise<ParsedEntry[]> {\n const prompt =\n config.prompt ??\n `你是一个专业的 HTML 内容提取助手。请仔细分析以下 HTML 页面,提取所有可点击的内容条目(如文章、笔记、帖子、视频等)。\n\n要求:\n1. 返回 JSON 对象,格式为 {\"items\": [...]}\n2. 每个条目必须包含以下字段:\n - title: 标题(必填)\n - link: 完整链接(必填)。**必须从 HTML 的 href 原样提取**:若为绝对路径(以 / 开头)则直接使用;若为相对路径则补全为完整 URL。当前页: ${url}\n - description: 摘要或描述(200字内,必填)\n - author: 作者(可选)\n - published: 发布日期 ISO 字符串(可选)\n3. 如果页面是列表页,提取所有列表项\n4. 如果页面是详情页,将整个页面作为一个条目提取\n5. 如果页面是用户主页/个人资料页:\n - 优先尝试提取该用户发布的内容列表\n - 如果无法提取列表,至少提取用户基本信息作为一个条目(title 为用户名称或页面标题,description 为用户简介或页面描述,link 为当前 URL)\n6. 如果页面是单页应用(SPA),尝试从以下位置提取数据:\n - <title> 标签中的页面标题\n - <meta name=\"description\"> 或 <meta property=\"og:description\"> 中的描述\n - <meta property=\"og:title\"> 中的标题\n - <link rel=\"canonical\" href=\"...\"> 中的规范链接(优先使用)\n - <script> 标签中的 JSON 数据(搜索包含 \"title\", \"description\", \"user\", \"author\", \"items\", \"list\" 等关键词的 JSON)\n - 任何包含 href 属性的 <a> 标签,**link 必须使用 href 的原始值**(以 / 开头的路径表示从站根算起)\n - 任何包含文本内容的元素\n7. **重要**:即使页面看起来是空的 SPA,也必须至少返回一个条目:\n - 从 <title> 提取标题(如果没有则使用 URL)\n - 从 <meta> 标签提取描述(如果没有则使用 \"页面内容通过 JavaScript 动态加载\")\n - link 使用当前 URL: ${url}\n - 如果页面 URL 包含 \"user\" 或 \"profile\",可以推断这是一个用户主页,title 可以是 \"用户主页\",description 可以是页面 URL\n\n**强制要求**:绝对不能返回空数组 {\"items\": []},至少要返回一个包含页面基本信息的条目。\n\nHTML:\n${html}`;\n const parsed = await chatJson(\n prompt,\n { apiKey: config.apiKey, apiUrl: config.apiUrl, model: config.model },\n { debugLabel: \"parseWithLLM\" }\n );\n let entries: ParsedEntry[] = [];\n if (parsed.items && Array.isArray(parsed.items)) {\n entries = parsed.items as ParsedEntry[];\n } else if (parsed.entries && Array.isArray(parsed.entries)) {\n entries = parsed.entries as ParsedEntry[];\n } else if (Array.isArray(parsed)) {\n entries = parsed as ParsedEntry[];\n } else if (parsed && typeof parsed === \"object\") {\n entries = [parsed as unknown as ParsedEntry];\n }\n return entries.map((e) => {\n const raw = e.link || url;\n if (raw.startsWith(\"http\")) return { ...e, link: raw };\n const path = raw.startsWith(\"/\") ? raw : `/${raw}`;\n try {\n const base = new URL(url);\n return { ...e, link: new URL(path, base.origin).href };\n } catch {\n return { ...e, link: new URL(raw, url).href };\n }\n });\n}\n\n\n// 将 ParsedEntry 转为 FeedItem(不包含详细内容)\nfunction toFeedItem(entry: ParsedEntry, includeContent: boolean): FeedItem {\n return {\n guid: entry.guid ?? generateGuid(entry.link),\n title: entry.title,\n link: entry.link,\n pubDate: entry.published ? new Date(entry.published) : new Date(),\n author: entry.author ? [entry.author] : undefined,\n summary: entry.description,\n content: includeContent ? entry.content : undefined,\n imageUrl: entry.imageUrl,\n };\n}\n\n\n/** 解析 HTML 列表页,返回包含 items 的对象(类似 RSS feed 结构) */\nexport async function parseHtml(html: string, config: ParserConfig = {}): Promise<ParsedListResult> {\n const { url = \"\", mode, customParser, llmConfig, includeContent = false, purify } = config;\n let entries: ParsedEntry[] = [];\n const actualMode = mode ?? (llmConfig != null ? \"llm\" : customParser != null ? \"custom\" : \"llm\");\n if (actualMode === \"llm\") {\n if (llmConfig == null && !getLLMConfig().apiKey) {\n throw new Error('mode 为 \"llm\" 时必须提供 llmConfig,或在后台「设置 → LLM」/ OPENAI_API_KEY 中配置 Key');\n }\n const htmlForLLM = applyPurify(html, purify !== false);\n entries = await parseWithLLM(htmlForLLM, url, llmConfig ?? {});\n } else if (actualMode === \"custom\") {\n if (customParser == null) {\n throw new Error('mode 为 \"custom\" 时必须提供 customParser');\n }\n entries = await customParser(html, url);\n if (!Array.isArray(entries)) {\n entries = [entries];\n }\n } else {\n throw new Error(`不支持的解析模式: ${actualMode}`);\n }\n const items = entries.map((e) => toFeedItem(e, includeContent));\n return {\n items,\n url,\n mode: actualMode,\n };\n}\n","// 站点抽象接口:声明 URL 模式及 fetchItems 能力(WebSource 专用)\n\nimport type { PluginHostDeps } from \"../../../plugins/hostDeps.js\";\nimport type { AuthFlow, CheckAuthFn } from \"../../auth/index.js\";\nimport type { RefreshInterval } from \"../../../utils/refreshInterval.js\";\nimport type { FeedItem } from \"../../../types/feedItem.js\";\n\n\n/** 框架注入给插件的调用上下文,提供浏览器抓取工具与默认正文提取 */\nexport interface SiteContext {\n /** 缓存目录 */\n cacheDir?: string;\n /** 是否无头浏览器 */\n headless?: boolean;\n /** 代理地址 */\n proxy?: string;\n /** 与 SourceContext.deps 相同,宿主注入依赖,用户插件勿从 npm 直接 import */\n deps: PluginHostDeps;\n /**\n * 用浏览器抓取指定 URL,返回渲染后的 HTML。\n * 插件需要访问网页时调用此方法,框架负责浏览器管理与 cookie 注入。\n */\n fetchHtml(\n url: string,\n opts?: {\n waitMs?: number;\n purify?: boolean;\n waitForSelector?: string;\n waitForSelectorTimeoutMs?: number;\n scrollBeforeSnapshot?: {\n selector?: string;\n rounds?: number;\n pauseMs?: number;\n };\n /** 使用导航 HTTP 响应原文(适合 RSS/XML),见 RequestConfig.useHttpResponseBody */\n useHttpResponseBody?: boolean;\n }\n ): Promise<{ html: string; finalUrl: string; status: number }>;\n /**\n * 默认正文提取:拉取 item.link 的 HTML 后用 Readability 提取正文,合并回条目并返回。\n * 可在 fetchItems 内按需 await ctx.extractItem(单条) 使用。\n */\n extractItem(\n item: FeedItem,\n opts?: { cacheKey?: string }\n ): Promise<FeedItem>;\n}\n\n\n/** 站点抽象接口:声明该站点支持的 URL 模式及数据获取能力 */\nexport interface Site {\n /** 站点标识,如 \"xiaohongshu\"、\"lingowhale\" */\n readonly id: string;\n readonly name?: string;\n /** 列表页 URL 匹配模式,{placeholder} 匹配路径段 */\n readonly listUrlPattern: string | RegExp;\n /** 条目有效时间窗口;不填默认 1day */\n readonly refreshInterval?: RefreshInterval;\n /** 代理地址;不填则使用 env HTTP_PROXY */\n readonly proxy?: string;\n /**\n * 核心能力:给定 sourceId,返回条目列表。\n * 插件自行决定如何获取数据(调用 ctx.fetchHtml、直接 fetch API 等均可)。\n */\n fetchItems(sourceId: string, ctx: SiteContext): Promise<FeedItem[]>;\n /** 认证:检查是否已登录 */\n checkAuth?: CheckAuthFn | null;\n /** 认证:登录页 URL */\n loginUrl?: string | null;\n /** 认证:域名,cookies 保存在 domains/{domain}.json */\n domain?: string | null;\n /** 认证:等待登录超时毫秒,默认 300000 */\n loginTimeoutMs?: number | null;\n /** 认证:轮询 checkAuth 间隔毫秒,默认 2000 */\n pollIntervalMs?: number | null;\n}\n\n\n/** 将字符串 URL 模式转为正则:{xxx} → [^/]+,末尾允许 ?query */\nfunction patternToRegex(pattern: string | RegExp): RegExp {\n if (pattern instanceof RegExp) return pattern;\n const pathOnly = pattern.split(\"?\")[0];\n const pl = \"<<<__PL__>>>\";\n const s = pathOnly\n .replace(/\\{[^}]*\\}/g, pl)\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => \"\\\\\" + c)\n .replace(new RegExp(pl.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\"), \"[^/]+\");\n return new RegExp(\"^\" + s + \"(\\\\?.*)?$\");\n}\n\n\n/** 从 Site 扁平字段构建 AuthFlow,无需登录时返回 undefined */\nexport function toAuthFlow(site: Site): AuthFlow | undefined {\n if (!site.checkAuth || !site.loginUrl) return undefined;\n return {\n checkAuth: site.checkAuth,\n loginUrl: site.loginUrl,\n domain: site.domain ?? undefined,\n loginTimeoutMs: site.loginTimeoutMs ?? undefined,\n pollIntervalMs: site.pollIntervalMs ?? undefined,\n };\n}\n\n\n/** 判断 URL 是否匹配站点的 listUrlPattern */\nexport function matchesListUrl(site: Site, url: string): boolean {\n try {\n return patternToRegex(site.listUrlPattern).test(url);\n } catch {\n return false;\n }\n}\n\n\n/** 根据 listUrlPattern 自动计算 URL 匹配具体度(不匹配返回 -1) */\nexport function computeSpecificity(site: Site, url: string): number {\n if (!matchesListUrl(site, url)) return -1;\n const p = site.listUrlPattern;\n if (typeof p === \"string\") {\n const pathOnly = p.split(\"?\")[0];\n return 1000 + pathOnly.split(\"/\").filter(Boolean).length;\n }\n return p.source.length;\n}\n\n\n/** 根据 URL 查找匹配的站点实例,返回具体度最高的站点 */\nexport function getSiteByUrl(url: string, sites: Site[]): Site | undefined {\n const matched = sites\n .map((s) => ({ site: s, score: computeSpecificity(s, url) }))\n .filter((x) => x.score >= 0)\n .sort((a, b) => b.score - a.score);\n return matched[0]?.site;\n}\n","// HTTP 错误:router 捕获后返回对应状态码页面\n\nexport class AuthRequiredError extends Error {\n constructor(message = \"需要登录\") {\n super(message);\n this.name = \"AuthRequiredError\";\n }\n}\n\n\nexport class NotFoundError extends Error {\n constructor(message = \"未找到\") {\n super(message);\n this.name = \"NotFoundError\";\n }\n}\n","// 插件加载器:从 app/plugins/builtin/ 与 .rssany/plugins/ 加载 Site / Source 插件\n\nimport { readdir } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { join } from \"node:path\";\nimport type { Site } from \"../scraper/sources/web/site.js\";\nimport type { Source } from \"../scraper/sources/types.js\";\nimport { BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR } from \"../config/paths.js\";\nimport { logger } from \"../core/logger/index.js\";\n\n\n/** LLM 帮助函数,由 feeder 注入到插件上下文 */\nexport interface PluginLlm {\n chatJson: (prompt: string, config?: Record<string, unknown>, options?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>>;\n chatText: (prompt: string, config?: Record<string, unknown>, options?: { maxTokens?: number; debugLabel?: string }) => Promise<string>;\n}\n\n/** DB 帮助函数,由 feeder 注入到插件上下文 */\nexport interface PluginDb {\n getSystemTags: () => Promise<string[]>;\n}\n\n/** 插件统一上下文,由 feeder 在执行前注入 llm / db */\nexport interface PluginContext {\n sourceUrl?: string;\n llm?: PluginLlm;\n db?: PluginDb;\n [key: string]: unknown;\n}\n\n\nconst PLUGIN_EXTENSIONS = [\".rssany.js\", \".rssany.ts\"];\n\n\n/** 判断对象是否为有效的 Site 实现 */\nfunction isValidSite(obj: unknown): obj is Site {\n if (obj == null || typeof obj !== \"object\") return false;\n const s = obj as Record<string, unknown>;\n return (\n typeof s.id === \"string\" &&\n (typeof s.listUrlPattern === \"string\" || s.listUrlPattern instanceof RegExp) &&\n typeof s.fetchItems === \"function\"\n );\n}\n\n/** 判断对象是否为有效的 Source 实现 */\nfunction isValidSource(obj: unknown): obj is Source {\n if (obj == null || typeof obj !== \"object\") return false;\n const s = obj as Record<string, unknown>;\n return (\n typeof s.id === \"string\" &&\n (typeof s.pattern === \"string\" || s.pattern instanceof RegExp) &&\n typeof s.fetchItems === \"function\" &&\n s.listUrlPattern === undefined\n );\n}\n\n/** 从单个目录加载 Site / Source 插件,并记录每个 Site / Source 的文件路径 */\nasync function loadSourcePluginsFromDir(\n dir: string,\n label: string,\n): Promise<{\n siteEntries: Array<{ site: Site; filePath: string }>;\n sources: Array<{ source: Source; filePath: string }>;\n}> {\n const siteEntries: Array<{ site: Site; filePath: string }> = [];\n const sources: Array<{ source: Source; filePath: string }> = [];\n let entries: { name: string; isFile: () => boolean }[];\n try {\n const raw = await readdir(dir, { withFileTypes: true, encoding: \"utf-8\" });\n entries = raw as { name: string; isFile: () => boolean }[];\n } catch {\n return { siteEntries, sources };\n }\n for (const e of entries) {\n const name = String(e.name);\n if (!e.isFile()) continue;\n if (!PLUGIN_EXTENSIONS.some((ext) => name.endsWith(ext))) continue;\n const filePath = join(dir, name);\n try {\n const mod = await import(pathToFileURL(filePath).href);\n const plugin = mod.default ?? mod;\n if (isValidSite(plugin)) {\n siteEntries.push({ site: plugin, filePath });\n } else if (isValidSource(plugin)) {\n sources.push({ source: plugin, filePath });\n } else {\n logger.warn(\"plugin\", \"插件未实现 Site 或 Source 接口,已跳过\", { label, name });\n }\n } catch (err) {\n logger.warn(\"plugin\", \"插件加载失败\", { label, name, err: err instanceof Error ? err.message : String(err) });\n }\n }\n return { siteEntries, sources };\n}\n\n\nasync function loadBuiltinAndUser(): Promise<{\n builtin: {\n siteEntries: Array<{ site: Site; filePath: string }>;\n sources: Array<{ source: Source; filePath: string }>;\n };\n user: {\n siteEntries: Array<{ site: Site; filePath: string }>;\n sources: Array<{ source: Source; filePath: string }>;\n };\n}> {\n const [builtin, user] = await Promise.all([\n loadSourcePluginsFromDir(BUILTIN_PLUGINS_DIR, \"builtin\"),\n loadSourcePluginsFromDir(USER_PLUGINS_DIR, \"user\"),\n ]);\n return { builtin, user };\n}\n\n\n/** Site / Source 插件 id → 当前生效的文件路径(用户覆盖内置后的路径) */\nconst pluginSitePaths = new Map<string, string>();\n\n/** 将 Source 插件路径写入 pathMap(不与 Site id 冲突;用户覆盖内置 Source) */\nfunction mergeSourcePluginPaths(\n siteIds: Set<string>,\n pathMap: Map<string, string>,\n builtinSources: Array<{ source: Source; filePath: string }>,\n userSources: Array<{ source: Source; filePath: string }>,\n): void {\n for (const { source, filePath } of builtinSources) {\n if (siteIds.has(source.id)) {\n logger.warn(\"plugin\", \"Source 插件 id 与 Site 插件冲突,已忽略 Source 路径\", { sourceId: source.id });\n continue;\n }\n pathMap.set(source.id, filePath);\n }\n for (const { source, filePath } of userSources) {\n if (siteIds.has(source.id)) {\n logger.warn(\"plugin\", \"Source 插件 id 与 Site 插件冲突,已忽略 Source 路径\", { sourceId: source.id });\n continue;\n }\n if (pathMap.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\n pathMap.set(source.id, filePath);\n }\n}\n\n/** 根据插件 id 获取其源文件路径(用于详情页编辑,仅当前生效的插件有路径) */\nexport function getPluginFilePath(id: string): string | undefined {\n return pluginSitePaths.get(id);\n}\n\n/** 加载所有 Site 插件;用户插件可覆盖同 id 内置 */\nexport async function loadPlugins(): Promise<Site[]> {\n const { builtin, user } = await loadBuiltinAndUser();\n const merged = new Map<string, Site>();\n const pathMap = new Map<string, string>();\n for (const { site, filePath } of builtin.siteEntries) {\n merged.set(site.id, site);\n pathMap.set(site.id, filePath);\n }\n for (const { site, filePath } of user.siteEntries) {\n if (merged.has(site.id)) logger.info(\"plugin\", \"用户插件覆盖同名内置插件\", { pluginId: site.id });\n merged.set(site.id, site);\n pathMap.set(site.id, filePath);\n }\n mergeSourcePluginPaths(new Set(merged.keys()), pathMap, builtin.sources, user.sources);\n pluginSitePaths.clear();\n pathMap.forEach((path, id) => pluginSitePaths.set(id, path));\n return Array.from(merged.values());\n}\n\n\n/** 加载所有 Source 插件;用户插件可覆盖同 id */\nexport async function loadSourcePlugins(): Promise<Source[]> {\n const { builtin, user } = await loadBuiltinAndUser();\n const merged = new Map<string, Source>();\n for (const { source } of builtin.sources) merged.set(source.id, source);\n for (const { source } of user.sources) {\n if (merged.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\n merged.set(source.id, source);\n }\n return Array.from(merged.values());\n}\n\n\n/** 加载 Site 与 Source:合并去重,供 initSources 使用;同时更新 pluginSitePaths */\nexport async function loadSiteAndSourcePlugins(): Promise<{ sites: Site[]; sources: Source[] }> {\n const { builtin, user } = await loadBuiltinAndUser();\n const siteMap = new Map<string, Site>();\n const pathMap = new Map<string, string>();\n for (const { site: s, filePath } of builtin.siteEntries) {\n siteMap.set(s.id, s);\n pathMap.set(s.id, filePath);\n }\n for (const { site: s, filePath } of user.siteEntries) {\n if (siteMap.has(s.id)) logger.info(\"plugin\", \"用户插件覆盖同名内置\", { pluginId: s.id });\n siteMap.set(s.id, s);\n pathMap.set(s.id, filePath);\n }\n mergeSourcePluginPaths(new Set(siteMap.keys()), pathMap, builtin.sources, user.sources);\n const sourceMap = new Map<string, Source>();\n for (const { source } of builtin.sources) sourceMap.set(source.id, source);\n for (const { source } of user.sources) {\n if (sourceMap.has(source.id)) logger.info(\"plugin\", \"用户 Source 插件覆盖同名内置\", { sourceId: source.id });\n sourceMap.set(source.id, source);\n }\n pluginSitePaths.clear();\n pathMap.forEach((path, id) => pluginSitePaths.set(id, path));\n return { sites: Array.from(siteMap.values()), sources: Array.from(sourceMap.values()) };\n}\n","// 插件加载与包装:将 Site 插件包装为 Source 接口,注入 SiteContext 工具\n\nimport { fetchHtml as fetchHtmlFn, preCheckAuth } from \"./fetcher/index.js\";\nimport { extractHtml } from \"./extractor/index.js\";\nimport { parseHtml } from \"./parser/index.js\";\nimport { toAuthFlow, getSiteByUrl } from \"./site.js\";\nimport { AuthRequiredError } from \"../../auth/index.js\";\nimport type { Site, SiteContext } from \"./site.js\";\nimport type { Source, SourceContext } from \"../types.js\";\nimport type { FeedItem } from \"../../../types/feedItem.js\";\nimport { normalizeAuthor } from \"../../../types/feedItem.js\";\n\n\n/** 从 SourceContext + Site 构建注入了工具的 SiteContext */\nexport function buildSiteContext(site: Site, ctx: SourceContext): SiteContext {\n const proxy = ctx.proxy ?? site.proxy;\n const authFlow = toAuthFlow(site);\n return {\n cacheDir: ctx.cacheDir,\n headless: ctx.headless,\n proxy,\n deps: ctx.deps,\n async fetchHtml(url, opts) {\n const res = await fetchHtmlFn(url, {\n cacheDir: ctx.cacheDir,\n useCache: false,\n authFlow,\n headless: ctx.headless,\n proxy,\n waitAfterLoadMs: opts?.waitMs,\n purify: opts?.purify,\n waitForSelector: opts?.waitForSelector,\n waitForSelectorTimeoutMs: opts?.waitForSelectorTimeoutMs,\n scrollBeforeSnapshot: opts?.scrollBeforeSnapshot,\n useHttpResponseBody: opts?.useHttpResponseBody,\n });\n return { html: res.body, finalUrl: res.finalUrl ?? url, status: res.status };\n },\n async extractItem(item, opts) {\n const res = await fetchHtmlFn(item.link, {\n cacheDir: ctx.cacheDir,\n useCache: false,\n authFlow,\n headless: ctx.headless,\n proxy,\n });\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`默认正文提取失败: HTTP ${res.status} ${res.statusText} for ${item.link}`);\n }\n const extracted = await extractHtml(res.body, {\n url: res.finalUrl ?? item.link,\n cacheDir: ctx.cacheDir ?? undefined,\n mode: \"readability\",\n useCache: true,\n cacheKey: opts?.cacheKey,\n });\n const pubDate =\n extracted.pubDate != null\n ? typeof extracted.pubDate === \"string\"\n ? new Date(extracted.pubDate)\n : extracted.pubDate\n : item.pubDate;\n return {\n ...item,\n author: normalizeAuthor(extracted.author ?? item.author),\n title: extracted.title ?? item.title,\n summary: extracted.summary ?? item.summary,\n content: extracted.content ?? item.content,\n pubDate,\n };\n },\n };\n}\n\n\n/** 将 Site 插件包装为统一 Source 接口 */\nexport function createWebSource(site: Site): Source {\n const authFlow = toAuthFlow(site);\n return {\n id: site.id,\n name: site.name,\n pattern: site.listUrlPattern,\n priority: 50,\n refreshInterval: site.refreshInterval ?? undefined,\n proxy: site.proxy ?? undefined,\n preCheck: authFlow\n ? async (ctx: SourceContext) => {\n if (!ctx.cacheDir) return;\n const passed = await preCheckAuth(authFlow, ctx.cacheDir, {\n proxy: ctx.proxy,\n headless: ctx.headless,\n });\n if (!passed) throw new AuthRequiredError(`站点 ${site.id} 需要登录,请先执行 ensureAuth`);\n }\n : undefined,\n async fetchItems(sourceId: string, ctx: SourceContext): Promise<FeedItem[]> {\n return site.fetchItems(sourceId, buildSiteContext(site, ctx));\n },\n };\n}\n\n\n/** 通用 WebSource:兜底匹配所有 http/https URL,使用浏览器抓取 + LLM 解析 */\nexport const genericWebSource: Source = {\n id: \"generic\",\n pattern: /^https?:\\/\\//,\n priority: 200,\n async fetchItems(sourceId: string, ctx: SourceContext): Promise<FeedItem[]> {\n const res = await fetchHtmlFn(sourceId, {\n cacheDir: ctx.cacheDir,\n useCache: true,\n headless: ctx.headless,\n proxy: ctx.proxy,\n });\n // 304 Not Modified:浏览器用缓存,page.content() 仍会返回完整 HTML,视为成功\n if (res.status !== 200 && res.status !== 304) {\n throw new Error(`抓取失败: HTTP ${res.status} ${res.statusText}`);\n }\n const parsed = await parseHtml(res.body, {\n url: res.finalUrl ?? sourceId,\n });\n return parsed.items;\n },\n};\n\n\n/** 保存已加载的 Site 对象(供 auth 路由、调试路由直接访问) */\nconst loadedSites: Site[] = [];\n\n\n/** 更新已加载站点列表(由 sources/index.ts 调用) */\nexport function setLoadedSites(sites: Site[]): void {\n loadedSites.length = 0;\n loadedSites.push(...sites);\n}\n\n\n/** 根据 id 获取底层 Site(用于 auth 路由) */\nexport function getWebSite(id: string): Site | undefined {\n return loadedSites.find((s) => s.id === id);\n}\n\n\n/** 获取所有已加载的 Site(用于插件列表 API) */\nexport function getPluginSites(): Site[] {\n return loadedSites.filter((s) => s.id !== \"generic\");\n}\n\n\n/** 根据 URL 获取最具体匹配的 Site(用于调试路由) */\nexport function getBestSite(url: string): Site | undefined {\n return getSiteByUrl(url, loadedSites);\n}\n\n\nexport type { Site, SiteContext } from \"./site.js\";\nexport { toAuthFlow, computeSpecificity } from \"./site.js\";\nexport { loadPlugins } from \"../../../plugins/loader.js\";\n","// 宿主应用解析的常用依赖,注入到 SourceContext / SiteContext.deps\n// 用户目录下的 .rssany/plugins 内脚本无法解析应用 node_modules,须通过 ctx.deps 使用\n\nimport { parse, NodeType } from \"node-html-parser\";\nimport { createHash } from \"node:crypto\";\nimport RssParser from \"rss-parser\";\nimport { HttpsProxyAgent } from \"https-proxy-agent\";\nimport { ImapFlow } from \"imapflow\";\nimport { simpleParser } from \"mailparser\";\nimport { logger } from \"../core/logger/index.js\";\n\nexport const PLUGIN_HOST_DEPS = {\n parseHtml: parse,\n NodeType,\n createHash,\n RssParser,\n HttpsProxyAgent,\n ImapFlow,\n simpleParser,\n logger,\n} as const;\n\nexport type PluginHostDeps = typeof PLUGIN_HOST_DEPS;\n","import { PLUGIN_HOST_DEPS } from \"../../plugins/hostDeps.js\";\nimport type { SourceContext } from \"./types.js\";\nimport { fetchHtml as fetchHtmlFn } from \"./web/fetcher/index.js\";\n\n/** 构造带 deps 的信源上下文(抓取、preCheck、插件 fetchItems 均须使用) */\nexport function buildSourceContext(partial: {\n cacheDir?: string;\n headless?: boolean;\n proxy?: string;\n}): SourceContext {\n const { cacheDir, headless, proxy } = partial;\n return {\n ...partial,\n deps: PLUGIN_HOST_DEPS,\n async fetchHtml(url, opts) {\n const res = await fetchHtmlFn(url, {\n cacheDir,\n useCache: false,\n headless,\n proxy,\n waitAfterLoadMs: opts?.waitMs,\n purify: opts?.purify,\n waitForSelector: opts?.waitForSelector,\n waitForSelectorTimeoutMs: opts?.waitForSelectorTimeoutMs,\n useHttpResponseBody: opts?.useHttpResponseBody,\n });\n return { html: res.body, finalUrl: res.finalUrl ?? url, status: res.status };\n },\n };\n}\n","// 统一信源注册表:汇聚 WebSource 插件、Source 插件(RSS/Email)、genericWebSource,提供 getSource 查找入口\n\nimport { createWebSource, genericWebSource, setLoadedSites } from \"./web/index.js\";\nimport { loadSiteAndSourcePlugins } from \"../../plugins/loader.js\";\nimport type { Source } from \"./types.js\";\nimport { logger } from \"../../core/logger/index.js\";\nexport { buildSourceContext } from \"./context.js\";\n\n/** 所有已注册的信源(按 priority 排序后迭代匹配) */\nexport const registeredSources: Source[] = [];\n\n/** 将字符串 URL 模式转为正则:{placeholder} 匹配单个路径段,末尾允许 query */\nfunction sourcePatternToRegex(pattern: string | RegExp): RegExp {\n if (pattern instanceof RegExp) return pattern;\n const pathOnly = pattern.split(\"?\")[0];\n const pl = \"<<<__PL__>>>\";\n const escaped = pathOnly\n .replace(/\\{[^}]*\\}/g, pl)\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => \"\\\\\" + c)\n .replace(new RegExp(pl.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\"), \"[^/]+\");\n return new RegExp(\"^\" + escaped + \"(\\\\?.*)?$\");\n}\n\n\n/** 根据 sourceId 查找匹配度最高的 Source;按 priority 升序,优先用 match 否则用 pattern */\nexport function getSource(sourceId: string): Source {\n for (const source of registeredSources) {\n const matches = source.match ? source.match(sourceId) : (() => {\n try {\n return sourcePatternToRegex(source.pattern).test(sourceId);\n } catch {\n return false;\n }\n })();\n if (matches) return source;\n }\n return genericWebSource;\n}\n\n\n/** 根据 id 精确查找 Source(用于内部调试) */\nexport function getSourceById(id: string): Source | undefined {\n return registeredSources.find((s) => s.id === id);\n}\n\n\n/** 初始化所有信源:加载 sources 插件、构建注册表(pipeline 为固定流程,见 app/pipeline/) */\nexport async function initSources(): Promise<void> {\n const siteResult = await loadSiteAndSourcePlugins();\n const { sites, sources: sourcePlugins } = siteResult;\n setLoadedSites(sites);\n registeredSources.length = 0;\n const webSources = sites.map((s) => createWebSource(s));\n const all: Source[] = [\n ...sourcePlugins,\n ...webSources,\n genericWebSource,\n ];\n all.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));\n registeredSources.push(...all);\n logger.info(\"scheduler\", \"信源已注册\", {\n total: registeredSources.length,\n siteCount: sites.length,\n sourcePluginCount: sourcePlugins.length,\n });\n}\n","// 订阅配置类型:一个订阅由多个信源组成,支持网页、RSS、邮件、API 等多种类型\n\nimport type { RefreshInterval } from \"../../utils/refreshInterval.js\";\n\n\n/** 信源类型枚举 */\nexport type SourceType = \"web\" | \"rss\" | \"email\";\n\n\n/**\n * 单个信源配置\n *\n * ref 格式示例:\n * web / rss → https://sspai.com/feed\n * → https://xiaohongshu.com/user/profile/xxx\n * email → imaps://user:password@imap.gmail.com:993/INBOX\n * → imap://user:password@imap.qq.com:143/INBOX\n * → imaps://me%40gmail.com:app-password@imap.gmail.com/INBOX\n * (用户名含 @ 时用 %40 编码;Gmail 需使用「应用专用密码」)\n */\nexport interface SubscriptionSource {\n /** 信源标识符:HTTP(S) URL、imaps?:// 连接串、或插件自定义协议(如 lingowhale://) */\n ref: string;\n /** 信源类型(省略时由 getSource 自动识别) */\n type?: SourceType;\n /** 显示名称,用于界面展示和报错信息(省略则显示 ref) */\n label?: string;\n /** 简短描述,用于界面展示信源用途或内容说明 */\n description?: string;\n /** 单源有效时间窗口覆盖:优先级高于 Source 声明;不填则使用 Source 声明 */\n refresh?: RefreshInterval;\n /** 单源 cron 表达式(如 \"0 9 * * *\" 每天 9:00);有值时优先于 refresh */\n cron?: string;\n /** 单源代理覆盖:优先级高于 Source.proxy;不填则使用 Source.proxy 或 env HTTP_PROXY */\n proxy?: string;\n /** 信源权重,用于排序与优先级控制;默认 0,值越大优先级越高 */\n weight?: number;\n}\n\n\n/** sources.json 标准格式:扁平信源列表,无频道/订阅层级 */\nexport interface SourcesFile {\n /** 所有要抓取的信源 */\n sources: SubscriptionSource[];\n}\n\n\n/** 从旧格式({ url })或新格式({ ref })中提取信源标识符,确保向后兼容 */\nexport function resolveRef(src: SubscriptionSource | { url?: string; ref?: string }): string {\n return (src as SubscriptionSource).ref ?? (src as { url?: string }).url ?? \"\";\n}\n","// Global HTTP(S) proxy settings: read/write .rssany/config.json.\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { CONFIG_PATH } from \"./paths.js\";\n\nexport type ProxySettings = {\n globalProxy: string;\n proxyList: string[];\n};\n\nfunction normalizeProxyList(raw: unknown): string[] {\n if (!Array.isArray(raw)) return [];\n const seen = new Set<string>();\n const out: string[] = [];\n for (const v of raw) {\n if (typeof v !== \"string\") continue;\n const t = v.trim();\n if (!t || seen.has(t)) continue;\n seen.add(t);\n out.push(t);\n }\n return out;\n}\n\nasync function readConfigRoot(): Promise<Record<string, unknown>> {\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function readProxySettingsFromConfig(): Promise<ProxySettings> {\n const root = await readConfigRoot();\n const globalProxy = typeof root.globalProxy === \"string\" ? root.globalProxy.trim() : \"\";\n return {\n globalProxy,\n proxyList: normalizeProxyList(root.proxyList),\n };\n}\n\nexport async function readGlobalProxyFromConfig(): Promise<string | undefined> {\n const { globalProxy } = await readProxySettingsFromConfig();\n return globalProxy.length > 0 ? globalProxy : undefined;\n}\n\nexport async function readProxyListFromConfig(): Promise<string[]> {\n const { proxyList } = await readProxySettingsFromConfig();\n return proxyList;\n}\n\nexport async function saveProxySettingsToConfig(settings: ProxySettings): Promise<void> {\n const root = await readConfigRoot();\n const globalProxy = settings.globalProxy.trim();\n const proxyList = normalizeProxyList(settings.proxyList);\n\n if (globalProxy) {\n root.globalProxy = globalProxy;\n } else {\n delete root.globalProxy;\n }\n\n if (proxyList.length > 0) {\n root.proxyList = proxyList;\n } else {\n delete root.proxyList;\n }\n\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** Write or clear globalProxy while preserving proxyList. */\nexport async function saveGlobalProxyToConfig(proxy: string): Promise<void> {\n const current = await readProxySettingsFromConfig();\n await saveProxySettingsToConfig({ ...current, globalProxy: proxy });\n}\n\n/** Plugin Site.proxy takes precedence over config globalProxy. */\nexport async function resolveProxyForSite(site: { proxy?: string }): Promise<string | undefined> {\n const s = site.proxy?.trim();\n if (s) return s;\n return readGlobalProxyFromConfig();\n}\n","// 信源配置模块:读取 .rssany/sources.json(扁平信源列表,供 scheduler 使用)\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { SOURCES_CONFIG_PATH } from \"../../config/paths.js\";\nimport type { SubscriptionSource, SourcesFile } from \"./types.js\";\nimport { resolveRef } from \"./types.js\";\nimport type { Source } from \"../sources/types.js\";\nimport { readGlobalProxyFromConfig } from \"../../config/globalProxy.js\";\n\nexport type { SubscriptionSource, SourcesFile } from \"./types.js\";\nexport { resolveRef } from \"./types.js\";\n\n\n/** 从 .rssany/sources.json 加载;支持新格式 { sources: [] } 与旧格式 { [id]: { sources: [] } },统一扁平为 SubscriptionSource[] */\nasync function loadSourcesFile(): Promise<SubscriptionSource[]> {\n try {\n const raw = await readFile(SOURCES_CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\") return [];\n if (Array.isArray((parsed as SourcesFile).sources)) {\n return (parsed as SourcesFile).sources.filter((s) => resolveRef(s));\n }\n const entries = Object.values(parsed as Record<string, { sources?: SubscriptionSource[] }>);\n const list: SubscriptionSource[] = [];\n for (const entry of entries) {\n if (entry && Array.isArray(entry.sources)) {\n for (const s of entry.sources) {\n if (resolveRef(s)) list.push(s);\n }\n }\n }\n return list;\n } catch {\n return [];\n }\n}\n\n\n/** 获取所有信源(扁平列表),供 scheduler 使用 */\nexport async function getAllSources(): Promise<SubscriptionSource[]> {\n return loadSourcesFile();\n}\n\n/** 去重后的 ref 列表(与 sources.json 一致),供 Feed / 聚合查询使用 */\nexport async function getAllSubscriptionRefs(): Promise<string[]> {\n const list = await loadSourcesFile();\n const seen = new Set<string>();\n const out: string[] = [];\n for (const s of list) {\n const r = resolveRef(s);\n if (r && !seen.has(r)) {\n seen.add(r);\n out.push(r);\n }\n }\n return out;\n}\n\n\n/** 将扁平列表写回 sources.json(新格式);供 raw API 写入 */\nexport async function saveSourcesFile(sources: SubscriptionSource[]): Promise<void> {\n await writeFile(\n SOURCES_CONFIG_PATH,\n JSON.stringify({ sources }, null, 2) + \"\\n\",\n \"utf-8\"\n );\n}\n\n\n/**\n * 代理优先级:sources.json 单源 → 插件 Source.proxy → config.json globalProxy →(未写入则由 fetcher resolveProxy 使用 HTTP_PROXY)\n */\nexport async function getEffectiveProxyForListUrl(listUrl: string, source: Source): Promise<string | undefined> {\n const list = await getAllSources();\n const sub = list.find((s) => resolveRef(s) === listUrl);\n const fromSub = sub?.proxy?.trim();\n if (fromSub) return fromSub;\n const fromPlugin = source.proxy?.trim();\n if (fromPlugin) return fromPlugin;\n return readGlobalProxyFromConfig();\n}\n\n/** 读取 sources.json 原始内容(用于 GET /api/sources/raw);旧格式会转为新格式返回 */\nexport async function getSourcesRaw(): Promise<string> {\n try {\n const raw = await readFile(SOURCES_CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\") return JSON.stringify({ sources: [] }, null, 2);\n if (Array.isArray((parsed as SourcesFile).sources)) {\n return raw;\n }\n const list = await loadSourcesFile();\n return JSON.stringify({ sources: list }, null, 2);\n } catch {\n return JSON.stringify({ sources: [] }, null, 2);\n }\n}\n","/**\n * Pipeline 步骤:LLM 内容质量过滤\n *\n * 判定为低质量(无实质信息、垃圾占位、纯广告等)时标记条目为丢弃;\n * feeder 会从数据库删除该条并不再输出到 RSS。\n *\n * 需在 .rssany/config.json 的 pipeline.steps 中启用,且配置 OPENAI_API_KEY 等 LLM 环境变量。\n * 建议放在 tagger / translator 之前以省 token。\n */\n\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { markPipelineDrop } from \"../types/feedItem.js\";\nimport { logger } from \"../core/logger/index.js\";\n\nconst MAX_BODY_CHARS = 4000;\n\nconst SYSTEM = `你是信息质量评估助手。根据标题、摘要与正文片段,判断该条是否值得保留在订阅信息流中。\n- 保留(keep: true):有实质信息、观点、技术内容、新闻价值,或对读者有参考意义。\n- 过滤(keep: false):明显垃圾广告、纯日期/站务占位、无意义一句话、乱码或空白、纯导航无内容、重复无信息量的模板句。\n- 只输出 JSON,格式:{\"keep\": true 或 false, \"reason\": \"一句话中文理由\"}`;\n\nexport interface QualityFilterContext {\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\n}\n\nfunction combinedForJudge(item: FeedItem): string {\n const title = (item.title ?? \"\").trim();\n const summary = (item.summary ?? \"\").trim();\n let body = (item.content ?? \"\").trim();\n if (body.length > MAX_BODY_CHARS) body = body.slice(0, MAX_BODY_CHARS) + \"\\n\\n[... 正文已截断 ...]\";\n const parts: string[] = [];\n if (title) parts.push(`标题:\\n${title}`);\n if (summary) parts.push(`摘要:\\n${summary}`);\n if (body) parts.push(`正文:\\n${body}`);\n return parts.join(\"\\n\\n---\\n\\n\");\n}\n\n/** 无 LLM 时跳过 */\nexport function qualityFilterMatch(_item: FeedItem, ctx: QualityFilterContext): boolean {\n return !!ctx.llm;\n}\n\nexport async function runQualityFilter(item: FeedItem, ctx: QualityFilterContext): Promise<FeedItem> {\n if (!ctx.llm) return item;\n\n const text = combinedForJudge(item);\n if (!text.trim()) return item;\n\n const prompt = `${SYSTEM}\\n\\n请评估以下条目:\\n\\n${text}`;\n\n let res: Record<string, unknown>;\n try {\n res = await ctx.llm.chatJson(prompt, undefined, { maxTokens: 256, debugLabel: \"qualityFilter\" });\n } catch {\n return item;\n }\n\n const isKeep = res.keep === true || res.keep === \"true\";\n const isDrop = res.keep === false || res.keep === \"false\";\n if (isKeep) return item;\n if (!isDrop) return item;\n\n const reason = typeof res.reason === \"string\" ? res.reason.trim() : \"\";\n logger.info(\"pipeline\", \"质量过滤移除条目\", { link: item.link, reason: reason || undefined });\n return markPipelineDrop(item);\n}\n","/**\n * Pipeline 步骤:LLM 自动打标签\n *\n * 从系统标签库(话题 tags 并集)中选取匹配的标签写入 item.tags。\n * 模型建议的 tag 若不在系统标签库中则忽略。\n *\n * 需要配置 OPENAI_API_KEY(或等效 LLM 环境变量)。\n */\n\nimport type { FeedItem } from \"../types/feedItem.js\";\n\nconst SYSTEM = `你是一个信息分类助手。根据文章标题和摘要,从候选标签库中选出最匹配的标签,最多 5 个。\n- 只能使用候选标签库中已有的标签,不要输出库中不存在的标签。\n- 如果没有合适的标签,输出空数组 {\"tags\": []},不要硬选不相关的标签。\n- 只输出 JSON,格式:{\"tags\": [\"tag1\", \"tag2\", ...]}`;\n\nexport interface TaggerContext {\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\n db?: { getSystemTags: () => Promise<string[]> };\n}\n\n/** 跳过已有 tags 的条目;无 LLM 则跳过 */\nexport function taggerMatch(item: FeedItem, ctx: TaggerContext): boolean {\n return !item.tags?.length && !!ctx.llm;\n}\n\nexport async function runTagger(item: FeedItem, ctx: TaggerContext): Promise<FeedItem> {\n if (!ctx.db) return item;\n const systemTags = await ctx.db.getSystemTags();\n if (!systemTags.length || !ctx.llm) return item;\n\n const candidateList = `候选标签库(只能从中选取):\\n${systemTags.join(\", \")}\\n\\n`;\n const prompt = `${SYSTEM}\\n\\n${candidateList}文章标题:${item.title ?? \"\"}\\n文章摘要:${item.summary ?? item.content?.slice(0, 300) ?? \"(无摘要)\"}`;\n\n let suggested: string[];\n try {\n const res = await ctx.llm.chatJson(prompt, undefined, { maxTokens: 256, debugLabel: \"tagger\" });\n suggested = Array.isArray(res.tags) ? res.tags.filter((t): t is string => typeof t === \"string\") : [];\n } catch {\n return item;\n }\n\n if (!suggested.length) return item;\n\n const set = new Set(systemTags.map((t) => t.toLowerCase()));\n const confirmed = suggested.filter((t) => set.has(t.toLowerCase()));\n\n if (confirmed.length) {\n item.tags = [...new Set([...(item.tags ?? []), ...confirmed])];\n }\n\n return item;\n}\n","/**\n * Pipeline 步骤:LLM 翻译为中文\n *\n * 将条目的 title、summary、content 翻译为中文,写入 item.translations[\"zh-CN\"]。\n * 路由支持 lng=zh-CN 时可据此返回译文。\n *\n * 需要配置 OPENAI_API_KEY(或等效 LLM 环境变量)。\n *\n * 粗判已为中文的条目不调 LLM(省 token);展示时 lng=zh-CN 无译文则回退原文。\n */\n\nimport type { FeedItem } from \"../types/feedItem.js\";\n\nconst ZH_CN = \"zh-CN\";\nconst MAX_CONTENT_CHARS = 6000;\n/** 用于语言检测的正文上限(与 runTranslator 截断策略大致一致) */\nconst DETECT_CONTENT_PREFIX = 2000;\n\nconst SYSTEM = `你是一个专业翻译助手。将用户提供的英文(或其他语言)内容翻译为简体中文。\n- 保持专业、准确、流畅。\n- 若原文已是中文,则保持原样或轻微润色。\n- 只输出 JSON,格式:{\"title\": \"译文标题\", \"summary\": \"译文摘要\", \"content\": \"译文正文\"}\n- 若某字段为空或用户未提供,对应输出空字符串 \"\"。`;\n\nexport interface TranslatorContext {\n llm?: { chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>> };\n}\n\nfunction combinedTextForDetection(item: FeedItem): string {\n const title = (item.title ?? \"\").trim();\n const summary = (item.summary ?? item.content?.slice(0, 500) ?? \"\").trim();\n const content = (item.content ?? \"\").trim().slice(0, DETECT_CONTENT_PREFIX);\n return `${title}\\n${summary}\\n${content}`;\n}\n\n/**\n * 粗判主体是否已是中文(汉字占比高),用于跳过 LLM。\n * 含日文假名 / 韩文谚文时不视为「已是中文」,避免误跳日文/韩文条目。\n */\nexport function isLikelyChineseContent(text: string): boolean {\n const t = text.trim();\n if (t.length < 8) return false;\n if (/[\\u3040-\\u309f\\u30a0-\\u30ff]/.test(t)) return false;\n if (/[\\uac00-\\ud7af]/.test(t)) return false;\n\n const cjk = (t.match(/[\\u4e00-\\u9fff\\u3400-\\u4dbf]/g) ?? []).length;\n const latin = (t.match(/[A-Za-z]/g) ?? []).length;\n const letterish = cjk + latin;\n if (letterish < 12) return false;\n return cjk / letterish >= 0.55;\n}\n\n/** 跳过已有 zh-CN 译文、无 LLM、或粗判已为中文的条目 */\nexport function translatorMatch(item: FeedItem, ctx: TranslatorContext): boolean {\n const hasZh = item.translations?.[ZH_CN];\n if (hasZh || !ctx.llm) return false;\n if (isLikelyChineseContent(combinedTextForDetection(item))) return false;\n return true;\n}\n\nexport async function runTranslator(item: FeedItem, ctx: TranslatorContext): Promise<FeedItem> {\n if (!ctx.llm) return item;\n\n const title = (item.title ?? \"\").trim();\n const summary = (item.summary ?? item.content?.slice(0, 500) ?? \"\").trim();\n const content = (item.content ?? \"\").trim();\n const contentTruncated =\n content.length > MAX_CONTENT_CHARS ? content.slice(0, MAX_CONTENT_CHARS) + \"\\n\\n[... 内容已截断 ...]\" : content;\n\n if (!title && !summary && !content) return item;\n\n const parts: string[] = [];\n if (title) parts.push(`标题:\\n${title}`);\n if (summary) parts.push(`摘要:\\n${summary}`);\n if (contentTruncated) parts.push(`正文:\\n${contentTruncated}`);\n\n const prompt = `${SYSTEM}\\n\\n请翻译以下内容:\\n\\n${parts.join(\"\\n\\n---\\n\\n\")}`;\n\n let res: Record<string, unknown>;\n try {\n res = await ctx.llm.chatJson(prompt, undefined, {\n maxTokens: Math.min(8192, Math.ceil((title.length + summary.length + contentTruncated.length) * 1.5)),\n debugLabel: \"translator\",\n });\n } catch {\n return item;\n }\n\n const tTitle = typeof res.title === \"string\" ? res.title.trim() : \"\";\n const tSummary = typeof res.summary === \"string\" ? res.summary.trim() : \"\";\n const tContent = typeof res.content === \"string\" ? res.content.trim() : \"\";\n\n if (!tTitle && !tSummary && !tContent) return item;\n\n item.translations = item.translations ?? {};\n item.translations[ZH_CN] = {\n title: tTitle || undefined,\n summary: tSummary || undefined,\n content: tContent || undefined,\n };\n\n return item;\n}\n","/**\n * Pipeline 配置:从 .rssany/config.json 的 pipeline 块读取\n *\n * 格式:{ \"pipeline\": { \"steps\": [{ \"id\": \"qualityFilter\", \"enabled\": false }, ...] } }\n * - steps 数组顺序即执行顺序,enabled: false 的步骤跳过\n */\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { CONFIG_PATH } from \"../config/paths.js\";\n\nexport interface PipelineStepConfig {\n id: string;\n enabled: boolean;\n}\n\nexport interface PipelineConfig {\n steps: PipelineStepConfig[];\n}\n\n/** 默认配置(入库前) */\nexport const DEFAULT_PIPELINE_STEPS: PipelineStepConfig[] = [\n { id: \"qualityFilter\", enabled: false },\n { id: \"tagger\", enabled: false },\n { id: \"translator\", enabled: false },\n];\n\n/** 可用步骤 id */\nexport const PIPELINE_STEP_IDS = [\"qualityFilter\", \"tagger\", \"translator\"] as const;\n\nfunction parseSteps(rawSteps: unknown[]): PipelineStepConfig[] {\n const steps: PipelineStepConfig[] = [];\n const seen = new Set<string>();\n for (const s of rawSteps) {\n if (s && typeof s === \"object\" && typeof (s as { id?: unknown }).id === \"string\") {\n const obj = s as { id: string; enabled?: unknown };\n const id = obj.id.trim();\n if (!id || seen.has(id)) continue;\n seen.add(id);\n const enabled = obj.enabled;\n steps.push({\n id,\n enabled: enabled !== false && enabled !== 0,\n });\n }\n }\n return steps;\n}\n\n/** 与默认步骤表对齐:保证 JSON 里出现的步骤 id 齐全、顺序与默认一致,已有项保留 enabled */\nfunction mergeWithDefaultSteps(userSteps: PipelineStepConfig[]): PipelineStepConfig[] {\n const map = new Map(userSteps.map((s) => [s.id, s]));\n return DEFAULT_PIPELINE_STEPS.map((def) => {\n const u = map.get(def.id);\n return { id: def.id, enabled: u ? u.enabled : def.enabled };\n });\n}\n\n/** 读取 pipeline 配置,缺失时返回默认;已存在的 config 会与默认步骤合并,使新步骤(如 qualityFilter)始终出现在列表中 */\nexport async function loadPipelineConfig(): Promise<PipelineConfig> {\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const parsed = JSON.parse(raw) as { pipeline?: { steps?: unknown[] } };\n const rawSteps = Array.isArray(parsed?.pipeline?.steps) ? parsed.pipeline.steps : [];\n const steps = mergeWithDefaultSteps(parseSteps(rawSteps));\n if (steps.length > 0) return { steps };\n } catch {\n // 文件不存在或解析失败\n }\n return { steps: [...DEFAULT_PIPELINE_STEPS] };\n}\n\n/** 保存 pipeline 配置到 config.json(合并其他块,不覆盖) */\nexport async function savePipelineConfig(config: PipelineConfig): Promise<void> {\n let root: Record<string, unknown> = {};\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n root = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // 文件不存在或解析失败,使用空对象\n }\n root.pipeline = { steps: config.steps };\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2), \"utf-8\");\n}\n","/**\n * Pipeline:入库前固定处理链(翻译、打标签等)\n *\n * 与 plugins 同级别,作为固定流程而非插件系统。\n * 步骤开关与排序由 .rssany/config.json 的 pipeline.steps 配置。\n */\n\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { qualityFilterMatch, runQualityFilter } from \"./qualityFilter.js\";\nimport { taggerMatch, runTagger } from \"./tagger.js\";\nimport { translatorMatch, runTranslator } from \"./translator.js\";\nimport { loadPipelineConfig } from \"./config.js\";\nimport { logger } from \"../core/logger/index.js\";\n\nexport interface PipelineContext {\n sourceUrl?: string;\n llm?: {\n chatJson: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<Record<string, unknown>>;\n chatText: (prompt: string, config?: unknown, opts?: { maxTokens?: number; debugLabel?: string }) => Promise<string>;\n };\n db?: { getSystemTags: () => Promise<string[]> };\n}\n\n/** Pipeline 步骤注册表 */\nconst STEP_REGISTRY: Record<string, { match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }> = {\n qualityFilter: { match: qualityFilterMatch, run: runQualityFilter },\n tagger: { match: taggerMatch, run: runTagger },\n translator: { match: translatorMatch, run: runTranslator },\n};\n\n/** 根据配置解析出要执行的步骤(按配置顺序,仅启用且存在的) */\nasync function getResolvedSteps(): Promise<Array<{ id: string; match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }>> {\n const config = await loadPipelineConfig();\n const out: Array<{ id: string; match: (item: FeedItem, ctx: PipelineContext) => boolean; run: (item: FeedItem, ctx: PipelineContext) => Promise<FeedItem> }> = [];\n for (const { id, enabled } of config.steps) {\n if (!enabled) continue;\n const step = STEP_REGISTRY[id];\n if (!step) {\n logger.debug(\"pipeline\", \"未知步骤已跳过\", { id });\n continue;\n }\n out.push({ id, ...step });\n }\n return out;\n}\n\n/**\n * 对单条条目执行 pipeline\n */\nexport async function runPipeline(\n item: FeedItem,\n ctx: PipelineContext\n): Promise<FeedItem> {\n const steps = await getResolvedSteps();\n let current = item;\n for (const step of steps) {\n if (!step.match(current, ctx)) continue;\n try {\n current = await step.run(current, ctx);\n } catch (err) {\n logger.warn(\"pipeline\", \"步骤执行失败\", {\n stepId: step.id,\n item_url: item.link,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n return current;\n}\n\n/**\n * 对多条条目执行 pipeline\n */\nexport async function runPipelineBatch(\n items: FeedItem[],\n ctx: PipelineContext\n): Promise<FeedItem[]> {\n const out: FeedItem[] = [];\n for (let i = 0; i < items.length; i++) {\n out.push(await runPipeline(items[i], ctx));\n }\n return out;\n}\n","// 将频道 + 条目构建为 RSS 2.0 XML\n\nimport type { RssChannel, RssEntry } from \"./types.js\";\n\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n\nfunction isoToRfc822(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toUTCString();\n}\n\n\nfunction buildItem(entry: RssEntry): string {\n const title = escapeXml(entry.title ?? \"\");\n const link = escapeXml(entry.link ?? \"\");\n const descRaw = entry.description ?? \"\";\n const desc = descRaw.includes(\"<\") || descRaw.includes(\">\")\n ? `<![CDATA[${descRaw.replace(/\\]\\]>/g, \"]]]]><![CDATA[>\")}]]>`\n : escapeXml(descRaw);\n const pubDate = entry.published ? escapeXml(isoToRfc822(entry.published)) : \"\";\n const guid = entry.guid ?? entry.link ?? \"\";\n const guidEscaped = escapeXml(guid);\n let buf = ` <item>\\n <title>${title}</title>\\n <link>${link}</link>\\n <description>${desc}</description>\\n`;\n if (pubDate) buf += ` <pubDate>${pubDate}</pubDate>\\n`;\n if (entry.imageUrl?.trim()) {\n const encUrl = escapeXml(entry.imageUrl.trim());\n const encType = escapeXml(entry.imageType?.trim() || \"image/jpeg\");\n buf += ` <enclosure url=\"${encUrl}\" length=\"0\" type=\"${encType}\"/>\\n`;\n }\n buf += ` <guid isPermaLink=\"true\">${guidEscaped}</guid>\\n`;\n buf += ` </item>\\n`;\n return buf;\n}\n\n\nexport function buildRssXml(channel: RssChannel, entries: RssEntry[]): string {\n const title = escapeXml(channel.title);\n const link = escapeXml(channel.link);\n const desc = escapeXml(channel.description ?? \"\");\n const lang = escapeXml(channel.language ?? \"zh-CN\");\n const now = new Date().toUTCString();\n const items = entries.map(buildItem).join(\"\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\">\n <channel>\n <title>${title}</title>\n <link>${link}</link>\n <description>${desc}</description>\n <language>${lang}</language>\n <lastBuildDate>${now}</lastBuildDate>\n\n${items} </channel>\n</rss>`;\n}\n","// 事件总线:进程内单例 EventEmitter,供 db 层 emit、HTTP 层 subscribe\n\nimport { EventEmitter } from \"node:events\";\n\n\n/** feed:updated 事件的载荷 */\nexport interface FeedUpdatedEvent {\n sourceUrl: string;\n newCount: number;\n}\n\n\n/** 全局单例事件总线,setMaxListeners 避免 SSE 多连接时的警告 */\nexport const eventBus = new EventEmitter();\neventBus.setMaxListeners(200);\n\n\n/** 向事件总线广播新条目事件 */\nexport function emitFeedUpdated(payload: FeedUpdatedEvent): void {\n eventBus.emit(\"feed:updated\", payload);\n}\n\n\n/** 订阅 feed:updated 事件,返回取消订阅函数 */\nexport function onFeedUpdated(fn: (e: FeedUpdatedEvent) => void): () => void {\n eventBus.on(\"feed:updated\", fn);\n return () => eventBus.off(\"feed:updated\", fn);\n}\n","// 投递目标:.rssany/config.json 的 deliver.gateway / deliver.token\n// Gateway 基址示例:https://agidaily.cc/api/gateway\n// 出站固定为:POST {gateway}/items、POST {gateway}/sources、连通性测试 POST {gateway}/test\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { CONFIG_PATH } from \"./paths.js\";\n\nexport interface DeliverConfig {\n /** 基址,不含 /items;如 https://agidaily.cc/api/gateway */\n gateway: string;\n /** 与下游 Gateway(如 agidaily `data/token.txt`)一致:非空时请求头带 `Authorization: Bearer <token>` */\n token: string;\n}\n\ntype DeliverFileShape = {\n deliver?: { gateway?: string; url?: string; sourcesUrl?: string; token?: string };\n};\n\n/** 从旧版 deliver.url(…/items)或 deliver.sourcesUrl 推断 gateway 基址 */\nfunction migrateGatewayFromFile(j: DeliverFileShape): string {\n const g = j?.deliver?.gateway?.trim();\n if (g) return g;\n const u = j?.deliver?.url?.trim() ?? \"\";\n if (u) {\n return u\n .replace(/\\/items\\/?$/i, \"\")\n .replace(/\\/+$/, \"\")\n .trim();\n }\n const s = j?.deliver?.sourcesUrl?.trim() ?? \"\";\n if (s) {\n return s\n .replace(/\\/sources\\/?$/i, \"\")\n .replace(/\\/+$/, \"\")\n .trim();\n }\n return \"\";\n}\n\nexport async function getDeliverConfig(): Promise<DeliverConfig> {\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const j = JSON.parse(raw) as DeliverFileShape;\n const t = j?.deliver?.token;\n return {\n gateway: migrateGatewayFromFile(j),\n token: typeof t === \"string\" ? t.trim() : \"\",\n };\n } catch {\n return { gateway: \"\", token: \"\" };\n }\n}\n\n/** 非空 gateway 表示启用条目投递(不影响是否写库) */\nexport async function getDeliverUrl(): Promise<string> {\n const { gateway } = await getDeliverConfig();\n const base = gateway.trim().replace(/\\/+$/, \"\");\n return base ? `${base}/items` : \"\";\n}\n\nexport async function saveDeliverConfig(config: DeliverConfig): Promise<void> {\n let root: Record<string, unknown> = {};\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n root = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // 无文件则新建\n }\n const gateway = config.gateway.trim();\n const token = config.token.trim();\n const next: Record<string, unknown> = {};\n if (gateway) next.gateway = gateway;\n if (token) next.token = token;\n root.deliver = next;\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\n}\n","// 将条目 / 信源 POST 到下游 Gateway:路径相对基址固定为 /items、/sources、/test\n\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { pubDateToIsoOrNull } from \"../types/feedItem.js\";\nimport { logger } from \"../core/logger/index.js\";\n\nexport function feedItemsToPayload(items: FeedItem[]): unknown[] {\n return items.map((i) => ({\n guid: i.guid,\n title: i.title,\n link: i.link,\n pubDate: pubDateToIsoOrNull(i.pubDate) ?? new Date().toISOString(),\n author: i.author,\n summary: i.summary,\n content: i.content,\n tags: i.tags,\n sourceRef: i.sourceRef,\n translations: i.translations,\n }));\n}\n\nexport interface PostDeliverOptions {\n /** 非空时设置 `Authorization: Bearer <token>`(与 agidaily Gateway 一致) */\n bearerToken?: string;\n}\n\n/** Gateway 基址(如 https://agidaily.cc/api/gateway)与路径 items | sources | test 拼接 */\nexport function joinGatewayPath(gatewayBase: string, segment: \"items\" | \"sources\" | \"test\"): string {\n const base = gatewayBase.trim().replace(/\\/+$/, \"\");\n if (!base) return \"\";\n return `${base}/${segment}`;\n}\n\n/** POST { sourceRef, items } 到 {gateway}/items */\nexport async function postDeliverItems(\n url: string,\n sourceRef: string,\n items: FeedItem[],\n options?: PostDeliverOptions,\n): Promise<void> {\n if (!url.trim() || items.length === 0) return;\n const body = JSON.stringify({ sourceRef, items: feedItemsToPayload(items) });\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n const t = options?.bearerToken?.trim();\n if (t) headers.Authorization = `Bearer ${t}`;\n const res = await fetch(url.trim(), {\n method: \"POST\",\n headers,\n body,\n signal: AbortSignal.timeout(120_000),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\n }\n}\n\nexport async function postDeliverItemsSafe(\n url: string,\n sourceRef: string,\n items: FeedItem[],\n options?: PostDeliverOptions,\n): Promise<void> {\n try {\n await postDeliverItems(url, sourceRef, items, options);\n } catch (err) {\n logger.warn(\"deliver\", \"投递失败\", {\n sourceRef,\n count: items.length,\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n/** POST 当前 sources.json 正文到 {gateway}/sources */\nexport async function postDeliverSources(\n url: string,\n sourcesJson: string,\n options?: PostDeliverOptions,\n): Promise<void> {\n if (!url.trim() || !sourcesJson.trim()) return;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json; charset=utf-8\",\n };\n const t = options?.bearerToken?.trim();\n if (t) headers.Authorization = `Bearer ${t}`;\n const res = await fetch(url.trim(), {\n method: \"POST\",\n headers,\n body: sourcesJson,\n signal: AbortSignal.timeout(120_000),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\n }\n}\n\nexport async function postDeliverSourcesSafe(\n url: string,\n sourcesJson: string,\n options?: PostDeliverOptions,\n): Promise<void> {\n try {\n await postDeliverSources(url, sourcesJson, options);\n } catch (err) {\n logger.warn(\"deliver\", \"信源配置投递失败\", {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n/** POST 连通性测试体到 {gateway}/test(由路由组装的 JSON) */\nexport async function postDeliverGatewayTest(\n gateway: string,\n body: unknown,\n options?: PostDeliverOptions,\n): Promise<void> {\n const url = joinGatewayPath(gateway, \"test\");\n if (!url) throw new Error(\"gateway 不能为空\");\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json; charset=utf-8\",\n };\n const t = options?.bearerToken?.trim();\n if (t) headers.Authorization = `Bearer ${t}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(120_000),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : \"\"}`);\n }\n}\n","// Feeder:根据 URL 生成 RSS,直接通过 Source 接口驱动,与具体信源解耦\n\nimport { cacheKey, cacheKeyFromCron } from \"../core/cacher/index.js\";\nimport { getSource } from \"../scraper/sources/index.js\";\nimport { runPipeline } from \"../pipeline/index.js\";\nimport { AuthRequiredError } from \"../scraper/auth/index.js\";\nimport { buildRssXml } from \"./rss.js\";\nimport type { RssChannel, RssEntry } from \"./types.js\";\nimport type { FeedItem } from \"../types/feedItem.js\";\nimport { normalizeAuthor, getEffectiveItemFields, isPipelineDroppedItem, pubDateToIsoOrNull } from \"../types/feedItem.js\";\nimport type { FeederConfig } from \"./types.js\";\nimport { buildSourceContext } from \"../scraper/sources/context.js\";\nimport { upsertItems, updateItemContent, getSystemTags, deleteItem } from \"../db/index.js\";\nimport { emitFeedUpdated } from \"../core/events/index.js\";\nimport { chatJson, chatText } from \"../core/llm.js\";\nimport type { PipelineContext } from \"../pipeline/index.js\";\nimport { logger } from \"../core/logger/index.js\";\nimport { getDeliverConfig } from \"../config/deliver.js\";\nimport { joinGatewayPath, postDeliverItemsSafe } from \"../deliver/post.js\";\nimport { getEffectiveProxyForListUrl } from \"../scraper/subscription/index.js\";\nimport { canonicalHttpSourceRef } from \"../utils/httpSourceRef.js\";\n\n/** 主动拉取默认有头;仅显式 headless:true 时用无头;其余场景沿用 config.headless */\nfunction resolveHeadlessForFeeder(config: FeederConfig): boolean | undefined {\n if (config.force === true) {\n return config.headless === true ? true : false;\n }\n return config.headless;\n}\n\n/** 根据 listUrl + items 构建 RssChannel(与 generateAndCache 一致,用于缓存命中时实时生成 XML);lng 存在时设置 channel.language */\nfunction buildChannelFromItems(listUrl: string, items: FeedItem[], lng?: string | null): RssChannel {\n const channel: RssChannel = {\n title: items[0]?.author?.length ? `${items[0].author[0]} 的订阅` : \"RSS 订阅\",\n link: listUrl,\n description: `来自 ${listUrl} 的订阅`,\n };\n if (lng) channel.language = lng;\n return channel;\n}\n\n\n/** 根据条目生成 RssEntry:有 lng 且存在译文则用译文,否则用原文;有正文用 content,否则用 summary */\nfunction toRssEntry(item: FeedItem, lng?: string | null): RssEntry {\n const eff = getEffectiveItemFields(item, lng);\n const hasContent = eff.content != null && eff.content !== \"\";\n const desc = hasContent ? eff.content : eff.summary;\n return {\n title: eff.title,\n link: item.link,\n description: desc,\n guid: item.guid,\n published: pubDateToIsoOrNull(item.pubDate) ?? undefined,\n imageUrl: item.imageUrl,\n };\n}\n\n\n/** 同一 URL 的首次生成任务去重(仅在初始 fetch+parse 阶段有效) */\nconst generatingKeys = new Map<string, Promise<{ items: FeedItem[] }>>();\n\n\n/** Pipeline 上下文 */\nconst pipelineCtx: PipelineContext = {\n llm: { chatJson, chatText } as PipelineContext[\"llm\"],\n db: { getSystemTags },\n};\n\n/** 单条 pipeline */\nasync function runPipelineOnItem(item: FeedItem, ctx: { sourceUrl: string }): Promise<FeedItem> {\n return runPipeline(item, { ...pipelineCtx, ...ctx });\n}\n\n\n/** 执行生成流程:抓取 → 入库 → 对新条目跑 pipeline → 更新库 */\nasync function generateAndCache(\n listUrl: string,\n key: string,\n config: FeederConfig,\n proxy: string | undefined,\n): Promise<{ items: FeedItem[] }> {\n const { cacheDir = \"cache\" } = config;\n const headless = resolveHeadlessForFeeder(config);\n const source = getSource(listUrl);\n const ctx = buildSourceContext({ cacheDir, headless, proxy });\n let items: FeedItem[];\n try {\n items = await source.fetchItems(listUrl, ctx);\n } catch (err) {\n generatingKeys.delete(key);\n const message = err instanceof Error ? err.message : String(err);\n logger.error(\"scraper\", \"抓取失败\", { source_url: listUrl, err: message });\n throw err;\n }\n const sourceRefStored = canonicalHttpSourceRef(listUrl);\n items.forEach((i) => {\n i.sourceRef = sourceRefStored;\n i.author = normalizeAuthor(i.author);\n });\n generatingKeys.delete(key);\n logger.info(\"scraper\", \"抓取成功\", { source_url: listUrl, count: items.length });\n\n const { gateway: deliverGateway, token: deliverToken } = await getDeliverConfig();\n\n let newCount = 0;\n let newIds = new Set<string>();\n const upsertResult = await upsertItems(items).catch((err) => {\n logger.warn(\"db\", \"upsertItems 失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) });\n return { newCount: 0, newIds: new Set<string>() };\n });\n newCount = upsertResult.newCount;\n newIds = upsertResult.newIds;\n\n let pipelineDroppedNew = 0;\n const shouldRunPipelineRow = (guid: string) => newIds.has(guid);\n\n for (let i = 0; i < items.length; i++) {\n if (!shouldRunPipelineRow(items[i].guid)) continue;\n const processed = await runPipelineOnItem(items[i], { sourceUrl: sourceRefStored });\n items[i] = processed;\n if (isPipelineDroppedItem(processed)) {\n await deleteItem(processed.guid).catch((err) =>\n logger.warn(\"db\", \"质量过滤后删除条目失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) })\n );\n pipelineDroppedNew++;\n } else {\n updateItemContent(processed).catch((err) =>\n logger.warn(\"db\", \"updateItemContent 失败\", { source_url: listUrl, err: err instanceof Error ? err.message : String(err) })\n );\n }\n }\n if (newCount > 0) {\n emitFeedUpdated({ sourceUrl: sourceRefStored, newCount: newCount - pipelineDroppedNew });\n }\n const out = items.filter((i) => !isPipelineDroppedItem(i));\n if (deliverGateway.trim() && out.length > 0) {\n await postDeliverItemsSafe(joinGatewayPath(deliverGateway, \"items\"), sourceRefStored, out, {\n bearerToken: deliverToken || undefined,\n });\n }\n return { items: out };\n}\n\n\n/** 根据 list URL 获取条目列表:按 cron 或 refresh 策略生成时间窗口 key 用于去重,每次均重新抓取 */\nexport async function crawlSource(listUrl: string, config: FeederConfig = {}): Promise<{ items: FeedItem[] }> {\n const source = getSource(listUrl);\n const proxy = await getEffectiveProxyForListUrl(listUrl, source);\n const headless = resolveHeadlessForFeeder(config);\n const key = config.cron\n ? cacheKeyFromCron(listUrl, config.cron)\n : cacheKey(listUrl, config.refreshInterval ?? source.refreshInterval ?? \"1day\");\n if (source.preCheck != null) {\n try {\n await source.preCheck(\n buildSourceContext({\n cacheDir: config.cacheDir ?? \"cache\",\n headless,\n proxy,\n }),\n );\n } catch (err) {\n if (err instanceof AuthRequiredError) throw err;\n throw err;\n }\n }\n let task = config.force ? undefined : generatingKeys.get(key);\n if (!task) {\n task = generateAndCache(listUrl, key, config, proxy);\n if (!config.force) generatingKeys.set(key, task);\n }\n const { items } = await task;\n return { items };\n}\n\nexport async function getItems(listUrl: string, config: FeederConfig = {}): Promise<{ items: FeedItem[]; fromCache: boolean }> {\n const { items } = await crawlSource(listUrl, config);\n return { items, fromCache: false };\n}\n\n\n/** 将 FeedItem[] 转为 RSS 2.0 XML 字符串;可选 channelTitle/channelDesc 覆盖默认 */\nexport function feedItemsToRssXml(\n items: FeedItem[],\n listUrl: string,\n lng?: string | null,\n opts?: { channelTitle?: string; channelDesc?: string }\n): string {\n const channel = buildChannelFromItems(listUrl, items, lng);\n if (opts?.channelTitle) channel.title = opts.channelTitle;\n if (opts?.channelDesc) channel.description = opts.channelDesc;\n return buildRssXml(channel, items.map((it) => toRssEntry(it, lng)));\n}\n","// 通用调度器:cron 定时任务、重试与分组并发\n\nimport { CronExpressionParser } from \"cron-parser\";\nimport { schedule as cronSchedule, validate as cronValidate } from \"node-cron\";\n\n/** 校验 cron 表达式是否合法 */\nexport const validateCron = cronValidate;\n\n/** 调度任务:返回 Promise,失败时由调度器负责重试 */\nexport type ScheduledTask = () => Promise<void>;\n\n\n/** 调度选项 */\nexport interface ScheduleOptions {\n /** cron 表达式;不填或空表示一次性任务 */\n cron?: string;\n /** 失败时重试次数,默认 0 */\n retries?: number;\n /** 重试间隔(毫秒),默认 5000 */\n retryDelayMs?: number;\n /** 分组并发数,首次使用该分组时生效,默认 10 */\n concurrency?: number;\n /** 定时任务:注册后是否立即执行一次,默认 false */\n runNow?: boolean;\n /** 一次性任务:是否插队到队首,默认 false */\n priority?: boolean;\n /** 分组名(内部使用,由 schedule 注入) */\n group?: string;\n}\n\n\n/** 分组配置 */\nexport interface GroupConfig {\n /** 该组最大并发数 */\n concurrency: number;\n}\n\n\n/** 已注册任务 */\ninterface RegisteredTask {\n id: string;\n cronExpr: string;\n task: ScheduledTask;\n options: ScheduleOptions;\n stop: () => void;\n /** 上次触发时间(用于计算下次执行) */\n lastRunTime: number;\n}\n\n\n/** 分组队列项 */\ninterface QueuedItem {\n id: string;\n task: ScheduledTask | (() => Promise<unknown>);\n options: ScheduleOptions;\n resolve?: () => void;\n resolveValue?: (value: unknown) => void;\n rejectValue?: (err: unknown) => void;\n}\n\n\nconst tasks = new Map<string, RegisteredTask>();\nconst groups = new Map<string, { config: GroupConfig; running: number; queue: QueuedItem[]; completedCount: number }>();\nconst DEFAULT_RETRY_DELAY_MS = 5000;\nconst DEFAULT_GROUP_CONCURRENCY = 10;\n\n\nasync function runWithRetry(\n task: ScheduledTask,\n options: ScheduleOptions\n): Promise<void> {\n const retries = options.retries ?? 0;\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n let lastErr: unknown;\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n await task();\n return;\n } catch (err) {\n lastErr = err;\n if (attempt < retries) {\n await new Promise((r) => setTimeout(r, retryDelayMs));\n }\n }\n }\n throw lastErr;\n}\n\n\nasync function runWithRetryAndResult<T>(\n task: () => Promise<T>,\n options: ScheduleOptions\n): Promise<T> {\n const retries = options.retries ?? 0;\n const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n let lastErr: unknown;\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await task();\n } catch (err) {\n lastErr = err;\n if (attempt < retries) {\n await new Promise((r) => setTimeout(r, retryDelayMs));\n }\n }\n }\n throw lastErr;\n}\n\n\nfunction ensureGroup(group: string, concurrency: number): void {\n if (!groups.has(group)) {\n groups.set(group, { config: { concurrency }, running: 0, queue: [], completedCount: 0 });\n } else {\n const g = groups.get(group)!;\n g.config.concurrency = concurrency;\n if (g.completedCount === undefined) g.completedCount = 0;\n }\n}\n\n\nfunction enqueueAndProcess(\n group: string,\n id: string,\n task: ScheduledTask | (() => Promise<unknown>),\n options: ScheduleOptions,\n resolve?: () => void,\n priority?: boolean,\n resolveValue?: (value: unknown) => void,\n rejectValue?: (err: unknown) => void\n): void {\n const g = groups.get(group);\n if (!g) return;\n g.queue = g.queue.filter((it) => it.id !== id);\n const item: QueuedItem = { id, task, options, resolve, resolveValue, rejectValue };\n if (priority) {\n g.queue.unshift(item);\n } else {\n g.queue.push(item);\n }\n processGroupQueue(group);\n}\n\n\nfunction processGroupQueue(group: string): void {\n const g = groups.get(group);\n if (!g || g.running >= g.config.concurrency || g.queue.length === 0) return;\n const item = g.queue.shift()!;\n g.running += 1;\n const done = () => {\n g.running -= 1;\n g.completedCount = (g.completedCount ?? 0) + 1;\n processGroupQueue(group);\n };\n if (item.resolveValue != null || item.rejectValue != null) {\n runWithRetryAndResult(item.task as () => Promise<unknown>, item.options)\n .then((result) => {\n item.resolveValue?.(result);\n })\n .catch((err) => {\n item.rejectValue?.(err);\n })\n .finally(done);\n } else {\n runWithRetry(item.task as ScheduledTask, item.options)\n .catch(() => {})\n .finally(() => {\n item.resolve?.();\n done();\n });\n }\n}\n\n\n/**\n * 调度任务:options.cron 有值时注册定时任务,否则一次性入队\n * @param group 分组名\n * @param id 任务唯一标识\n * @param task 异步任务函数\n * @param options cron 有值=定时任务,无值=一次性任务;可选 retries、retryDelayMs、concurrency、runNow、priority\n */\nexport function schedule(group: string, id: string, task: ScheduledTask, options: ScheduleOptions & { cron: string }): boolean;\nexport function schedule<T>(group: string, id: string, task: () => Promise<T>, options?: ScheduleOptions): Promise<T>;\nexport function schedule<T>(\n group: string,\n id: string,\n task: ScheduledTask | (() => Promise<T>),\n options: ScheduleOptions = {}\n): boolean | Promise<T> {\n const cronExpr = options.cron?.trim();\n ensureGroup(group, options.concurrency ?? groups.get(group)?.config.concurrency ?? DEFAULT_GROUP_CONCURRENCY);\n\n if (cronExpr && cronValidate(cronExpr)) {\n unschedule(id);\n const optsWithGroup = { ...options, group };\n const job = cronSchedule(cronExpr, () => {\n const reg = tasks.get(id);\n if (reg) reg.lastRunTime = Date.now();\n enqueueAndProcess(group, id, task as ScheduledTask, optsWithGroup);\n });\n tasks.set(id, {\n id,\n cronExpr,\n task: task as ScheduledTask,\n options: optsWithGroup,\n stop: () => job.stop(),\n lastRunTime: 0,\n });\n if (options.runNow) {\n runNow(id, true).catch(() => {});\n }\n return true;\n }\n\n return new Promise<T>((resolve, reject) => {\n enqueueAndProcess(\n group,\n id,\n task as () => Promise<unknown>,\n { ...options, group },\n undefined,\n options.priority ?? false,\n resolve as (v: unknown) => void,\n reject\n );\n });\n}\n\n\n/**\n * 取消任务\n */\nexport function unschedule(id: string): void {\n const reg = tasks.get(id);\n if (reg) {\n reg.stop();\n tasks.delete(id);\n }\n}\n\n\n/**\n * 取消指定分组下的所有定时任务(不清理队列,用于 reschedule 前仅移除本组任务)\n */\nexport function unscheduleGroup(group: string): void {\n const ids = [...tasks.entries()].filter(([, reg]) => reg.options.group === group).map(([id]) => id);\n for (const id of ids) unschedule(id);\n}\n\n\n/**\n * 立即执行一次任务(不等待下次定时)\n * @param id 任务 id\n * @param priority 有分组时,true 表示插入队首优先执行,默认 false 插入队尾\n */\nexport function runNow(id: string, priority = false): Promise<void> {\n const reg = tasks.get(id);\n if (!reg) return Promise.resolve();\n reg.lastRunTime = Date.now();\n const group = reg.options.group;\n if (group) {\n return new Promise<void>((resolve) => {\n enqueueAndProcess(group, id, reg.task, reg.options, resolve, priority);\n });\n }\n return runWithRetry(reg.task, reg.options);\n}\n\n\n/**\n * 清空所有任务(含各分组队列中的待执行项)\n */\nexport function clearAll(): void {\n for (const [, reg] of tasks) {\n reg.stop();\n }\n tasks.clear();\n for (const g of groups.values()) {\n g.queue.length = 0;\n }\n}\n\n\n/**\n * 获取已注册任务 id 列表\n */\nexport function getTaskIds(): string[] {\n return [...tasks.keys()];\n}\n\n\n/** 分组统计 */\nexport interface GroupStats {\n /** 正在执行的任务数 */\n running: number;\n /** 队列中等待的任务数 */\n queued: number;\n /** 该组最大并发数 */\n concurrency: number;\n /** 该组下已注册的定时任务数量 */\n scheduledCount: number;\n /** 已完成任务数(含定时任务每次执行 + 单次任务,进程启动后累计) */\n completedCount: number;\n /**\n * 下次任务时间(毫秒时间戳)\n * - 0:正在执行\n * - -1:无定时任务\n * - 其他:下次预计执行时间戳\n */\n nextRunTime: number;\n}\n\n\nfunction getNextRunForTask(reg: RegisteredTask): number {\n try {\n const base = reg.lastRunTime > 0 ? new Date(reg.lastRunTime) : new Date();\n const expr = CronExpressionParser.parse(reg.cronExpr, { currentDate: base });\n let next = expr.next().getTime();\n if (next <= Date.now()) {\n const retry = CronExpressionParser.parse(reg.cronExpr, { currentDate: new Date() });\n next = retry.next().getTime();\n }\n return next;\n } catch {\n return -1;\n }\n}\n\n\n/**\n * 获取各分组的执行统计,用于管理页进度条等\n */\nexport function getGroupStats(): Record<string, GroupStats> {\n const result: Record<string, GroupStats> = {};\n for (const [name, g] of groups) {\n const groupTasks = [...tasks.values()].filter((t) => t.options.group === name);\n const scheduledCount = groupTasks.length;\n let nextRunTime: number;\n if (g.running > 0) {\n nextRunTime = 0;\n } else if (scheduledCount === 0) {\n nextRunTime = -1;\n } else {\n const times = groupTasks.map(getNextRunForTask).filter((t) => t > 0);\n nextRunTime = times.length > 0 ? Math.min(...times) : -1;\n }\n result[name] = {\n running: g.running,\n queued: g.queue.length,\n concurrency: g.config.concurrency,\n scheduledCount,\n completedCount: g.completedCount ?? 0,\n nextRunTime,\n };\n }\n return result;\n}\n\n\n","// 信源调度:根据 sources.json 中的信源 refresh 定时触发 getItems,使用通用调度器\n\nimport { watch } from \"node:fs\";\nimport { getAllSources, getSourcesRaw } from \"../subscription/index.js\";\nimport { resolveRef } from \"../subscription/types.js\";\nimport { crawlSource } from \"../../feeder/index.js\";\nimport { SOURCES_CONFIG_PATH } from \"../../config/paths.js\";\nimport { getDeliverConfig } from \"../../config/deliver.js\";\nimport { joinGatewayPath, postDeliverSourcesSafe } from \"../../deliver/post.js\";\nimport type { RefreshInterval } from \"../../utils/refreshInterval.js\";\nimport { refreshIntervalToCron } from \"../../utils/refreshInterval.js\";\nimport * as scheduler from \"../../scheduler/index.js\";\n\nconst DEFAULT_REFRESH: RefreshInterval = \"1day\";\nconst SOURCES_CONCURRENCY = 1;\n\nfunction createPullTask(ref: string, cacheDir: string, cronExpr: string): scheduler.ScheduledTask {\n return async () => {\n await crawlSource(ref, {\n cacheDir,\n cron: cronExpr,\n });\n };\n}\n\nexport const SOURCES_GROUP = \"sources\";\n\n/** sources.json 变更且 config.deliver.gateway 非空时,向 {gateway}/sources POST 当前信源 JSON */\nasync function deliverSourcesConfigIfConfigured(): Promise<void> {\n const { gateway, token } = await getDeliverConfig();\n if (!gateway.trim()) return;\n let raw: string;\n try {\n raw = await getSourcesRaw();\n } catch {\n return;\n }\n await postDeliverSourcesSafe(joinGatewayPath(gateway, \"sources\"), raw, { bearerToken: token || undefined });\n}\n\nasync function rescheduleSources(cacheDir: string, runNow: boolean): Promise<void> {\n scheduler.unscheduleGroup(SOURCES_GROUP);\n let sources: Awaited<ReturnType<typeof getAllSources>>;\n try {\n sources = await getAllSources();\n } catch {\n sources = [];\n }\n\n for (const src of sources) {\n const ref = resolveRef(src);\n if (!ref) continue;\n const cronExpr: string = src.cron\n ? src.cron\n : refreshIntervalToCron(src.refresh ?? DEFAULT_REFRESH);\n if (!scheduler.validateCron(cronExpr)) continue;\n scheduler.schedule(SOURCES_GROUP, ref, createPullTask(ref, cacheDir, cronExpr), {\n cron: cronExpr,\n retries: 2,\n retryDelayMs: 5000,\n concurrency: SOURCES_CONCURRENCY,\n runNow,\n });\n }\n}\n\nexport async function initScheduler(cacheDir: string): Promise<void> {\n await rescheduleSources(cacheDir, false);\n let debounceTimer: NodeJS.Timeout | null = null;\n try {\n const watcher = watch(SOURCES_CONFIG_PATH, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n void rescheduleSources(cacheDir, false)\n .then(() => deliverSourcesConfigIfConfigured())\n .catch(() => {});\n }, 500);\n });\n watcher.on(\"error\", () => {});\n } catch {\n /* sources.json 尚不存在 */\n }\n}\n","// 本地运行:管理 API 不做令牌校验(原 admin-token 逻辑已移除)\n\nimport type { Context, Next } from \"hono\";\n\n/** 占位中间件,与既有路由注册兼容,始终放行 */\nexport function requireAdmin() {\n return async (_c: Context, next: Next): Promise<Response | void> => {\n return next();\n };\n}\n","// /api/server-info\n\nimport { networkInterfaces } from \"node:os\";\nimport type { Hono } from \"hono\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nconst PORT = Number(process.env.PORT) || 18473;\n\nexport function registerServerRoutes(app: Hono): void {\n app.get(\"/api/server-info\", requireAdmin(), (c) => {\n const lanIp = Object.values(networkInterfaces())\n .flat()\n .find((iface) => iface?.family === \"IPv4\" && !iface.internal)?.address;\n const lanUrl = lanIp ? `http://${lanIp}:${PORT}` : null;\n return c.json({ port: PORT, lanUrl });\n });\n}\n","// /api/rss(JSON,按 URL 抓取返回条目列表)\n\nimport { createHash } from \"node:crypto\";\nimport type { Hono } from \"hono\";\nimport { getItems } from \"../../../feeder/index.js\";\nimport { SOURCES_GROUP } from \"../../../scraper/scheduler/index.js\";\nimport * as scheduler from \"../../../scheduler/index.js\";\nimport { CACHE_DIR } from \"../../../config/paths.js\";\nimport { AuthRequiredError, NotFoundError } from \"../../../scraper/auth/index.js\";\nimport { getEffectiveItemFields, pubDateToIsoOrNull } from \"../../../types/feedItem.js\";\n\nexport function registerRssApiRoutes(app: Hono): void {\n app.get(\"/api/rss\", async (c) => {\n const url = c.req.query(\"url\");\n if (!url) return c.json({ error: \"url 参数缺失\" }, 400);\n const headlessParam = c.req.query(\"headless\");\n const headless = headlessParam === \"false\" || headlessParam === \"0\" ? false : undefined;\n const lng = c.req.query(\"lng\") ?? undefined;\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 20), 200);\n const offset = Number(c.req.query(\"offset\") ?? 0);\n try {\n const httpId = \"http-\" + createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\n const { items: allItems, fromCache } = await scheduler.schedule(\n SOURCES_GROUP,\n httpId,\n () => getItems(url, { cacheDir: CACHE_DIR, headless, lng })\n );\n const total = allItems.length;\n const pageItems = allItems.slice(offset, offset + limit);\n return c.json({\n fromCache,\n total,\n hasMore: offset + pageItems.length < total,\n items: pageItems.map((item) => {\n const { title, summary } = lng ? getEffectiveItemFields(item, lng) : { title: item.title, summary: item.summary ?? \"\" };\n return {\n guid: item.guid,\n title,\n link: item.link,\n summary,\n author: item.author,\n pubDate: pubDateToIsoOrNull(item.pubDate),\n };\n }),\n });\n } catch (err) {\n if (err instanceof AuthRequiredError) return c.json({ error: \"需要登录\", code: \"AUTH_REQUIRED\" }, 401);\n if (err instanceof NotFoundError) return c.json({ error: err.message, code: \"NOT_FOUND\" }, 404);\n return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);\n }\n });\n}\n","// /api/scheduler/stats\n\nimport type { Hono } from \"hono\";\nimport * as scheduler from \"../../../scheduler/index.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nexport function registerSchedulerRoutes(app: Hono): void {\n app.get(\"/api/scheduler/stats\", requireAdmin(), (c) => {\n const stats = scheduler.getGroupStats();\n return c.json(stats);\n });\n}\n","// /api/plugins、POST /api/plugins(从模板新建)、/api/plugins/:id(读写字节码插件源文件)\n\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, resolve, sep } from \"node:path\";\nimport type { Hono } from \"hono\";\nimport { getPluginSites } from \"../../../scraper/sources/web/index.js\";\nimport { registeredSources } from \"../../../scraper/sources/index.js\";\nimport { getPluginFilePath } from \"../../../plugins/loader.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport { initSources } from \"../../../scraper/sources/index.js\";\nimport { BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR, PLUGIN_SITE_TEMPLATE_PATH } from \"../../../config/paths.js\";\n\nconst SITE_TEMPLATE_FALLBACK = `/**\n * Site plugin template created from the /plugins page.\n * Plugin protocol: named exports. No export default is required.\n * Parse HTML with ctx.deps.parseHtml; do not import app dependencies directly.\n */\n\n// Predefined fields stay together at the top.\nexport const id = \"__PLUGIN_ID__\";\nexport const name = \"__PLUGIN_ID__\";\nexport const listUrlPattern = __LIST_URL_PATTERN__;\nexport const refreshInterval = \"1day\";\n\nexport async function fetchItems(sourceId, ctx) {\n const { html, finalUrl } = await ctx.fetchHtml(sourceId, {\n waitMs: 2000,\n purify: true,\n });\n void ctx.deps.parseHtml(html);\n void finalUrl;\n return [];\n}\n`;\n\nfunction isValidNewPluginId(id: string): boolean {\n return /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(id) && id !== \"generic\" && id !== \"new\";\n}\n\n/** 与模板中 `listUrlPattern: __LIST_URL_PATTERN__` 注入一致:非空、无换行、长度上限 */\nfunction isValidNewListUrlPattern(pattern: string): boolean {\n if (pattern.length === 0 || pattern.length > 2048) return false;\n if (/[\\r\\n]/.test(pattern)) return false;\n return true;\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isAllowedPluginPath(absPath: string): boolean {\n const f = resolve(absPath);\n for (const root of [BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR]) {\n const r = resolve(root);\n if (f === r || f.startsWith(r + sep)) return true;\n }\n return false;\n}\n\nexport function registerPluginsRoutes(app: Hono): void {\n /** 从模板在 .rssany/plugins/{id}.rssany.js 新建 Site 插件 */\n app.post(\"/api/plugins\", requireAdmin(), async (c) => {\n let body: { id?: string; listUrlPattern?: string };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: \"无效 JSON\" }, 400);\n }\n const id = typeof body.id === \"string\" ? body.id.trim() : \"\";\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\n if (!isValidNewPluginId(id)) {\n return c.json({ error: \"id 须为字母开头,仅含字母数字、下划线、连字符;不能为 generic 或 new\" }, 400);\n }\n const listUrlPatternRaw = typeof body.listUrlPattern === \"string\" ? body.listUrlPattern.trim() : \"\";\n if (!listUrlPatternRaw) {\n return c.json({ error: \"缺少支持的站点(listUrlPattern),例如 https://example.com/*\" }, 400);\n }\n if (!isValidNewListUrlPattern(listUrlPatternRaw)) {\n return c.json({ error: \"支持的站点须为非空字符串,不超过 2048 字符,且不能含换行\" }, 400);\n }\n await mkdir(USER_PLUGINS_DIR, { recursive: true });\n const outPath = join(USER_PLUGINS_DIR, `${id}.rssany.js`);\n if (await fileExists(outPath)) return c.json({ error: \"该 id 已存在同名文件\" }, 409);\n let tpl = SITE_TEMPLATE_FALLBACK;\n try {\n tpl = await readFile(PLUGIN_SITE_TEMPLATE_PATH, \"utf-8\");\n } catch {\n // 使用内置模板\n }\n const patternLiteral = JSON.stringify(listUrlPatternRaw);\n const content = tpl.replace(/__PLUGIN_ID__/g, id).replace(/__LIST_URL_PATTERN__/g, patternLiteral);\n if (!isAllowedPluginPath(outPath)) return c.json({ error: \"路径不允许\" }, 403);\n try {\n await writeFile(outPath, content, \"utf-8\");\n await initSources();\n return c.json({ ok: true, filePath: outPath, id });\n } catch (e) {\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\n }\n });\n\n app.get(\"/api/plugins\", requireAdmin(), (c) => {\n const sites = getPluginSites().map((s) => ({\n kind: \"site\" as const,\n id: s.id,\n name: s.name ?? s.id,\n listUrlPattern: typeof s.listUrlPattern === \"string\" ? s.listUrlPattern : String(s.listUrlPattern),\n hasAuth: !!(s.checkAuth && s.loginUrl),\n }));\n const siteIds = new Set(sites.map((p) => p.id));\n const sources = registeredSources\n .filter((src) => src.id !== \"generic\" && !siteIds.has(src.id))\n .map((src) => ({\n kind: \"source\" as const,\n id: src.id,\n name: src.name ?? src.id,\n listUrlPattern: typeof src.pattern === \"string\" ? src.pattern : String(src.pattern),\n hasAuth: false,\n }));\n return c.json([...sites, ...sources]);\n });\n\n app.get(\"/api/plugins/:id\", requireAdmin(), async (c) => {\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\n const filePath = getPluginFilePath(id);\n if (!filePath) return c.json({ error: \"未找到该插件或无可编辑文件\" }, 404);\n if (!isAllowedPluginPath(filePath)) return c.json({ error: \"路径不允许\" }, 403);\n try {\n const content = await readFile(filePath, \"utf-8\");\n return c.json({ id, filePath, content });\n } catch (e) {\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\n }\n });\n\n app.put(\"/api/plugins/:id\", requireAdmin(), async (c) => {\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\n if (!id) return c.json({ error: \"缺少 id\" }, 400);\n let body: { content?: string };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: \"无效 JSON\" }, 400);\n }\n if (typeof body.content !== \"string\") return c.json({ error: \"需要 content 字符串\" }, 400);\n const filePath = getPluginFilePath(id);\n if (!filePath) return c.json({ error: \"未找到该插件\" }, 404);\n if (!isAllowedPluginPath(filePath)) return c.json({ error: \"路径不允许\" }, 403);\n try {\n await writeFile(filePath, body.content, \"utf-8\");\n await initSources();\n return c.json({ ok: true });\n } catch (e) {\n return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);\n }\n });\n}\n","// /api/pipeline(步骤开关与排序)\n\nimport type { Hono } from \"hono\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport {\n loadPipelineConfig,\n savePipelineConfig,\n DEFAULT_PIPELINE_STEPS,\n PIPELINE_STEP_IDS,\n} from \"../../../pipeline/config.js\";\n\ntype StepInput = { id: string; enabled?: boolean };\n\nfunction parseSteps(rawSteps: unknown[]): Array<{ id: string; enabled: boolean }> {\n return rawSteps\n .filter((s) => s && typeof s === \"object\" && typeof (s as StepInput).id === \"string\")\n .map((s) => {\n const obj = s as StepInput;\n const e: unknown = obj.enabled;\n return {\n id: String(obj.id).trim(),\n enabled: e !== false && e !== 0,\n };\n })\n .filter((s) => s.id.length > 0);\n}\n\nfunction dedupeSteps<T extends { id: string }>(steps: T[]): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const s of steps) {\n if (seen.has(s.id)) continue;\n seen.add(s.id);\n out.push(s);\n }\n return out;\n}\n\nexport function registerPipelineRoutes(app: Hono): void {\n app.get(\"/api/pipeline\", requireAdmin(), async (c) => {\n const config = await loadPipelineConfig();\n return c.json({\n steps: config.steps,\n availableIds: [...PIPELINE_STEP_IDS],\n defaults: DEFAULT_PIPELINE_STEPS,\n });\n });\n\n app.put(\"/api/pipeline\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ steps?: unknown[] }>();\n const rawSteps = Array.isArray(body?.steps) ? body.steps : [];\n const steps = dedupeSteps(parseSteps(rawSteps));\n await savePipelineConfig({ steps });\n return c.json({ ok: true, steps });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n}\n","// /api/feed、/api/events\n\nimport type { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport { onFeedUpdated } from \"../../../core/events/index.js\";\nimport { getAllSources, getAllSubscriptionRefs, resolveRef } from \"../../../scraper/subscription/index.js\";\nimport { getEffectiveItemFields, type ItemTranslationFields } from \"../../../types/feedItem.js\";\nimport { queryItems } from \"../../../db/index.js\";\n\nexport function registerFeedRoutes(app: Hono): void {\n app.get(\"/api/feed\", async (c) => {\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 50), 200);\n const offset = Number(c.req.query(\"offset\") ?? 0);\n const refFilter = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? undefined;\n const lng = c.req.query(\"lng\") ?? undefined;\n const since = c.req.query(\"since\");\n const until = c.req.query(\"until\");\n\n const sources = await getAllSources();\n const refSet = new Set(sources.map((s) => resolveRef(s)).filter(Boolean));\n let sourceRefs: string[];\n if (refFilter) {\n sourceRefs = refSet.has(refFilter) ? [refFilter] : [];\n } else {\n sourceRefs = await getAllSubscriptionRefs();\n }\n\n const labelByRef = new Map(sources.map((s) => [resolveRef(s), s.label ?? resolveRef(s)] as const));\n const sourcesMeta = sources.map((s) => ({\n ref: resolveRef(s),\n label: s.label ?? resolveRef(s),\n }));\n\n const parseDateBound = (value: string | undefined, endExclusive: boolean): Date | undefined => {\n if (!value) return undefined;\n if (value.length === 10) {\n const d = new Date(endExclusive ? `${value}T12:00:00Z` : `${value}T00:00:00.000Z`);\n if (endExclusive) d.setUTCDate(d.getUTCDate() + 1);\n return d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? undefined : d;\n };\n const result = sourceRefs.length > 0\n ? await queryItems({\n sourceUrls: sourceRefs,\n limit: limit + 1,\n offset,\n since: parseDateBound(since ?? undefined, false),\n until: parseDateBound(until ?? undefined, true),\n })\n : { items: [], total: 0 };\n const hasMore = result.items.length > limit;\n const dbItems = hasMore ? result.items.slice(0, limit) : result.items;\n const items = dbItems.map((item) => {\n const refKey = item.source_url ?? \"\";\n const base = {\n ...item,\n sub_id: refKey,\n sub_title: labelByRef.get(refKey) ?? \"\",\n };\n if (!lng) return base;\n const view = {\n title: item.title ?? \"\",\n summary: item.summary ?? \"\",\n content: item.content ?? \"\",\n translations: (item as { translations?: Record<string, ItemTranslationFields> }).translations,\n };\n const eff = getEffectiveItemFields(view, lng);\n return { ...base, title: eff.title, summary: eff.summary, content: eff.content };\n });\n return c.json({ sources: sourcesMeta, items, hasMore });\n });\n\n app.get(\"/api/events\", (c) => {\n return streamSSE(c, async (stream) => {\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\n const off = onFeedUpdated((e) => {\n stream.writeSSE({ data: JSON.stringify({ type: \"feed:updated\", sourceUrl: e.sourceUrl, newCount: e.newCount }) }).catch(() => {});\n });\n const heartbeat = setInterval(() => {\n stream.writeSSE({ data: \"\", event: \"ping\" }).catch(() => {});\n }, 25000);\n stream.onAbort(() => {\n off();\n clearInterval(heartbeat);\n });\n await new Promise<void>((resolve) => stream.onAbort(resolve));\n });\n });\n}\n","// /api/items、/api/items/pending-push、/api/items/mark-pushed、/api/items/by-source(须在 :id 之前注册)、/api/items/:id\n\n// /api/user/items(JWT 认证用户)\n\n\n\nimport type { Hono } from \"hono\";\n\nimport { getAllSubscriptionRefs } from \"../../../scraper/subscription/index.js\";\n\nimport { getEffectiveItemFields, type ItemTranslationFields } from \"../../../types/feedItem.js\";\n\nimport { queryItems, getPendingPushItems, markPushed, deleteItem, deleteItemsBySourceUrl } from \"../../../db/index.js\";\n\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nimport { canonicalHttpSourceRef } from \"../../../utils/httpSourceRef.js\";\n\n\n\nfunction parseSubscribedFlag(v: string | undefined): boolean {\n\n return v === \"1\" || v === \"true\" || v === \"yes\";\n\n}\n\n\n\nexport function registerItemsRoutes(app: Hono): void {\n\n app.get(\"/api/items/pending-push\", async (c) => {\n\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 100), 500);\n\n const items = await getPendingPushItems(limit);\n\n return c.json({ items, count: items.length });\n\n });\n\n\n\n app.post(\"/api/items/mark-pushed\", async (c) => {\n\n try {\n\n const { ids } = await c.req.json<{ ids?: string[] }>();\n\n if (!Array.isArray(ids) || ids.length === 0) return c.json({ ok: false, message: \"ids 不能为空\" }, 400);\n\n await markPushed(ids);\n\n return c.json({ ok: true, count: ids.length });\n\n } catch (err) {\n\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n\n }\n\n });\n\n\n\n /** 清空指定信源(source_url)下所有已入库条目 — 必须早于 /api/items/:id,否则 \"by-source\" 会被当成 id */\n\n app.delete(\"/api/items/by-source\", requireAdmin(), async (c) => {\n\n const sourceUrl = (c.req.query(\"source_url\") ?? \"\").trim();\n\n if (!sourceUrl) return c.json({ ok: false, message: \"source_url 不能为空\" }, 400);\n\n const deleted = await deleteItemsBySourceUrl(sourceUrl);\n\n return c.json({ ok: true, deleted });\n\n });\n\n\n\n app.delete(\"/api/items/:id\", async (c) => {\n\n const id = decodeURIComponent(c.req.param(\"id\") ?? \"\").trim();\n\n if (!id) return c.json({ ok: false, message: \"id 不能为空\" }, 400);\n\n const deleted = await deleteItem(id);\n\n if (!deleted) return c.json({ ok: false, message: \"条目不存在或已删除\" }, 404);\n\n return c.json({ ok: true });\n\n });\n\n\n\n app.get(\"/api/items\", async (c) => {\n\n const ref = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? undefined;\n\n const subscribed = parseSubscribedFlag(c.req.query(\"subscribed\"));\n\n const author = c.req.query(\"author\") ?? undefined;\n\n const q = c.req.query(\"q\") ?? undefined;\n\n const tagsParam = c.req.query(\"tags\") ?? undefined;\n\n const tags = tagsParam ? tagsParam.split(\",\").map((t) => t.trim()).filter(Boolean) : undefined;\n\n // days=0 或不传 days 表示不按天数筛选;days=N 表示最近 N 天;since/until 仍可单独传\n\n const daysParam = c.req.query(\"days\");\n\n const sinceParam = c.req.query(\"since\") ?? undefined;\n\n const untilParam = c.req.query(\"until\") ?? undefined;\n\n let since: Date | undefined;\n\n let until: Date | undefined;\n\n const daysNum = daysParam !== undefined && daysParam !== \"\" ? Number(daysParam) : NaN;\n\n if (Number.isFinite(daysNum) && daysNum > 0) {\n\n const n = Math.max(1, Math.min(365, Math.floor(daysNum)));\n\n const now = new Date();\n\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n\n const todayEnd = new Date(todayStart);\n\n todayEnd.setDate(todayEnd.getDate() + 1);\n\n since = new Date(todayStart);\n\n since.setDate(since.getDate() - (n - 1));\n\n until = todayEnd;\n\n } else {\n\n since = sinceParam ? new Date(sinceParam) : undefined;\n\n if (untilParam) {\n\n if (untilParam.length === 10) {\n\n const d = new Date(untilParam + \"T12:00:00Z\");\n\n d.setUTCDate(d.getUTCDate() + 1);\n\n until = d;\n\n } else {\n\n until = new Date(untilParam);\n\n }\n\n }\n\n }\n\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 200), 500);\n\n const offset = Number(c.req.query(\"offset\") ?? 0);\n\n const lng = c.req.query(\"lng\") ?? undefined;\n\n\n\n let sourceUrls: string[] | undefined;\n\n let effectiveSourceUrl: string | undefined;\n\n if (ref) {\n\n effectiveSourceUrl = ref;\n\n sourceUrls = undefined;\n\n } else if (subscribed) {\n\n const refs = await getAllSubscriptionRefs();\n\n sourceUrls = refs.length > 0 ? refs : [];\n\n }\n\n\n\n if (!effectiveSourceUrl && sourceUrls?.length === 0) {\n\n return c.json({ items: [], total: 0, hasMore: false });\n\n }\n\n\n const result = await queryItems({\n\n sourceUrl: sourceUrls ? undefined : (effectiveSourceUrl ? canonicalHttpSourceRef(effectiveSourceUrl) : undefined),\n\n sourceUrls,\n\n author,\n\n q,\n\n tags,\n\n since,\n\n until,\n\n limit,\n\n offset,\n\n });\n\n const items =\n\n lng && result.items.length > 0\n\n ? result.items.map((it) => {\n\n const view = {\n\n title: it.title ?? \"\",\n\n summary: it.summary ?? \"\",\n\n content: it.content ?? \"\",\n\n translations: (it as { translations?: Record<string, ItemTranslationFields> }).translations,\n\n };\n\n const eff = getEffectiveItemFields(view, lng);\n\n return { ...it, title: eff.title, summary: eff.summary, content: eff.content };\n\n })\n\n : result.items;\n\n const hasMore = offset + items.length < result.total;\n\n return c.json({ items, total: result.total, hasMore });\n\n });\n\n}\n\n","// /api/logs\n\nimport type { Hono } from \"hono\";\nimport { clearAllLogs, queryLogs } from \"../../../db/index.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nexport function registerLogsRoutes(app: Hono): void {\n app.delete(\"/api/logs\", requireAdmin(), async (c) => {\n const deleted = await clearAllLogs();\n return c.json({ ok: true, deleted });\n });\n\n app.get(\"/api/logs\", requireAdmin(), async (c) => {\n const levelParam = c.req.query(\"level\");\n const level = levelParam === \"error\" || levelParam === \"warn\" || levelParam === \"info\" || levelParam === \"debug\" ? levelParam : undefined;\n const categoryRaw = c.req.query(\"category\");\n const category = typeof categoryRaw === \"string\" && categoryRaw.trim() ? categoryRaw.trim() : undefined;\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 100), 200);\n const offset = Number(c.req.query(\"offset\") ?? 0);\n const sinceParam = c.req.query(\"since\");\n const since = sinceParam ? new Date(sinceParam) : undefined;\n const result = await queryLogs({ level, category, limit, offset, since });\n return c.json(result);\n });\n}\n","// /api/admin/verify, /api/admin/integrity-check\n\nimport type { Hono } from \"hono\";\nimport { runIntegrityCheck } from \"../../../db/index.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nexport function registerAdminApiRoutes(app: Hono): void {\n app.get(\"/api/admin/verify\", requireAdmin(), async (c) => {\n return c.json({ ok: true });\n });\n\n /** SQLite 主库 PRAGMA integrity_check */\n app.get(\"/api/admin/integrity-check\", requireAdmin(), async (c) => {\n try {\n const result = await runIntegrityCheck();\n const ok = result === \"ok\";\n return c.json({ ok, result });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, result: `error: ${message}` }, 500);\n }\n });\n}\n","// /api/sources/stats、/api/sources/raw、/api/sources/plugin-match(admin)\n\nimport type { Hono } from \"hono\";\nimport { getSourceStats } from \"../../../db/index.js\";\nimport { getSource } from \"../../../scraper/sources/index.js\";\nimport { getPluginSites } from \"../../../scraper/sources/web/index.js\";\nimport { getSourcesRaw, saveSourcesFile, getEffectiveProxyForListUrl } from \"../../../scraper/subscription/index.js\";\nimport { openBrowserPage, resolveProxy } from \"../../../scraper/sources/web/fetcher/index.js\";\nimport { CACHE_DIR } from \"../../../config/paths.js\";\nimport type { SourceType } from \"../../../scraper/subscription/types.js\";\nimport type { RefreshInterval } from \"../../../utils/refreshInterval.js\";\nimport { VALID_INTERVALS } from \"../../../utils/refreshInterval.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport { canonicalHttpSourceRef } from \"../../../utils/httpSourceRef.js\";\n\nexport function registerSourcesRoutes(app: Hono): void {\n app.get(\"/api/sources/stats\", requireAdmin(), async (c) => {\n const stats = await getSourceStats();\n return c.json(stats);\n });\n\n app.post(\"/api/sources/plugin-match\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ refs?: string[] }>();\n const refs = Array.isArray(body?.refs) ? body.refs : [];\n const pluginIds = new Set(getPluginSites().map((s) => s.id));\n const result: Record<string, string | null> = {};\n for (const ref of refs) {\n const source = getSource(ref);\n result[ref] = pluginIds.has(source.id) ? source.id : null;\n }\n return c.json(result);\n } catch {\n return c.json({});\n }\n });\n\n /**\n * 在有头 Chrome 中打开 URL:与抓取共用 CACHE_DIR/browser_data、代理优先级与 /auth/open 一致。\n * 浏览器在本机服务端弹出,非用户默认浏览器。\n */\n app.post(\"/api/sources/open-browser\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ url?: string }>();\n const raw = typeof body?.url === \"string\" ? body.url.trim() : \"\";\n if (!raw) return c.json({ ok: false, message: \"缺少 url\" }, 400);\n const lower = raw.toLowerCase();\n if (!lower.startsWith(\"http://\") && !lower.startsWith(\"https://\")) {\n return c.json({ ok: false, message: \"仅支持 http(s) URL\" }, 400);\n }\n const url = raw;\n const source = getSource(url);\n const merged = await getEffectiveProxyForListUrl(url, source);\n const proxy = resolveProxy({ proxy: merged });\n void openBrowserPage(url, CACHE_DIR, { proxy }).catch(() => {});\n return c.json({ ok: true, message: \"已在爬虫浏览器中打开\" });\n } catch {\n return c.json({ ok: false, message: \"请求体无效\" }, 400);\n }\n });\n\n app.get(\"/api/sources/raw\", requireAdmin(), async (c) => {\n try {\n const raw = await getSourcesRaw();\n return c.text(raw, 200, { \"Content-Type\": \"application/json; charset=utf-8\" });\n } catch {\n return c.text(JSON.stringify({ sources: [] }, null, 2), 200, { \"Content-Type\": \"application/json; charset=utf-8\" });\n }\n });\n\n app.put(\"/api/sources/raw\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ sources?: unknown[] }>();\n const list = Array.isArray(body?.sources) ? body.sources : [];\n const sources: { ref: string; type?: SourceType; label?: string; description?: string; refresh?: RefreshInterval; proxy?: string; weight?: number }[] = list\n .filter((s): s is Record<string, unknown> => s != null && typeof s === \"object\" && typeof (s as { ref?: unknown }).ref === \"string\")\n .map((s) => {\n const t = (s as { type?: string }).type;\n const type: SourceType | undefined =\n t === \"web\" || t === \"rss\" || t === \"email\" ? t : undefined;\n const r = (s as { refresh?: string }).refresh;\n const refresh: RefreshInterval | undefined =\n r && VALID_INTERVALS.includes(r as RefreshInterval) ? (r as RefreshInterval) : undefined;\n const w = (s as { weight?: unknown }).weight;\n const weight: number | undefined = typeof w === \"number\" ? w : undefined;\n return {\n ref: canonicalHttpSourceRef(String((s as { ref: string }).ref)),\n type,\n label: (s as { label?: string }).label,\n description: (s as { description?: string }).description,\n refresh,\n proxy: (s as { proxy?: string }).proxy,\n weight,\n };\n });\n await saveSourcesFile(sources);\n return c.json({ ok: true });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n}\n","// /api/tags — 系统标签(pipeline tagger);原 /api/topics 任务接口已迁至 /api/agent-tasks\n\nimport type { Hono } from \"hono\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport {\n getSuggestedTags,\n getSystemTags,\n getSystemTagStats,\n saveSystemTagsToFile,\n removeTagFromAllItems,\n} from \"../../../db/index.js\";\n\nexport function registerTopicsRoutes(app: Hono): void {\n /** 系统标签:来自 .rssany/tags.json,供 pipeline tagger 使用 */\n app.get(\"/api/tags\", async (c) => {\n const [tags, stats, suggested] = await Promise.all([\n getSystemTags(),\n getSystemTagStats(),\n getSuggestedTags(),\n ]);\n return c.json({\n tags,\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n });\n });\n\n app.put(\"/api/tags\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ tags?: string[] }>();\n const list = Array.isArray(body?.tags) ? body.tags : [];\n await saveSystemTagsToFile(list);\n const [tags, stats, suggested] = await Promise.all([\n getSystemTags(),\n getSystemTagStats(),\n getSuggestedTags(),\n ]);\n return c.json({\n ok: true,\n tags,\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n\n /** 从所有条目的 tags 中移除指定标签 */\n app.post(\"/api/tags/remove-from-items\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{ tag?: string }>();\n const tag = typeof body?.tag === \"string\" ? body.tag.trim() : \"\";\n if (!tag) return c.json({ ok: false, message: \"tag 参数缺失\" }, 400);\n const count = await removeTagFromAllItems(tag);\n const [tags, stats, suggested] = await Promise.all([\n getSystemTags(),\n getSystemTagStats(),\n getSuggestedTags(),\n ]);\n return c.json({\n ok: true,\n removedCount: count,\n tags,\n stats: stats.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n suggestedTags: suggested.map((s) => ({ name: s.name, count: s.count, hotness: s.hotness })),\n });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n}\n","// /api/deliver、/api/deliver/test — Gateway 基址;固定 POST {gateway}/items、{gateway}/sources,测试 POST {gateway}/test\n\nimport type { Hono } from \"hono\";\nimport type { FeedItem } from \"../../../types/feedItem.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport { getDeliverConfig, saveDeliverConfig } from \"../../../config/deliver.js\";\nimport { getSourcesRaw } from \"../../../scraper/subscription/index.js\";\nimport { feedItemsToPayload, postDeliverGatewayTest } from \"../../../deliver/post.js\";\n\nexport function registerDeliverRoutes(app: Hono): void {\n app.get(\"/api/deliver\", requireAdmin(), async (c) => {\n const { gateway, token } = await getDeliverConfig();\n return c.json({ gateway, token });\n });\n\n app.put(\"/api/deliver\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{\n gateway?: string;\n token?: string;\n /** 旧版:完整 …/items URL,将迁移为 gateway 基址 */\n url?: string;\n }>();\n const prev = await getDeliverConfig();\n const explicitGateway = body != null && \"gateway\" in body;\n const explicitUrl = body != null && \"url\" in body;\n const explicitToken = body != null && \"token\" in body;\n let gateway = typeof body?.gateway === \"string\" ? body.gateway.trim() : \"\";\n if (!gateway && typeof body?.url === \"string\") {\n gateway = body.url\n .trim()\n .replace(/\\/items\\/?$/i, \"\")\n .replace(/\\/+$/, \"\");\n }\n if (!explicitGateway && !explicitUrl) {\n gateway = prev.gateway;\n }\n let token = typeof body?.token === \"string\" ? body.token.trim() : \"\";\n if (!explicitToken) {\n token = prev.token;\n }\n await saveDeliverConfig({ gateway, token });\n return c.json({ ok: true, gateway, token });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n\n /** 合并测试:仅 POST 到 {gateway}/test,体含示例 items 批次与当前 sources 文档 */\n app.post(\"/api/deliver/test\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{\n gateway?: string;\n token?: string;\n url?: string;\n }>();\n const prev = await getDeliverConfig();\n let gateway = typeof body?.gateway === \"string\" ? body.gateway.trim() : \"\";\n if (!gateway && typeof body?.url === \"string\") {\n gateway = body.url\n .trim()\n .replace(/\\/items\\/?$/i, \"\")\n .replace(/\\/+$/, \"\");\n }\n if (!gateway) gateway = prev.gateway;\n const token =\n typeof body?.token === \"string\" ? body.token.trim() : prev.token;\n if (!gateway.trim()) return c.json({ ok: false, message: \"gateway 不能为空\" }, 400);\n\n const now = Date.now();\n const sample: FeedItem = {\n guid: \"deliver-test-\" + now,\n title: \"投递连通性测试\",\n link: \"https://example.com/rssany-deliver-test\",\n pubDate: new Date(),\n summary: \"若下游 /test 收到此条,说明 Gateway 可用。\",\n sourceRef: \"rssany-deliver-test\",\n };\n const raw = await getSourcesRaw();\n let sourcesDoc: unknown;\n try {\n sourcesDoc = JSON.parse(raw) as unknown;\n } catch {\n sourcesDoc = { sources: [] };\n }\n\n const payload = {\n rssanyConnectivityTest: true,\n items: {\n sourceRef: \"rssany-deliver-test\",\n items: feedItemsToPayload([sample]),\n },\n sources: sourcesDoc,\n };\n await postDeliverGatewayTest(gateway.trim(), payload, { bearerToken: token || undefined });\n return c.json({ ok: true });\n } catch (err) {\n return c.json({ ok: false, message: err instanceof Error ? err.message : String(err) }, 400);\n }\n });\n}\n","// LLM:读写 .rssany/config.json 的 llm 块;与 OPENAI_* 环境变量合并见 core/llmConfig.ts\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { CONFIG_PATH } from \"./paths.js\";\nimport { invalidateLLMConfigCache } from \"../core/llmConfig.js\";\n\nexport interface LlmFileConfig {\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n}\n\nfunction trimOrUndef(s: unknown): string | undefined {\n if (typeof s !== \"string\") return undefined;\n const t = s.trim();\n return t.length > 0 ? t : undefined;\n}\n\n/** 读取 config.json 中的 llm 块(无则空) */\nexport async function readLlmFileConfig(): Promise<LlmFileConfig> {\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const j = JSON.parse(raw) as { llm?: unknown };\n const llm = j?.llm;\n if (!llm || typeof llm !== \"object\") return {};\n const o = llm as Record<string, unknown>;\n return {\n apiKey: typeof o.apiKey === \"string\" ? o.apiKey : undefined,\n baseUrl: trimOrUndef(o.baseUrl),\n model: trimOrUndef(o.model),\n };\n } catch {\n return {};\n }\n}\n\nexport interface SaveLlmSettingsInput {\n baseUrl: string;\n model: string;\n apiKey?: string;\n}\n\n/** 合并写入 config.json 的 llm 块,不覆盖其它顶层字段 */\nexport async function saveLlmSettings(input: SaveLlmSettingsInput): Promise<void> {\n let root: Record<string, unknown> = {};\n try {\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n root = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // 新建\n }\n const prev = await readLlmFileConfig();\n const next: Record<string, unknown> = {\n baseUrl: input.baseUrl.trim(),\n model: input.model.trim(),\n };\n\n const newKey = typeof input.apiKey === \"string\" && input.apiKey.length > 0 ? input.apiKey : undefined;\n if (newKey) {\n next.apiKey = newKey;\n } else if (prev.apiKey) {\n next.apiKey = prev.apiKey;\n }\n\n root.llm = next;\n await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + \"\\n\", \"utf-8\");\n invalidateLLMConfigCache();\n}\n","// /api/llm — 读写 LLM 配置(config.json.llm);GET 不返回完整 Key\n\nimport type { Hono } from \"hono\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport { getLLMConfig } from \"../../../core/llmConfig.js\";\nimport { chatText } from \"../../../core/llm.js\";\nimport {readLlmFileConfig,saveLlmSettings} from \"../../../config/llmSettings.js\";\n\n\nexport function registerLlmRoutes(app: Hono): void {\n app.get(\"/api/llm\", requireAdmin(), async (c) => {\n const resolved = getLLMConfig();\n const file = await readLlmFileConfig();\n const hasApiKey = !!resolved.apiKey;\n const apiKeyInFile = !!(file.apiKey && file.apiKey.length > 0);\n return c.json({\n baseUrl: resolved.baseUrl,\n model: resolved.model,\n hasApiKey,\n apiKeyInFile,\n });\n });\n\n \n app.put(\"/api/llm\", requireAdmin(), async (c) => {\n try {\n const body = await c.req.json<{\n baseUrl?: unknown;\n model?: unknown;\n apiKey?: unknown;\n }>();\n const baseUrl = typeof body.baseUrl === \"string\" ? body.baseUrl : \"\";\n const model = typeof body.model === \"string\" ? body.model : \"\";\n const apiKey = typeof body.apiKey === \"string\" ? body.apiKey : undefined;\n await saveLlmSettings({\n baseUrl,\n model,\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n const resolved = getLLMConfig();\n const file = await readLlmFileConfig();\n return c.json({\n ok: true,\n baseUrl: resolved.baseUrl,\n model: resolved.model,\n hasApiKey: !!resolved.apiKey,\n apiKeyInFile: !!(file.apiKey && file.apiKey.length > 0),\n });\n } catch (err) {\n return c.json(\n { ok: false, message: err instanceof Error ? err.message : String(err) },\n 400,\n );\n }\n });\n\n app.post(\"/api/llm/test\", requireAdmin(), async (c) => {\n const t0 = Date.now();\n try {\n const cfg = getLLMConfig();\n if (!cfg.apiKey) {\n return c.json({ ok: false, message: \"未配置 API Key(请在界面或 OPENAI_API_KEY 中设置)\" }, 400);\n }\n const reply = await chatText(\"Reply with exactly the single word: ok\", undefined, {\n maxTokens: 32768,\n debugLabel: \"llmSettingsTest\",\n });\n return c.json({ ok: true, reply });\n } catch (err) {\n const ms = Date.now() - t0;\n const message = err instanceof Error ? err.message : String(err);\n console.error(\"[llm/test] fail\", { ms, message });\n return c.json({ ok: false, message }, 400);\n }\n });\n}\n","// GET/PUT /api/proxy — config.json globalProxy / proxyList (admin)\n\nimport type { Hono } from \"hono\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\nimport { readProxySettingsFromConfig, saveProxySettingsToConfig } from \"../../../config/globalProxy.js\";\n\nexport function registerProxySettingsRoutes(app: Hono): void {\n app.get(\"/api/proxy\", requireAdmin(), async (c) => {\n return c.json(await readProxySettingsFromConfig());\n });\n\n app.put(\"/api/proxy\", requireAdmin(), async (c) => {\n try {\n const body = (await c.req.json().catch(() => ({}))) as {\n globalProxy?: unknown;\n proxyList?: unknown;\n };\n const globalProxy = typeof body.globalProxy === \"string\" ? body.globalProxy : \"\";\n const proxyList = Array.isArray(body.proxyList)\n ? body.proxyList.filter((v): v is string => typeof v === \"string\")\n : [];\n\n await saveProxySettingsToConfig({ globalProxy, proxyList });\n return c.json({ ok: true, ...(await readProxySettingsFromConfig()) });\n } catch (err) {\n return c.json(\n { ok: false, message: err instanceof Error ? err.message : String(err) },\n 400,\n );\n }\n });\n}\n","// 后端任务:提交后立即返回 taskId,任务在后台执行,前端轮询状态\n\nconst tasks = new Map<string, { id: string; status: string; result?: unknown; error?: string; createdAt: number; updatedAt: number }>();\nlet idCounter = 0;\n\nfunction nextId(): string {\n idCounter += 1;\n return `t_${Date.now().toString(36)}_${idCounter}`;\n}\n\nexport function createTask(): string {\n const id = nextId();\n const now = Date.now();\n tasks.set(id, { id, status: \"pending\", createdAt: now, updatedAt: now });\n return id;\n}\n\nexport function getTask(id: string) {\n return tasks.get(id) ?? null;\n}\n\nexport function setTaskRunning(id: string): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"running\";\n t.updatedAt = Date.now();\n }\n}\n\nexport function setTaskDone<T>(id: string, result: T): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"done\";\n t.result = result as unknown;\n t.updatedAt = Date.now();\n }\n}\n\nexport function setTaskError(id: string, error: string): void {\n const t = tasks.get(id);\n if (t) {\n t.status = \"error\";\n t.error = error;\n t.updatedAt = Date.now();\n }\n}\n\n/** 清理 1 小时前的已完成/失败任务 */\nexport function pruneTasks(): void {\n const cutoff = Date.now() - 60 * 60 * 1000;\n for (const [id, t] of tasks) {\n if ((t.status === \"done\" || t.status === \"error\") && t.updatedAt < cutoff) {\n tasks.delete(id);\n }\n }\n}\n","// 任务 API:POST /api/tasks 提交拉取信源,GET /api/tasks/:id 轮询\n\nimport type { Hono } from \"hono\";\nimport * as taskStore from \"../../../tasks/index.js\";\nimport * as scheduler from \"../../../scheduler/index.js\";\nimport { CACHE_DIR } from \"../../../config/paths.js\";\nimport { crawlSource } from \"../../../feeder/index.js\";\nimport { SOURCES_GROUP } from \"../../../scraper/scheduler/index.js\";\nimport { requireAdmin } from \"../../../auth/middleware.js\";\n\nexport function registerTasksRoutes(app: Hono): void {\n app.get(\"/api/tasks/:id\", (c) => {\n const id = c.req.param(\"id\") ?? \"\";\n const task = taskStore.getTask(id);\n if (!task) return c.json({ error: \"任务不存在\" }, 404);\n return c.json(task);\n });\n\n app.post(\"/api/tasks\", requireAdmin(), async (c) => {\n try {\n const body = (await c.req.json().catch(() => ({}))) as { type?: string; ref?: string };\n const type = body.type ?? \"\";\n if (type === \"source-pull\") {\n const ref = typeof body.ref === \"string\" ? body.ref.trim() : \"\";\n if (!ref) return c.json({ error: \"ref 不能为空\" }, 400);\n const taskId = taskStore.createTask();\n scheduler.schedule(SOURCES_GROUP, taskId, async () => {\n taskStore.setTaskRunning(taskId);\n try {\n await crawlSource(ref, { cacheDir: CACHE_DIR, force: true });\n taskStore.setTaskDone(taskId, { ok: true });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n taskStore.setTaskError(taskId, msg);\n throw err;\n }\n }, { priority: true }).catch(() => {});\n return c.json({ taskId });\n }\n return c.json({ error: `未知任务类型: ${type}` }, 400);\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);\n }\n });\n}\n","// GET /api/feed-favicon?domain=example.com — 信源列表站点图标;磁盘缓存在 CACHE_DIR/feed-favicons。\n//\n// 合法 domain 时始终返回 200 + 可用图片:上游全部失败则回退为字母 SVG(按域名稳定着色)。\n// 磁盘与 HTTP 缓存均为 3 天(mtime / max-age),过期后重新抓取。\n//\n// 多路上游并行竞速(Promise.any)。默认不含 Google s2(国内访问差):设 FAVICON_INCLUDE_GOOGLE=1 可启用。\n// 境外聚合源(DDG / icon.horse / unavatar)在国内网络可能慢或失败;若仅需直连站点图标,设 FAVICON_THIRD_PARTY=0。\n// 解析首页 HTML 中 link[rel~=icon] 等(尊重 <base href>);设 FAVICON_SKIP_HTML=1 可关闭。\n// 同域名 in-flight 合并。\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, writeFile, mkdir, unlink, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse } from \"node-html-parser\";\nimport type { Hono } from \"hono\";\nimport { CACHE_DIR } from \"../../../config/paths.js\";\n\nconst CACHE_SUBDIR = \"feed-favicons\";\nconst CACHE_KEY_PREFIX = \"feed-favicon:v1:\";\n/** 磁盘与 CDN/浏览器缓存:3 天 */\nconst CACHE_MAX_AGE_SEC = 3 * 24 * 60 * 60;\nconst CACHE_MAX_AGE_MS = CACHE_MAX_AGE_SEC * 1000;\nconst CACHE_CONTROL = `public, max-age=${CACHE_MAX_AGE_SEC}`;\n\nconst FETCH_TIMEOUT_MS = 6_000;\nconst MAX_ICON_BYTES = 2 * 1024 * 1024;\nconst MAX_HTML_BYTES = 512 * 1024;\n\nconst inflightByDomain = new Map<string, Promise<{ buf: Buffer; mime: string }>>();\n\nconst MAX_DOMAIN_LEN = 253;\n\nfunction isPlausibleHostname(s: string): boolean {\n if (s.length === 0 || s.length > MAX_DOMAIN_LEN) return false;\n return /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i.test(s);\n}\n\nfunction cacheFilePath(domainKey: string): string {\n const h = createHash(\"sha256\").update(CACHE_KEY_PREFIX + domainKey.toLowerCase()).digest(\"hex\");\n return join(CACHE_DIR, CACHE_SUBDIR, h);\n}\n\n/** 直连目标站常见图标路径(不依赖境外 CDN,适合国内部署优先尝试) */\nfunction originFaviconUrls(domain: string): string[] {\n const d = domain.toLowerCase();\n const hosts: string[] = [`https://${d}`];\n if (d.startsWith(\"www.\")) {\n const bare = d.slice(4);\n if (bare) hosts.push(`https://${bare}`);\n } else {\n hosts.push(`https://www.${d}`);\n }\n const paths = [\"/favicon.ico\", \"/favicon.png\", \"/apple-touch-icon.png\"];\n const urls: string[] = [];\n for (const base of [...new Set(hosts)]) {\n for (const p of paths) {\n urls.push(`${base}${p}`);\n }\n }\n return urls;\n}\n\nfunction homepageUrlsForDomain(domain: string): string[] {\n const d = domain.toLowerCase();\n const urls = [`https://${d}/`];\n if (d.startsWith(\"www.\")) {\n const bare = d.slice(4);\n if (bare) urls.push(`https://${bare}/`);\n } else {\n urls.push(`https://www.${d}/`);\n }\n return [...new Set(urls)];\n}\n\nfunction isIconLinkRel(rel: string): boolean {\n const tokens = rel\n .toLowerCase()\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n if (tokens.some((x) => x === \"mask-icon\")) return true;\n if (tokens.some((x) => x === \"apple-touch-icon\" || x === \"apple-touch-icon-precomposed\")) return true;\n if (tokens.includes(\"shortcut\") && tokens.includes(\"icon\")) return true;\n return tokens.includes(\"icon\");\n}\n\n/** 从 HTML 提取 <link rel=\"icon\" …> 等 href,按文档顺序去重 */\nfunction parseLinkIconHrefs(html: string, pageUrl: string): string[] {\n const root = parse(html, { lowerCaseTagName: true });\n let base = pageUrl;\n const baseEl = root.querySelector(\"base[href]\");\n if (baseEl) {\n const bh = baseEl.getAttribute(\"href\")?.trim();\n if (bh) {\n try {\n base = new URL(bh, pageUrl).href;\n } catch {\n /* keep pageUrl */\n }\n }\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const el of root.querySelectorAll(\"link[href]\")) {\n const rel = el.getAttribute(\"rel\") ?? \"\";\n if (!isIconLinkRel(rel)) continue;\n const href = el.getAttribute(\"href\")?.trim();\n if (!href || href.startsWith(\"data:\") || href.startsWith(\"blob:\")) continue;\n try {\n const abs = new URL(href, base).href;\n if ((abs.startsWith(\"http:\") || abs.startsWith(\"https:\")) && !seen.has(abs)) {\n seen.add(abs);\n out.push(abs);\n }\n } catch {\n /* skip */\n }\n }\n return out;\n}\n\nasync function fetchHtmlPage(url: string): Promise<string | null> {\n try {\n const upstream = await fetch(url, {\n redirect: \"follow\",\n headers: {\n Accept: \"text/html,application/xhtml+xml;q=0.9,*/*;q=0.1\",\n \"User-Agent\": \"Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon\",\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (!upstream.ok) return null;\n const ab = await upstream.arrayBuffer();\n const buf = Buffer.from(ab);\n const slice = buf.subarray(0, Math.min(buf.length, MAX_HTML_BYTES));\n return slice.toString(\"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** 抓取首页 HTML,解析 link 图标;按候选首页顺序直到得到至少一条 href */\nasync function discoverIconUrlsFromHomepage(domain: string): Promise<string[]> {\n if (process.env.FAVICON_SKIP_HTML === \"1\" || process.env.FAVICON_SKIP_HTML === \"true\") {\n return [];\n }\n for (const pageUrl of homepageUrlsForDomain(domain)) {\n const html = await fetchHtmlPage(pageUrl);\n if (!html) continue;\n const hrefs = parseLinkIconHrefs(html, pageUrl);\n if (hrefs.length > 0) return hrefs;\n }\n return [];\n}\n\nfunction duckduckgoFaviconUrl(domain: string): string {\n return `https://icons.duckduckgo.com/ip3/${domain}.ico`;\n}\n\nfunction iconHorseUrl(domain: string): string {\n return `https://icon.horse/icon/${encodeURIComponent(domain)}`;\n}\n\nfunction unavatarUrl(domain: string): string {\n return `https://unavatar.io/${encodeURIComponent(domain)}`;\n}\n\nfunction googleFaviconUrl(domain: string): string {\n return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=64`;\n}\n\nfunction letterCharFromDomain(domain: string): string {\n const d = domain.toLowerCase().replace(/^www\\./, \"\");\n const m = d.match(/[a-z0-9]/);\n return m ? m[0]!.toUpperCase() : \"?\";\n}\n\nfunction hueFromDomain(domain: string): number {\n const h = createHash(\"sha256\").update(domain.toLowerCase()).digest();\n return ((h[0]! << 8) | h[1]!) % 360;\n}\n\nfunction escapeXmlText(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\n/** 上游全部失败时的稳定回退:圆角方块 + 首字母 */\nfunction letterAvatarSvg(domain: string): Buffer {\n const letter = escapeXmlText(letterCharFromDomain(domain));\n const hue = hueFromDomain(domain);\n const bg = `hsl(${hue} 42% 44%)`;\n const svg = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" rx=\"12\" fill=\"${bg}\"/>\n <text x=\"32\" y=\"32\" dominant-baseline=\"central\" text-anchor=\"middle\" fill=\"#ffffff\" font-family=\"system-ui,Segoe UI,Helvetica,sans-serif\" font-size=\"28\" font-weight=\"600\">${letter}</text>\n</svg>`;\n return Buffer.from(svg.trim(), \"utf-8\");\n}\n\nfunction letterAvatarForDomain(domain: string): { buf: Buffer; mime: string } {\n return { buf: letterAvatarSvg(domain), mime: \"image/svg+xml\" };\n}\n\nfunction isEnoent(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as NodeJS.ErrnoException).code === \"ENOENT\";\n}\n\nfunction sniffImageMime(buf: Buffer): string | null {\n if (buf.length < 4) return null;\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return \"image/png\";\n if (buf.length >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) return \"image/gif\";\n if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return \"image/jpeg\";\n if (\n buf.length >= 12 &&\n buf.subarray(0, 4).toString(\"ascii\") === \"RIFF\" &&\n buf.subarray(8, 12).toString(\"ascii\") === \"WEBP\"\n ) {\n return \"image/webp\";\n }\n if (buf.length >= 6 && buf.readUInt16LE(0) === 0 && (buf[2] === 1 || buf[2] === 2) && buf[3] === 0) {\n return \"image/x-icon\";\n }\n const head = buf.subarray(0, Math.min(256, buf.length)).toString(\"utf-8\").trimStart();\n if (head.startsWith(\"<svg\") || head.startsWith(\"<?xml\")) return \"image/svg+xml\";\n return null;\n}\n\nconst IMAGE_CT_PREFIX = \"image/\";\n\nfunction mimeFromFetch(ct: string | null): string | null {\n if (!ct) return null;\n const base = ct.split(\";\")[0].trim().toLowerCase();\n return base.startsWith(IMAGE_CT_PREFIX) ? base : null;\n}\n\nfunction resolveImageMime(buf: Buffer, ct: string | null): string | null {\n return sniffImageMime(buf) ?? mimeFromFetch(ct);\n}\n\nasync function fetchIconCandidate(url: string): Promise<{ buf: Buffer; ct: string | null } | null> {\n let upstream: Response;\n try {\n upstream = await fetch(url, {\n redirect: \"follow\",\n headers: {\n Accept: \"image/avif,image/webp,image/apng,image/*,*/*;q=0.8\",\n \"User-Agent\": \"Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon\",\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n } catch {\n return null;\n }\n if (!upstream.ok) return null;\n const ab = await upstream.arrayBuffer();\n const buf = Buffer.from(ab);\n if (buf.length === 0 || buf.length > MAX_ICON_BYTES) return null;\n return { buf, ct: upstream.headers.get(\"content-type\") };\n}\n\nfunction isValidIcon(got: { buf: Buffer; ct: string | null } | null): got is { buf: Buffer; ct: string | null } {\n if (!got) return false;\n const mime = resolveImageMime(got.buf, got.ct);\n return !!(mime && mime.startsWith(IMAGE_CT_PREFIX));\n}\n\nfunction upstreamFaviconUrls(domain: string, htmlIconUrls: string[]): string[] {\n const urls: string[] = [...originFaviconUrls(domain), ...htmlIconUrls];\n const thirdPartyOff =\n process.env.FAVICON_THIRD_PARTY === \"0\" || process.env.FAVICON_THIRD_PARTY === \"false\";\n if (!thirdPartyOff) {\n urls.push(duckduckgoFaviconUrl(domain), iconHorseUrl(domain), unavatarUrl(domain));\n }\n const includeGoogle =\n process.env.FAVICON_INCLUDE_GOOGLE === \"1\" || process.env.FAVICON_INCLUDE_GOOGLE === \"true\";\n if (includeGoogle) urls.push(googleFaviconUrl(domain));\n return urls;\n}\n\nasync function fetchFaviconFromNetwork(domain: string): Promise<{ buf: Buffer; mime: string }> {\n const htmlIconUrls = await discoverIconUrlsFromHomepage(domain);\n const urls = upstreamFaviconUrls(domain, htmlIconUrls);\n const tasks = urls.map(async (url) => {\n const got = await fetchIconCandidate(url);\n if (!isValidIcon(got)) {\n throw new Error(\"not-an-icon\");\n }\n const mime = resolveImageMime(got.buf, got.ct)!;\n return { buf: got.buf, mime };\n });\n try {\n return await Promise.any(tasks);\n } catch {\n return letterAvatarForDomain(domain);\n }\n}\n\nfunction fetchFaviconDeduped(domain: string): Promise<{ buf: Buffer; mime: string }> {\n let p = inflightByDomain.get(domain);\n if (p) return p;\n p = fetchFaviconFromNetwork(domain).finally(() => {\n if (inflightByDomain.get(domain) === p) inflightByDomain.delete(domain);\n });\n inflightByDomain.set(domain, p);\n return p;\n}\n\nexport function registerFeedFaviconRoutes(app: Hono): void {\n app.get(\"/api/feed-favicon\", async (c) => {\n const raw = (c.req.query(\"domain\") ?? \"\").trim();\n if (!raw || !isPlausibleHostname(raw)) {\n return new Response(null, { status: 400 });\n }\n const domain = raw.toLowerCase();\n const path = cacheFilePath(domain);\n\n let diskStale = false;\n try {\n const st = await stat(path);\n if (Date.now() - st.mtimeMs >= CACHE_MAX_AGE_MS) {\n diskStale = true;\n await unlink(path).catch(() => {});\n }\n } catch (e) {\n if (!isEnoent(e)) {\n return new Response(null, { status: 500 });\n }\n }\n\n if (!diskStale) {\n try {\n const cached = await readFile(path);\n const mime = resolveImageMime(cached, null);\n if (mime) {\n return new Response(new Uint8Array(cached), {\n status: 200,\n headers: {\n \"Content-Type\": mime,\n \"Cache-Control\": CACHE_CONTROL,\n },\n });\n }\n await unlink(path).catch(() => {});\n } catch (e) {\n if (!isEnoent(e)) {\n return new Response(null, { status: 500 });\n }\n }\n }\n\n const resolved = await fetchFaviconDeduped(domain);\n const { buf, mime } = resolved;\n\n try {\n await mkdir(join(CACHE_DIR, CACHE_SUBDIR), { recursive: true });\n await writeFile(path, buf);\n } catch {\n return new Response(null, { status: 500 });\n }\n\n return new Response(new Uint8Array(buf), {\n status: 200,\n headers: {\n \"Content-Type\": mime,\n \"Cache-Control\": CACHE_CONTROL,\n },\n });\n });\n}\n","// GET /api/cover-img?url=… — 代理条目封面图,绕过部分 CDN 防盗链\n\nimport type { Hono } from \"hono\";\n\nconst MAX_BYTES = 5 * 1024 * 1024;\nconst FETCH_TIMEOUT_MS = 12_000;\n\nfunction isAllowedImageUrl(raw: string): URL | null {\n try {\n const url = new URL(raw);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return null;\n if (!url.hostname) return null;\n return url;\n } catch {\n return null;\n }\n}\n\nexport function registerCoverImgRoutes(app: Hono): void {\n app.get(\"/api/cover-img\", async (c) => {\n const raw = (c.req.query(\"url\") ?? \"\").trim();\n if (!raw) return c.text(\"missing url\", 400);\n\n const url = isAllowedImageUrl(raw);\n if (!url) return c.text(\"invalid url\", 400);\n\n try {\n const res = await fetch(url.toString(), {\n redirect: \"follow\",\n headers: {\n Accept: \"image/avif,image/webp,image/apng,image/*,*/*;q=0.8\",\n \"User-Agent\":\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Referer: `${url.origin}/`,\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (!res.ok) return c.text(\"upstream error\", 502);\n\n const buf = Buffer.from(await res.arrayBuffer());\n if (buf.length > MAX_BYTES) return c.text(\"too large\", 413);\n\n const ct = (res.headers.get(\"content-type\") ?? \"application/octet-stream\").split(\";\")[0].trim();\n return new Response(buf, {\n headers: {\n \"Content-Type\": ct,\n \"Cache-Control\": \"public, max-age=86400\",\n },\n });\n } catch {\n return c.text(\"fetch failed\", 502);\n }\n });\n}\n","// API 路由汇总:server、rss、items、feed、sources、scheduler、plugins、logs、admin、tags、tasks\n\nimport type { Hono } from \"hono\";\nimport { registerServerRoutes } from \"./server.js\";\nimport { registerRssApiRoutes } from \"./rss.js\";\nimport { registerSchedulerRoutes } from \"./scheduler.js\";\nimport { registerPluginsRoutes } from \"./plugins.js\";\nimport { registerPipelineRoutes } from \"./pipeline.js\";\nimport { registerFeedRoutes } from \"./feed.js\";\nimport { registerItemsRoutes } from \"./items.js\";\nimport { registerLogsRoutes } from \"./logs.js\";\nimport { registerAdminApiRoutes } from \"./admin.js\";\nimport { registerSourcesRoutes } from \"./sources.js\";\nimport { registerTopicsRoutes } from \"./topics.js\";\nimport { registerDeliverRoutes } from \"./deliver.js\";\nimport { registerLlmRoutes } from \"./llm.js\";\nimport { registerProxySettingsRoutes } from \"./proxy.js\";\nimport { registerTasksRoutes } from \"./tasks.js\";\nimport { registerFeedFaviconRoutes } from \"./feed-favicon.js\";\nimport { registerCoverImgRoutes } from \"./cover-img.js\";\n\nexport function registerApiRoutes(app: Hono): void {\n registerServerRoutes(app);\n registerFeedFaviconRoutes(app);\n registerCoverImgRoutes(app);\n registerRssApiRoutes(app);\n registerSchedulerRoutes(app);\n registerPluginsRoutes(app);\n registerPipelineRoutes(app);\n registerFeedRoutes(app);\n registerItemsRoutes(app);\n registerLogsRoutes(app);\n registerAdminApiRoutes(app);\n registerSourcesRoutes(app);\n registerTopicsRoutes(app);\n registerDeliverRoutes(app);\n registerLlmRoutes(app);\n registerProxySettingsRoutes(app);\n registerTasksRoutes(app);\n}\n","// 认证路由:登录检查、打开登录页、ensureAuth\n\nimport type { Hono } from \"hono\";\nimport { getWebSite, getBestSite, toAuthFlow } from \"../../scraper/sources/web/index.js\";\nimport { ensureAuth, preCheckAuth, openBrowserPage, resolveProxy } from \"../../scraper/sources/web/fetcher/index.js\";\nimport { CACHE_DIR } from \"../../config/paths.js\";\nimport { resolveProxyForSite } from \"../../config/globalProxy.js\";\n\nexport function registerAuthRoutes(app: Hono): void {\n app.get(\"/auth/check\", async (c) => {\n const siteIdParam = c.req.query(\"siteId\");\n if (!siteIdParam) {\n return c.json({ ok: false, message: \"请提供 siteId\" }, 400);\n }\n const site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n try {\n const authenticated = await preCheckAuth(authFlow, CACHE_DIR, { proxy: await resolveProxyForSite(site) });\n return c.json({ ok: true, authenticated });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.json({ ok: false, message: `检查失败: ${msg}` }, 500);\n }\n });\n\n app.post(\"/auth/open\", async (c) => {\n const siteIdParam = c.req.query(\"siteId\");\n if (!siteIdParam) {\n return c.json({ ok: false, message: \"请提供 siteId\" }, 400);\n }\n const site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n const { loginUrl } = authFlow;\n const proxy = await resolveProxyForSite(site);\n void openBrowserPage(loginUrl, CACHE_DIR, { proxy: resolveProxy({ proxy }) }).catch(() => {});\n return c.json({ ok: true, message: \"已打开登录页面\" });\n });\n\n app.post(\"/auth/ensure\", async (c) => {\n const urlParam = c.req.query(\"url\");\n const siteIdParam = c.req.query(\"siteId\");\n let site;\n if (urlParam) {\n const decoded = decodeURIComponent(urlParam);\n site = getBestSite(decoded);\n if (!site) return c.json({ ok: false, message: \"无匹配站点\" }, 404);\n } else if (siteIdParam) {\n site = getWebSite(siteIdParam);\n if (!site) return c.json({ ok: false, message: \"无此站点\" }, 404);\n } else {\n return c.json({ ok: false, message: \"请提供 url 或 siteId\" }, 400);\n }\n const authFlow = toAuthFlow(site);\n if (!authFlow) return c.json({ ok: false, message: \"该站点无需登录\" }, 400);\n ensureAuth(authFlow, CACHE_DIR, { proxy: await resolveProxyForSite(site) }).then(() => {}).catch(() => {});\n return c.json({ ok: true, message: \"已打开登录窗口,请在弹出的浏览器中完成登录,完成后刷新订阅页面即可。\" });\n });\n}\n","// 路由层共享工具函数\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\n\nexport const STATICS_DIR = join(PACKAGE_ROOT, \"app/statics\");\n\n/** 从路径提取 URL(与 /rss/* 一致) */\nexport function parseUrlFromPath(path: string, prefix: string): string | null {\n const raw = path.slice(prefix.length) || \"\";\n const decoded = decodeURIComponent(raw.startsWith(\"/\") ? raw.slice(1) : raw);\n if (!decoded) return null;\n return decoded.startsWith(\"http\") ? decoded : `https://${decoded}`;\n}\n\n/** 读取静态 HTML(app/statics/ 目录,用于 401/404 错误页) */\nexport async function readStaticHtml(name: string, fallback: string): Promise<string> {\n try {\n return await readFile(join(STATICS_DIR, `${name}.html`), \"utf-8\");\n } catch {\n return fallback;\n }\n}\n\n/** HTML 转义,用于注入到页面中的不可信内容 */\nexport function escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","// Admin 路由:解析调试、正文提取调试\n\nimport type { Hono } from \"hono\";\nimport { getSource } from \"../../scraper/sources/index.js\";\nimport { buildSourceContext } from \"../../scraper/sources/context.js\";\nimport { extractFromLink } from \"../../scraper/sources/web/extractor/index.js\";\nimport { CACHE_DIR } from \"../../config/paths.js\";\nimport { AuthRequiredError } from \"../../scraper/auth/index.js\";\nimport { parseUrlFromPath, readStaticHtml, escapeHtml } from \"../utils.js\";\nimport { requireAdmin } from \"../../auth/middleware.js\";\nimport { getEffectiveProxyForListUrl } from \"../../scraper/subscription/index.js\";\n\n/** 与 fetcher `resolveProxy` 一致:调试 query 优先 → sources.json / Source.proxy → 环境变量 */\nfunction effectiveProxyUsed(override: string | undefined, mergedFromSource: string | undefined): string | undefined {\n const o = override?.trim();\n if (o) return o;\n const s = mergedFromSource?.trim();\n if (s) return s;\n return process.env.HTTP_PROXY?.trim() || process.env.HTTPS_PROXY?.trim();\n}\n\nfunction redactProxyForLog(p: string | undefined): string | null {\n if (!p) return null;\n try {\n const u = new URL(p);\n if (u.username) u.username = \"***\";\n if (u.password) u.password = \"***\";\n return u.toString();\n } catch {\n return null;\n }\n}\n\nexport function registerAdminRoutes(app: Hono): void {\n async function render401(listUrl: string): Promise<string> {\n const raw = await readStaticHtml(\"401\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>401</title></head><body><h1>401 需要登录</h1></body></html>\");\n return raw.replace(/\\{\\{listUrl\\}\\}/g, escapeHtml(listUrl));\n }\n\n /** Parse 与插件解耦:始终通过 getSource(url) 解析,无匹配插件时自动走 generic(浏览器抓取 + LLM 解析),与 /rss/* 行为一致 */\n app.get(\"/admin/parse/*\", requireAdmin(), async (c) => {\n const url = parseUrlFromPath(c.req.path, \"/admin/parse\");\n if (!url) return c.text(\"无效 URL,格式: /admin/parse/https://... 或 /admin/parse/example.com/...\", 400);\n try {\n // 调试默认有头浏览器;仅 headless=true|1 时使用无头\n const headlessParam = c.req.query(\"headless\");\n const headless = headlessParam === \"true\" || headlessParam === \"1\";\n const proxyOverride = c.req.query(\"proxy\")?.trim();\n const source = getSource(url);\n const fromSource = await getEffectiveProxyForListUrl(url, source);\n const ctx = buildSourceContext({\n cacheDir: CACHE_DIR,\n headless,\n proxy: proxyOverride || fromSource,\n });\n const items = await source.fetchItems(url, ctx);\n const mode = source.id === \"generic\" ? \"generic\" : \"plugin\";\n const effective = effectiveProxyUsed(proxyOverride, fromSource);\n return c.json({\n items,\n url,\n mode,\n pluginId: source.id,\n effectiveProxy: redactProxyForLog(effective),\n });\n } catch (err) {\n if (err instanceof AuthRequiredError) {\n const html = await render401(url);\n return c.html(html, 401);\n }\n const msg = err instanceof Error ? err.message : String(err);\n return c.text(`解析失败: ${msg}`, 500);\n }\n });\n\n app.get(\"/admin/extractor/*\", requireAdmin(), async (c) => {\n const url = parseUrlFromPath(c.req.path, \"/admin/extractor\");\n if (!url) return c.text(\"无效 URL,格式: /admin/extractor/https://... 或 /admin/extractor/example.com/...\", 400);\n try {\n const headlessParam = c.req.query(\"headless\");\n const headless = headlessParam === \"true\" || headlessParam === \"1\";\n const proxyOverride = c.req.query(\"proxy\")?.trim();\n const source = getSource(url);\n const fromSource = await getEffectiveProxyForListUrl(url, source);\n const proxy = proxyOverride || fromSource;\n const result = await extractFromLink(url, {}, { timeoutMs: 60_000, headless, proxy });\n const effective = effectiveProxyUsed(proxyOverride, fromSource);\n return c.json({\n title: result.title ?? null,\n author: result.author ?? null,\n pubDate: result.pubDate ?? null,\n content: result.content ?? null,\n _extractor: \"readability\",\n effectiveProxy: redactProxyForLog(effective),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.text(`提取失败: ${msg}`, 500);\n }\n });\n}\n","// RSS 路由:/rss/* 生成 RSS XML\n\n\n\nimport { createHash } from \"node:crypto\";\n\nimport type { Hono } from \"hono\";\n\nimport { getItems, feedItemsToRssXml } from \"../../feeder/index.js\";\n\nimport { queryItems } from \"../../db/index.js\";\n\nimport { getAllSubscriptionRefs } from \"../../scraper/subscription/index.js\";\n\nimport { SOURCES_GROUP } from \"../../scraper/scheduler/index.js\";\n\nimport * as scheduler from \"../../scheduler/index.js\";\n\nimport { CACHE_DIR } from \"../../config/paths.js\";\n\nimport { AuthRequiredError, NotFoundError } from \"../../scraper/auth/index.js\";\n\nimport { parseUrlFromPath, readStaticHtml, escapeHtml } from \"../utils.js\";\n\n\n\nfunction parseSubscribedFlag(v: string | undefined): boolean {\n\n return v === \"1\" || v === \"true\" || v === \"yes\";\n\n}\n\n\n\nexport function registerRssRoutes(app: Hono): void {\n\n async function render401(listUrl: string): Promise<string> {\n\n const raw = await readStaticHtml(\"401\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>401</title></head><body><h1>401 需要登录</h1></body></html>\");\n\n return raw.replace(/\\{\\{listUrl\\}\\}/g, escapeHtml(listUrl));\n\n }\n\n\n\n /** 查询式 RSS:按 search、sourceUrl、subscribed、author、tags 过滤,返回匹配条目的 XML */\n\n app.get(\"/rss\", async (c) => {\n\n const search = c.req.query(\"search\") ?? c.req.query(\"q\") ?? undefined;\n\n const ref = c.req.query(\"ref\") ?? c.req.query(\"source\") ?? c.req.query(\"sourceUrl\") ?? undefined;\n\n const subscribed = parseSubscribedFlag(c.req.query(\"subscribed\"));\n\n const author = c.req.query(\"author\") ?? undefined;\n\n const tagsParam = c.req.query(\"tags\") ?? undefined;\n\n const tags = tagsParam ? tagsParam.split(\",\").map((t) => t.trim()).filter(Boolean) : undefined;\n\n const lng = c.req.query(\"lng\") ?? undefined;\n\n const limit = Math.min(Number(c.req.query(\"limit\") ?? 50), 200);\n\n const offset = Number(c.req.query(\"offset\") ?? 0);\n\n const title = c.req.query(\"title\") ?? undefined;\n\n const daysParam = c.req.query(\"days\");\n\n const sinceParam = c.req.query(\"since\") ?? undefined;\n\n const untilParam = c.req.query(\"until\") ?? undefined;\n\n let since: Date | undefined;\n\n let until: Date | undefined;\n\n if (daysParam) {\n\n const n = Math.max(1, Math.min(365, Number(daysParam) || 1));\n\n const now = new Date();\n\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n\n const todayEnd = new Date(todayStart);\n\n todayEnd.setDate(todayEnd.getDate() + 1);\n\n since = new Date(todayStart);\n\n since.setDate(since.getDate() - (n - 1));\n\n until = todayEnd;\n\n } else {\n\n since = sinceParam ? new Date(sinceParam) : undefined;\n\n if (untilParam) {\n\n if (untilParam.length === 10) {\n\n const d = new Date(untilParam + \"T12:00:00Z\");\n\n d.setUTCDate(d.getUTCDate() + 1);\n\n until = d;\n\n } else {\n\n until = new Date(untilParam);\n\n }\n\n }\n\n }\n\n\n\n let sourceUrls: string[] | undefined;\n\n if (ref) {\n\n sourceUrls = undefined;\n\n } else if (subscribed) {\n\n sourceUrls = await getAllSubscriptionRefs();\n\n }\n\n\n\n if (sourceUrls?.length === 0) {\n\n const xml = feedItemsToRssXml([], new URL(c.req.url).href, lng, {\n\n channelTitle: title ?? \"RSS 订阅\",\n\n channelDesc: \"无匹配条目\",\n\n });\n\n return c.body(xml, 200, { \"Content-Type\": \"application/rss+xml; charset=utf-8\" });\n\n }\n\n\n\n const result = await queryItems({\n\n sourceUrl: sourceUrls ? undefined : ref,\n\n sourceUrls,\n\n author,\n\n q: search,\n\n tags,\n\n since,\n\n until,\n\n limit,\n\n offset,\n\n });\n\n const feedItems = result.items.map((dbItem) => ({\n\n guid: dbItem.id,\n\n title: dbItem.title ?? \"\",\n\n link: dbItem.url,\n\n pubDate: dbItem.pub_date ? new Date(dbItem.pub_date) : new Date(),\n\n author: dbItem.author ?? undefined,\n\n summary: dbItem.summary ?? undefined,\n\n content: dbItem.content ?? undefined,\n\n imageUrl: dbItem.image_url ?? undefined,\n\n tags: dbItem.tags ?? undefined,\n\n sourceRef: dbItem.source_url,\n\n translations: dbItem.translations ?? undefined,\n\n }));\n\n\n\n const rssUrl = new URL(c.req.url);\n\n const channelTitle = title ?? \"RSS 订阅\";\n\n const xml = feedItemsToRssXml(feedItems, rssUrl.href, lng, {\n\n channelTitle,\n\n channelDesc: `来自 ${rssUrl.href} 的订阅`,\n\n });\n\n return c.body(xml, 200, {\n\n \"Content-Type\": \"application/rss+xml; charset=utf-8\",\n\n });\n\n });\n\n\n\n /** 按 URL 抓取式 RSS:/rss/https://... 从信源实时抓取 */\n\n app.get(\"/rss/*\", async (c) => {\n\n const url = parseUrlFromPath(c.req.path, \"/rss\");\n\n if (!url) return c.text(\"无效 URL,格式: /rss/https://... 或 /rss/www.xiaohongshu.com/...\", 400);\n\n try {\n\n const headlessParam = c.req.query(\"headless\");\n\n const headless = headlessParam === \"false\" || headlessParam === \"0\" ? false : undefined;\n\n const lng = c.req.query(\"lng\") ?? undefined;\n\n const httpId = \"rss-\" + createHash(\"sha256\").update(url).digest(\"hex\").slice(0, 16);\n\n const { items } = await scheduler.schedule(\n\n SOURCES_GROUP,\n\n httpId,\n\n () => getItems(url, { cacheDir: CACHE_DIR, headless, lng }),\n\n );\n\n const xml = feedItemsToRssXml(items, url, lng);\n\n return c.body(xml, 200, {\n\n \"Content-Type\": \"application/rss+xml; charset=utf-8\",\n\n });\n\n } catch (err) {\n\n if (err instanceof AuthRequiredError) {\n\n const html = await render401(url);\n\n return c.html(html, 401);\n\n }\n\n if (err instanceof NotFoundError) {\n\n const html = await readStaticHtml(\"404\", \"<!DOCTYPE html><html><head><meta charset=\\\"utf-8\\\"><title>404</title></head><body><h1>404 未找到</h1></body></html>\");\n\n return c.html(html, 404);\n\n }\n\n const msg = err instanceof Error ? err.message : String(err);\n\n return c.text(`生成 RSS 失败: ${msg}`, 500);\n\n }\n\n });\n\n}\n\n","// 生产环境:在同一端口托管 SvelteKit 静态构建(adapter-static + 200.html SPA fallback)\n\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport type { Context, Hono } from \"hono\";\nimport { PACKAGE_ROOT } from \"../packageRoot.js\";\n\n/** 与 svelte.config.js 中 adapter-static 的 pages/assets 输出目录一致 */\nexport function getWebUiBuildDir(): string {\n const w = process.env.WEBUI_BUILD_DIR?.trim();\n if (w) {\n if (w.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(w)) return w;\n return join(process.cwd(), w);\n }\n return join(PACKAGE_ROOT, \"app/webui/build\");\n}\n\n/** 仅后端接口路径,不走静态/SPA;注意 /admin 为前端路由,仅 /admin/parse、/admin/extractor 为后端 */\nfunction isBackendOnlyPath(pathname: string): boolean {\n if (pathname.startsWith(\"/api\")) return true;\n if (pathname.startsWith(\"/rss\")) return true;\n if (pathname.startsWith(\"/auth\")) return true;\n if (pathname.startsWith(\"/admin/parse\") || pathname.startsWith(\"/admin/extractor\")) return true;\n return false;\n}\n\n/** 明显是静态资源路径但磁盘上无对应文件时,不应返回 200.html */\nfunction looksLikeStaticAsset(pathname: string): boolean {\n return /\\.[a-zA-Z0-9]{1,12}$/.test(pathname);\n}\n\n/**\n * 在已注册全部 API 路由之后调用。\n * `serveStatic` 的 root 需为相对 cwd 的路径(见 @hono/node-server/serve-static)。\n */\nexport function registerWebUiRoutes(app: Hono): void {\n const absRoot = getWebUiBuildDir();\n if (!existsSync(absRoot)) {\n console.warn(\n \"未找到 WebUI 构建目录,静态路由已注册,等待前端 watch 构建:\",\n absRoot,\n \"(开发模式:npm run dev;单独构建:npm run webui:build)\",\n );\n }\n\n const relRoot = relative(process.cwd(), absRoot).replace(/\\\\/g, \"/\");\n const staticRoot =\n relRoot === \"\" || relRoot === \".\"\n ? \".\"\n : relRoot.startsWith(\".\") || relRoot.startsWith(\"/\") || /^[A-Za-z]:/.test(relRoot)\n ? relRoot\n : `./${relRoot}`;\n\n const staticMw = serveStatic({\n root: staticRoot,\n index: \"200.html\",\n });\n\n app.use(\"*\", async (c, next) => {\n if (isBackendOnlyPath(c.req.path)) return next();\n return staticMw(c, next);\n });\n\n const spaFallback = async (c: Context) => {\n const p = c.req.path;\n if (isBackendOnlyPath(p)) return c.notFound();\n if (looksLikeStaticAsset(p)) return c.notFound();\n try {\n const html = await readFile(join(absRoot, \"200.html\"), \"utf-8\");\n return c.html(html);\n } catch {\n return c.notFound();\n }\n };\n\n app.get(\"*\", spaFallback);\n}\n","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n/** 运行时读取仓库根目录 package.json(编译后为 dist/version.js → 上一级为根) */\nexport function getAppVersion(): string {\n try {\n const pkgPath = join(here, \"../package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n","// App 入口:Hono 服务,与 feeder 解耦;可替换为 Express 等\n\nimport \"dotenv/config\";\nimport { watch } from \"node:fs\";\nimport { networkInterfaces } from \"node:os\";\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { initSources as initSites } from \"../scraper/sources/index.js\";\nimport { initScheduler } from \"../scraper/scheduler/index.js\";\nimport { initUserDir, BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR, CACHE_DIR } from \"../config/paths.js\";\nimport { logger } from \"../core/logger/index.js\";\nimport { registerApiRoutes } from \"./routes/api/index.js\";\nimport { registerAuthRoutes } from \"./routes/auth.js\";\nimport { registerAdminRoutes } from \"./routes/admin.js\";\nimport { registerRssRoutes } from \"./routes/rss.js\";\nimport { registerWebUiRoutes } from \"./webui.js\";\nimport { getAppVersion } from \"../version.js\";\n\nconst PORT = Number(process.env.PORT) || 18473;\nconst IS_DEV = process.env.NODE_ENV === \"development\" || process.argv.includes(\"--watch\");\nconst PLUGIN_WATCH_EXTS = [\".rssany.js\", \".rssany.ts\"];\n\nfunction createApp(): Hono {\n const app = new Hono();\n\n app.use(\n \"*\",\n cors({\n origin: \"*\",\n allowMethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"],\n allowHeaders: [\"Content-Type\", \"Authorization\", \"X-Admin-Token\"],\n }),\n );\n\n registerApiRoutes(app);\n registerAuthRoutes(app);\n registerAdminRoutes(app);\n registerRssRoutes(app);\n registerWebUiRoutes(app);\n\n return app;\n}\n\nfunction watchPlugins(): void {\n let reloadTimer: NodeJS.Timeout | null = null;\n const debouncedReload = async () => {\n if (reloadTimer) clearTimeout(reloadTimer);\n reloadTimer = setTimeout(async () => {\n try {\n await initSites();\n } catch (err) {\n logger.error(\"plugin\", \"插件重新加载失败\", { err: err instanceof Error ? err.message : String(err) });\n }\n }, 300);\n };\n for (const dir of [BUILTIN_PLUGINS_DIR, USER_PLUGINS_DIR]) {\n const watcher = watch(dir, { recursive: true }, (eventType, filename) => {\n if (!filename || !PLUGIN_WATCH_EXTS.some((ext) => filename.endsWith(ext))) return;\n if (eventType === \"rename\" || eventType === \"change\") debouncedReload();\n });\n watcher.on(\"error\", (err) => {\n logger.warn(\"plugin\", \"插件目录监听错误\", { dir, err: err.message });\n });\n }\n}\n\nasync function main(): Promise<void> {\n await initUserDir();\n await initSites();\n await initScheduler(CACHE_DIR);\n const app = createApp();\n const server = serve({ fetch: app.fetch, port: PORT, hostname: \"0.0.0.0\" });\n server.setMaxListeners(32);\n console.log(\n `RssAny ${getAppVersion()} 服务已启动 http://127.0.0.1:${PORT}/(API + 静态前端单地址)`,\n );\n const lanIp = Object.values(networkInterfaces()).flat().find((iface) => iface?.family === \"IPv4\" && !iface.internal)?.address;\n if (lanIp) console.log(`局域网访问 http://${lanIp}:${PORT}/`);\n if (IS_DEV) {\n watchPlugins();\n }\n}\nmain();\n"],"names":["s","now","mergeSourceStatsRows","base","page","resolve","authenticated","cacherCacheKey","fetchHtmlFn","SYSTEM","parseSteps","cronValidate","tasks","cronSchedule","runNow","scheduler.unscheduleGroup","scheduler.validateCron","scheduler.schedule","PORT","scheduler.getGroupStats","d","parseSubscribedFlag","taskStore.getTask","taskStore.createTask","taskStore.setTaskRunning","taskStore.setTaskDone","taskStore.setTaskError","FETCH_TIMEOUT_MS","mime","xml","initSites"],"mappings":"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"}
|