@klarkxy/dsh-zhihu 0.2.11 → 0.3.0
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/README.md +2 -7
- package/docs/README.zh-CN.md +2 -7
- package/lib/client.js +3 -1
- package/lib/contracts.d.ts +5 -13
- package/lib/contracts.js +5 -13
- package/lib/contracts.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/open-platform.js.map +1 -1
- package/lib/tools.js +165 -578
- package/lib/tools.js.map +1 -1
- package/package.json +8 -8
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/zhihu-knowledge.ts","../src/host-rpc.ts","../src/web-search-provider.ts","../src/sync-types.ts","../src/sync-content.ts","../src/sync-api.ts","../src/sync-media.ts","../src/sync-service.ts","../src/index.ts"],"sourcesContent":["/**\n * 知乎知识库的管理侧能力:列知识库、上传文件。仅供设置页 RPC 使用,\n * 不暴露为模型工具——上传会把文件送入知乎云端,必须是用户在界面上显式发起。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 列表:GET /api/v1/knowledge/bases?Scope=all|created|subscribed\n * - 上传:POST /api/v1/knowledge/files(multipart: File + 可选 KnowledgeBaseID),同步解析\n */\nimport {\n parseZhihuEnvelope,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_KNOWLEDGE_LIST_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_UPLOAD_VERSION = 1\n/** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */\nexport const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20 * 1024 * 1024\nexport const ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS = 180_000\n\nconst KNOWLEDGE_BASES_PATH = '/api/v1/knowledge/bases'\nconst KNOWLEDGE_FILES_PATH = '/api/v1/knowledge/files'\n\nexport const ZHIHU_KNOWLEDGE_FILE_EXTENSIONS = [\n '.pdf', '.md', '.txt', '.ppt', '.pptx', '.xlsx', '.xls', '.docx', '.doc',\n '.webp', '.png', '.jpg', '.mobi', '.epub', '.csv', '.azw3',\n] as const\n\nconst KNOWLEDGE_CONTENT_TYPES: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.ppt': 'application/vnd.ms-powerpoint',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.xls': 'application/vnd.ms-excel',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.webp': 'image/webp',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.mobi': 'application/x-mobipocket-ebook',\n '.epub': 'application/epub+zip',\n '.csv': 'text/csv',\n '.azw3': 'application/vnd.amazon.ebook',\n}\n\nexport type ZhihuKnowledgeBase = {\n id: string\n name: string\n relation: string\n visibility: string\n isDefault: boolean\n contentCount: number\n}\n\nexport type ZhihuKnowledgeBaseList = {\n version: typeof ZHIHU_KNOWLEDGE_LIST_VERSION\n bases: ZhihuKnowledgeBase[]\n}\n\nfunction normalizeBase(raw: Record<string, unknown>): ZhihuKnowledgeBase {\n return {\n id: typeof raw.KnowledgeBaseID === 'string' ? raw.KnowledgeBaseID : '',\n name: typeof raw.Name === 'string' && raw.Name.length > 0 ? raw.Name : '(未命名知识库)',\n relation: typeof raw.Relation === 'string' ? raw.Relation : '',\n visibility: typeof raw.Visibility === 'string' ? raw.Visibility : '',\n isDefault: raw.IsDefault === true,\n contentCount: typeof raw.ContentCount === 'number' ? raw.ContentCount : 0,\n }\n}\n\nexport async function listZhihuKnowledgeBases(options: ZhihuClientOptions = {}): Promise<ZhihuKnowledgeBaseList> {\n const body = await zhihuFetchJson(KNOWLEDGE_BASES_PATH, {\n params: { Scope: 'all' },\n }, '知乎知识库列表', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown }\n const items = Array.isArray(data.Items) ? data.Items : []\n return {\n version: ZHIHU_KNOWLEDGE_LIST_VERSION,\n bases: items\n .filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item))\n .map(normalizeBase)\n .filter((base) => base.id.length > 0),\n }\n}\n\nexport type ZhihuKnowledgeUploadInput = {\n fileName: string\n data: Uint8Array\n /** 指定目标知识库;缺省进默认库。 */\n knowledgeBaseId?: string\n}\n\nexport type ZhihuKnowledgeUpload = {\n version: typeof ZHIHU_KNOWLEDGE_UPLOAD_VERSION\n knowledgeBaseId: string\n recallContentId: string\n fileName: string\n fileSize: number\n title: string\n abstract: string\n originUrl: string\n}\n\n/** 校验文件名与大小,返回小写扩展名。 */\nexport function checkKnowledgeFile(fileName: string, size: number): string {\n const cleaned = fileName.trim()\n if (!cleaned) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能为空。')\n // 去掉路径成分,只保留文件名\n const base = cleaned.split(/[\\\\/]/).pop() ?? cleaned\n for (const ch of base) {\n const code = ch.codePointAt(0) ?? 0\n if (code < 32 || code === 127) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能包含控制字符。')\n }\n if (new TextEncoder().encode(base).length > 255) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能超过 255 个 UTF-8 字节。')\n }\n if (size <= 0) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件内容不能为空。')\n if (size > ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库文件大小不能超过 ${ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 1024 / 1024}MB。`)\n }\n const dot = base.lastIndexOf('.')\n const ext = dot >= 0 ? base.slice(dot).toLowerCase() : ''\n if (!(ZHIHU_KNOWLEDGE_FILE_EXTENSIONS as readonly string[]).includes(ext)) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库上传仅支持 ${ZHIHU_KNOWLEDGE_FILE_EXTENSIONS.join('、')}。`)\n }\n return ext\n}\n\nexport async function uploadZhihuKnowledgeFile(\n input: ZhihuKnowledgeUploadInput,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeUpload> {\n const cleaned = (input.fileName.trim().split(/[\\\\/]/).pop() ?? input.fileName.trim())\n const ext = checkKnowledgeFile(input.fileName, input.data.byteLength)\n const form = new FormData()\n if (input.knowledgeBaseId) form.set('KnowledgeBaseID', input.knowledgeBaseId)\n form.set('File', new Blob([input.data.buffer as ArrayBuffer], { type: KNOWLEDGE_CONTENT_TYPES[ext] ?? 'application/octet-stream' }), cleaned)\n const body = await zhihuFetchJson(KNOWLEDGE_FILES_PATH, {\n formData: form,\n }, '知乎知识库上传', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS })\n const data = parseZhihuEnvelope(body) as Record<string, unknown>\n return {\n version: ZHIHU_KNOWLEDGE_UPLOAD_VERSION,\n knowledgeBaseId: typeof data.KnowledgeBaseID === 'string' ? data.KnowledgeBaseID : '',\n recallContentId: typeof data.RecallContentID === 'string' ? data.RecallContentID : '',\n fileName: typeof data.FileName === 'string' ? data.FileName : cleaned,\n fileSize: typeof data.FileSize === 'number' ? data.FileSize : input.data.byteLength,\n title: typeof data.Title === 'string' ? data.Title : '',\n abstract: typeof data.Abstract === 'string' ? data.Abstract : '',\n originUrl: typeof data.OriginUrl === 'string' ? data.OriginUrl : '',\n }\n}\n","import { registerHostRpcTransport, sendHostRpcResponse, type HostRpcContext } from '@klarkxy/dsh-plugin-kit/host-rpc'\n\nconst CHANNEL_PATTERN = /^\\/[A-Za-z0-9._~-]+$/\nconst ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/\nconst MAX_BODY_BYTES = 64 * 1024 * 1024\n\nexport type { HostRpcContext }\nexport type HostRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>\n\nfunction isEndpoint(endpoint: string): boolean {\n return endpoint.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..' && ENDPOINT_SEGMENT.test(segment))\n}\n\n/** Zhihu keeps its gateway-compatible bad-request envelope and upload allowance. */\nexport function registerHostRpc(ctx: HostRpcContext, channel: string, handler: HostRpcHandler): () => void {\n if (!CHANNEL_PATTERN.test(channel) || channel === '/api') {\n throw new Error(`invalid RPC channel ${JSON.stringify(channel)}`)\n }\n return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {\n const record = body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}\n const rpcId = typeof record.rpcId === 'string' ? record.rpcId : 'invalid-request'\n if (record.type !== 'client-request' || record.method !== endpoint) {\n sendHostRpcResponse(res, rpcId, {\n ok: false,\n error: {\n code: 'gateway/bad-request',\n message: record.method !== endpoint\n ? `method ${JSON.stringify(record.method)} does not match endpoint ${JSON.stringify(endpoint)}`\n : 'invalid client-request message',\n details: { issues: [] },\n },\n }, false)\n return\n }\n sendHostRpcResponse(res, rpcId, await handler(endpoint, record.payload, signal), false)\n }, {\n maxBodyBytes: MAX_BODY_BYTES,\n isEndpoint,\n caseInsensitiveContentType: true,\n emptyBodyAsNull: true,\n errorBody: (status, error) => {\n if (status === 503) return 'host authorization policy is unavailable'\n if (status === 401) return 'unauthorized'\n if (status === 403) return 'forbidden'\n if (status === 404) return 'not found'\n if (status === 415) return 'content type must be application/json'\n if (status === 400) return 'body is not JSON'\n if (status === 500) return `handler failure: ${String(error)}`\n return undefined\n },\n })\n}\n","/**\n * Native ctx.web backend. Configuration and credentials belong to Zhihu.\n * Official model tool remains `web_search`. Dedicated `zhihu_global_search` is unchanged.\n */\nimport { WEB_PROVIDER_AVAILABILITY_EVENT, type DshWebManagementDescriptor } from '@klarkxy/dsh-plugin-kit/contracts'\nimport { ZHIHU_CREDENTIAL_REF } from './contracts.ts'\nimport { reportExecuted, type ZhihuClientOptions, type ZhihuSearchExecuted } from './zhihu-client.ts'\nimport {\n executeZhihuGlobalSearch,\n ZHIHU_GLOBAL_SEARCH_MAX_COUNT,\n} from './operations.ts'\n\ntype ZhihuWebSearchHost = {\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\nexport const ZHIHU_WEB_SEARCH_ID = 'zhihu-global'\n\nexport const ZHIHU_WEB_SEARCH_DESCRIPTOR: DshWebManagementDescriptor = {\n id: ZHIHU_WEB_SEARCH_ID,\n label: '知乎全网搜索',\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n credentialRef: ZHIHU_CREDENTIAL_REF,\n credentialShared: true,\n credentialHint: '与「知乎资料」共用 Access Secret',\n billing: 'request' as const,\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n signupUrl: 'https://developer.zhihu.com',\n configurationOwner: '@klarkxy/dsh-zhihu',\n}\n\nexport type ZhihuWebSearchSource = { url: string; title?: string; snippet?: string }\nexport type ZhihuWebSearchResult = { sources: ZhihuWebSearchSource[]; truncated?: boolean }\nexport type ZhihuWebSearchRequest = { query: string; maxResults?: number }\n\nexport type ZhihuWebSearchProvider = {\n readonly id: string\n available(): boolean\n search(request: ZhihuWebSearchRequest, signal?: AbortSignal): Promise<ZhihuWebSearchResult>\n}\n\nexport type ZhihuWebRegistry = {\n registerSearchProvider(provider: ZhihuWebSearchProvider & {\n dshWebManagement: typeof ZHIHU_WEB_SEARCH_DESCRIPTOR\n }): () => void\n}\n\nexport type ZhihuWebSearchProviderOptions = ZhihuClientOptions & {\n apiKey?: string\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction sourceFrom(url: string, title: string, snippet: string): ZhihuWebSearchSource | undefined {\n let parsed: URL\n try { parsed = new URL(url) } catch { return }\n if (!['https:', 'http:'].includes(parsed.protocol) || parsed.username || parsed.password) return\n return {\n url: parsed.href,\n ...(title ? { title: title.slice(0, 1000) } : {}),\n ...(snippet ? { snippet: snippet.slice(0, 8000) } : {}),\n }\n}\n\nfunction isWebRegistry(value: unknown): value is ZhihuWebRegistry {\n return !!value && typeof value === 'object' && typeof (value as ZhihuWebRegistry).registerSearchProvider === 'function'\n}\n\nexport function createZhihuWebSearchProvider(options: ZhihuWebSearchProviderOptions): ZhihuWebSearchProvider {\n const { apiKey, onExecuted, ...client } = options\n return {\n id: ZHIHU_WEB_SEARCH_ID,\n available() { return Boolean(apiKey?.trim()) },\n async search(request, signal) {\n if (!apiKey?.trim()) throw new Error('知乎全网搜索未配置 Access Secret。')\n if (!request.query?.trim() || request.query.length > 4000) throw new Error('查询无效。')\n const count = request.maxResults ?? 5\n if (!Number.isInteger(count) || count < 1 || count > ZHIHU_GLOBAL_SEARCH_MAX_COUNT) {\n throw new Error(`结果数须为 1 至 ${ZHIHU_GLOBAL_SEARCH_MAX_COUNT}。`)\n }\n try {\n signal?.throwIfAborted()\n const result = await executeZhihuGlobalSearch(request.query.trim(), count, 'all', {\n ...client,\n signal,\n env: client.env ?? process.env,\n resolveCredential: async () => apiKey,\n })\n reportExecuted(onExecuted, { ok: true, results: result.items.length })\n const sources = result.items.flatMap(item => sourceFrom(item.url, item.title, item.summary) ?? [])\n return { sources: sources.slice(0, count), truncated: sources.length > count }\n } catch (error) {\n reportExecuted(onExecuted, { ok: false, results: 0 })\n throw error\n }\n },\n }\n}\n\nexport function registerZhihuGlobalSearchProvider(\n web: ZhihuWebRegistry,\n service: ZhihuWebSearchHost,\n configured: () => boolean,\n): () => void {\n return web.registerSearchProvider({\n id: ZHIHU_WEB_SEARCH_ID, dshWebManagement: ZHIHU_WEB_SEARCH_DESCRIPTOR,\n available: configured,\n search: (request, signal) => service.run(\n async combined => {\n combined.throwIfAborted()\n const apiKey = await service.toolOptions.resolveCredential?.()\n combined.throwIfAborted()\n const provider = createZhihuWebSearchProvider({ ...service.toolOptions, apiKey })\n return provider.search(request, combined)\n },\n signal ?? new AbortController().signal,\n ),\n })\n}\n\ntype BindingContext = {\n get(name: string): unknown\n on?(event: 'credentials/reference-updated', handler: (ref: string) => void): () => void\n emit?(event: typeof WEB_PROVIDER_AVAILABILITY_EVENT, kind: 'search', id: string): void\n}\n/** Wait for the native web service; no manager dependency and no network probe. */\nexport function bindZhihuWebSearch(\n ctx: { inject(deps: readonly string[], apply: (inner: BindingContext) => (() => void) | void): unknown },\n service: ZhihuWebSearchHost,\n): void {\n ctx.inject(['web'], (inner) => {\n let web: unknown\n try { web = inner.get('web') }\n catch { return }\n if (!isWebRegistry(web)) return\n let configured = false, alive = true, revision = 0\n const refresh = async () => {\n const current = ++revision\n let ready = false\n try { ready = Boolean((await service.toolOptions.resolveCredential?.())?.trim()) } catch { /* unavailable */ }\n if (!alive || current !== revision) return\n if (configured !== ready) {\n configured = ready\n inner.emit?.(WEB_PROVIDER_AVAILABILITY_EVENT, 'search', ZHIHU_WEB_SEARCH_ID)\n }\n }\n const off = registerZhihuGlobalSearchProvider(web, service, () => configured)\n const unlisten = inner.on?.('credentials/reference-updated', ref => {\n if (ref === ZHIHU_CREDENTIAL_REF) { void refresh() }\n })\n void refresh()\n return () => { alive = false; revision++; configured = false; unlisten?.(); off() }\n })\n}\n","/**\n * 知乎同步(zhihu-sync)发布链路的共享类型契约。\n *\n * 参考(只读镜像,H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-validate/reference/conversion.md — 标题/正文/媒体转换规则\n * zhihu-sync/zhihu-validate/reference/config.md — 话题/目录/创作声明/评论权限/圈子\n * zhihu-sync/zhihu-publish/reference/publish-*.md — 各形态发布字段与交互确认项\n *\n * 字段命名约定:SyncDraft 用 camelCase;进入知乎 OpenAPI 请求体时由\n * sync-content 的 buildZhihuPublishRequest 映射为 snake_case。\n */\n\nexport type SyncContentType = 'article' | 'question' | 'pin'\nexport type SyncFormat = 'markdown' | 'html'\n\n/** 文章可用的评论权限(publish-article.md 枚举)。 */\nexport const SYNC_ARTICLE_COMMENT_PERMISSIONS = ['all', 'nobody', 'followee', 'censor', 'follower'] as const\n/** 想法可用的评论权限(publish-pin.md 枚举)。 */\nexport const SYNC_PIN_COMMENT_PERMISSIONS = ['all', 'nobody', 'followee', 'censor', 'follower_n_days'] as const\nexport type SyncCommentPermission =\n | (typeof SYNC_ARTICLE_COMMENT_PERMISSIONS)[number]\n | 'follower_n_days'\n\n/** 文章创作声明枚举(publish-article.md);pin 只允许 'ai_creation'。 */\nexport const SYNC_ARTICLE_CREATION_STATEMENTS = [\n 'spoiler',\n 'medical_advice',\n 'fictional_creation',\n 'contain_finance',\n 'ai_creation',\n] as const\nexport type SyncCreationStatement = (typeof SYNC_ARTICLE_CREATION_STATEMENTS)[number]\n\n/**\n * 一张内容图片。url 始终是发布阶段使用的最终地址:\n * 由上传流程填好(有水印且成功时使用水印 URL,否则使用原图 URL)。\n * 本层不根据 mediaId 拼接或回退 URL。\n */\nexport interface SyncImage {\n /** 发布用最终 URL(知乎媒体地址)。 */\n url: string\n /**\n * 用户提供该图时的原始来源:本地文件绝对路径 / file: URI / 原始 http(s) URL。\n * 浏览器 File API 可能给出 `blob:` 或 `data:` 临时地址——此时该字段也允许\n * 是浏览器生成的临时引用(仅作标识,不保证可持久化)。\n * 仅用于正文图片按出现顺序一一映射和预览层展示,永不写入发布请求体。\n */\n originalUrl?: string\n width?: number\n height?: number\n /** 水印处理成功(watermark_image_key 与 media_key 不同)时为 true。 */\n watermark?: boolean\n /** media_key(v2-{md5hex})。只作为记录字段,不用于拼接 URL。 */\n mediaId?: string\n}\n\n/** 想法独立链接卡片(想法正文之外的 linkCard,最多 1 个)。 */\nexport interface SyncLinkCard {\n /** 卡片目标链接(必填,绝对 http(s) URL)。 */\n url: string\n /** 卡片标题(对应 data_draft_title)。 */\n title?: string\n /** 封面图 URL(对应 data_draft_cover)。 */\n cover?: string\n /** 链接解析接口返回的数据,外链可省略。 */\n contentType?: string\n contentId?: string\n isFromForward?: boolean\n}\n\n/**\n * 发布阶段可交互确认的设置项。validate 阶段只收集与校验;\n * 是否真正写入请求体由 buildZhihuPublishRequest 按形态决定。\n */\nexport interface SyncPublishSettings {\n /**\n * 评论权限。article 可用 SYNC_ARTICLE_COMMENT_PERMISSIONS,\n * pin 可用 SYNC_PIN_COMMENT_PERMISSIONS;question 不发送。\n */\n commentPermission?: SyncCommentPermission\n /** 文章目录开关,仅 article 使用,默认 false。 */\n tableOfContentsEnabled?: boolean\n /**\n * 创作声明。article 可用全部枚举;pin 仅支持 'ai_creation';\n * question 不支持该字段。\n */\n creationStatement?: SyncCreationStatement\n /**\n * 话题链接列表(用户粘贴的知乎话题 URL,形如\n * https://www.zhihu.com/topic/{topic_token}/hot)。\n * article 最多 3 个,question 最多 5 个;pin 话题在正文 #话题名# 中,忽略本字段。\n */\n topics?: string[]\n /**\n * 知乎圈子链接(https://www.zhihu.com/ring/host/{ring_id}),仅 pin 使用,最多 1 个。\n */\n ring?: string\n}\n\n/**\n * 保存与传输用的规范化草稿。等价于 zhihu-sync 的 draft + 已注入上传结果\n * 的图片引用(images[] 对应想法独立媒体区,正文图片在 body 中用\n * SyncImage.originalUrl 标记用户来源,由转换层解析为发布 URL)。\n */\nexport interface SyncDraft {\n type: SyncContentType\n /** 标题。question 结尾无问号时由转换层自动补全「?」;pin 可为空字符串。 */\n title: string\n /**\n * 正文源文本。format='markdown' 时为 Markdown;format='html' 时为\n * 用户/上游给出的 HTML 片段(白名单消毒后使用)。\n */\n body: string\n format: SyncFormat\n /** 图片清单:正文内图片(与 body 中图片按出现顺序一一对应)+ 想法独立媒体图。 */\n images?: SyncImage[]\n /** 想法独立链接卡片(与独立图片互斥,仅 pin)。 */\n linkCard?: SyncLinkCard | null\n settings?: SyncPublishSettings\n}\n\nexport type SyncIssueSeverity = 'error' | 'warning'\n\nexport interface SyncIssue {\n severity: SyncIssueSeverity\n /** 稳定机器码,如 'title.too-long' / 'body.unsafe-html'。 */\n code: string\n message: string\n}\n\nexport interface SyncCounts {\n /** 标题字数(question 含自动补全的问号)。 */\n titleChars: number\n /** 正文纯文本字数(去除 HTML 标签、公式/代码等语义内容仍计入)。 */\n bodyChars: number\n /** 生效图片数:article/question 为正文图片引用数,pin 为独立媒体区图片数。 */\n images: number\n /** 生效的话题数。 */\n topics: number\n}\n\nexport interface SyncPrepared {\n /** 规范化后的草稿(可直接保存)。 */\n draft: SyncDraft\n /** 知乎适配正文 HTML(发布请求 content.html 的来源;想法仅正文部分)。 */\n html: string\n /** 校验/转换问题列表;error 会阻止发布,warning 表示已降级或需注意。 */\n issues: SyncIssue[]\n /** issues 中无 error 级时为 true。 */\n canPublish: boolean\n counts: SyncCounts\n}\n","/**\n * 知乎同步内容转换与校验(确定性实现;规则来源见下,未复制上游代码)。\n *\n * 规则来源(只读镜像 H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-validate/reference/conversion.md — 标题/正文/媒体/链接卡片/公式/表格\n * zhihu-sync/zhihu-validate/reference/config.md — topics/目录/创作声明/评论权限/圈子\n * zhihu-sync/zhihu-validate/reference/code-languages.md — <pre lang> 语言 ID 表\n * zhihu-sync/zhihu-publish/reference/publish-{article,question,pin}.md — 发布字段\n *\n * 分层:\n * prepareZhihuSyncDraft(input) 规范化 SyncDraft → 知乎适配 html + issues + counts\n * buildZhihuPublishRequest(prepared) SyncPrepared → publish OpenAPI request envelope\n *\n * 必须遵守的硬约束(不静默丢内容):\n * - 标题/正文超限 → error issue,不截断、不自动精简(conversion.md §1.3)。\n * - question 标题结尾无问号自动补「?」且计入长度(§2.1)。\n * - 图片必须已上传:SyncImage.url 是发布 URL;originalUrl 仅用于按出现顺序匹配\n * 正文图片引用与预览层展示,永不写入发布 body。正文图片无法解析 → error。\n * - 公式 LaTeX 移除 $/$$ 定界符后【不 trim】;alt 与 src?tex= 来自同一串(§4.11)。\n * - 想法正文仅纯文本段落、text-link、hash_tag(#话题名#,≤10);富文本降级+warning。\n * - 想法独立图片(media 区)与 linkCard 互斥;两者并存 → error(§5.3/5.4)。\n * - HTML 输入经 parse5 白名单消毒:script/iframe/object/embed/svg/style、on*、\n * javascript:|vbscript:|file:|data: URL → error,危险节点不进入输出。\n * - JSON 序列化只发生一次(发布请求体),body 不预转义。\n */\nimport { lexer, type Token, type Tokens } from 'marked'\nimport { parseFragment, type DefaultTreeAdapterMap } from 'parse5'\nimport {\n SYNC_ARTICLE_COMMENT_PERMISSIONS,\n SYNC_ARTICLE_CREATION_STATEMENTS,\n SYNC_PIN_COMMENT_PERMISSIONS,\n type SyncCommentPermission,\n type SyncCreationStatement,\n type SyncDraft,\n type SyncImage,\n type SyncIssue,\n type SyncLinkCard,\n type SyncPrepared,\n} from './sync-types.ts'\n\nexport const SYNC_LIMITS = {\n questionTitleMin: 4,\n questionTitleMax: 51,\n pinTitleMax: 50,\n articleTitleMax: 100,\n articleBodyMin: 9,\n articleBodyMax: 100_000,\n pinBodyMax: 2_000,\n pinImagesMax: 18,\n pinTopicsMax: 10,\n articleTopicsMax: 3,\n questionTopicsMax: 5,\n captionMax: 140,\n} as const\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\n\nconst escapeHtml = (v: string) =>\n v.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\nconst escapeAttr = escapeHtml\n\nfunction isAbsoluteHttpUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'http:' || u.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * 纯文本计数(conversion.md §3「按纯文本计,不含 HTML 标签」):\n * 用 parse5 把 html 解析为文本节点再拼接 —— 解码 & / < 等实体,\n * 不去除用户正文中的正常空白(空格/换行仍算一个字符,按码点计)。\n * 内部占位符(公式/图片)先剔除再计数,不计入用户正文。\n */\nfunction plainTextLength(html: string): number {\n const cleaned = html.replace(/ZHIHUIMG\\d+PLACEHOLDER/g, '').replace(/ZHIHUFORMULA\\d+PLACEHOLDER/g, '')\n const frag = parseFragment(cleaned)\n let text = ''\n const walk = (nodes: DefaultTreeAdapterMap['childNode'][]) => {\n for (const n of nodes) {\n if (n.nodeName === '#text') text += (n as DefaultTreeAdapterMap['textNode']).value\n else walk((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])\n }\n }\n walk(frag.childNodes as DefaultTreeAdapterMap['childNode'][])\n return [...text].length\n}\n\n// ---------------------------------------------------------------------------\n// 支持语言 ID(code-languages.md 全表按 id 录入;未列出 → lang=\"\"。jsx/raw 不支持)\n// ---------------------------------------------------------------------------\n\nconst SUPPORTED_LANGS = new Set([\n 'abap','abnf','actionscript','ada','adl','agda','ahk','alloy','antlr','apacheconf','apl',\n 'applescript','arduino','as','as3','asciidoc','aspectj','asy','at','autoit','awk','basemake',\n 'bash','bat','bbcode','bc','befunge','bib','blitzbasic','blitzmax','bnf','boo','boogie',\n 'brainfuck','bro','bugs','c','c++','c#','ca65','cadl','camkes','cbmbas','ceylon','cfengine3',\n 'cfm','cfs','chai','chapel','cheetah','cirru','clay','clojure','clojurescript','cmake','cobol',\n 'cobolfree','coffee-script','common-lisp','componentpascal','console','control','coq','cpp',\n 'cpp-objdump','cpsa','crmsh','croc','cryptol','csharp','csound','csound-document',\n 'csound-score','css','cuda','cython','d','d-objdump','darcs','dart','debcontrol','debian',\n 'delphi','dg','diff','django','docker','dpatch','dtd','duel','dylan','dylan-console',\n 'dylanlid','ebnf','ecl','eiffel','elixir','elm','emacslisp','erb','erl','erlang','evoque',\n 'factor','fancy','fantom','felix','fortran','foxpro','fsharp','gas','genshi','glsl','gnuplot',\n 'go','golo','gooddata-cl','gosu','groff','groovy','gst','haml','handlebars','haskell','haxeml',\n 'html','httpd','hy','idl','idris','igor','inform6','inform7','ini','io','ioke','irb','jade',\n 'java','js','json','julia','jvm','kal','kconfig','koka','kotlin','lagda','lasso','lhs','lidr',\n 'lighty','lisp','live-script','llvm','logos','logtalk','lua','make','mako','mason','mask',\n 'matlab','md','minid','modelica','modula2','monkey','moocode','moon','mscgen','mupad','mxml',\n 'mysql','nasm','newlisp','nginx','nim','nit','nixos','nsis','numpy','objdump-c','objdump-cpp',\n 'objdump-d','objdump-nasm','objective-c','objective-j','ocaml','octave','odin','ooc','opa',\n 'openedge','pacmanconf','pan','pawn','perl','php','pig','pike','pkgconfig','plpgsql',\n 'postscript','powershell','prolog','properties','protobuf','psql','puppet','py','pycon',\n 'pypylog','pytb','python','python3','qbasic','r','rake','rb','rbcon','rebol','red','redcode',\n 'registry','resources','robotframework','rq','rql','rst','rts','rust','sass','scala','scaml',\n 'scheme','scilab','scss','sh','shell','smarty','snobol','sourceslist','sp','sparql','sql',\n 'squidconf','sre','stan','swift','swig','systemverilog','tads3','tap','tcl','tcsh','tea',\n 'termcap','terminfo','terraform','text','textile','thrift','todotxt','trac-wiki','treetop',\n 'ts','turtle','twig','urbiscript','v','vala','vb.net','vctreestatus','velocity','verilog',\n 'vgl','vhdl','vim','x10','xml','xquery','xslt','xtend','xul+mozpreproc','yaml','yaml+jinja',\n 'zephir',\n])\n\n/** lang ID 归一化:不在支持表 → ''。 */\nexport function normalizeCodeLang(raw: string | undefined | null): string {\n if (!raw) return ''\n const id = raw.trim().toLowerCase()\n return SUPPORTED_LANGS.has(id) ? id : ''\n}\n\nfunction latexToEquationImg(latex: string, block: boolean): string {\n // 不 trim LaTeX;alt 与 src?tex= 用同一串(§4.11)。\n return `<img eeimg=\"${block ? 2 : 1}\" src=\"//www.zhihu.com/equation?tex=${encodeURIComponent(latex)}\" alt=\"${escapeAttr(latex)}\" />`\n}\n\n// ---------------------------------------------------------------------------\n// 转换上下文\n// ---------------------------------------------------------------------------\n\ntype ImageRef = {\n /** 用户在 draft.body 中给出的图片来源(本地路径或 URL,可能是相对形式)。 */\n src: string\n caption: string\n /** 匹配到的已上传 SyncImage(由 prepare 阶段按顺序匹配填充)。 */\n resolved?: SyncImage\n}\n\ntype Placeholders = ReturnType<typeof makePlaceholders>\n\ntype Ctx = {\n issues: SyncIssue[]\n type: SyncDraft['type']\n pinMode: boolean\n /** 正文图片引用,按出现顺序;与 draft.images(已上传清单)一一对应。 */\n bodyImages: ImageRef[]\n /** 公式按文本 token 顺序收集(extractFormulas 在 inlineToHtml 的 text 分支内调用)。 */\n formulas: { block: boolean; latex: string }[]\n ph: Placeholders\n}\n\nconst error = (ctx: Ctx, code: string, message: string) =>\n ctx.issues.push({ severity: 'error', code, message })\nconst warning = (ctx: Ctx, code: string, message: string) =>\n ctx.issues.push({ severity: 'warning', code, message })\n\n// ---------------------------------------------------------------------------\n// Markdown → 知乎 HTML(marked token 树驱动,不用正则解析 HTML)\n// ---------------------------------------------------------------------------\n\n/** 每次 prepare 生成唯一占位前缀,避免与用户正文碰撞。 */\nfunction makePlaceholders(): { img: (i: number) => string; formula: (i: number) => string } {\n const prefix = `ZHIHU_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}_`\n return {\n img: (i: number) => `${prefix}IMG${i}`,\n formula: (i: number) => `${prefix}FORMULA${i}`,\n }\n}\n\nconst VOID_RAW_TAGS = new Set(['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr'])\n\n/** 行内 raw HTML 开标签 → 在 token 流中找匹配 </tag>,把整段 raw 交给 parse5 消毒。\n * marked 会把 <b>x</b> 拆成独立 html token,单独 sanitize 会得到空壳 <b></b>。\n * 返回整段 raw;找不到闭合标签或自闭合/void 时只返回当前 token raw。 */\nfunction rawHtmlRun(tokens: Token[], start: number): { raw: string; next: number } {\n const first = (tokens[start] as Tokens.HTML).raw ?? ''\n const m = first.match(/^<([a-zA-Z][a-zA-Z0-9-]*)\\b/)\n if (!m || /\\/>$/.test(first) || VOID_RAW_TAGS.has(m[1].toLowerCase())) {\n return { raw: first, next: start + 1 }\n }\n const tag = m[1].toLowerCase()\n const closeRe = new RegExp(`^</${tag}\\\\s*>`, 'i')\n for (let i = start + 1; i < tokens.length; i++) {\n const t = tokens[i] as Tokens.Generic\n if (t.type === 'html' && closeRe.test(t.raw ?? '')) {\n return { raw: tokens.slice(start, i + 1).map(x => (x as Tokens.Generic).raw ?? '').join(''), next: i + 1 }\n }\n if (t.type !== 'html' && t.type !== 'text' && t.type !== 'escape') break\n }\n return { raw: first, next: start + 1 }\n}\n\nfunction inlineToHtml(tokens: Token[] | undefined, ctx: Ctx): string {\n if (!tokens?.length) return ''\n let out = ''\n for (let ti = 0; ti < tokens.length; ti++) {\n const t = tokens[ti]\n switch (t.type) {\n case 'text': {\n const tok = t as Tokens.Text\n // marked 的 text token 可能有嵌套 tokens;先 decode HTML 实体再 escape,\n // 避免 < → &lt; 双转义(CommonMark 语义:实体按可见字符处理)。\n // 公式只在纯文本 token 上提取(marked 已隔离 code/html),\n // extractFormulas 返回的占位符原样输出,prepare 末尾统一恢复为 equation img。\n if (tok.tokens?.length) {\n out += inlineToHtml(tok.tokens, ctx)\n } else {\n const base = ctx.formulas.length\n const extracted = extractFormulas(decodeEntities(tok.text), { ...ctx.ph, formula: index => ctx.ph.formula(base + index) })\n ctx.formulas.push(...extracted.formulas)\n out += escapeHtml(extracted.text)\n }\n break\n }\n case 'escape': out += escapeHtml((t as Tokens.Escape).text); break\n case 'strong': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持加粗,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Strong).tokens, ctx)\n } else out += `<b>${inlineToHtml((t as Tokens.Strong).tokens, ctx)}</b>`\n break\n }\n case 'em': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持斜体,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Em).tokens, ctx)\n } else out += `<i>${inlineToHtml((t as Tokens.Em).tokens, ctx)}</i>`\n break\n }\n case 'codespan': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持行内代码,已降级为纯文本。')\n out += escapeHtml((t as Tokens.Codespan).text)\n } else out += `<code>${escapeHtml((t as Tokens.Codespan).text)}</code>`\n break\n }\n case 'del': {\n // 知乎正文无删除线能力:降级为文本(不静默丢内容)。\n warning(ctx, 'unsupported.del', '删除线不受支持,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Del).tokens, ctx)\n break\n }\n case 'link': {\n const link = t as Tokens.Link\n const inner = inlineToHtml(link.tokens, ctx)\n const innerText = inner.replace(/<[^>]*>/g, '')\n const href = (link.href ?? '').trim()\n if (href.startsWith('#')) { out += inner; break } // 页内锚点:保留文字\n if (!isAbsoluteHttpUrl(href)) {\n warning(ctx, 'link.unsafe-url', `链接地址不受支持,已保留为纯文本:${href || '(空)'}。`)\n out += inner\n break\n }\n if (ctx.pinMode) {\n // 想法 text-link(conversion.md §5.1)。\n out += `<a href=\"${escapeAttr(href)}\" data-insert-way=\"force\" data-draft-node=\"inline\" data-draft-type=\"text-link\" data-draft-title=\"${escapeAttr(innerText || href)}\">${inner}</a>`\n } else {\n // 文章/提问文字链接(§4.10)。\n out += `<a href=\"${escapeAttr(href)}\" data-draft-title=\"${escapeAttr(innerText || href)}\">${inner}</a>`\n }\n break\n }\n case 'image': {\n const img = t as Tokens.Image\n const idx = ctx.bodyImages.length\n ctx.bodyImages.push({ src: (img.href ?? '').trim(), caption: img.text ?? '' })\n out += ctx.ph.img(idx)\n break\n }\n case 'br': out += '<br>'; break\n case 'html': {\n // 行内原始 HTML:marked 会把 <b>x</b> 拆成独立 token,需先按 raw 重组\n // 完整片段再交 parse5 消毒(不手写 regex 解析内部结构)。\n const run = rawHtmlRun(tokens, ti)\n ti = run.next - 1\n const raw = run.raw.trim()\n if (raw) out += sanitizeInlineHtml(raw, ctx)\n break\n }\n default: {\n const anyT = t as Tokens.Generic\n if (anyT.tokens?.length) out += inlineToHtml(anyT.tokens, ctx)\n else if (typeof anyT.text === 'string') out += escapeHtml(anyT.text)\n else if (typeof anyT.raw === 'string') out += escapeHtml(anyT.raw)\n }\n }\n }\n return out\n}\n\nfunction blockToHtml(tokens: Token[] | undefined, ctx: Ctx): string {\n if (!tokens?.length) return ''\n let out = ''\n for (const t of tokens) {\n switch (t.type) {\n case 'space': break\n case 'paragraph': {\n const inner = inlineToHtml((t as Tokens.Paragraph).tokens, ctx)\n out += inner.trim() ? `<p>${inner}</p>` : '<p><br></p>'\n break\n }\n case 'heading': {\n const h = t as Tokens.Heading\n const inner = inlineToHtml(h.tokens, ctx)\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持标题,已降级为段落文本。')\n if (inner.trim()) out += `<p>${inner}</p>`\n } else {\n // 知乎标题只到 h3;更深降级为 h3(§4.2:h2/h3)。\n const level = h.depth <= 2 ? 2 : 3\n out += `<h${level}>${inner}</h${level}>`\n }\n break\n }\n case 'hr': out += '<hr />'; break\n case 'code': {\n const code = t as Tokens.Code\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持代码块,已降级为文本。')\n out += `<p>${escapeHtml(code.text.trimEnd())}</p>`\n } else {\n out += `<pre lang=\"${normalizeCodeLang(code.lang)}\">${escapeHtml(code.text.replace(/\\n$/, ''))}</pre>`\n }\n break\n }\n case 'blockquote': {\n const inner = blockToHtml((t as Tokens.Blockquote).tokens, ctx)\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持引用块,已降级为文本。')\n const text = inner.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim()\n if (text) out += `<p>${escapeHtml(text)}</p>`\n } else {\n // 多段引用以 <br> 连接(§4.5)。\n const flattened = inner.replace(/<\\/p>\\s*<p>/g, '<br>').replace(/^<p>/, '').replace(/<\\/p>$/, '')\n out += `<blockquote>${flattened}</blockquote>`\n }\n break\n }\n case 'list': {\n const list = t as Tokens.List\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持列表,已降级为段落文本。')\n for (const item of list.items) {\n const inner = inlineToHtml(item.tokens, ctx)\n if (inner.trim()) out += `<p>${inner}</p>`\n }\n break\n }\n const tag = list.ordered ? 'ol' : 'ul'\n const items = list.items.map(item => `<li>${inlineToHtml(item.tokens, ctx)}</li>`).join('')\n out += `<${tag}>${items}</${tag}>`\n break\n }\n case 'table': {\n const table = t as Tokens.Table\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持表格,已降级为文本。')\n const text = table.raw.replace(/<[^>]+>/g, ' ').replace(/[|:-]+/g, ' ').replace(/\\s+/g, ' ').trim()\n if (text) out += `<p>${escapeHtml(text)}</p>`\n break\n }\n const head = table.header.map(c => `<th>${inlineToHtml(c.tokens, ctx)}</th>`).join('')\n const rows = table.rows.map(r => `<tr>${r.map(c => `<td>${inlineToHtml(c.tokens, ctx)}</td>`).join('')}</tr>`).join('')\n out += `<table data-draft-node=\"block\" data-draft-type=\"table\" data-size=\"normal\" data-row-style=\"normal\"><tbody><tr>${head}</tr>${rows}</tbody></table>`\n break\n }\n case 'html': {\n // 块级原始 HTML:统一走 parse5 消毒(同 format='html'),\n // 不做快速 img/br 提取(那样会绕过 on*/unsafe-url 检测)。\n const raw = ((t as Tokens.HTML).text ?? (t as Tokens.HTML).raw ?? '')\n if (raw.trim()) out += sanitizeInputHtml(raw, ctx)\n break\n }\n default: {\n const anyT = t as Tokens.Generic\n if (anyT.tokens?.length) {\n const inner = blockToHtml(anyT.tokens, ctx)\n if (inner.trim()) out += inner\n } else if (typeof anyT.raw === 'string' && anyT.raw.trim()) {\n out += `<p>${escapeHtml(anyT.raw.trim())}</p>`\n }\n }\n }\n }\n return out\n}\n\n/**\n * 提取 $...$ / $$...$$ 公式(marked 无内建数学扩展,预扫描后在输出中恢复)。\n * 移除定界符后保留内侧全部字符(含首尾空白)——不 trim。\n * 货币场景保真:`$5`、`$10` 这类 $ 紧跟数字的当普通文字,不当公式定界。\n * $$...$$ 始终以 $$ 开头,不受单 $ 货币规则影响。\n */\nfunction extractFormulas(text: string, ph: Placeholders): { text: string; formulas: { block: boolean; latex: string }[] } {\n const formulas: { block: boolean; latex: string }[] = []\n let out = ''\n let i = 0\n while (i < text.length) {\n const dollar = text.indexOf('$', i)\n if (dollar < 0) { out += text.slice(i); break }\n // \\$ 转义不算定界符。\n if (dollar > 0 && text[dollar - 1] === '\\\\') { out += text.slice(i, dollar + 1); i = dollar + 1; continue }\n const isDouble = text[dollar + 1] === '$'\n const openLen = isDouble ? 2 : 1\n // 货币/变量保真:单 $ 紧跟数字 → 价格($5);紧跟两个大写字母 → 环境变量\n // ($HOME、$PATH)。$E=mc^2$ 不受影响(E 后是 =)。\n const afterOpen = text.slice(dollar + openLen, dollar + openLen + 2)\n if (!isDouble && (/\\d/.test(afterOpen[0] ?? '') || /^[A-Z][A-Z]/.test(afterOpen))) {\n out += text.slice(i, dollar + 1); i = dollar + 1; continue\n }\n const close = text.indexOf(isDouble ? '$$' : '$', dollar + openLen)\n if (close < 0) { out += text.slice(i); break }\n const latex = text.slice(dollar + openLen, close)\n if (!latex) { out += text.slice(i, close + openLen); i = close + openLen; continue }\n // 货币尾保真:单 $ 闭定界后紧跟数字(如 $x$5 实为价格片段)→ 整个当普通文字。\n if (!isDouble && /\\d/.test(text[close + openLen] ?? '')) {\n out += text.slice(i, close + openLen); i = close + openLen; continue\n }\n formulas.push({ block: isDouble, latex })\n out += text.slice(i, dollar) + ph.formula(formulas.length - 1)\n i = close + openLen\n }\n return { text: out, formulas }\n}\n\n// ---------------------------------------------------------------------------\n// 想法正文(纯文本段落 + text-link + hash_tag)\n// ---------------------------------------------------------------------------\n\n/**\n * 扫描纯文本里的 #话题名#,转成 hash_tag <a>。\n * 用 parse5 遍历 DOM 树,只在文本节点内做替换——不触碰属性值,\n * 不会在已有 <a> 内嵌套 <a>,不会双转义。\n */\nfunction injectPinHashTags(html: string): { html: string; count: number } {\n const frag = parseFragment(html)\n let count = 0\n const walk = (parent: { childNodes: P5AnyNode[] }) => {\n for (let i = 0; i < parent.childNodes.length; i++) {\n const node = parent.childNodes[i]\n if (node.nodeName === '#text') {\n const text = (node as DefaultTreeAdapterMap['textNode']).value\n if (!text.includes('#')) continue\n // 文本节点内 #xxx# → 拆分:话题部分变 anchor,其余保留为文本。\n const parts: P5AnyNode[] = []\n let last = 0\n text.replace(/#([^#\\n<>]{1,200})#/g, (whole, name: string, offset: number) => {\n if (offset > last) {\n parts.push({ nodeName: '#text', value: text.slice(last, offset), parentNode: null, nextNode: null, prevNode: null } as unknown as P5AnyNode)\n }\n const topic = `#${name}#`\n const anchor = {\n nodeName: 'a', tagName: 'a', attrs: [\n { name: 'class', value: 'hash_tag' },\n { name: 'data-topic-name', value: topic },\n ],\n childNodes: [{ nodeName: '#text', value: topic }],\n namespaceURI: 'http://www.w3.org/1999/xhtml',\n } as unknown as P5Element\n parts.push(anchor as P5AnyNode)\n count += 1\n last = offset + whole.length\n return whole\n })\n if (count > 0 && parts.length) {\n if (last < text.length) parts.push({ nodeName: '#text', value: text.slice(last), parentNode: null, nextNode: null, prevNode: null } as unknown as P5AnyNode)\n parent.childNodes.splice(i, 1, ...parts)\n i += parts.length - 1\n }\n continue\n }\n // 递归进入子节点,但不进入已生成的 hash_tag anchor(避免嵌套 a)。\n const tag = (node as P5Element).tagName?.toLowerCase()\n if (tag === 'a') continue\n if ('childNodes' in node) walk(node as { childNodes: P5AnyNode[] })\n }\n }\n walk(frag as unknown as { childNodes: P5AnyNode[] })\n // serialize 返回包含 entity 的 HTML;对文本节点中的 # 已处理,\n // 对 hash_tag 的文本内容(#话题名#)保持原样。\n const serialized = frag.childNodes.map(c => serializeNode(c)).join('')\n return { html: serialized, count }\n}\n\nfunction serializeNode(node: P5AnyNode): string {\n if (node.nodeName === '#text') {\n return escapeHtml((node as DefaultTreeAdapterMap['textNode']).value)\n }\n const el = node as P5Element\n const tag = el.tagName?.toLowerCase()\n if (!tag) return ''\n if (VOID_TAGS.has(tag)) {\n const attrs = (el.attrs ?? []).map(a => `${a.name}=\"${escapeAttr(a.value)}\"`).join(' ')\n return `<${tag}${attrs ? ' ' + attrs : ''} />`\n }\n const attrs = (el.attrs ?? []).map(a => `${a.name}=\"${escapeAttr(a.value)}\"`).join(' ')\n const inner = (el.childNodes ?? []).map(c => serializeNode(c)).join('')\n return `<${tag}${attrs ? ' ' + attrs : ''}>${inner}</${tag}>`\n}\n\n// ---------------------------------------------------------------------------\n// 正文图片与上传结果的匹配\n// ---------------------------------------------------------------------------\n\n/**\n * 把 draft.body 中的图片引用按出现顺序与 draft.images 中的 SyncImage 匹配。\n * 匹配键:SyncImage.originalUrl 等于引用 src(允许 file:// 归一化差异);\n * 若未提供 originalUrl,则按顺序消费。无法匹配到已上传 URL → error。\n */\nfunction resolveBodyImages(ctx: Ctx, draft: SyncDraft) {\n const uploaded = draft.images ?? []\n const unused = new Set<number>(uploaded.map((_, i) => i))\n const normalize = (v: string) => {\n let s = v.trim().replace(/^file:\\/\\//i, '').replace(/\\\\/g, '/')\n // 图片 src 与 originalUrl 可能在 %20 等编码形式上不一致;\n // 仅在解码无歧义(往返一致)时归一,避免 %25 → % 这类单向下沉。\n try { if (encodeURIComponent(decodeURIComponent(s)) === s) s = decodeURIComponent(s) } catch { /* keep raw */ }\n return s.toLowerCase()\n }\n for (const ref of ctx.bodyImages) {\n let matched = -1\n for (const i of unused) {\n const img = uploaded[i]\n if (img.originalUrl && normalize(img.originalUrl) === normalize(ref.src)) { matched = i; break }\n }\n if (matched < 0) {\n // 顺序回退:仍按声明顺序取第一个未用条目;其 originalUrl 必须等于引用 src 或为空。\n for (const i of unused) {\n const img = uploaded[i]\n if (!img.originalUrl || normalize(img.originalUrl) === normalize(ref.src)) { matched = i; break }\n }\n }\n if (matched < 0) {\n error(ctx, 'image.unresolved', `正文图片未找到对应的上传结果:${ref.src || '(空 src)'}。请先上传图片后再保存/发布。`)\n continue\n }\n unused.delete(matched)\n ref.resolved = uploaded[matched]\n }\n // 声明了上传结果但正文未引用:不静默丢弃,提示用户检查。\n // pin 的 images 属于独立媒体区(不经正文引用),跳过此检查。\n if (!ctx.pinMode) {\n for (const i of unused) {\n warning(ctx, 'image.unused', `已上传图片未在正文中引用:${uploaded[i].originalUrl || uploaded[i].url}。`)\n }\n }\n}\n\n/** 知乎域白名单(与媒体 owner 一致):^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$ */\nconst ZHIHU_DOMAIN_RE = /^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction isZhihuImageUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'https:' && !u.username && !u.password && ZHIHU_DOMAIN_RE.test(u.hostname)\n } catch { return false }\n}\n\nfunction renderBodyImage(ref: ImageRef, ctx: Ctx): string {\n const img = ref.resolved\n if (!img?.url) return ''\n if (!isZhihuImageUrl(img.url)) {\n error(ctx, 'image.unsafe-url', `图片发布 URL 不是 HTTPS 知乎图片域:${img.url}。`)\n return ''\n }\n const caption = ref.caption || ''\n if (caption.length > SYNC_LIMITS.captionMax) {\n error(ctx, 'image.caption-too-long', `图片注释超过 ${SYNC_LIMITS.captionMax} 字,请缩短后重试。`)\n return ''\n }\n const attrs: string[] = [`src=\"${escapeAttr(img.url)}\"`]\n attrs.push(`data-caption=\"${escapeAttr(caption)}\"`)\n const w = img.width ?? 0\n attrs.push(`data-size=\"${w > 0 && w < 343 ? 'small' : 'normal'}\"`)\n if (img.width !== undefined) attrs.push(`data-rawwidth=\"${img.width}\"`)\n if (img.height !== undefined) attrs.push(`data-rawheight=\"${img.height}\"`)\n if (img.watermark) attrs.push('data-watermark=\"watermark\"')\n // data-original-src 只保留知乎域地址;外部原始链接只用于预览匹配,不写入发布 body。\n if (img.originalUrl && isZhihuImageUrl(img.originalUrl)) {\n attrs.push(`data-original-src=\"${escapeAttr(img.originalUrl)}\"`)\n } else {\n attrs.push('data-original-src=\"\"')\n }\n attrs.push('data-watermark-src=\"\"', 'data-private-watermark-src=\"\"')\n return `<img ${attrs.join(' ')} />`\n}\n\n// ---------------------------------------------------------------------------\n// HTML 输入消毒(format='html',parse5 解析;不用正则充当 HTML 解析器)\n// ---------------------------------------------------------------------------\n\ntype P5Element = DefaultTreeAdapterMap['element']\ntype P5AnyNode = DefaultTreeAdapterMap['childNode']\n\nconst SAFE_TAGS = new Set([\n 'p', 'br', 'h2', 'h3', 'b', 'i', 'ol', 'ul', 'li', 'blockquote', 'hr',\n 'pre', 'code', 'sup', 'img', 'a', 'table', 'tbody', 'tr', 'th', 'td',\n])\nconst DANGEROUS_TAGS = new Set([\n 'script', 'iframe', 'object', 'embed', 'svg', 'style', 'link', 'meta', 'base',\n 'form', 'input', 'button', 'select', 'textarea', 'option', 'video', 'audio',\n 'source', 'track', 'noscript', 'template', 'frame', 'frameset', 'applet',\n])\nconst VOID_TAGS = new Set(['br', 'hr', 'img'])\nconst DANGEROUS_URL = /^\\s*(javascript|vbscript|file|data):/i\n\nfunction attr(el: P5Element, name: string): string {\n return el.attrs?.find(a => a.name.toLowerCase() === name)?.value ?? ''\n}\n\nfunction sanitizeAnchorAttrs(el: P5Element, ctx: Ctx): { ok: boolean; html: string } {\n const dataAttrs = Object.fromEntries((el.attrs ?? []).map(a => [a.name, a.value]))\n const isLinkCard = dataAttrs['data-draft-type'] === 'link-card' && dataAttrs['data-draft-node'] === 'block'\n const isTextLink = dataAttrs['data-draft-type'] === 'text-link'\n const isHashTag = (dataAttrs['class'] ?? '').split(/\\s+/).includes('hash_tag') || (dataAttrs['class'] ?? '').includes('zed-topic')\n const href = (dataAttrs['href'] ?? '').trim()\n if (!href || !isAbsoluteHttpUrl(href) || DANGEROUS_URL.test(href)) {\n error(ctx, 'html.unsafe-link-url', `链接地址不受支持,已拒绝:${href || '(空)'}`)\n return { ok: false, html: '' }\n }\n let attrs = `href=\"${escapeAttr(href)}\"`\n if (isHashTag) {\n const topicName = dataAttrs['data-topic-name'] ?? ''\n attrs = `class=\"hash_tag\" href=\"${escapeAttr(href)}\" data-topic-name=\"${escapeAttr(topicName)}\"`\n const topicId = dataAttrs['data-topic-id']\n if (topicId) attrs += ` data-topic-id=\"${escapeAttr(topicId)}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n if (isLinkCard) {\n attrs += ` data-draft-node=\"block\" data-draft-type=\"link-card\" data-draft-title=\"${escapeAttr(dataAttrs['data-draft-title'] ?? '')}\" data-draft-cover=\"${escapeAttr(dataAttrs['data-draft-cover'] ?? '')}\"`\n if (dataAttrs['data-content-type']) attrs += ` data-content-type=\"${escapeAttr(dataAttrs['data-content-type'])}\"`\n if (dataAttrs['data-content-id']) attrs += ` data-content-id=\"${escapeAttr(dataAttrs['data-content-id'])}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n if (isTextLink || ctx.pinMode) {\n attrs += ` data-insert-way=\"force\" data-draft-node=\"inline\" data-draft-type=\"text-link\" data-draft-title=\"${escapeAttr(dataAttrs['data-draft-title'] ?? '')}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n const title = dataAttrs['data-draft-title']\n if (title) attrs += ` data-draft-title=\"${escapeAttr(title)}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n}\n\nfunction sanitizeImg(el: P5Element, ctx: Ctx): string {\n const eeimg = attr(el, 'eeimg')\n const src = (attr(el, 'src') ?? '').trim()\n if (DANGEROUS_URL.test(src)) {\n error(ctx, 'html.unsafe-img-url', `图片地址不受支持,已拒绝:${src || '(空)'}`)\n return ''\n }\n if (eeimg === '1' || eeimg === '2') {\n // 公式图片:src 必须是知乎 equation 地址或占位。\n if (!/^(https?:)?\\/\\/www\\.zhihu\\.com\\/equation\\?/i.test(src) && src) {\n warning(ctx, 'formula.invalid-src', '公式图片地址异常,已重建。')\n const latex = attr(el, 'alt')\n return latexToEquationImg(latex, eeimg === '2')\n }\n const alt = attr(el, 'alt')\n return `<img eeimg=\"${eeimg}\" src=\"${escapeAttr(src)}\" alt=\"${escapeAttr(alt)}\" />`\n }\n // 内容图片:src 必须能匹配已上传结果(originalUrl 或 url)。\n const idx = ctx.bodyImages.length\n ctx.bodyImages.push({ src, caption: attr(el, 'data-caption') || attr(el, 'alt') || '' })\n return ctx.ph.img(idx)\n}\n\nfunction sanitizeSup(el: P5Element, ctx: Ctx): { wrapper: [string, string] | null } {\n // 注释(仅文章;提问不支持)。属性:data-text / data-url / data-numero + 固定两值。\n if (attr(el, 'data-draft-type') !== 'reference') {\n warning(ctx, 'unsupported.sup', '非注释语义的 <sup> 已降级为文本。')\n return { wrapper: null }\n }\n if (ctx.type !== 'article') {\n error(ctx, 'reference.unsupported', '提问与想法不支持注释(<sup data-draft-type=\"reference\">)。')\n return { wrapper: null }\n }\n const text = attr(el, 'data-text')\n const url = attr(el, 'data-url')\n if (!text && !url) {\n error(ctx, 'reference.empty', '注释需要 data-text 或 data-url 至少一项非空。')\n return { wrapper: null }\n }\n if (url && !isAbsoluteHttpUrl(url)) {\n error(ctx, 'reference.bad-url', `注释 data-url 不是合法链接:${url}`)\n return { wrapper: null }\n }\n const numero = attr(el, 'data-numero')\n const attrs = `data-text=\"${escapeAttr(text)}\" data-url=\"${escapeAttr(url)}\" data-draft-node=\"inline\" data-draft-type=\"reference\" data-numero=\"${escapeAttr(numero)}\"`\n return { wrapper: [`<sup ${attrs}>`, '</sup>'] }\n}\n\nfunction sanitizeNode(node: P5AnyNode, ctx: Ctx): string {\n if (node.nodeName === '#text') {\n return escapeHtml((node as DefaultTreeAdapterMap['textNode']).value)\n }\n if (node.nodeName === '#comment' || node.nodeName === '#documentType') return ''\n const el = node as P5Element\n const tag = (el.tagName ?? '').toLowerCase()\n if (!tag) return ''\n if (DANGEROUS_TAGS.has(tag)) {\n error(ctx, 'html.unsafe-tag', `危险标签已移除且阻止发布:<${tag}>`)\n return ''\n }\n const children = (el.childNodes ?? []) as P5AnyNode[]\n const inner = () => children.map(c => sanitizeNode(c, ctx)).join('')\n if (!SAFE_TAGS.has(tag)) {\n // 不危险但不受支持:剥掉标签保留子内容(div/span/font/p→文本流)。\n warning(ctx, 'html.unsupported-tag', `不支持的标签 <${tag}> 已降级为纯文本。`)\n return inner()\n }\n switch (tag) {\n case 'a': {\n const r = sanitizeAnchorAttrs(el, ctx)\n if (!r.ok) return '' // 危险链接:丢弃整个锚(文本也含在锚内,不静默透传危险目标)。\n return r.html.replace('{INNER}', inner())\n }\n case 'img': return sanitizeImg(el, ctx)\n case 'sup': {\n const r = sanitizeSup(el, ctx)\n if (!r.wrapper) return inner()\n return r.wrapper[0] + inner() + r.wrapper[1]\n }\n case 'pre': {\n const lang = normalizeCodeLang(attr(el, 'lang'))\n // <pre><code>text</code></pre>:code 是知乎不支持的包装,取内部纯文本;\n // 不能只看直接子文本节点(那样 <pre><code>body</code></pre> 会丢掉全部正文)。\n const collectText = (nodes: DefaultTreeAdapterMap['childNode'][]): string =>\n nodes.map(n => n.nodeName === '#text'\n ? (n as DefaultTreeAdapterMap['textNode']).value\n : collectText((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])).join('')\n const text = collectText(children)\n return `<pre lang=\"${lang}\">${escapeHtml(text)}</pre>`\n }\n case 'table': {\n return `<table data-draft-node=\"block\" data-draft-type=\"table\" data-size=\"normal\" data-row-style=\"normal\">${inner()}</table>`\n }\n case 'thead': case 'tfoot': {\n // 输出只允许 tbody(§4.12):thead/tfoot 行并入主流。\n warning(ctx, 'table.unsupported-section', `<${tag}> 已并入 tbody。`)\n return inner()\n }\n case 'hr': return '<hr />'\n case 'br': return '<br>'\n case 'code': {\n const text = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持行内代码,已降级为文本。'); return text }\n return `<code>${text}</code>`\n }\n case 'b': case 'i': {\n const content = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', `想法正文不支持 <${tag}>,已降级为文本。`); return content }\n return `<${tag}>${content}</${tag}>`\n }\n case 'h2': case 'h3': {\n const content = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持标题,已降级为文本。'); return `<p>${content}</p>` }\n return `<${tag}>${content}</${tag}>`\n }\n case 'ol': case 'ul': case 'li': case 'p': case 'blockquote': case 'tbody': case 'tr': case 'th': case 'td': {\n if (ctx.pinMode && tag !== 'p') {\n warning(ctx, 'pin.unsupported-rich-text', `想法正文不支持 <${tag}>,已降级为文本。`)\n return inner()\n }\n return `<${tag}>${inner()}</${tag}>`\n }\n default:\n return inner()\n }\n}\n\nfunction sanitizeInputHtml(body: string, ctx: Ctx): string {\n const frag = parseFragment(body)\n return (frag.childNodes as P5AnyNode[]).map(c => sanitizeNode(c, ctx)).join('')\n}\n\n/** 行内原始 HTML 消毒:与 format='html' 走同一 parse5 路径,\n * 但只返回内联内容(不包 <p>),供 markdown 行内 html token 使用。 */\nfunction sanitizeInlineHtml(raw: string, ctx: Ctx): string {\n const frag = parseFragment(raw)\n return (frag.childNodes as P5AnyNode[]).map(c => sanitizeNode(c, ctx)).join('')\n}\n\n/** 用 parse5 把 HTML 实体解码为可见字符(< → <)。 */\nfunction decodeEntities(text: string): string {\n const frag = parseFragment(text)\n let out = ''\n const walk = (nodes: DefaultTreeAdapterMap['childNode'][]) => {\n for (const n of nodes) {\n if (n.nodeName === '#text') out += (n as DefaultTreeAdapterMap['textNode']).value\n else walk((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])\n }\n }\n walk(frag.childNodes as DefaultTreeAdapterMap['childNode'][])\n return out\n}\n\n// ---------------------------------------------------------------------------\n// 规范化输入草稿\n// ---------------------------------------------------------------------------\n\nfunction normalizeDraft(input: unknown, issues: SyncIssue[]): SyncDraft {\n if (!isRecord(input)) {\n issues.push({ severity: 'error', code: 'input.invalid', message: '草稿输入必须是对象。' })\n return { type: 'article', title: '', body: '', format: 'markdown' }\n }\n const type = (['article', 'question', 'pin'].includes(String(input.type))\n ? input.type\n : 'article') as SyncDraft['type']\n if (input.type !== undefined && input.type !== type) {\n issues.push({ severity: 'error', code: 'input.bad-type', message: `不支持的内容形态:${String(input.type)}。` })\n }\n const format: SyncDraft['format'] = input.format === 'html' ? 'html' : 'markdown'\n const draft: SyncDraft = {\n type,\n title: typeof input.title === 'string' ? input.title : '',\n body: typeof input.body === 'string' ? input.body : '',\n format,\n }\n if (input.images !== undefined) {\n if (!Array.isArray(input.images)) {\n issues.push({ severity: 'error', code: 'input.bad-images', message: 'images 必须是数组。' })\n } else {\n draft.images = input.images\n .filter((i): i is SyncImage => isRecord(i))\n .map(i => ({\n url: typeof i.url === 'string' ? i.url : '',\n originalUrl: typeof i.originalUrl === 'string' ? i.originalUrl : undefined,\n width: typeof i.width === 'number' && Number.isFinite(i.width) ? i.width : undefined,\n height: typeof i.height === 'number' && Number.isFinite(i.height) ? i.height : undefined,\n watermark: i.watermark === true,\n mediaId: typeof i.mediaId === 'string' ? i.mediaId : undefined,\n }))\n .filter(i => i.url || i.originalUrl)\n }\n }\n if (input.linkCard !== undefined && input.linkCard !== null) {\n if (isRecord(input.linkCard) && typeof input.linkCard.url === 'string' && input.linkCard.url.trim()) {\n const lc = input.linkCard\n const lcUrl = lc.url as string\n draft.linkCard = {\n url: lcUrl.trim(),\n title: typeof lc.title === 'string' ? lc.title : undefined,\n cover: typeof lc.cover === 'string' ? lc.cover : undefined,\n contentType: typeof lc.contentType === 'string' ? lc.contentType : undefined,\n contentId: typeof lc.contentId === 'string' ? lc.contentId : undefined,\n isFromForward: lc.isFromForward === true ? true : undefined,\n }\n } else {\n issues.push({ severity: 'warning', code: 'input.bad-linkcard', message: 'linkCard 缺少有效 url,已忽略。' })\n }\n // linkCard.url 是外部链接:只需 http(s) + 无 userinfo(与正文 <a> 同策略);\n // linkCard.cover 是图片:需 https + 知乎图片域(与媒体同策略)。\n if (draft.linkCard) {\n if (!isAbsoluteHttpUrl(draft.linkCard.url)) {\n issues.push({ severity: 'error', code: 'linkcard.url-unsafe', message: `linkCard.url 必须是 http(s) 链接:${draft.linkCard.url}` })\n }\n if (draft.linkCard.cover && !isZhihuImageUrl(draft.linkCard.cover)) {\n issues.push({ severity: 'error', code: 'linkcard.cover-unsafe', message: `linkCard.cover 必须是 HTTPS 知乎图片域链接:${draft.linkCard.cover}` })\n }\n }\n }\n if (input.settings !== undefined && input.settings !== null) {\n if (!isRecord(input.settings)) {\n issues.push({ severity: 'warning', code: 'input.bad-settings', message: 'settings 必须是对象,已忽略。' })\n } else {\n const s = input.settings\n const settings: NonNullable<SyncDraft['settings']> = {}\n if (s.commentPermission !== undefined) settings.commentPermission = String(s.commentPermission) as SyncCommentPermission\n if (s.tableOfContentsEnabled !== undefined) settings.tableOfContentsEnabled = s.tableOfContentsEnabled === true\n if (s.creationStatement !== undefined) settings.creationStatement = String(s.creationStatement) as SyncCreationStatement\n if (Array.isArray(s.topics)) settings.topics = s.topics.filter((t): t is string => typeof t === 'string' && !!t.trim()).map(t => t.trim())\n if (typeof s.ring === 'string' && s.ring.trim()) settings.ring = s.ring.trim()\n if (Object.keys(settings).length) draft.settings = settings\n }\n }\n return draft\n}\n\n// ---------------------------------------------------------------------------\n// prepareZhihuSyncDraft\n// ---------------------------------------------------------------------------\n\nexport function prepareZhihuSyncDraft(input: unknown): SyncPrepared {\n const issues: SyncIssue[] = []\n const draft = normalizeDraft(input, issues)\n const ph = makePlaceholders()\n const ctx: Ctx = { issues, type: draft.type, pinMode: draft.type === 'pin', bodyImages: [], formulas: [], ph }\n\n // ---- 标题(§2):question 自动补「?」并计入长度 ----\n let title = draft.title\n if (draft.type === 'question') {\n title = title.trimEnd()\n if (title && !/[??]\\s*$/.test(title)) title += '?'\n draft.title = title\n }\n const titleChars = [...draft.title].length\n if (/\\n/.test(draft.title)) error(ctx, 'title.multiline', '标题禁止换行。')\n if (draft.type === 'question') {\n if (titleChars === 0) error(ctx, 'title.required', '提问标题必填。')\n if (titleChars > 0 && titleChars < SYNC_LIMITS.questionTitleMin) {\n error(ctx, 'title.too-short', `提问标题 4~51 字(含结尾问号),当前 ${titleChars} 字。`)\n }\n if (titleChars > SYNC_LIMITS.questionTitleMax) {\n error(ctx, 'title.too-long', `提问标题 4~51 字(含结尾问号),当前 ${titleChars} 字。`)\n }\n const questionMarks = (draft.title.match(/[??]/g) ?? []).length\n if (questionMarks > 1) {\n warning(ctx, 'title.multi-question', '标题含多个问号,请确认只表达一个明确问题。')\n }\n } else if (draft.type === 'pin') {\n if (titleChars > SYNC_LIMITS.pinTitleMax) {\n error(ctx, 'title.too-long', `想法标题最多 ${SYNC_LIMITS.pinTitleMax} 字,当前 ${titleChars} 字。`)\n }\n } else {\n if (titleChars === 0) error(ctx, 'title.required', '文章标题必填。')\n if (titleChars > SYNC_LIMITS.articleTitleMax) {\n error(ctx, 'title.too-long', `文章标题最多 ${SYNC_LIMITS.articleTitleMax} 字,当前 ${titleChars} 字。`)\n }\n }\n\n // ---- 正文转换 ----\n // markdown:先 lexer 再逐 text token 提取公式——code/codespan/html token\n // 不参与,避免 ```$x$``` 与行内代码里的 $ 被误判为公式定界符。\n let html = ''\n if (draft.format === 'markdown') {\n const tokens = lexer(draft.body) as Token[]\n html = blockToHtml(tokens, ctx)\n } else {\n html = sanitizeInputHtml(draft.body, ctx)\n }\n\n // 恢复公式占位符(前缀含 prepare 内随机成分,不会与正文碰撞)。\n // 占位符在 escapeHtml 之后仍是纯字母数字下划线,不会被转义破坏。\n const formulaPrefix = ph.formula(0).replace(/0$/, '')\n const formulaRe = new RegExp(`${formulaPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\d+)`, 'g')\n html = html.replace(formulaRe, (_, idx: string) => {\n const f = ctx.formulas[Number(idx)]\n return f ? latexToEquationImg(f.latex, f.block) : ''\n })\n\n // 想法:纯文本扫描 #话题# → hash_tag;文章/提问不处理话题语法。\n let pinTopicCount = 0\n if (draft.type === 'pin') {\n const injected = injectPinHashTags(html)\n html = injected.html\n pinTopicCount = injected.count\n if (pinTopicCount > SYNC_LIMITS.pinTopicsMax) {\n error(ctx, 'topics.too-many', `想法话题最多 ${SYNC_LIMITS.pinTopicsMax} 个,当前 ${pinTopicCount} 个。`)\n }\n }\n\n // 正文图片:按出现顺序匹配上传结果。\n resolveBodyImages(ctx, draft)\n const imgPrefix = ph.img(0).replace(/0$/, '')\n const imgRe = new RegExp(`${imgPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\d+)`, 'g')\n html = html.replace(imgRe, (_, idx: string) => {\n // 想法正文不支持内嵌图片(§5.3):只从 body 中移除占位符,不渲染 img 标签;\n // 已上传的媒体由下方移入独立 media 区。\n if (ctx.pinMode) return ''\n return renderBodyImage(ctx.bodyImages[Number(idx)], ctx)\n })\n\n // 想法:draft.images 全部属于独立媒体区(正文里出现的图片 markdown 不合法)。\n if (draft.type === 'pin' && ctx.bodyImages.length) {\n // pin 正文图片引用不进入 body;把它们移到独立媒体区(顺序在已有 images 之后)。\n const stray = ctx.bodyImages.filter(r => r.resolved?.url)\n if (stray.length) {\n warning(ctx, 'pin.body-image-moved', '想法正文不支持内嵌图片,已将正文图片移入独立媒体区。')\n draft.images = [...(draft.images ?? []).filter(i => !stray.some(r => r.resolved === i)), ...stray.map(r => r.resolved!)]\n }\n }\n\n // ---- 正文长度 ----\n const bodyChars = plainTextLength(html)\n if (draft.type === 'article') {\n if (bodyChars < SYNC_LIMITS.articleBodyMin) {\n error(ctx, 'body.too-short', `文章正文至少 ${SYNC_LIMITS.articleBodyMin} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n if (bodyChars > SYNC_LIMITS.articleBodyMax) {\n error(ctx, 'body.too-long', `文章正文最多 ${SYNC_LIMITS.articleBodyMax} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n } else if (draft.type === 'pin') {\n if (bodyChars > SYNC_LIMITS.pinBodyMax) {\n error(ctx, 'body.too-long', `想法正文最多 ${SYNC_LIMITS.pinBodyMax} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n const hasContent = bodyChars > 0 || (draft.images?.length ?? 0) > 0 || !!draft.linkCard\n if (!hasContent) error(ctx, 'body.required', '想法正文、图片或链接卡片至少需要一项有效内容。')\n // publish-pin.md:content.html 必须非空(validate 允许空正文仅当有媒体/卡片)。\n else if (bodyChars === 0) error(ctx, 'body.required', '想法正文不能为空,请补充正文内容。')\n }\n\n // ---- 想法互斥:独立图片 vs linkCard(publish-pin.md:同时存在即拒绝发布)----\n const pinImages = draft.type === 'pin' ? (draft.images ?? []).filter(i => i.url) : []\n if (draft.type === 'pin') {\n // pin 独立媒体图也必须是 HTTPS 知乎域(预览/发布都会请求)。\n for (const img of pinImages) {\n if (!isZhihuImageUrl(img.url)) {\n error(ctx, 'image.unsafe-url', `独立图片发布 URL 不是 HTTPS 知乎图片域:${img.url}。`)\n }\n }\n if (pinImages.length > SYNC_LIMITS.pinImagesMax) {\n error(ctx, 'images.too-many', `想法独立图片最多 ${SYNC_LIMITS.pinImagesMax} 张,当前 ${pinImages.length} 张。`)\n }\n if (pinImages.length && draft.linkCard) {\n error(ctx, 'pin.media-linkcard-exclusive', '单条想法不支持同时包含独立图片与链接卡片。')\n }\n }\n\n // ---- 发布设置校验(不修改 draft.settings;错误只进 issues)----\n validateSettings(draft, issues, pinImages.length)\n\n return {\n draft,\n html,\n issues,\n canPublish: !issues.some(i => i.severity === 'error'),\n counts: {\n titleChars,\n bodyChars,\n images: draft.type === 'pin' ? pinImages.length : ctx.bodyImages.length,\n topics: draft.type === 'pin' ? pinTopicCount : (draft.settings?.topics?.length ?? 0),\n },\n }\n}\n\nfunction validateSettings(draft: SyncDraft, issues: SyncIssue[], pinImageCount: number) {\n const ctx: Ctx = { issues, type: draft.type, pinMode: draft.type === 'pin', bodyImages: [], formulas: [], ph: { img: () => '', formula: () => '' } }\n const s = draft.settings ?? {}\n\n // 形态-设置项支持矩阵(config.md §7 / publish-*.md)。\n if (draft.type === 'question') {\n for (const [key, label] of [\n ['commentPermission', '评论权限'],\n ['creationStatement', '创作声明'],\n ['tableOfContentsEnabled', '目录'],\n ['ring', '圈子'],\n ] as const) {\n if (s[key as keyof typeof s] !== undefined) warning(ctx, `settings.unsupported-${key}`, `提问不支持${label}配置,发布时将忽略。`)\n }\n }\n if (draft.type === 'article') {\n if (s.ring !== undefined) warning(ctx, 'settings.unsupported-ring', '文章不支持圈子配置,发布时将忽略。')\n }\n if (s.topics?.length) {\n if (draft.type === 'pin') {\n warning(ctx, 'settings.unsupported-topics', '想法话题属于正文(#话题名#),不使用 settings.topics,发布时将忽略。')\n } else {\n const max = draft.type === 'article' ? SYNC_LIMITS.articleTopicsMax : SYNC_LIMITS.questionTopicsMax\n const valid = s.topics.filter(t => strictZhihuUrl(t, /^\\/topic\\/(\\d+)(?:\\/|$)/))\n if (valid.length !== s.topics.length) {\n error(ctx, 'topics.invalid', '话题必须是知乎话题链接(https://www.zhihu.com/topic/{token}/...)。')\n }\n if (s.topics.length > max) {\n error(ctx, 'topics.too-many', `${draft.type === 'article' ? '文章' : '提问'}话题最多 ${max} 个,当前 ${s.topics.length} 个。`)\n }\n }\n }\n if (s.commentPermission !== undefined && draft.type !== 'question') {\n const allowed: readonly string[] = draft.type === 'pin' ? SYNC_PIN_COMMENT_PERMISSIONS : SYNC_ARTICLE_COMMENT_PERMISSIONS\n if (!allowed.includes(s.commentPermission)) {\n error(ctx, 'comment-permission.invalid', `当前形态不支持的评论权限:${String(s.commentPermission)}。`)\n }\n }\n if (s.creationStatement !== undefined) {\n if (draft.type === 'pin') {\n if (s.creationStatement !== 'ai_creation') {\n error(ctx, 'creation-statement.invalid', `想法仅支持 ai_creation 创作声明,当前:${String(s.creationStatement)}。`)\n }\n } else if (draft.type === 'article') {\n if (!(SYNC_ARTICLE_CREATION_STATEMENTS as readonly string[]).includes(String(s.creationStatement))) {\n error(ctx, 'creation-statement.invalid', `不支持的创作声明:${String(s.creationStatement)}。`)\n }\n }\n }\n if (s.ring !== undefined && draft.type === 'pin') {\n if (!strictZhihuUrl(s.ring, /^\\/ring\\/host\\/(\\d+)(?:\\/|$)/)) {\n error(ctx, 'ring.invalid', '圈子必须是知乎圈子链接(https://www.zhihu.com/ring/host/{id})。')\n }\n }\n void pinImageCount\n}\n\n// ---------------------------------------------------------------------------\n// buildZhihuPublishRequest(publish-*.md 字段映射;不二次校验内容字段)\n// ---------------------------------------------------------------------------\n\nexport type ZhihuPublishRequest = {\n type: SyncDraft['type']\n confirmed: true\n confirm_note: string\n content: Record<string, unknown>\n}\n\nexport function buildZhihuPublishRequest(prepared: unknown): ZhihuPublishRequest {\n if (!isRecord(prepared) || prepared.canPublish !== true || !isRecord(prepared.draft)) {\n throw new Error('buildZhihuPublishRequest: 需要 prepareZhihuSyncDraft 返回且 canPublish 为 true 的结果。')\n }\n const draft = prepared.draft as unknown as SyncDraft\n const html = typeof prepared.html === 'string' ? prepared.html : ''\n const s = draft.settings ?? {}\n const base = { type: draft.type, confirmed: true as const, confirm_note: 'confirmed by user after local preview' }\n\n if (draft.type === 'article') {\n const content: Record<string, unknown> = {\n title: draft.title,\n html,\n comment_permission: s.commentPermission ?? 'all',\n table_of_contents_enabled: s.tableOfContentsEnabled ?? false,\n }\n if (s.creationStatement) content.creation_statement = s.creationStatement\n const topics = extractTopicTokens(s.topics ?? [], SYNC_LIMITS.articleTopicsMax)\n if (topics.length) content.topics = topics\n return { ...base, content }\n }\n\n if (draft.type === 'question') {\n const content: Record<string, unknown> = { title: draft.title, html }\n const topics = extractTopicTokens(s.topics ?? [], SYNC_LIMITS.questionTopicsMax)\n if (topics.length) content.topics = topics\n return { ...base, content }\n }\n\n // pin:publish 要求 content.html 非空(publish-pin.md 前置校验)。\n const content: Record<string, unknown> = {\n title: draft.title,\n html,\n comment_permission: s.commentPermission ?? 'all',\n }\n const images = (draft.images ?? []).filter(i => typeof i.url === 'string' && i.url)\n if (images.length) {\n // publish-pin.md:images[].url 优先水印 URL 再原图 URL——SyncImage.url 已按此约定填充。\n content.images = images.map(i => ({ url: i.url }))\n } else if (draft.linkCard) {\n const lc = draft.linkCard\n content.linkcard = {\n data_content_type: lc.contentType ?? 'common_url',\n data_content_id: lc.contentId ?? '0',\n url: lc.url,\n data_draft_title: lc.title ?? '',\n data_draft_cover: lc.cover ?? '',\n is_from_forward: lc.isFromForward ?? false,\n }\n }\n if (s.creationStatement === 'ai_creation') content.creation_statement = 'ai_creation'\n const ring = extractRingId(s.ring)\n if (ring) content.ring = ring\n return { ...base, content }\n}\n\nfunction strictZhihuUrl(raw: string, pathRe: RegExp): string | '' {\n try {\n const u = new URL(raw)\n if (u.protocol !== 'https:' || u.username || u.password || u.port) return ''\n if (u.hostname !== 'www.zhihu.com') return ''\n const m = u.pathname.match(pathRe)\n return m?.[1] ?? ''\n } catch { return '' }\n}\n\nfunction extractTopicTokens(urls: string[], max: number): { topic_id: string; topic_token: string; topic_name: string }[] {\n const out: { topic_id: string; topic_token: string; topic_name: string }[] = []\n const seen = new Set<string>()\n for (const u of urls) {\n const token = strictZhihuUrl(String(u), /^\\/topic\\/(\\d+)(?:\\/|$)/)\n if (!token || seen.has(token)) continue\n seen.add(token)\n out.push({ topic_id: '', topic_token: token, topic_name: '' })\n if (out.length >= max) break\n }\n return out\n}\n\nfunction extractRingId(url: string | undefined): { ring_id: string; ring_name: string } | undefined {\n if (!url) return undefined\n const id = strictZhihuUrl(url, /^\\/ring\\/host\\/(\\d+)(?:\\/|$)/)\n return id ? { ring_id: id, ring_name: '' } : undefined\n}\n","/**\n * 知乎 OpenAPI 发布/媒体上传共享 HTTP 层(HMAC-SHA256 签名)。\n *\n * 契约来源(只读镜像 H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-publish/reference/publish-openapi.md — POST /openapi/publish\n * zhihu-mediacloud-uploader/mediacloud_uploader/api.py — /openapi/media/* 上传三步\n *\n * 签名:\n * sign_string = app_key:{X-App-Key}|ts:{X-Timestamp}|logid:{X-Log-Id}|extra_info:{X-Extra-Info}\n * X-Sign = Base64(HMAC-SHA256(sign_string, app_secret))\n *\n * 边界:\n * - 请求目标 origin 固定为 openapi.zhihu.com。baseUrl 仅用于同源等价地址\n * (如企业内部镜像);任何非该 origin 的相对/绝对地址一律拒绝——媒体上传\n * commit_address 等同源绝对 URL 也走同一校验。\n * - 不跟随重定向(redirect:'error')。\n * - appSecret / X-Sign / 上游 msg 中的长 base64 串永不进入返回值、错误或日志;\n * 错误消息统一过 sanitizeMessage。\n * - init.headers 只能「添加」业务头(媒体场景名/安全 token/内容 hash 等),\n * 不能覆盖 Content-Type 与任何 X-* 签名头;冲突时抛 INVALID_URL。\n * - 信封解包:body 带 status 字段按发布信封(status!==0 → UPSTREAM_REJECTED,\n * definite);媒体 Apply/Commit 返回扁平 JSON(无 status)时原样返回。\n */\nimport { createHmac, randomUUID } from 'node:crypto'\nimport { buildZhihuPublishRequest } from './sync-content.ts'\n\nexport const ZHIHU_SYNC_DEFAULT_BASE_URL = 'https://openapi.zhihu.com'\nexport const ZHIHU_SYNC_PUBLISH_PATH = '/openapi/publish'\nexport const ZHIHU_SYNC_DEFAULT_TIMEOUT_MS = 30_000\nexport const ZHIHU_SYNC_ALLOWED_ORIGIN = 'https://openapi.zhihu.com'\n\nexport type ZhihuSyncErrorCode =\n | 'TOKEN_MISSING'\n | 'INVALID_URL'\n | 'CANCELLED'\n | 'TIMEOUT'\n | 'NETWORK_ERROR'\n | 'AUTH_REJECTED'\n | 'RATE_LIMITED'\n | 'UPSTREAM_REJECTED'\n | 'HTTP_ERROR'\n | 'BAD_RESPONSE'\n\n/**\n * 发布/媒体调用错误。definite === true 表示已确定「这次请求没有成功」\n * (上游业务拒绝 / 鉴权拒绝 / 限流 / 参数错误 / URL 非法),可安全重试;\n * definite === false 表示结果不确定(超时 / 网络错 / 5xx / 异常响应),\n * service 必须保留 publishing 意图并提示用户核对,避免重复发送。\n */\nexport class ZhihuSyncApiError extends Error {\n constructor(\n readonly code: ZhihuSyncErrorCode,\n message: string,\n readonly definite: boolean,\n readonly httpStatus?: number,\n readonly upstreamStatus?: number,\n readonly upstreamMsg?: string,\n ) {\n super(message)\n this.name = 'ZhihuSyncApiError'\n }\n}\n\nexport type ZhihuSyncTransportOptions = {\n /** 由调用方解析凭证;本层不持久化也不输出。 */\n resolveCredentials: () => Promise<{ appKey: string; appSecret: string }>\n /** 默认 globalThis.fetch;测试注入假 fetcher 验证请求。 */\n fetcher?: typeof fetch\n /** 单次请求超时(毫秒),默认 30s。 */\n timeoutMs?: number\n /**\n * 允许的请求 origin。即使显式传入,也仍要求与请求目标一致且为 HTTPS;\n * 推荐保持缺省 https://openapi.zhihu.com,测试用 fetcher 即可拦截。\n */\n baseUrl?: string\n}\n\nexport type ZhihuSyncRequestInit = {\n method: 'GET' | 'POST' | 'PATCH'\n /** JSON body;自动 stringify 一次。 */\n body?: unknown\n /**\n * 附加业务头(媒体场景名 X-Media-Scene-Name、X-Security-Token、\n * X-Upload-Content-Type / -Hash 等)。不允许覆盖签名/内容类型头。\n */\n headers?: Record<string, string>\n /** 参与签名的 X-Extra-Info;缺省空字符串,即使为空也必须发送该头。 */\n extraInfo?: string\n}\n\nconst SIGNED_HEADER_NAMES = new Set([\n 'content-type',\n 'x-app-key',\n 'x-timestamp',\n 'x-log-id',\n 'x-extra-info',\n 'x-sign',\n])\n\n/**\n * 上游错误消息脱敏。secrets 可能以任意长度/形态出现在上游 msg、HTTP body 或\n * 网络错误 message 中——只做长 base64/hex 替换不足以保护短 secret 或 appKey,\n * 因此对「当前凭据」与「签名值」做精确字符串替换,其余仍按长度折叠。\n */\nfunction sanitizeMessage(value: unknown, secrets: readonly string[] = []): string {\n if (typeof value !== 'string' || !value) return ''\n let out = value\n for (const s of secrets) {\n if (s) out = out.split(s).join('<redacted>')\n }\n return out\n .replace(/[A-Za-z0-9+/=]{24,}/g, '<redacted>')\n .replace(/[0-9a-f]{32,}/gi, '<redacted>')\n .slice(0, 400)\n}\n\nfunction resolveBase(options: ZhihuSyncTransportOptions): URL {\n const raw = options.baseUrl ?? ZHIHU_SYNC_DEFAULT_BASE_URL\n let base: URL\n try {\n base = new URL(raw)\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的 baseUrl:${sanitizeMessage(raw)}`, true)\n }\n // 即使显式传入也只允许官方源;测试应注入 fetcher 而不是放宽目标主机。\n if (base.protocol !== 'https:' || base.origin !== ZHIHU_SYNC_ALLOWED_ORIGIN) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `OpenAPI baseUrl 必须等于 ${ZHIHU_SYNC_ALLOWED_ORIGIN}。`,\n true,\n )\n }\n return base\n}\n\nfunction syncUrl(pathOrUrl: string, base: URL): string {\n let url: URL\n if (/^https?:\\/\\//i.test(pathOrUrl)) {\n try {\n url = new URL(pathOrUrl)\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的请求地址:${sanitizeMessage(pathOrUrl)}`, true)\n }\n } else {\n try {\n url = new URL(pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`, base.origin + '/')\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的请求路径:${sanitizeMessage(pathOrUrl)}`, true)\n }\n }\n if (url.protocol !== 'https:' || url.username || url.password) {\n throw new ZhihuSyncApiError('INVALID_URL', '请求地址必须为无账号密码的 HTTPS。', true)\n }\n if (url.origin !== base.origin) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `拒绝向非 OpenAPI 源发起请求:${url.origin}(允许 ${base.origin})。`,\n true,\n )\n }\n return url.toString()\n}\n\nfunction sign(appKey: string, timestamp: string, logId: string, extraInfo: string, secret: string): string {\n const signString = `app_key:${appKey}|ts:${timestamp}|logid:${logId}|extra_info:${extraInfo}`\n return createHmac('sha256', secret).update(signString, 'utf8').digest('base64')\n}\n\nfunction buildHeaders(\n appKey: string,\n timestamp: string,\n logId: string,\n extraInfo: string,\n appSecret: string,\n extraHeaders: Record<string, string> | undefined,\n): Record<string, string> {\n const signed: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-App-Key': appKey,\n 'X-Timestamp': timestamp,\n 'X-Log-Id': logId,\n 'X-Extra-Info': extraInfo,\n 'X-Sign': sign(appKey, timestamp, logId, extraInfo, appSecret),\n }\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n const lower = name.toLowerCase()\n if (SIGNED_HEADER_NAMES.has(lower)) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `init.headers 不允许覆盖签名或内容类型头:${name}。`,\n true,\n )\n }\n signed[name] = value\n }\n return signed\n}\n\n/**\n * 发起一次签名 OpenAPI 请求并解包响应。\n *\n * 返回规则:\n * - body 是对象且带 status 字段(发布信封):status===0 → 返回 body.data;\n * 其余 → UPSTREAM_REJECTED(definite,消息含 status / 已脱敏 msg)。\n * - body 不带 status(媒体 Apply/Commit 扁平响应):原样返回 body。\n * - 非 2xx:401→AUTH_REJECTED、429→RATE_LIMITED、5xx→HTTP_ERROR(不确定)、\n * 其他 4xx→HTTP_ERROR(definite);若 body 是可解析信封仍按信封处理。\n */\nexport async function syncRequest(\n pathOrUrl: string,\n init: ZhihuSyncRequestInit,\n options: ZhihuSyncTransportOptions,\n signal?: AbortSignal,\n): Promise<unknown> {\n const base = resolveBase(options)\n const url = syncUrl(pathOrUrl, base)\n const credentials = await options.resolveCredentials()\n const appKey = credentials.appKey?.trim()\n const appSecret = credentials.appSecret?.trim()\n if (!appKey || !appSecret) {\n throw new ZhihuSyncApiError('TOKEN_MISSING', '缺少知乎 OpenAPI 凭证(app key / app secret)。', true)\n }\n\n const timestamp = String(Math.floor(Date.now() / 1000))\n const logId = randomUUID().replace(/-/g, '')\n const extraInfo = init.extraInfo ?? ''\n const headers = buildHeaders(appKey, timestamp, logId, extraInfo, appSecret, init.headers)\n\n const fetcher = options.fetcher ?? globalThis.fetch\n if (!fetcher) throw new ZhihuSyncApiError('NETWORK_ERROR', '当前环境没有可用的 fetch。', false)\n\n // 参与脱敏的机密集合:凭据与本次签名值(X-Sign)。\n const secrets = [appKey, appSecret, headers['X-Sign']].filter(Boolean) as string[]\n\n const timeout = options.timeoutMs ?? ZHIHU_SYNC_DEFAULT_TIMEOUT_MS\n const timeoutController = new AbortController()\n const timer = setTimeout(() => timeoutController.abort(), timeout)\n const combined = signal\n ? AbortSignal.any([signal, timeoutController.signal])\n : timeoutController.signal\n\n let response: Response\n let text: string\n let body: unknown\n try {\n // pre-fetch abort(用户主动取消)是确定行为;fetch 已发出后 abort 结果不定。\n if (signal?.aborted) throw new ZhihuSyncApiError('CANCELLED', '请求已取消。', true)\n combined.throwIfAborted()\n response = await fetcher(url, {\n method: init.method,\n headers,\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n redirect: 'error',\n signal: combined,\n })\n // 超时覆盖整个请求:headers 返回不代表结束,body 读取同样计入。\n // response.text() 不接收 abort signal——手动 race 超时;finally 移除监听防泄漏。\n let abortReject!: (e: unknown) => void\n const abortPromise = new Promise<never>((_, reject) => {\n abortReject = () => reject(new DOMException('aborted', 'AbortError'))\n combined.addEventListener('abort', abortReject, { once: true })\n })\n // 未决 reject 被 GC 前无害:race 已结束后 abort 触发只产生未消费拒绝。\n abortPromise.catch(() => {})\n try {\n text = await Promise.race([response.text(), abortPromise])\n } finally {\n combined.removeEventListener('abort', abortReject)\n }\n if (text) body = JSON.parse(text)\n } catch (error) {\n if (error instanceof ZhihuSyncApiError) throw error\n if (signal?.aborted) throw new ZhihuSyncApiError('CANCELLED', '请求已取消。', false)\n if (timeoutController.signal.aborted) {\n throw new ZhihuSyncApiError('TIMEOUT', `请求超时(${Math.round(timeout / 1000)}s)。`, false)\n }\n // JSON 解析失败(body 非 JSON)→ BAD_RESPONSE;其余 fetch/body 错误 → NETWORK_ERROR。\n // 非 JSON 响应结果不定(可能是代理/网关截断),始终非 definite——\n // 上游返回 5xx 时 body 可能是 HTML,同样不能当确定失败。\n if (error instanceof SyntaxError) {\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `响应不是合法 JSON(HTTP ${response!.status})。`,\n false,\n response!.status,\n )\n }\n // 网络错误不透传原始 message——可能含目标主机/凭据线索;统一脱敏。\n throw new ZhihuSyncApiError(\n 'NETWORK_ERROR',\n '网络请求失败,请检查连接后重试。',\n false,\n )\n } finally {\n clearTimeout(timer)\n }\n\n const envelope = body && typeof body === 'object' && !Array.isArray(body)\n ? (body as Record<string, unknown>)\n : undefined\n const hasStatus = envelope !== undefined && 'status' in envelope\n\n if (!response.ok) {\n if (hasStatus && typeof envelope!.status === 'number' && envelope!.status !== 0) {\n throw new ZhihuSyncApiError(\n 'UPSTREAM_REJECTED',\n `发布被服务端拒绝(status=${envelope!.status}):${sanitizeMessage(envelope!.msg, secrets)}`,\n true,\n response.status,\n envelope!.status,\n sanitizeMessage(envelope!.msg, secrets),\n )\n }\n if (hasStatus && typeof envelope!.status !== 'number') {\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `发布响应 status 不是数字(收到 ${typeof envelope!.status})。`,\n false,\n response.status,\n )\n }\n if (response.status === 401) {\n throw new ZhihuSyncApiError('AUTH_REJECTED', '鉴权失败(HTTP 401)。请检查 app key / app secret。', true, 401)\n }\n if (response.status === 429) {\n throw new ZhihuSyncApiError('RATE_LIMITED', '发布频率超限(每人每天最多 50 次)。', true, 429)\n }\n throw new ZhihuSyncApiError(\n 'HTTP_ERROR',\n `HTTP ${response.status}:${sanitizeMessage(envelope?.msg, secrets) || '请求失败'}`,\n response.status >= 400 && response.status < 500,\n response.status,\n undefined,\n sanitizeMessage(envelope?.msg, secrets),\n )\n }\n\n if (hasStatus) {\n if (typeof envelope!.status === 'number' && envelope!.status === 0) {\n return envelope!.data\n }\n // status 非 0 数字 → 确定的业务拒绝;status 非数字(字符串/其他类型)→ 畸形响应。\n if (typeof envelope!.status === 'number') {\n throw new ZhihuSyncApiError(\n 'UPSTREAM_REJECTED',\n `发布被服务端拒绝(status=${envelope!.status}):${sanitizeMessage(envelope!.msg, secrets)}`,\n true,\n response.status,\n envelope!.status,\n sanitizeMessage(envelope!.msg, secrets),\n )\n }\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `发布响应 status 不是数字(收到 ${typeof envelope!.status})。`,\n false,\n response.status,\n )\n }\n return body\n}\n\n/** 知乎发布成功 URL:必须是 https 且属于知乎域(与 content 的 ZHIHU_DOMAIN_RE 一致)。 */\nconst ZHIHU_PUBLISH_DOMAIN_RE = /^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction isValidPublishUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'https:' && !u.username && !u.password && ZHIHU_PUBLISH_DOMAIN_RE.test(u.hostname)\n } catch { return false }\n}\n\n/**\n * 将 prepareZhihuSyncDraft 的结果组装并发送到 /openapi/publish。\n *\n * 发送前不做内容字段二次校验(发布字段唯一事实源是 SyncPrepared);\n * 上层负责:预览确认、持久化 publishing 意图、按 definite 标记结果。\n */\nexport async function publishZhihuSync(\n prepared: unknown,\n options: ZhihuSyncTransportOptions,\n signal?: AbortSignal,\n): Promise<{ url: string; contentToken: string; type: string }> {\n const request = buildZhihuPublishRequest(prepared)\n const data = await syncRequest(ZHIHU_SYNC_PUBLISH_PATH, { method: 'POST', body: request }, options, signal)\n // syncRequest 只在上游 status===0 时返回 data;扁平 {url,content_token} 无 status\n // 不会被当成成功(publish-openapi.md:成功判断 = status===0)。\n const row = (data ?? {}) as Record<string, unknown>\n const contentToken =\n typeof row.content_token === 'string' || typeof row.content_token === 'number'\n ? String(row.content_token)\n : ''\n const url = typeof row.url === 'string' ? row.url : ''\n const type = typeof row.type === 'string' ? row.type : ''\n if (!contentToken || !url) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', '发布成功响应缺少 content_token 或 url。', false)\n }\n if (!isValidPublishUrl(url)) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', `发布成功响应返回了无效的 url:${sanitizeMessage(url, [])}`, false)\n }\n if (type !== request.type) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', `发布响应 type 与请求不一致:${String(type)} ≠ ${request.type}。`, false)\n }\n return { url, contentToken, type }\n}\n","/**\n * 知乎官方媒体上传(仅图片)。协议来源:只读参考\n * zhihu-mediacloud-uploader v0.1.4(mediacloud_uploader/api.py、client.py、cloud/ali.py、\n * watermark_poller.py、image_validator.py),移植为 Node TypeScript 原生 fetch。\n *\n * 流程:本地校验 → ApplyUpload(POST /openapi/media/uploads)→ 阿里 OSS PutObject\n * (自建 HTTPS 直传,OSS v1 签名)→ CommitUpload(PATCH)→ 水印轮询\n * (GET /openapi/media/images/{id}/watermark)。秒传(UPLOAD_SUCCESS)跳过云直传。\n *\n * 安全约束:重定向拒绝;OSS 目的主机仅允许 https 且 *.aliyuncs.com;\n * 凭据、STS、签名绝不进入返回值或错误消息(统一脱敏)。\n */\nimport { createHash, createHmac } from 'node:crypto'\nimport { syncRequest, type ZhihuSyncTransportOptions } from './sync-api.ts'\n\nexport type ZhihuSyncMediaOptions = ZhihuSyncTransportOptions & {\n /** 测试注入点;缺省用 ./sync-api.ts 的共享签名传输。 */\n syncRequest?: typeof syncRequest\n /** 水印轮询节奏(测试可调快)。 */\n watermarkPoll?: {\n intervalMs?: number\n timeoutMs?: number\n sleep?: (ms: number, signal: AbortSignal) => Promise<void>\n }\n}\n\nexport type ZhihuSyncMediaInput = {\n fileName: string\n contentBase64: string\n scene: 'article' | 'question' | 'pin'\n kind?: 'image' | 'video'\n}\n\nexport type ZhihuSyncMediaResult = {\n kind: 'image'\n /** 最终可公开 URL(水印成功时为水印图 URL)。 */\n url: string\n /** 水印前原图 URL(仅水印成功时有值)。 */\n originalUrl?: string\n /** 最终媒资 token:水印成功为服务端 watermark_image_token,否则为 v2-{md5}。 */\n mediaId?: string\n width?: number\n height?: number\n /** 仅当服务端确认返回了不同的水印图 key 时为 true;绝不自拼 URL 伪造。 */\n watermark?: boolean\n warnings?: string[]\n}\n\n// ── 错误 ───────────────────────────────────────────────────────────────────────\n\nexport type ZhihuSyncMediaErrorCode =\n | 'INVALID_INPUT' | 'UNSUPPORTED_TYPE' | 'NEEDS_TRANSCODE' | 'LIMIT_EXCEEDED'\n | 'API_ERROR' | 'AUTH_ERROR' | 'OSS_ERROR'\n | 'UNSUPPORTED_VENDOR' | 'SESSION_EXPIRED' | 'CANCELLED' | 'TIMEOUT' | 'BAD_RESPONSE'\n\nexport class ZhihuSyncMediaError extends Error {\n readonly code: ZhihuSyncMediaErrorCode\n readonly httpStatus?: number\n readonly vendorCode?: string\n constructor(code: ZhihuSyncMediaErrorCode, message: string, extra: { httpStatus?: number; vendorCode?: string } = {}) {\n super(message)\n this.name = 'ZhihuSyncMediaError'\n this.code = code\n if (extra.httpStatus !== undefined) this.httpStatus = extra.httpStatus\n if (extra.vendorCode !== undefined) this.vendorCode = extra.vendorCode\n }\n}\n\n// ── 常量 ───────────────────────────────────────────────────────────────────────\n\nconst PATH_APPLY_UPLOAD = '/openapi/media/uploads'\nconst PATH_COMMIT_UPLOAD = (sessionKey: string) => `/openapi/media/uploads/${sessionKey}`\nconst PATH_IMAGE_WATERMARK = (imageId: string) => `/openapi/media/images/${imageId}/watermark`\n\nconst TEMPLATE_IMAGE = 'image-upload-default'\nconst MEDIA_TYPE_IMAGE = 'image'\nconst PLATFORM_WEB = 'web'\n\nconst UPLOADING = 'UPLOADING'\nconst UPLOAD_SUCCESS = 'UPLOAD_SUCCESS'\nconst UPLOAD_FAIL = 'UPLOAD_FAIL'\n\nconst SIZE_LIMIT_GIF = 15 * 1024 * 1024\nconst SIZE_LIMIT_OTHER = 30 * 1024 * 1024\nconst MAX_LONG_SIDE = 4 * 4096\nconst MAX_TOTAL_PIXELS = 200_000_000\n\nconst WATERMARK_INTERVAL_MS = 500\nconst WATERMARK_TIMEOUT_MS = 30_000\nconst OSS_TIMEOUT_MS = 120_000\n\nconst VENDOR_ALI = 'ali'\n\nconst OSS_EXPIRED_CODES = new Set(['InvalidAccessKeyId', 'SecurityTokenExpired', 'AccessDenied', 'InvalidSecurityToken'])\n\n// ── 工具 ───────────────────────────────────────────────────────────────────────\n\nconst record = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v)\nconst str = (v: unknown): string => (typeof v === 'string' ? v : '')\nconst num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0)\n\n/** 从待抛出消息中抹除一切已知敏感串(STS、对象名等不参与签名但属临时授权信息)。 */\nfunction redact(message: string, secrets: readonly string[]): string {\n let out = message\n for (const s of secrets) {\n if (s && s.length >= 4) out = out.split(s).join('[REDACTED]')\n }\n return out\n}\n\nfunction throwAborted(signal: AbortSignal): never {\n throw new ZhihuSyncMediaError('CANCELLED', '上传已取消。')\n}\n\ntype MediaUrl = { primary: string; backups: string[] }\nfunction parseMediaUrl(raw: unknown): MediaUrl | undefined {\n if (!record(raw)) return undefined\n const backups = Array.isArray(raw.backups) ? raw.backups.filter((b): b is string => typeof b === 'string') : []\n const primary = str(raw.primary) || backups[0] || ''\n return { primary, backups }\n}\n// 可公开图片 URL 只接受知乎真实图片域的 HTTPS 地址——上游若回内网/他域地址,\n// 前端 preview 会变成 SSRF 通道。域名策略与 sync-content 图片引用一致。\nconst PUBLIC_IMAGE_HOST_RE = /^(?:[a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction publicImageUrl(u: MediaUrl | undefined): string {\n for (const candidate of [u?.primary, ...(u?.backups ?? [])]) {\n if (!candidate) continue\n try {\n const url = new URL(candidate)\n if (url.protocol !== 'https:' || url.username || url.password) continue\n if (!PUBLIC_IMAGE_HOST_RE.test(url.hostname)) continue\n return candidate\n } catch { continue }\n }\n return ''\n}\nconst firstUrl = publicImageUrl // 兼容命名:语义即「首个合法可公开 URL」\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) return reject(new ZhihuSyncMediaError('CANCELLED', '上传已取消。'))\n const timer = setTimeout(() => { cleanup(); resolve() }, ms)\n const onAbort = () => { cleanup(); reject(new ZhihuSyncMediaError('CANCELLED', '上传已取消。')) }\n const cleanup = () => { clearTimeout(timer); signal.removeEventListener('abort', onAbort) }\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n// ── 图片嗅探(只读文件头;不支持的格式明确报错,不静默改写)─────────────────────\n\ntype SniffedImage = { format: string; ext: string; contentType: string; width: number; height: number; gif: boolean }\n\nfunction sniffImage(buf: Uint8Array): SniffedImage {\n const bad = () => new ZhihuSyncMediaError('UNSUPPORTED_TYPE',\n '无法识别图片格式,仅支持 JPEG / PNG / GIF / WebP / BMP;HEIC / AVIF / TIFF 等请先转换为上述格式。')\n if (buf.byteLength < 12) throw bad()\n\n // HEIC / HEIF / AVIF(ISOBMFF ftyp 品牌)——参考实现需转码,这里明确报错。\n if (buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) { // 'ftyp'\n const brand = String.fromCharCode(buf[8], buf[9], buf[10], buf[11]).toLowerCase()\n if (/^(heic|heix|hevc|hevx|mif1|msf1|avif|avis)$/.test(brand)) {\n throw new ZhihuSyncMediaError('NEEDS_TRANSCODE',\n `检测到 ${brand.toUpperCase()} 格式,当前不内置转码,请先转换为 JPEG / PNG / WebP 后再上传。`)\n }\n throw bad()\n }\n\n // PNG\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47\n && buf[4] === 0x0d && buf[5] === 0x0a && buf[6] === 0x1a && buf[7] === 0x0a) {\n if (buf.byteLength < 24) throw bad()\n const w = (buf[16]! << 24 | buf[17]! << 16 | buf[18]! << 8 | buf[19]!) >>> 0\n const h = (buf[20]! << 24 | buf[21]! << 16 | buf[22]! << 8 | buf[23]!) >>> 0\n if (!w || !h) throw bad()\n return { format: 'PNG', ext: '.png', contentType: 'image/png', width: w, height: h, gif: false }\n }\n\n // GIF\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) {\n if (buf.byteLength < 10) throw bad()\n const w = buf[6]! | (buf[7]! << 8)\n const h = buf[8]! | (buf[9]! << 8)\n if (!w || !h) throw bad()\n return { format: 'GIF', ext: '.gif', contentType: 'image/gif', width: w, height: h, gif: true }\n }\n\n // BMP\n if (buf[0] === 0x42 && buf[1] === 0x4d) { // 'BM'\n if (buf.byteLength < 26) throw bad()\n const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n if (dv.getUint32(14, true) < 40) throw bad()\n const w = Math.abs(dv.getInt32(18, true))\n const h = Math.abs(dv.getInt32(22, true))\n if (!w || !h) throw bad()\n return { format: 'BMP', ext: '.bmp', contentType: 'image/bmp', width: w, height: h, gif: false }\n }\n\n // WebP: RIFF....WEBP + VP8 / VP8L / VP8X chunk\n if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46\n && buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {\n if (buf.byteLength < 30) throw bad()\n const four = String.fromCharCode(buf[12], buf[13], buf[14], buf[15])\n if (four === 'VP8X') {\n const w = (buf[24]! | (buf[25]! << 8) | (buf[26]! << 16)) + 1\n const h = (buf[27]! | (buf[28]! << 8) | (buf[29]! << 16)) + 1\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp', width: w, height: h, gif: false }\n }\n if (four === 'VP8L') {\n const bits = buf[21]! | (buf[22]! << 8) | (buf[23]! << 16) | (buf[24]! << 24)\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp',\n width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1, gif: false }\n }\n if (four === 'VP8 ') {\n if (buf[23] !== 0x9d || buf[24] !== 0x01 || buf[25] !== 0x2a) throw bad()\n const w = (buf[26]! | (buf[27]! << 8)) & 0x3fff\n const h = (buf[28]! | (buf[29]! << 8)) & 0x3fff\n if (!w || !h) throw bad()\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp', width: w, height: h, gif: false }\n }\n throw bad()\n }\n\n // JPEG: FF D8 + SOF marker\n if (buf[0] === 0xff && buf[1] === 0xd8) {\n let off = 2\n while (off + 9 < buf.byteLength) {\n if (buf[off] !== 0xff) { off++; continue }\n let marker = buf[off + 1]!\n while (marker === 0xff && off + 2 < buf.byteLength) { off++; marker = buf[off + 1]! }\n if (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) { off += 2; continue }\n if (off + 3 >= buf.byteLength) break\n const len = (buf[off + 2]! << 8) | buf[off + 3]!\n if (len < 2) break\n const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc\n if (isSof && off + 8 < buf.byteLength) {\n const h = (buf[off + 5]! << 8) | buf[off + 6]!\n const w = (buf[off + 7]! << 8) | buf[off + 8]!\n if (!w || !h) throw bad()\n return { format: 'JPEG', ext: '.jpg', contentType: 'image/jpeg', width: w, height: h, gif: false }\n }\n off += 2 + len\n }\n throw bad()\n }\n\n throw bad()\n}\n\nfunction validateImage(meta: SniffedImage, sizeBytes: number): void {\n const limit = meta.gif ? SIZE_LIMIT_GIF : SIZE_LIMIT_OTHER\n if (sizeBytes > limit) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED',\n `图片大小超限(${(sizeBytes / 1048576).toFixed(1)} MB),上限 ${limit / 1048576} MB${meta.gif ? '(GIF)' : ''},请压缩后重试。`)\n }\n const longSide = Math.max(meta.width, meta.height)\n if (longSide > MAX_LONG_SIDE) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED',\n `图片长边 ${longSide}px 超限,上限 ${MAX_LONG_SIDE}px。`)\n }\n if (meta.width * meta.height > MAX_TOTAL_PIXELS) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED', '图片总像素数超限(上限 2 亿像素)。')\n }\n}\n\n// ── 服务端下发数据解析 ─────────────────────────────────────────────────────────\n\ntype ApplyResponse = {\n state: string\n sessionKey: string\n vendorCode: string\n commitAddress: string\n objectKey: string\n processes: string\n extraParams: Record<string, unknown>\n auth?: { accessKeyId: string; secretAccessKey: string; securityToken: string; expiration: number }\n address?: { endpoint: string; bucketName: string; objectName: string; isCname: boolean }\n}\n\nfunction parseApply(data: unknown): ApplyResponse {\n if (!record(data)) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'ApplyUpload 响应不是对象。')\n const config = record(data.upload_config) ? data.upload_config : {}\n const addressRaw = record(config.address) ? config.address : undefined\n const authRaw = record(data.upload_auth) ? data.upload_auth : undefined\n const media = record(data.upload_media) ? data.upload_media : {}\n return {\n state: str(data.upload_state),\n sessionKey: str(data.session_key),\n vendorCode: str(config.vendor_code),\n commitAddress: str(media.commit_address),\n objectKey: str(media.key),\n processes: str(media.processes),\n extraParams: record(data.extra_params) ? data.extra_params : {},\n auth: authRaw ? {\n accessKeyId: str(authRaw.access_key_id),\n secretAccessKey: str(authRaw.secret_access_key),\n securityToken: str(authRaw.security_token),\n expiration: num(authRaw.expiration),\n } : undefined,\n address: addressRaw ? {\n endpoint: str(addressRaw.endpoint),\n bucketName: str(addressRaw.bucket_name),\n objectName: str(addressRaw.object_name),\n isCname: addressRaw.is_cname === true,\n } : undefined,\n }\n}\n\ntype CommitResponse = {\n result: string\n mediaKey: string\n mediaUrl?: MediaUrl\n width?: number\n height?: number\n}\n\nfunction parseCommit(data: unknown): CommitResponse {\n if (!record(data)) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'CommitUpload 响应不是对象。')\n const meta = record(data.media_meta) ? data.media_meta : undefined\n return {\n result: str(data.upload_results),\n mediaKey: str(data.media_key),\n mediaUrl: parseMediaUrl(data.media_url),\n width: meta ? num(meta.width) || undefined : undefined,\n height: meta ? num(meta.height) || undefined : undefined,\n }\n}\n\n// ── 阿里 OSS PutObject(v1 签名,等价 oss2 StsAuth + Bucket.put_object)─────────\n\n// OSS 主机与 bucket 的最小校验。只接受 aliyuncs.com 下的主机名与合法 bucket 名:\n// 路径、端口、账号密码、反斜杠都可能把直传请求改投到别的主机上,而请求头里带着 STS 凭据。\nconst OSS_HOST_RE = /^(?:[a-z0-9-]+\\.)*aliyuncs\\.com$/\nconst OSS_BUCKET_RE = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/\n\nfunction ossEndpointHost(endpoint: string): string {\n const text = endpoint.trim()\n if (!text) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'OSS endpoint 为空。')\n if (/^http:\\/\\//i.test(text)) throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS endpoint 必须使用 HTTPS。')\n // 只接受裸主机名或 https://主机名。\n const host = text.replace(/^https:\\/\\//i, '').replace(/\\/+$/, '').toLowerCase()\n if (!OSS_HOST_RE.test(host)) {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS endpoint 必须是不带路径、端口与账号密码的 aliyuncs.com 主机名。')\n }\n return host\n}\n\n/**\n * 拼出直传地址,并校验最终 URL:主机名必须与预期的合规主机完全一致,且不得带账号密码。\n * 返回的就是实际发出去的那个 URL——校验与请求必须是同一个地址。\n */\nfunction ossTargetUrl(bucket: string, host: string, encodedKey: string, isCname: boolean): string {\n if (!OSS_BUCKET_RE.test(bucket)) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', 'OSS bucket 名称不合法。')\n }\n const requestHost = isCname ? host : `${bucket}.${host}`\n let target: URL\n try {\n target = new URL(`https://${requestHost}/${encodedKey}`)\n } catch {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS 目标地址不合法。')\n }\n if (target.protocol !== 'https:' || target.username || target.password || target.hostname !== requestHost) {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS 目标地址不合法。')\n }\n return target.toString()\n}\n\nfunction encodeObjectName(name: string): string {\n return name.split('/').map(seg => encodeURIComponent(seg)).join('/')\n}\n\ntype OssCredentials = { accessKeyId: string; secretAccessKey: string; securityToken: string }\n\n/**\n * OSS v1 header 签名:\n * StringToSign = VERB \\n Content-MD5 \\n Content-Type \\n Date \\n x-oss-* \\n CanonicalizedResource\n */\nfunction ossAuthorization(verb: string, contentMd5B64: string, contentType: string, date: string,\n auth: OssCredentials, bucket: string, encodedKey: string): string {\n const canonicalHeaders = `x-oss-security-token:${auth.securityToken}\\n`\n const canonicalResource = `/${bucket}/${encodedKey}`\n const toSign = `${verb}\\n${contentMd5B64}\\n${contentType}\\n${date}\\n${canonicalHeaders}${canonicalResource}`\n const sig = createHmac('sha1', auth.secretAccessKey).update(toSign, 'utf8').digest('base64')\n return `OSS ${auth.accessKeyId}:${sig}`\n}\n\nfunction parseOssErrorCode(xml: string): string {\n const m = /<Code>([^<]+)<\\/Code>/i.exec(xml)\n return m ? m[1]!.trim() : ''\n}\n\nasync function ossPutObject(\n fetcher: typeof fetch,\n auth: OssCredentials,\n address: NonNullable<ApplyResponse['address']>,\n body: Uint8Array,\n contentType: string,\n md5B64: string,\n signal: AbortSignal,\n timeoutMs: number,\n): Promise<{ ok: true } | { ok: false; ossCode: string; status: number }> {\n const host = ossEndpointHost(address.endpoint)\n const encodedKey = encodeObjectName(address.objectName)\n const url = ossTargetUrl(address.bucketName, host, encodedKey, address.isCname)\n const date = new Date().toUTCString()\n const authorization = ossAuthorization('PUT', md5B64, contentType, date, auth, address.bucketName, encodedKey)\n\n const controller = new AbortController()\n const onAbort = () => controller.abort(signal.reason)\n if (signal.aborted) onAbort()\n else signal.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => controller.abort(new Error('oss put timeout')), timeoutMs)\n try {\n const res = await fetcher(url, {\n method: 'PUT',\n redirect: 'error',\n headers: {\n Date: date,\n 'Content-Type': contentType,\n 'Content-MD5': md5B64,\n 'x-oss-security-token': auth.securityToken,\n Authorization: authorization,\n },\n body: body as unknown as BodyInit,\n signal: controller.signal,\n })\n if (res.ok) return { ok: true }\n const text = await res.text().catch(() => '')\n return { ok: false, ossCode: parseOssErrorCode(text), status: res.status }\n } finally {\n clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n }\n}\n\nfunction resolveSyncRequest(options: ZhihuSyncMediaOptions): typeof syncRequest {\n return options.syncRequest ?? syncRequest\n}\n\n// ── 主入口 ─────────────────────────────────────────────────────────────────────\n\nexport async function uploadZhihuSyncMedia(\n input: ZhihuSyncMediaInput,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n): Promise<ZhihuSyncMediaResult> {\n const secrets: string[] = []\n try {\n return await uploadImpl(input, options, signal, secrets)\n } catch (error) {\n if (error instanceof ZhihuSyncMediaError) {\n error.message = redact(error.message, secrets)\n throw error\n }\n throw new ZhihuSyncMediaError('API_ERROR', redact(\n `媒体上传失败:${error instanceof Error ? error.message : String(error)}`, secrets))\n }\n}\n\nasync function uploadImpl(\n input: ZhihuSyncMediaInput,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n secrets: string[],\n): Promise<ZhihuSyncMediaResult> {\n if (signal.aborted) throwAborted(signal)\n if (input.kind !== undefined && input.kind !== 'image') {\n throw new ZhihuSyncMediaError('UNSUPPORTED_TYPE', '当前仅支持图片上传;视频与对象文件未接入。')\n }\n const scene = input.scene\n if (scene !== 'article' && scene !== 'question' && scene !== 'pin') {\n throw new ZhihuSyncMediaError('INVALID_INPUT', `scene 必须是 article / question / pin,收到 ${String(scene)}。`)\n }\n const fileName = (input.fileName.trim().split(/[\\\\/]/).pop() ?? '').trim()\n if (!fileName || [...fileName].some(ch => (ch.codePointAt(0) ?? 0) < 32 || ch.codePointAt(0) === 127)) {\n throw new ZhihuSyncMediaError('INVALID_INPUT', 'fileName 不能为空且不能包含控制字符。')\n }\n const b64 = input.contentBase64.trim()\n // Buffer.from 对非法字符静默跳过,必须先严格校验,避免混入内容被当图片上传。\n if (!b64 || !/^[A-Za-z0-9+/]*={0,2}$/.test(b64) || b64.length % 4 !== 0) {\n throw new ZhihuSyncMediaError('INVALID_INPUT', 'contentBase64 不是合法 base64。')\n }\n const bytes = new Uint8Array(Buffer.from(b64, 'base64'))\n if (!bytes.byteLength) throw new ZhihuSyncMediaError('INVALID_INPUT', '图片内容为空。')\n\n const meta = sniffImage(bytes)\n validateImage(meta, bytes.byteLength)\n\n const md5Hex = createHash('md5').update(bytes).digest('hex')\n const md5B64 = createHash('md5').update(bytes).digest('base64')\n const syncRequest = resolveSyncRequest(options)\n // 提前登记 app 凭据用于错误消息脱敏(transport 会再解析一次,失败由其报错)。\n try {\n const creds = await options.resolveCredentials()\n if (creds?.appKey) secrets.push(creds.appKey)\n if (creds?.appSecret) secrets.push(creds.appSecret)\n } catch { /* 凭据解析失败由 syncRequest 统一报错 */ }\n if (signal.aborted) throwAborted(signal)\n\n const warnings: string[] = []\n const applyBody: Record<string, unknown> = {\n template_name: TEMPLATE_IMAGE,\n media_type: MEDIA_TYPE_IMAGE,\n file_name: fileName,\n file_extension: meta.ext,\n file_size: bytes.byteLength,\n }\n const applyHeaders = {\n 'X-Media-Scene-Name': scene,\n 'X-Media-Upload-Platform': PLATFORM_WEB,\n 'X-Upload-Content-Type': meta.contentType,\n 'X-Upload-Content-Hash': md5Hex,\n }\n\n let apply = parseApply(await syncRequest(PATH_APPLY_UPLOAD, { method: 'POST', headers: applyHeaders, body: applyBody }, options, signal))\n if (apply.auth) secrets.push(apply.auth.accessKeyId, apply.auth.secretAccessKey, apply.auth.securityToken)\n if (signal.aborted) throwAborted(signal)\n\n let mediaKey = ''\n let mediaUrl: MediaUrl | undefined\n let width = meta.width\n let height = meta.height\n\n if (apply.state === UPLOAD_SUCCESS) {\n // 秒传:apply 阶段即完成,extra_params 内嵌 meta 与 url。\n const ep = apply.extraParams\n mediaUrl = parseMediaUrl(ep.url)\n mediaKey = apply.objectKey\n width = num(ep.width) || width\n height = num(ep.height) || height\n } else if (apply.state === UPLOADING) {\n if (apply.vendorCode !== VENDOR_ALI) {\n throw new ZhihuSyncMediaError('UNSUPPORTED_VENDOR',\n `服务端下发未实现的云厂商 ${apply.vendorCode || '(空)'};当前仅支持阿里 OSS。`,\n { vendorCode: apply.vendorCode || undefined })\n }\n if (!apply.auth || !apply.address || !apply.auth.accessKeyId || !apply.auth.secretAccessKey\n || !apply.auth.securityToken || !apply.address.endpoint || !apply.address.bucketName || !apply.address.objectName) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', 'ApplyUpload 缺少 OSS 凭证或地址,无法直传。')\n }\n\n const fetcher = options.fetcher ?? (typeof fetch === 'function' ? fetch : undefined)\n if (!fetcher) throw new ZhihuSyncMediaError('BAD_RESPONSE', '当前环境没有可用的 fetch。')\n\n // OSS 直传;凭证过期码时携带 session_key 重调 apply 刷新一次。\n let oss = await ossPut(fetcher, apply, bytes, meta.contentType, md5B64, signal, options.timeoutMs)\n if (!oss.ok && OSS_EXPIRED_CODES.has(oss.ossCode)) {\n const refreshed = parseApply(await syncRequest(PATH_APPLY_UPLOAD, {\n method: 'POST', headers: applyHeaders,\n body: { template_name: TEMPLATE_IMAGE, media_type: MEDIA_TYPE_IMAGE, session_key: apply.sessionKey },\n }, options, signal))\n if (refreshed.state === UPLOAD_FAIL || !refreshed.sessionKey) {\n throw new ZhihuSyncMediaError('SESSION_EXPIRED', '上传会话已过期,刷新凭证被拒绝。')\n }\n apply = refreshed\n if (apply.auth) secrets.push(apply.auth.accessKeyId, apply.auth.secretAccessKey, apply.auth.securityToken)\n if (!apply.auth || !apply.address) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', '刷新后的 ApplyUpload 缺少 OSS 凭证或地址。')\n }\n oss = await ossPut(fetcher, apply, bytes, meta.contentType, md5B64, signal, options.timeoutMs)\n }\n\n let ossError = ''\n if (!oss.ok) ossError = `OSS PutObject HTTP ${oss.status}${oss.ossCode ? ` code=${oss.ossCode}` : ''}`\n\n // 无论成败都 CommitUpload,保持服务端状态一致。\n const commitBody: Record<string, unknown> = {\n media_type: MEDIA_TYPE_IMAGE,\n client_upload_state: oss.ok ? UPLOAD_SUCCESS : UPLOAD_FAIL,\n }\n if (ossError) commitBody.error_log = ossError\n if (apply.processes) commitBody.processes = apply.processes\n const commit = parseCommit(await syncRequest(apply.commitAddress || PATH_COMMIT_UPLOAD(apply.sessionKey), {\n method: 'PATCH',\n headers: {\n 'X-Media-Scene-Name': scene,\n 'X-Media-Upload-Platform': PLATFORM_WEB,\n 'X-Security-Token': apply.auth?.securityToken ?? '',\n },\n body: commitBody,\n }, options, signal))\n\n if (!oss.ok) throw new ZhihuSyncMediaError('OSS_ERROR', `云端直传失败:${ossError}`, { httpStatus: oss.status })\n if (commit.result !== UPLOAD_SUCCESS) {\n throw new ZhihuSyncMediaError('API_ERROR', `CommitUpload 未成功:${commit.result || '(空结果)'}。`)\n }\n mediaKey = commit.mediaKey || apply.objectKey\n mediaUrl = commit.mediaUrl\n width = commit.width ?? width\n height = commit.height ?? height\n } else {\n throw new ZhihuSyncMediaError('API_ERROR',\n `ApplyUpload 返回 ${apply.state || '(空状态)'}${apply.state === UPLOAD_FAIL ? ',会话被拒绝' : ''}。`)\n }\n\n // 图片成功:媒资 token 与 pier SDK 一致为 v2-{md5}。\n const originalKey = `v2-${md5Hex}`\n if (!mediaUrl || !firstUrl(mediaUrl)) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', '上传成功但服务端未返回可访问图片 URL。')\n }\n const originalUrl = firstUrl(mediaUrl)\n\n // ── 水印轮询:失败/超时降级原图;仅当 watermark key 与原 key 实际不同才算成功。\n const watermark = await pollWatermark(syncRequest, apply.sessionKey, scene, originalKey, options, signal, warnings)\n if (watermark) {\n return {\n kind: 'image',\n url: firstUrl(watermark.url),\n originalUrl,\n mediaId: watermark.token,\n width, height,\n watermark: true,\n ...(warnings.length ? { warnings } : {}),\n }\n }\n return {\n kind: 'image',\n url: originalUrl,\n mediaId: mediaKey && mediaKey !== apply.objectKey ? mediaKey : originalKey,\n width, height,\n watermark: false,\n ...(warnings.length ? { warnings } : {}),\n }\n}\n\nasync function ossPut(\n fetcher: typeof fetch,\n apply: ApplyResponse,\n bytes: Uint8Array,\n contentType: string,\n md5B64: string,\n signal: AbortSignal,\n timeoutMs: number | undefined,\n): Promise<{ ok: true } | { ok: false; ossCode: string; status: number }> {\n const auth = apply.auth!\n const address = apply.address!\n try {\n return await ossPutObject(fetcher, auth, address, bytes, contentType, md5B64, signal,\n timeoutMs && timeoutMs > 0 ? Math.max(timeoutMs, OSS_TIMEOUT_MS / 4) : OSS_TIMEOUT_MS)\n } catch (error) {\n if (error instanceof ZhihuSyncMediaError) throw error\n const name = (error as { name?: unknown })?.name\n if (name === 'AbortError') {\n if (signal.aborted) throwAborted(signal)\n throw new ZhihuSyncMediaError('TIMEOUT', '云端直传超时。')\n }\n return { ok: false, ossCode: '', status: 0 }\n }\n}\n\ntype WatermarkOk = { token: string; url: MediaUrl }\n\nasync function pollWatermark(\n request: typeof syncRequest,\n imageId: string,\n scene: string,\n originalKey: string,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n warnings: string[],\n): Promise<WatermarkOk | undefined> {\n if (!imageId) return undefined\n const interval = options.watermarkPoll?.intervalMs ?? WATERMARK_INTERVAL_MS\n const timeout = options.watermarkPoll?.timeoutMs ?? WATERMARK_TIMEOUT_MS\n const doSleep = options.watermarkPoll?.sleep ?? sleep\n const deadline = Date.now() + timeout\n\n while (Date.now() < deadline) {\n if (signal.aborted) throwAborted(signal)\n let data: unknown\n try {\n // 单次请求超时不超过剩余轮询预算,避免多次 30s transport 超时放大总等待。\n const remaining = deadline - Date.now()\n const scoped = { ...options, timeoutMs: Math.max(1, Math.min(options.timeoutMs ?? 30_000, remaining)) }\n data = await request(PATH_IMAGE_WATERMARK(imageId), {\n method: 'GET', headers: { 'X-Media-Scene-Name': scene },\n }, scoped, signal)\n } catch (error) {\n if (signal.aborted) throwAborted(signal)\n const status = (error as { httpStatus?: unknown; status?: unknown })?.httpStatus\n ?? (error as { status?: unknown })?.status\n if (typeof status === 'number' && status >= 400 && status < 500) break // 永久错误:降级\n await doSleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal)\n continue\n }\n if (!record(data)) break\n const state = str(data.state)\n if (state === 'success') {\n const token = str(data.watermark_image_token)\n const url = parseMediaUrl(data.watermark_image_url)\n // 水印必须返回不同的服务端 key 与真实 URL;相同或缺失一律降级,绝不自拼。\n if (url && firstUrl(url) && token && token !== originalKey) {\n if (!url.primary && url.backups[0]) warnings.push('水印图主 URL 为空,已使用备用 URL。')\n return { token, url }\n }\n break\n }\n if (state === 'failed') break\n await doSleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal)\n }\n warnings.push('水印处理未完成,已使用未加水印的原图 URL。')\n return undefined\n}\n","import { randomUUID } from 'node:crypto'\nimport { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\nimport { z } from 'zod'\nimport { prepareZhihuSyncDraft } from './sync-content.ts'\nimport { publishZhihuSync, ZhihuSyncApiError, type ZhihuSyncTransportOptions } from './sync-api.ts'\nimport { uploadZhihuSyncMedia } from './sync-media.ts'\nimport type { SyncDraft } from './sync-types.ts'\n\nexport const ZHIHU_SYNC_CREDENTIAL_REFS = ['ZHIHU_OPENAPI_APP_KEY', 'ZHIHU_OPENAPI_APP_SECRET'] as const\n\nexport type SyncDraftRecord = {\n id: string\n revision: number\n updatedAt: string\n draft: SyncDraft\n publishState?: 'publishing' | 'published' | 'uncertain'\n published?: { url: string; contentToken: string; type: string }\n lastError?: string\n}\n\nconst draftSchema = z.object({\n type: z.enum(['article', 'question', 'pin']), title: z.string().max(10_000),\n body: z.string().max(2_000_000), format: z.enum(['markdown', 'html']),\n images: z.array(z.object({\n url: z.string(), originalUrl: z.string().optional(), width: z.number().positive().optional(),\n height: z.number().positive().optional(), watermark: z.boolean().optional(), mediaId: z.string().optional(),\n }).strict()).max(200).optional(),\n linkCard: z.object({\n url: z.string(), title: z.string().optional(), cover: z.string().optional(),\n contentType: z.string().optional(), contentId: z.string().optional(), isFromForward: z.boolean().optional(),\n }).strict().nullable().optional(),\n settings: z.object({\n commentPermission: z.enum(['all', 'nobody', 'followee', 'censor', 'follower', 'follower_n_days']).optional(),\n creationStatement: z.enum(['spoiler', 'medical_advice', 'fictional_creation', 'contain_finance', 'ai_creation']).optional(),\n tableOfContentsEnabled: z.boolean().optional(), topics: z.array(z.string()).max(100).optional(), ring: z.string().optional(),\n }).strict().optional(),\n}).strict()\n\nconst recordSchema = z.object({\n id: z.string(), revision: z.number().int().positive(), updatedAt: z.string(),\n draft: draftSchema,\n publishState: z.enum(['publishing', 'published', 'uncertain']).optional(),\n published: z.object({ url: z.string(), contentToken: z.string(), type: z.string() }).optional(),\n lastError: z.string().optional(),\n})\n\nexport const zhihuSyncDomainSpec = defineDomain({\n name: 'dsh_zhihu_sync', version: 1,\n tables: { drafts: domainTable<string, SyncDraftRecord>(recordSchema) },\n})\n\nexport interface SyncDraftTable {\n get(id: string): SyncDraftRecord | undefined\n entries(): IterableIterator<[string, SyncDraftRecord]>\n put(id: string, record: SyncDraftRecord): Promise<void>\n}\n\nexport class ZhihuSyncServiceError extends Error {\n constructor(readonly code: string, message: string) { super(message) }\n}\n\nfunction object(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new ZhihuSyncServiceError('bad-request', '请求参数必须为对象。')\n return value as Record<string, unknown>\n}\n\n/** Review tickets bind an exact saved revision. Only the human UI receives them;\n * model tools can prepare content but cannot grant publishing authority. */\nexport function createZhihuSyncService(table: SyncDraftTable, options: ZhihuSyncTransportOptions) {\n const reviews = new Map<string, { revision: number; token: string; expires: number }>()\n const locks = new Map<string, Promise<unknown>>()\n async function locked<T>(id: string, operation: () => Promise<T>): Promise<T> {\n const previous = locks.get(id) ?? Promise.resolve()\n const current = previous.catch(() => {}).then(operation)\n locks.set(id, current)\n try { return await current } finally { if (locks.get(id) === current) locks.delete(id) }\n }\n function get(id: unknown) {\n if (typeof id !== 'string' || !id) throw new ZhihuSyncServiceError('bad-request', '缺少草稿 ID。')\n const record = table.get(id)\n if (!record) throw new ZhihuSyncServiceError('not-found', '草稿不存在。')\n return structuredClone(record)\n }\n function revision(record: SyncDraftRecord, expected: unknown) {\n if (record.revision !== expected) throw new ZhihuSyncServiceError('conflict', '草稿已更新,请重新载入后再操作。')\n }\n function editable(record: SyncDraftRecord) {\n if (record.publishState) throw new ZhihuSyncServiceError('publish-locked', '此草稿已提交发布。请先检查知乎发布结果;修改内容请保存为新草稿。')\n }\n return {\n async call(endpoint: string, payload: unknown, signal: AbortSignal, caller: 'ui' | 'tool' = 'ui'): Promise<unknown> {\n signal.throwIfAborted()\n const body = object(payload)\n if (endpoint === 'sync.list') {\n const drafts = [...table.entries()].map(([, value]) => ({\n id: value.id, revision: value.revision, updatedAt: value.updatedAt,\n title: value.draft.title, type: value.draft.type, publishState: value.publishState,\n published: value.published,\n })).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))\n const offset = body.offset === undefined ? 0 : body.offset\n if (typeof offset !== 'number' || !Number.isSafeInteger(offset) || offset < 0) throw new ZhihuSyncServiceError('bad-request', '草稿分页位置无效。')\n const page = drafts.slice(offset, offset + 100)\n return { drafts: page, total: drafts.length, ...(offset + page.length < drafts.length ? { nextOffset: offset + page.length } : {}) }\n }\n if (endpoint === 'sync.get') return get(body.id)\n if (endpoint === 'sync.media.upload') {\n return uploadZhihuSyncMedia(body as Parameters<typeof uploadZhihuSyncMedia>[0], options, signal)\n }\n if (endpoint === 'sync.publish' && caller !== 'ui') throw new ZhihuSyncServiceError('confirmation-required', 'Publishing requires a human-confirmed UI review; the agent tool can only save and validate drafts.')\n if (!['sync.save', 'sync.prepare', 'sync.publish'].includes(endpoint)) throw new ZhihuSyncServiceError('bad-request', '未知同步操作。')\n const id = endpoint === 'sync.save' && body.id === undefined ? randomUUID() : body.id\n if (typeof id !== 'string' || !id) throw new ZhihuSyncServiceError('bad-request', '缺少草稿 ID。')\n return locked(id, async () => {\n signal.throwIfAborted()\n if (endpoint === 'sync.save') {\n const old = table.get(id)\n if (body.id !== undefined) {\n if (!old) throw new ZhihuSyncServiceError('not-found', '草稿不存在。')\n revision(old, body.expectedRevision)\n editable(old)\n }\n // Saving invalid content is allowed; only the publish boundary requires\n // all checks to pass. No truncation or semantic rewriting occurs here.\n const parsed = draftSchema.safeParse(body.draft)\n if (!parsed.success) throw new ZhihuSyncServiceError('bad-request', '草稿字段格式无效或内容过大,请检查类型、正文、图片与发布设置。')\n const record: SyncDraftRecord = {\n id, revision: (old?.revision ?? 0) + 1, updatedAt: new Date().toISOString(),\n draft: parsed.data,\n }\n await table.put(id, record)\n reviews.delete(id)\n return structuredClone(record)\n }\n const record = get(id)\n revision(record, body.expectedRevision)\n editable(record)\n const prepared = prepareZhihuSyncDraft(record.draft)\n if (endpoint === 'sync.prepare') {\n if (caller === 'tool') return { record, prepared }\n // One bounded, short-lived ticket per draft. Preparing again replaces it.\n for (const [key, review] of reviews) if (review.expires < Date.now()) reviews.delete(key)\n const reviewToken = randomUUID()\n if (prepared.canPublish) reviews.set(id, { revision: record.revision, token: reviewToken, expires: Date.now() + 30 * 60_000 })\n return { record, prepared, reviewToken: prepared.canPublish ? reviewToken : '' }\n }\n const review = reviews.get(id)\n if (body.confirmed !== true || !review || review.token !== body.reviewToken\n || review.revision !== record.revision || review.expires < Date.now()) {\n throw new ZhihuSyncServiceError('confirmation-required', '请重新预览当前草稿并确认发布。')\n }\n if (!prepared.canPublish) throw new ZhihuSyncServiceError('validation-failed', '请先修正校验错误。')\n // Check configuration before recording a possibly sent request. Pin this\n // credential pair for this single operation, never across operations.\n const credentials = await options.resolveCredentials()\n signal.throwIfAborted()\n record.publishState = 'publishing'\n record.updatedAt = new Date().toISOString()\n await table.put(id, record)\n reviews.delete(id)\n // Persist intent before the network call: after process loss it must never\n // look like an unsent draft. There is no upstream idempotency contract.\n try {\n record.published = await publishZhihuSync(prepared, { ...options, resolveCredentials: async () => credentials }, signal)\n record.publishState = 'published'\n } catch (error) {\n if (error instanceof ZhihuSyncApiError && error.definite) {\n delete record.publishState\n record.lastError = error.message\n } else {\n record.publishState = 'uncertain'\n record.lastError = '未能确认发布结果,请检查知乎主页,避免重复发布。'\n }\n }\n record.updatedAt = new Date().toISOString()\n await table.put(id, record)\n return structuredClone(record)\n })\n },\n }\n}\n","import { resolveZhihuCallPolicy, type ZhihuCallPolicyOptions } from './call-policy.ts'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { credentialRef } from '@deepseek-ai/dsh-credentials'\nimport { executeZhihuSearch, ZHIHU_SEARCH_DEFAULT_COUNT } from './search-api.ts'\nimport {\n executeZhihuGlobalSearch, executeZhihuHotList, executeZhihuAsk, executeZhihuKnowledgeSearch,\n normalizeRecallScopes, ZHIHU_ASK_MODELS, ZHIHU_ASK_DEFAULT_MODEL,\n} from './operations.ts'\nimport { listZhihuKnowledgeBases, uploadZhihuKnowledgeFile, ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES } from './zhihu-knowledge.ts'\nimport { type ZhihuClientOptions, type ZhihuSearchExecuted, ZhihuSearchError } from './zhihu-client.ts'\nimport { createZhihuUsageRecorder, resolveDays, zhihuUsageDomainSpec, type ZhihuUsageRecorder } from './usage.ts'\nimport { ZHIHU_RPC_CHANNEL, ZHIHU_CREDENTIAL_REF, ZHIHU_SEARCH_EVENT, type ZhihuRpcResult } from './contracts.ts'\nimport { registerHostRpc, type HostRpcContext } from './host-rpc.ts'\nimport { bindZhihuWebSearch } from './web-search-provider.ts'\nimport { createZhihuSyncService, zhihuSyncDomainSpec, ZHIHU_SYNC_CREDENTIAL_REFS, ZhihuSyncServiceError } from './sync-service.ts'\nimport { executeZhihuOpenPlatform, resolveZhihuOpenPlatformOperation, ZHIHU_OPEN_PLATFORM_APIS, zhihuOpenPlatformResultCount } from './open-platform.ts'\n\nexport const name = '@klarkxy/dsh-zhihu'\nexport const inject = ['connection', 'credentials', 'storageDomain', 'webServer'] as const\nexport type ZhihuService = {\n call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult>\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n dispose(): Promise<void>\n usageSummary(days: unknown): Promise<{ days: unknown[] }>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n syncCall?: (endpoint: string, payload: unknown, signal: AbortSignal, caller: 'ui' | 'tool') => Promise<unknown>\n}\n\ntype Host = Context & HostRpcContext & {\n credentials: { resolve: (ref: ReturnType<typeof credentialRef>) => Promise<{ value: string } | undefined> }\n}\nconst fail = (code: string, message: string, details: Record<string, unknown> = {}): ZhihuRpcResult => ({ ok: false, error: { code, message, details } })\n\n/** One shared service for direct UI RPC and optional Tool wrappers. */\nexport function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify: (event: ZhihuSearchExecuted) => void = () => {}): ZhihuService {\n // Serialize read-modify-write counters; concurrent tools must not lose increments.\n let pendingUsage = Promise.resolve()\n const lifetime = new AbortController()\n const active = new Set<Promise<unknown>>()\n const run: ZhihuService['run'] = (execute, signal) => {\n const combined = AbortSignal.any([signal, lifetime.signal])\n const request = Promise.resolve().then(() => { combined.throwIfAborted(); return execute(combined) })\n active.add(request)\n return request.finally(() => active.delete(request))\n }\n const record = (event: ZhihuSearchExecuted) => {\n pendingUsage = pendingUsage.then(() => usage.record(event)).catch(() => {})\n try { notify(event) } catch { /* observer failure does not change a request */ }\n }\n const service = {\n toolOptions: { ...options, onExecuted: record },\n async usageSummary(days: unknown) { await pendingUsage; return { days: await usage.read(resolveDays(days)) } },\n async call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult> {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n const client = { ...options, signal }\n let counted = false\n const operation = resolveZhihuOpenPlatformOperation(endpoint)\n const quotaId = operation ? ZHIHU_OPEN_PLATFORM_APIS[operation].quotaId : undefined\n const metering = quotaId ? { quotaId } : {}\n try {\n if (endpoint === 'policy.status') return { ok: true, value: resolveZhihuCallPolicy(options.env, options.callPolicy) }\n if (endpoint === 'usage.summary') { await pendingUsage; return { ok: true, value: { days: await usage.read(resolveDays(body.days)) } } }\n const query = typeof body.query === 'string' ? body.query : ''\n if (['search', 'global.search', 'ask', 'knowledge.search'].includes(endpoint) && (!query.trim() || query.length > 4000)) {\n return fail('bad-request', '查询不超过 4000 字符')\n }\n let value: unknown\n let results = 0\n counted = true\n if (operation) {\n counted = operation !== 'quota'\n const result = await executeZhihuOpenPlatform(operation, payload === undefined ? {} : payload, client)\n value = result; results = zhihuOpenPlatformResultCount(result)\n } else if (endpoint === 'search') {\n const result = await executeZhihuSearch(query, typeof body.count === 'number' ? body.count : ZHIHU_SEARCH_DEFAULT_COUNT, client)\n value = result; results = result.items.length\n } else if (endpoint === 'global.search') {\n const result = await executeZhihuGlobalSearch(query, typeof body.count === 'number' ? body.count : 10,\n body.searchDb === 'realtime' || body.searchDb === 'static' ? body.searchDb : 'all', client)\n value = result; results = result.items.length\n } else if (endpoint === 'hot.list') {\n const result = await executeZhihuHotList(typeof body.limit === 'number' ? body.limit : 10, client)\n value = result; results = result.items.length\n } else if (endpoint === 'ask') {\n const model = (ZHIHU_ASK_MODELS as readonly unknown[]).includes(body.model) ? body.model as typeof ZHIHU_ASK_MODELS[number] : ZHIHU_ASK_DEFAULT_MODEL\n value = await executeZhihuAsk(query, model, client); results = 1\n } else if (endpoint === 'knowledge.search') {\n const result = await executeZhihuKnowledgeSearch(query, typeof body.limit === 'number' ? body.limit : 5, normalizeRecallScopes(body.recallScopes), client)\n value = result; results = result.items.length\n } else if (endpoint === 'knowledge.bases') {\n const result = await listZhihuKnowledgeBases(client); value = result; results = result.bases.length\n } else if (endpoint === 'knowledge.upload') {\n const contentBase64 = typeof body.contentBase64 === 'string' ? body.contentBase64 : ''\n if (!contentBase64) throw new Error('缺少文件内容。')\n if (contentBase64.length > Math.ceil(ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(contentBase64)) {\n return fail('bad-request', '文件内容无效或超过 20 MB')\n }\n const upload = await uploadZhihuKnowledgeFile({\n fileName: typeof body.fileName === 'string' ? body.fileName : '',\n data: new Uint8Array(Buffer.from(contentBase64, 'base64')),\n knowledgeBaseId: typeof body.knowledgeBaseId === 'string' && body.knowledgeBaseId.trim() ? body.knowledgeBaseId.trim() : undefined,\n }, client)\n value = { upload }; results = 1\n } else {\n counted = false\n return fail('bad-request', `unknown endpoint ${endpoint}`)\n }\n if (counted) record({ ok: true, results, ...metering })\n return { ok: true, value }\n } catch (error) {\n if (counted) record({ ok: false, results: 0, ...metering })\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const code = error instanceof ZhihuSearchError ? error.code.toLowerCase().replaceAll('_', '-') : 'internal'\n const details: Record<string, unknown> = {}\n if (error instanceof ZhihuSearchError) {\n for (const key of ['status', 'upstreamCode', 'retryAfter', 'retryable'] as const) {\n if (error[key] !== undefined) details[key] = error[key]\n }\n }\n return fail(code, error instanceof Error ? error.message : '知乎请求失败', details)\n }\n },\n }\n return {\n ...service,\n run,\n async call(endpoint, payload, signal) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n try { return await run(combined => service.call(endpoint, payload, combined), signal) }\n catch (error) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n throw error\n }\n },\n async dispose() {\n lifetime.abort()\n await Promise.allSettled([...active])\n await pendingUsage\n },\n }\n}\n\nexport async function apply(ctx: Context, config: ZhihuCallPolicyOptions = {}): Promise<void> {\n const host = ctx as Host\n const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec)\n const service = createZhihuService({\n callPolicy: config,\n resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value,\n }, createZhihuUsageRecorder(domain.table('daily')), event => {\n const emit = ctx.emit as unknown as (name: string, event: ZhihuSearchExecuted) => void\n emit.call(ctx, ZHIHU_SEARCH_EVENT, event)\n })\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'zhihu.usageDomainClose')\n const syncDomain = await ctx.storageDomain.open(zhihuSyncDomainSpec)\n const sync = createZhihuSyncService(syncDomain.table('drafts'), {\n resolveCredentials: async () => {\n const [key, secret] = await Promise.all(ZHIHU_SYNC_CREDENTIAL_REFS.map(ref => host.credentials.resolve(credentialRef(ref))))\n if (!key?.value.trim() || !secret?.value.trim()) throw new ZhihuSyncServiceError('sync-credentials-missing', '请在「设置 → 知乎」中填写发布 App Key 和 App Secret。')\n return { appKey: key.value.trim(), appSecret: secret.value.trim() }\n },\n })\n service.syncCall = (endpoint, payload, signal, caller) => service.run(combined => sync.call(endpoint, payload, combined, caller), signal)\n ctx.effect(() => async () => { await service.dispose(); await syncDomain.close() }, 'zhihu.syncDomainClose')\n ctx.provide('zhihu', service)\n ctx.effect(() => registerHostRpc(host, ZHIHU_RPC_CHANNEL, async (endpoint, payload, signal) => {\n if (!endpoint.startsWith('sync.')) return service.call(endpoint, payload, signal)\n try { return { ok: true, value: await service.syncCall!(endpoint, payload, signal, 'ui') } }\n catch (error) {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n return fail(error instanceof ZhihuSyncServiceError ? error.code : 'sync-failed', error instanceof Error ? error.message : '知乎同步失败。')\n }\n }))\n bindZhihuWebSearch(ctx, service)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/zhihu-knowledge.ts","../src/host-rpc.ts","../src/web-search-provider.ts","../src/sync-types.ts","../src/sync-content.ts","../src/sync-api.ts","../src/sync-media.ts","../src/sync-service.ts","../src/index.ts"],"sourcesContent":["/**\n * 知乎知识库的管理侧能力:列知识库、上传文件。仅供设置页 RPC 使用,\n * 不暴露为模型工具——上传会把文件送入知乎云端,必须是用户在界面上显式发起。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 列表:GET /api/v1/knowledge/bases?Scope=all|created|subscribed\n * - 上传:POST /api/v1/knowledge/files(multipart: File + 可选 KnowledgeBaseID),同步解析\n */\nimport {\n parseZhihuEnvelope,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_KNOWLEDGE_LIST_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_UPLOAD_VERSION = 1\n/** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */\nexport const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20 * 1024 * 1024\nexport const ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS = 180_000\n\nconst KNOWLEDGE_BASES_PATH = '/api/v1/knowledge/bases'\nconst KNOWLEDGE_FILES_PATH = '/api/v1/knowledge/files'\n\nexport const ZHIHU_KNOWLEDGE_FILE_EXTENSIONS = [\n '.pdf', '.md', '.txt', '.ppt', '.pptx', '.xlsx', '.xls', '.docx', '.doc',\n '.webp', '.png', '.jpg', '.mobi', '.epub', '.csv', '.azw3',\n] as const\n\nconst KNOWLEDGE_CONTENT_TYPES: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.ppt': 'application/vnd.ms-powerpoint',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.xls': 'application/vnd.ms-excel',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.webp': 'image/webp',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.mobi': 'application/x-mobipocket-ebook',\n '.epub': 'application/epub+zip',\n '.csv': 'text/csv',\n '.azw3': 'application/vnd.amazon.ebook',\n}\n\nexport type ZhihuKnowledgeBase = {\n id: string\n name: string\n relation: string\n visibility: string\n isDefault: boolean\n contentCount: number\n}\n\nexport type ZhihuKnowledgeBaseList = {\n version: typeof ZHIHU_KNOWLEDGE_LIST_VERSION\n bases: ZhihuKnowledgeBase[]\n}\n\nfunction normalizeBase(raw: Record<string, unknown>): ZhihuKnowledgeBase {\n return {\n id: typeof raw.KnowledgeBaseID === 'string' ? raw.KnowledgeBaseID : '',\n name: typeof raw.Name === 'string' && raw.Name.length > 0 ? raw.Name : '(未命名知识库)',\n relation: typeof raw.Relation === 'string' ? raw.Relation : '',\n visibility: typeof raw.Visibility === 'string' ? raw.Visibility : '',\n isDefault: raw.IsDefault === true,\n contentCount: typeof raw.ContentCount === 'number' ? raw.ContentCount : 0,\n }\n}\n\nexport async function listZhihuKnowledgeBases(options: ZhihuClientOptions = {}): Promise<ZhihuKnowledgeBaseList> {\n const body = await zhihuFetchJson(KNOWLEDGE_BASES_PATH, {\n params: { Scope: 'all' },\n }, '知乎知识库列表', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown }\n const items = Array.isArray(data.Items) ? data.Items : []\n return {\n version: ZHIHU_KNOWLEDGE_LIST_VERSION,\n bases: items\n .filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item))\n .map(normalizeBase)\n .filter((base) => base.id.length > 0),\n }\n}\n\nexport type ZhihuKnowledgeUploadInput = {\n fileName: string\n data: Uint8Array\n /** 指定目标知识库;缺省进默认库。 */\n knowledgeBaseId?: string\n}\n\nexport type ZhihuKnowledgeUpload = {\n version: typeof ZHIHU_KNOWLEDGE_UPLOAD_VERSION\n knowledgeBaseId: string\n recallContentId: string\n fileName: string\n fileSize: number\n title: string\n abstract: string\n originUrl: string\n}\n\n/** 校验文件名与大小,返回小写扩展名。 */\nexport function checkKnowledgeFile(fileName: string, size: number): string {\n const cleaned = fileName.trim()\n if (!cleaned) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能为空。')\n // 去掉路径成分,只保留文件名\n const base = cleaned.split(/[\\\\/]/).pop() ?? cleaned\n for (const ch of base) {\n const code = ch.codePointAt(0) ?? 0\n if (code < 32 || code === 127) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能包含控制字符。')\n }\n if (new TextEncoder().encode(base).length > 255) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能超过 255 个 UTF-8 字节。')\n }\n if (size <= 0) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件内容不能为空。')\n if (size > ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库文件大小不能超过 ${ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 1024 / 1024}MB。`)\n }\n const dot = base.lastIndexOf('.')\n const ext = dot >= 0 ? base.slice(dot).toLowerCase() : ''\n if (!(ZHIHU_KNOWLEDGE_FILE_EXTENSIONS as readonly string[]).includes(ext)) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库上传仅支持 ${ZHIHU_KNOWLEDGE_FILE_EXTENSIONS.join('、')}。`)\n }\n return ext\n}\n\nexport async function uploadZhihuKnowledgeFile(\n input: ZhihuKnowledgeUploadInput,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeUpload> {\n const cleaned = (input.fileName.trim().split(/[\\\\/]/).pop() ?? input.fileName.trim())\n const ext = checkKnowledgeFile(input.fileName, input.data.byteLength)\n const form = new FormData()\n if (input.knowledgeBaseId) form.set('KnowledgeBaseID', input.knowledgeBaseId)\n form.set('File', new Blob([input.data.buffer as ArrayBuffer], { type: KNOWLEDGE_CONTENT_TYPES[ext] ?? 'application/octet-stream' }), cleaned)\n const body = await zhihuFetchJson(KNOWLEDGE_FILES_PATH, {\n formData: form,\n }, '知乎知识库上传', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS })\n const data = parseZhihuEnvelope(body) as Record<string, unknown>\n return {\n version: ZHIHU_KNOWLEDGE_UPLOAD_VERSION,\n knowledgeBaseId: typeof data.KnowledgeBaseID === 'string' ? data.KnowledgeBaseID : '',\n recallContentId: typeof data.RecallContentID === 'string' ? data.RecallContentID : '',\n fileName: typeof data.FileName === 'string' ? data.FileName : cleaned,\n fileSize: typeof data.FileSize === 'number' ? data.FileSize : input.data.byteLength,\n title: typeof data.Title === 'string' ? data.Title : '',\n abstract: typeof data.Abstract === 'string' ? data.Abstract : '',\n originUrl: typeof data.OriginUrl === 'string' ? data.OriginUrl : '',\n }\n}\n","import { registerHostRpcTransport, sendHostRpcResponse, type HostRpcContext } from '@klarkxy/dsh-plugin-kit/host-rpc'\n\nconst CHANNEL_PATTERN = /^\\/[A-Za-z0-9._~-]+$/\nconst ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/\nconst MAX_BODY_BYTES = 64 * 1024 * 1024\n\nexport type { HostRpcContext }\nexport type HostRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>\n\nfunction isEndpoint(endpoint: string): boolean {\n return endpoint.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..' && ENDPOINT_SEGMENT.test(segment))\n}\n\n/** Zhihu keeps its gateway-compatible bad-request envelope and upload allowance. */\nexport function registerHostRpc(ctx: HostRpcContext, channel: string, handler: HostRpcHandler): () => void {\n if (!CHANNEL_PATTERN.test(channel) || channel === '/api') {\n throw new Error(`invalid RPC channel ${JSON.stringify(channel)}`)\n }\n return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {\n const record = body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}\n const rpcId = typeof record.rpcId === 'string' ? record.rpcId : 'invalid-request'\n if (record.type !== 'client-request' || record.method !== endpoint) {\n sendHostRpcResponse(res, rpcId, {\n ok: false,\n error: {\n code: 'gateway/bad-request',\n message: record.method !== endpoint\n ? `method ${JSON.stringify(record.method)} does not match endpoint ${JSON.stringify(endpoint)}`\n : 'invalid client-request message',\n details: { issues: [] },\n },\n }, false)\n return\n }\n sendHostRpcResponse(res, rpcId, await handler(endpoint, record.payload, signal), false)\n }, {\n maxBodyBytes: MAX_BODY_BYTES,\n isEndpoint,\n caseInsensitiveContentType: true,\n emptyBodyAsNull: true,\n errorBody: (status, error) => {\n if (status === 503) return 'host authorization policy is unavailable'\n if (status === 401) return 'unauthorized'\n if (status === 403) return 'forbidden'\n if (status === 404) return 'not found'\n if (status === 415) return 'content type must be application/json'\n if (status === 400) return 'body is not JSON'\n if (status === 500) return `handler failure: ${String(error)}`\n return undefined\n },\n })\n}\n","/**\n * Native ctx.web backend. Configuration and credentials belong to Zhihu.\n * Official model tool remains `web_search`. The `global_search` action of `zhihu_research` is unchanged.\n */\nimport { WEB_PROVIDER_AVAILABILITY_EVENT, type DshWebManagementDescriptor } from '@klarkxy/dsh-plugin-kit/contracts'\nimport { ZHIHU_CREDENTIAL_REF } from './contracts.ts'\nimport { reportExecuted, type ZhihuClientOptions, type ZhihuSearchExecuted } from './zhihu-client.ts'\nimport {\n executeZhihuGlobalSearch,\n ZHIHU_GLOBAL_SEARCH_MAX_COUNT,\n} from './operations.ts'\n\ntype ZhihuWebSearchHost = {\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\nexport const ZHIHU_WEB_SEARCH_ID = 'zhihu-global'\n\nexport const ZHIHU_WEB_SEARCH_DESCRIPTOR: DshWebManagementDescriptor = {\n id: ZHIHU_WEB_SEARCH_ID,\n label: '知乎全网搜索',\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n credentialRef: ZHIHU_CREDENTIAL_REF,\n credentialShared: true,\n credentialHint: '与「知乎资料」共用 Access Secret',\n billing: 'request' as const,\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n signupUrl: 'https://developer.zhihu.com',\n configurationOwner: '@klarkxy/dsh-zhihu',\n}\n\nexport type ZhihuWebSearchSource = { url: string; title?: string; snippet?: string }\nexport type ZhihuWebSearchResult = { sources: ZhihuWebSearchSource[]; truncated?: boolean }\nexport type ZhihuWebSearchRequest = { query: string; maxResults?: number }\n\nexport type ZhihuWebSearchProvider = {\n readonly id: string\n available(): boolean\n search(request: ZhihuWebSearchRequest, signal?: AbortSignal): Promise<ZhihuWebSearchResult>\n}\n\nexport type ZhihuWebRegistry = {\n registerSearchProvider(provider: ZhihuWebSearchProvider & {\n dshWebManagement: typeof ZHIHU_WEB_SEARCH_DESCRIPTOR\n }): () => void\n}\n\nexport type ZhihuWebSearchProviderOptions = ZhihuClientOptions & {\n apiKey?: string\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction sourceFrom(url: string, title: string, snippet: string): ZhihuWebSearchSource | undefined {\n let parsed: URL\n try { parsed = new URL(url) } catch { return }\n if (!['https:', 'http:'].includes(parsed.protocol) || parsed.username || parsed.password) return\n return {\n url: parsed.href,\n ...(title ? { title: title.slice(0, 1000) } : {}),\n ...(snippet ? { snippet: snippet.slice(0, 8000) } : {}),\n }\n}\n\nfunction isWebRegistry(value: unknown): value is ZhihuWebRegistry {\n return !!value && typeof value === 'object' && typeof (value as ZhihuWebRegistry).registerSearchProvider === 'function'\n}\n\nexport function createZhihuWebSearchProvider(options: ZhihuWebSearchProviderOptions): ZhihuWebSearchProvider {\n const { apiKey, onExecuted, ...client } = options\n return {\n id: ZHIHU_WEB_SEARCH_ID,\n available() { return Boolean(apiKey?.trim()) },\n async search(request, signal) {\n if (!apiKey?.trim()) throw new Error('知乎全网搜索未配置 Access Secret。')\n if (!request.query?.trim() || request.query.length > 4000) throw new Error('查询无效。')\n const count = request.maxResults ?? 5\n if (!Number.isInteger(count) || count < 1 || count > ZHIHU_GLOBAL_SEARCH_MAX_COUNT) {\n throw new Error(`结果数须为 1 至 ${ZHIHU_GLOBAL_SEARCH_MAX_COUNT}。`)\n }\n try {\n signal?.throwIfAborted()\n const result = await executeZhihuGlobalSearch(request.query.trim(), count, 'all', {\n ...client,\n signal,\n env: client.env ?? process.env,\n resolveCredential: async () => apiKey,\n })\n reportExecuted(onExecuted, { ok: true, results: result.items.length })\n const sources = result.items.flatMap(item => sourceFrom(item.url, item.title, item.summary) ?? [])\n return { sources: sources.slice(0, count), truncated: sources.length > count }\n } catch (error) {\n reportExecuted(onExecuted, { ok: false, results: 0 })\n throw error\n }\n },\n }\n}\n\nexport function registerZhihuGlobalSearchProvider(\n web: ZhihuWebRegistry,\n service: ZhihuWebSearchHost,\n configured: () => boolean,\n): () => void {\n return web.registerSearchProvider({\n id: ZHIHU_WEB_SEARCH_ID, dshWebManagement: ZHIHU_WEB_SEARCH_DESCRIPTOR,\n available: configured,\n search: (request, signal) => service.run(\n async combined => {\n combined.throwIfAborted()\n const apiKey = await service.toolOptions.resolveCredential?.()\n combined.throwIfAborted()\n const provider = createZhihuWebSearchProvider({ ...service.toolOptions, apiKey })\n return provider.search(request, combined)\n },\n signal ?? new AbortController().signal,\n ),\n })\n}\n\ntype BindingContext = {\n get(name: string): unknown\n on?(event: 'credentials/reference-updated', handler: (ref: string) => void): () => void\n emit?(event: typeof WEB_PROVIDER_AVAILABILITY_EVENT, kind: 'search', id: string): void\n}\n/** Wait for the native web service; no manager dependency and no network probe. */\nexport function bindZhihuWebSearch(\n ctx: { inject(deps: readonly string[], apply: (inner: BindingContext) => (() => void) | void): unknown },\n service: ZhihuWebSearchHost,\n): void {\n ctx.inject(['web'], (inner) => {\n let web: unknown\n try { web = inner.get('web') }\n catch { return }\n if (!isWebRegistry(web)) return\n let configured = false, alive = true, revision = 0\n const refresh = async () => {\n const current = ++revision\n let ready = false\n try { ready = Boolean((await service.toolOptions.resolveCredential?.())?.trim()) } catch { /* unavailable */ }\n if (!alive || current !== revision) return\n if (configured !== ready) {\n configured = ready\n inner.emit?.(WEB_PROVIDER_AVAILABILITY_EVENT, 'search', ZHIHU_WEB_SEARCH_ID)\n }\n }\n const off = registerZhihuGlobalSearchProvider(web, service, () => configured)\n const unlisten = inner.on?.('credentials/reference-updated', ref => {\n if (ref === ZHIHU_CREDENTIAL_REF) { void refresh() }\n })\n void refresh()\n return () => { alive = false; revision++; configured = false; unlisten?.(); off() }\n })\n}\n","/**\n * 知乎同步(zhihu-sync)发布链路的共享类型契约。\n *\n * 参考(只读镜像,H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-validate/reference/conversion.md — 标题/正文/媒体转换规则\n * zhihu-sync/zhihu-validate/reference/config.md — 话题/目录/创作声明/评论权限/圈子\n * zhihu-sync/zhihu-publish/reference/publish-*.md — 各形态发布字段与交互确认项\n *\n * 字段命名约定:SyncDraft 用 camelCase;进入知乎 OpenAPI 请求体时由\n * sync-content 的 buildZhihuPublishRequest 映射为 snake_case。\n */\n\nexport type SyncContentType = 'article' | 'question' | 'pin'\nexport type SyncFormat = 'markdown' | 'html'\n\n/** 文章可用的评论权限(publish-article.md 枚举)。 */\nexport const SYNC_ARTICLE_COMMENT_PERMISSIONS = ['all', 'nobody', 'followee', 'censor', 'follower'] as const\n/** 想法可用的评论权限(publish-pin.md 枚举)。 */\nexport const SYNC_PIN_COMMENT_PERMISSIONS = ['all', 'nobody', 'followee', 'censor', 'follower_n_days'] as const\nexport type SyncCommentPermission =\n | (typeof SYNC_ARTICLE_COMMENT_PERMISSIONS)[number]\n | 'follower_n_days'\n\n/** 文章创作声明枚举(publish-article.md);pin 只允许 'ai_creation'。 */\nexport const SYNC_ARTICLE_CREATION_STATEMENTS = [\n 'spoiler',\n 'medical_advice',\n 'fictional_creation',\n 'contain_finance',\n 'ai_creation',\n] as const\nexport type SyncCreationStatement = (typeof SYNC_ARTICLE_CREATION_STATEMENTS)[number]\n\n/**\n * 一张内容图片。url 始终是发布阶段使用的最终地址:\n * 由上传流程填好(有水印且成功时使用水印 URL,否则使用原图 URL)。\n * 本层不根据 mediaId 拼接或回退 URL。\n */\nexport interface SyncImage {\n /** 发布用最终 URL(知乎媒体地址)。 */\n url: string\n /**\n * 用户提供该图时的原始来源:本地文件绝对路径 / file: URI / 原始 http(s) URL。\n * 浏览器 File API 可能给出 `blob:` 或 `data:` 临时地址——此时该字段也允许\n * 是浏览器生成的临时引用(仅作标识,不保证可持久化)。\n * 仅用于正文图片按出现顺序一一映射和预览层展示,永不写入发布请求体。\n */\n originalUrl?: string\n width?: number\n height?: number\n /** 水印处理成功(watermark_image_key 与 media_key 不同)时为 true。 */\n watermark?: boolean\n /** media_key(v2-{md5hex})。只作为记录字段,不用于拼接 URL。 */\n mediaId?: string\n}\n\n/** 想法独立链接卡片(想法正文之外的 linkCard,最多 1 个)。 */\nexport interface SyncLinkCard {\n /** 卡片目标链接(必填,绝对 http(s) URL)。 */\n url: string\n /** 卡片标题(对应 data_draft_title)。 */\n title?: string\n /** 封面图 URL(对应 data_draft_cover)。 */\n cover?: string\n /** 链接解析接口返回的数据,外链可省略。 */\n contentType?: string\n contentId?: string\n isFromForward?: boolean\n}\n\n/**\n * 发布阶段可交互确认的设置项。validate 阶段只收集与校验;\n * 是否真正写入请求体由 buildZhihuPublishRequest 按形态决定。\n */\nexport interface SyncPublishSettings {\n /**\n * 评论权限。article 可用 SYNC_ARTICLE_COMMENT_PERMISSIONS,\n * pin 可用 SYNC_PIN_COMMENT_PERMISSIONS;question 不发送。\n */\n commentPermission?: SyncCommentPermission\n /** 文章目录开关,仅 article 使用,默认 false。 */\n tableOfContentsEnabled?: boolean\n /**\n * 创作声明。article 可用全部枚举;pin 仅支持 'ai_creation';\n * question 不支持该字段。\n */\n creationStatement?: SyncCreationStatement\n /**\n * 话题链接列表(用户粘贴的知乎话题 URL,形如\n * https://www.zhihu.com/topic/{topic_token}/hot)。\n * article 最多 3 个,question 最多 5 个;pin 话题在正文 #话题名# 中,忽略本字段。\n */\n topics?: string[]\n /**\n * 知乎圈子链接(https://www.zhihu.com/ring/host/{ring_id}),仅 pin 使用,最多 1 个。\n */\n ring?: string\n}\n\n/**\n * 保存与传输用的规范化草稿。等价于 zhihu-sync 的 draft + 已注入上传结果\n * 的图片引用(images[] 对应想法独立媒体区,正文图片在 body 中用\n * SyncImage.originalUrl 标记用户来源,由转换层解析为发布 URL)。\n */\nexport interface SyncDraft {\n type: SyncContentType\n /** 标题。question 结尾无问号时由转换层自动补全「?」;pin 可为空字符串。 */\n title: string\n /**\n * 正文源文本。format='markdown' 时为 Markdown;format='html' 时为\n * 用户/上游给出的 HTML 片段(白名单消毒后使用)。\n */\n body: string\n format: SyncFormat\n /** 图片清单:正文内图片(与 body 中图片按出现顺序一一对应)+ 想法独立媒体图。 */\n images?: SyncImage[]\n /** 想法独立链接卡片(与独立图片互斥,仅 pin)。 */\n linkCard?: SyncLinkCard | null\n settings?: SyncPublishSettings\n}\n\nexport type SyncIssueSeverity = 'error' | 'warning'\n\nexport interface SyncIssue {\n severity: SyncIssueSeverity\n /** 稳定机器码,如 'title.too-long' / 'body.unsafe-html'。 */\n code: string\n message: string\n}\n\nexport interface SyncCounts {\n /** 标题字数(question 含自动补全的问号)。 */\n titleChars: number\n /** 正文纯文本字数(去除 HTML 标签、公式/代码等语义内容仍计入)。 */\n bodyChars: number\n /** 生效图片数:article/question 为正文图片引用数,pin 为独立媒体区图片数。 */\n images: number\n /** 生效的话题数。 */\n topics: number\n}\n\nexport interface SyncPrepared {\n /** 规范化后的草稿(可直接保存)。 */\n draft: SyncDraft\n /** 知乎适配正文 HTML(发布请求 content.html 的来源;想法仅正文部分)。 */\n html: string\n /** 校验/转换问题列表;error 会阻止发布,warning 表示已降级或需注意。 */\n issues: SyncIssue[]\n /** issues 中无 error 级时为 true。 */\n canPublish: boolean\n counts: SyncCounts\n}\n","/**\n * 知乎同步内容转换与校验(确定性实现;规则来源见下,未复制上游代码)。\n *\n * 规则来源(只读镜像 H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-validate/reference/conversion.md — 标题/正文/媒体/链接卡片/公式/表格\n * zhihu-sync/zhihu-validate/reference/config.md — topics/目录/创作声明/评论权限/圈子\n * zhihu-sync/zhihu-validate/reference/code-languages.md — <pre lang> 语言 ID 表\n * zhihu-sync/zhihu-publish/reference/publish-{article,question,pin}.md — 发布字段\n *\n * 分层:\n * prepareZhihuSyncDraft(input) 规范化 SyncDraft → 知乎适配 html + issues + counts\n * buildZhihuPublishRequest(prepared) SyncPrepared → publish OpenAPI request envelope\n *\n * 必须遵守的硬约束(不静默丢内容):\n * - 标题/正文超限 → error issue,不截断、不自动精简(conversion.md §1.3)。\n * - question 标题结尾无问号自动补「?」且计入长度(§2.1)。\n * - 图片必须已上传:SyncImage.url 是发布 URL;originalUrl 仅用于按出现顺序匹配\n * 正文图片引用与预览层展示,永不写入发布 body。正文图片无法解析 → error。\n * - 公式 LaTeX 移除 $/$$ 定界符后【不 trim】;alt 与 src?tex= 来自同一串(§4.11)。\n * - 想法正文仅纯文本段落、text-link、hash_tag(#话题名#,≤10);富文本降级+warning。\n * - 想法独立图片(media 区)与 linkCard 互斥;两者并存 → error(§5.3/5.4)。\n * - HTML 输入经 parse5 白名单消毒:script/iframe/object/embed/svg/style、on*、\n * javascript:|vbscript:|file:|data: URL → error,危险节点不进入输出。\n * - JSON 序列化只发生一次(发布请求体),body 不预转义。\n */\nimport { lexer, type Token, type Tokens } from 'marked'\nimport { parseFragment, type DefaultTreeAdapterMap } from 'parse5'\nimport {\n SYNC_ARTICLE_COMMENT_PERMISSIONS,\n SYNC_ARTICLE_CREATION_STATEMENTS,\n SYNC_PIN_COMMENT_PERMISSIONS,\n type SyncCommentPermission,\n type SyncCreationStatement,\n type SyncDraft,\n type SyncImage,\n type SyncIssue,\n type SyncLinkCard,\n type SyncPrepared,\n} from './sync-types.ts'\n\nexport const SYNC_LIMITS = {\n questionTitleMin: 4,\n questionTitleMax: 51,\n pinTitleMax: 50,\n articleTitleMax: 100,\n articleBodyMin: 9,\n articleBodyMax: 100_000,\n pinBodyMax: 2_000,\n pinImagesMax: 18,\n pinTopicsMax: 10,\n articleTopicsMax: 3,\n questionTopicsMax: 5,\n captionMax: 140,\n} as const\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\n\nconst escapeHtml = (v: string) =>\n v.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\nconst escapeAttr = escapeHtml\n\nfunction isAbsoluteHttpUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'http:' || u.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * 纯文本计数(conversion.md §3「按纯文本计,不含 HTML 标签」):\n * 用 parse5 把 html 解析为文本节点再拼接 —— 解码 & / < 等实体,\n * 不去除用户正文中的正常空白(空格/换行仍算一个字符,按码点计)。\n * 内部占位符(公式/图片)先剔除再计数,不计入用户正文。\n */\nfunction plainTextLength(html: string): number {\n const cleaned = html.replace(/ZHIHUIMG\\d+PLACEHOLDER/g, '').replace(/ZHIHUFORMULA\\d+PLACEHOLDER/g, '')\n const frag = parseFragment(cleaned)\n let text = ''\n const walk = (nodes: DefaultTreeAdapterMap['childNode'][]) => {\n for (const n of nodes) {\n if (n.nodeName === '#text') text += (n as DefaultTreeAdapterMap['textNode']).value\n else walk((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])\n }\n }\n walk(frag.childNodes as DefaultTreeAdapterMap['childNode'][])\n return [...text].length\n}\n\n// ---------------------------------------------------------------------------\n// 支持语言 ID(code-languages.md 全表按 id 录入;未列出 → lang=\"\"。jsx/raw 不支持)\n// ---------------------------------------------------------------------------\n\nconst SUPPORTED_LANGS = new Set([\n 'abap','abnf','actionscript','ada','adl','agda','ahk','alloy','antlr','apacheconf','apl',\n 'applescript','arduino','as','as3','asciidoc','aspectj','asy','at','autoit','awk','basemake',\n 'bash','bat','bbcode','bc','befunge','bib','blitzbasic','blitzmax','bnf','boo','boogie',\n 'brainfuck','bro','bugs','c','c++','c#','ca65','cadl','camkes','cbmbas','ceylon','cfengine3',\n 'cfm','cfs','chai','chapel','cheetah','cirru','clay','clojure','clojurescript','cmake','cobol',\n 'cobolfree','coffee-script','common-lisp','componentpascal','console','control','coq','cpp',\n 'cpp-objdump','cpsa','crmsh','croc','cryptol','csharp','csound','csound-document',\n 'csound-score','css','cuda','cython','d','d-objdump','darcs','dart','debcontrol','debian',\n 'delphi','dg','diff','django','docker','dpatch','dtd','duel','dylan','dylan-console',\n 'dylanlid','ebnf','ecl','eiffel','elixir','elm','emacslisp','erb','erl','erlang','evoque',\n 'factor','fancy','fantom','felix','fortran','foxpro','fsharp','gas','genshi','glsl','gnuplot',\n 'go','golo','gooddata-cl','gosu','groff','groovy','gst','haml','handlebars','haskell','haxeml',\n 'html','httpd','hy','idl','idris','igor','inform6','inform7','ini','io','ioke','irb','jade',\n 'java','js','json','julia','jvm','kal','kconfig','koka','kotlin','lagda','lasso','lhs','lidr',\n 'lighty','lisp','live-script','llvm','logos','logtalk','lua','make','mako','mason','mask',\n 'matlab','md','minid','modelica','modula2','monkey','moocode','moon','mscgen','mupad','mxml',\n 'mysql','nasm','newlisp','nginx','nim','nit','nixos','nsis','numpy','objdump-c','objdump-cpp',\n 'objdump-d','objdump-nasm','objective-c','objective-j','ocaml','octave','odin','ooc','opa',\n 'openedge','pacmanconf','pan','pawn','perl','php','pig','pike','pkgconfig','plpgsql',\n 'postscript','powershell','prolog','properties','protobuf','psql','puppet','py','pycon',\n 'pypylog','pytb','python','python3','qbasic','r','rake','rb','rbcon','rebol','red','redcode',\n 'registry','resources','robotframework','rq','rql','rst','rts','rust','sass','scala','scaml',\n 'scheme','scilab','scss','sh','shell','smarty','snobol','sourceslist','sp','sparql','sql',\n 'squidconf','sre','stan','swift','swig','systemverilog','tads3','tap','tcl','tcsh','tea',\n 'termcap','terminfo','terraform','text','textile','thrift','todotxt','trac-wiki','treetop',\n 'ts','turtle','twig','urbiscript','v','vala','vb.net','vctreestatus','velocity','verilog',\n 'vgl','vhdl','vim','x10','xml','xquery','xslt','xtend','xul+mozpreproc','yaml','yaml+jinja',\n 'zephir',\n])\n\n/** lang ID 归一化:不在支持表 → ''。 */\nexport function normalizeCodeLang(raw: string | undefined | null): string {\n if (!raw) return ''\n const id = raw.trim().toLowerCase()\n return SUPPORTED_LANGS.has(id) ? id : ''\n}\n\nfunction latexToEquationImg(latex: string, block: boolean): string {\n // 不 trim LaTeX;alt 与 src?tex= 用同一串(§4.11)。\n return `<img eeimg=\"${block ? 2 : 1}\" src=\"//www.zhihu.com/equation?tex=${encodeURIComponent(latex)}\" alt=\"${escapeAttr(latex)}\" />`\n}\n\n// ---------------------------------------------------------------------------\n// 转换上下文\n// ---------------------------------------------------------------------------\n\ntype ImageRef = {\n /** 用户在 draft.body 中给出的图片来源(本地路径或 URL,可能是相对形式)。 */\n src: string\n caption: string\n /** 匹配到的已上传 SyncImage(由 prepare 阶段按顺序匹配填充)。 */\n resolved?: SyncImage\n}\n\ntype Placeholders = ReturnType<typeof makePlaceholders>\n\ntype Ctx = {\n issues: SyncIssue[]\n type: SyncDraft['type']\n pinMode: boolean\n /** 正文图片引用,按出现顺序;与 draft.images(已上传清单)一一对应。 */\n bodyImages: ImageRef[]\n /** 公式按文本 token 顺序收集(extractFormulas 在 inlineToHtml 的 text 分支内调用)。 */\n formulas: { block: boolean; latex: string }[]\n ph: Placeholders\n}\n\nconst error = (ctx: Ctx, code: string, message: string) =>\n ctx.issues.push({ severity: 'error', code, message })\nconst warning = (ctx: Ctx, code: string, message: string) =>\n ctx.issues.push({ severity: 'warning', code, message })\n\n// ---------------------------------------------------------------------------\n// Markdown → 知乎 HTML(marked token 树驱动,不用正则解析 HTML)\n// ---------------------------------------------------------------------------\n\n/** 每次 prepare 生成唯一占位前缀,避免与用户正文碰撞。 */\nfunction makePlaceholders(): { img: (i: number) => string; formula: (i: number) => string } {\n const prefix = `ZHIHU_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}_`\n return {\n img: (i: number) => `${prefix}IMG${i}`,\n formula: (i: number) => `${prefix}FORMULA${i}`,\n }\n}\n\nconst VOID_RAW_TAGS = new Set(['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr'])\n\n/** 行内 raw HTML 开标签 → 在 token 流中找匹配 </tag>,把整段 raw 交给 parse5 消毒。\n * marked 会把 <b>x</b> 拆成独立 html token,单独 sanitize 会得到空壳 <b></b>。\n * 返回整段 raw;找不到闭合标签或自闭合/void 时只返回当前 token raw。 */\nfunction rawHtmlRun(tokens: Token[], start: number): { raw: string; next: number } {\n const first = (tokens[start] as Tokens.HTML).raw ?? ''\n const m = first.match(/^<([a-zA-Z][a-zA-Z0-9-]*)\\b/)\n if (!m || /\\/>$/.test(first) || VOID_RAW_TAGS.has(m[1].toLowerCase())) {\n return { raw: first, next: start + 1 }\n }\n const tag = m[1].toLowerCase()\n const closeRe = new RegExp(`^</${tag}\\\\s*>`, 'i')\n for (let i = start + 1; i < tokens.length; i++) {\n const t = tokens[i] as Tokens.Generic\n if (t.type === 'html' && closeRe.test(t.raw ?? '')) {\n return { raw: tokens.slice(start, i + 1).map(x => (x as Tokens.Generic).raw ?? '').join(''), next: i + 1 }\n }\n if (t.type !== 'html' && t.type !== 'text' && t.type !== 'escape') break\n }\n return { raw: first, next: start + 1 }\n}\n\nfunction inlineToHtml(tokens: Token[] | undefined, ctx: Ctx): string {\n if (!tokens?.length) return ''\n let out = ''\n for (let ti = 0; ti < tokens.length; ti++) {\n const t = tokens[ti]\n switch (t.type) {\n case 'text': {\n const tok = t as Tokens.Text\n // marked 的 text token 可能有嵌套 tokens;先 decode HTML 实体再 escape,\n // 避免 < → &lt; 双转义(CommonMark 语义:实体按可见字符处理)。\n // 公式只在纯文本 token 上提取(marked 已隔离 code/html),\n // extractFormulas 返回的占位符原样输出,prepare 末尾统一恢复为 equation img。\n if (tok.tokens?.length) {\n out += inlineToHtml(tok.tokens, ctx)\n } else {\n const base = ctx.formulas.length\n const extracted = extractFormulas(decodeEntities(tok.text), { ...ctx.ph, formula: index => ctx.ph.formula(base + index) })\n ctx.formulas.push(...extracted.formulas)\n out += escapeHtml(extracted.text)\n }\n break\n }\n case 'escape': out += escapeHtml((t as Tokens.Escape).text); break\n case 'strong': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持加粗,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Strong).tokens, ctx)\n } else out += `<b>${inlineToHtml((t as Tokens.Strong).tokens, ctx)}</b>`\n break\n }\n case 'em': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持斜体,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Em).tokens, ctx)\n } else out += `<i>${inlineToHtml((t as Tokens.Em).tokens, ctx)}</i>`\n break\n }\n case 'codespan': {\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持行内代码,已降级为纯文本。')\n out += escapeHtml((t as Tokens.Codespan).text)\n } else out += `<code>${escapeHtml((t as Tokens.Codespan).text)}</code>`\n break\n }\n case 'del': {\n // 知乎正文无删除线能力:降级为文本(不静默丢内容)。\n warning(ctx, 'unsupported.del', '删除线不受支持,已降级为纯文本。')\n out += inlineToHtml((t as Tokens.Del).tokens, ctx)\n break\n }\n case 'link': {\n const link = t as Tokens.Link\n const inner = inlineToHtml(link.tokens, ctx)\n const innerText = inner.replace(/<[^>]*>/g, '')\n const href = (link.href ?? '').trim()\n if (href.startsWith('#')) { out += inner; break } // 页内锚点:保留文字\n if (!isAbsoluteHttpUrl(href)) {\n warning(ctx, 'link.unsafe-url', `链接地址不受支持,已保留为纯文本:${href || '(空)'}。`)\n out += inner\n break\n }\n if (ctx.pinMode) {\n // 想法 text-link(conversion.md §5.1)。\n out += `<a href=\"${escapeAttr(href)}\" data-insert-way=\"force\" data-draft-node=\"inline\" data-draft-type=\"text-link\" data-draft-title=\"${escapeAttr(innerText || href)}\">${inner}</a>`\n } else {\n // 文章/提问文字链接(§4.10)。\n out += `<a href=\"${escapeAttr(href)}\" data-draft-title=\"${escapeAttr(innerText || href)}\">${inner}</a>`\n }\n break\n }\n case 'image': {\n const img = t as Tokens.Image\n const idx = ctx.bodyImages.length\n ctx.bodyImages.push({ src: (img.href ?? '').trim(), caption: img.text ?? '' })\n out += ctx.ph.img(idx)\n break\n }\n case 'br': out += '<br>'; break\n case 'html': {\n // 行内原始 HTML:marked 会把 <b>x</b> 拆成独立 token,需先按 raw 重组\n // 完整片段再交 parse5 消毒(不手写 regex 解析内部结构)。\n const run = rawHtmlRun(tokens, ti)\n ti = run.next - 1\n const raw = run.raw.trim()\n if (raw) out += sanitizeInlineHtml(raw, ctx)\n break\n }\n default: {\n const anyT = t as Tokens.Generic\n if (anyT.tokens?.length) out += inlineToHtml(anyT.tokens, ctx)\n else if (typeof anyT.text === 'string') out += escapeHtml(anyT.text)\n else if (typeof anyT.raw === 'string') out += escapeHtml(anyT.raw)\n }\n }\n }\n return out\n}\n\nfunction blockToHtml(tokens: Token[] | undefined, ctx: Ctx): string {\n if (!tokens?.length) return ''\n let out = ''\n for (const t of tokens) {\n switch (t.type) {\n case 'space': break\n case 'paragraph': {\n const inner = inlineToHtml((t as Tokens.Paragraph).tokens, ctx)\n out += inner.trim() ? `<p>${inner}</p>` : '<p><br></p>'\n break\n }\n case 'heading': {\n const h = t as Tokens.Heading\n const inner = inlineToHtml(h.tokens, ctx)\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持标题,已降级为段落文本。')\n if (inner.trim()) out += `<p>${inner}</p>`\n } else {\n // 知乎标题只到 h3;更深降级为 h3(§4.2:h2/h3)。\n const level = h.depth <= 2 ? 2 : 3\n out += `<h${level}>${inner}</h${level}>`\n }\n break\n }\n case 'hr': out += '<hr />'; break\n case 'code': {\n const code = t as Tokens.Code\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持代码块,已降级为文本。')\n out += `<p>${escapeHtml(code.text.trimEnd())}</p>`\n } else {\n out += `<pre lang=\"${normalizeCodeLang(code.lang)}\">${escapeHtml(code.text.replace(/\\n$/, ''))}</pre>`\n }\n break\n }\n case 'blockquote': {\n const inner = blockToHtml((t as Tokens.Blockquote).tokens, ctx)\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持引用块,已降级为文本。')\n const text = inner.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim()\n if (text) out += `<p>${escapeHtml(text)}</p>`\n } else {\n // 多段引用以 <br> 连接(§4.5)。\n const flattened = inner.replace(/<\\/p>\\s*<p>/g, '<br>').replace(/^<p>/, '').replace(/<\\/p>$/, '')\n out += `<blockquote>${flattened}</blockquote>`\n }\n break\n }\n case 'list': {\n const list = t as Tokens.List\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持列表,已降级为段落文本。')\n for (const item of list.items) {\n const inner = inlineToHtml(item.tokens, ctx)\n if (inner.trim()) out += `<p>${inner}</p>`\n }\n break\n }\n const tag = list.ordered ? 'ol' : 'ul'\n const items = list.items.map(item => `<li>${inlineToHtml(item.tokens, ctx)}</li>`).join('')\n out += `<${tag}>${items}</${tag}>`\n break\n }\n case 'table': {\n const table = t as Tokens.Table\n if (ctx.pinMode) {\n warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持表格,已降级为文本。')\n const text = table.raw.replace(/<[^>]+>/g, ' ').replace(/[|:-]+/g, ' ').replace(/\\s+/g, ' ').trim()\n if (text) out += `<p>${escapeHtml(text)}</p>`\n break\n }\n const head = table.header.map(c => `<th>${inlineToHtml(c.tokens, ctx)}</th>`).join('')\n const rows = table.rows.map(r => `<tr>${r.map(c => `<td>${inlineToHtml(c.tokens, ctx)}</td>`).join('')}</tr>`).join('')\n out += `<table data-draft-node=\"block\" data-draft-type=\"table\" data-size=\"normal\" data-row-style=\"normal\"><tbody><tr>${head}</tr>${rows}</tbody></table>`\n break\n }\n case 'html': {\n // 块级原始 HTML:统一走 parse5 消毒(同 format='html'),\n // 不做快速 img/br 提取(那样会绕过 on*/unsafe-url 检测)。\n const raw = ((t as Tokens.HTML).text ?? (t as Tokens.HTML).raw ?? '')\n if (raw.trim()) out += sanitizeInputHtml(raw, ctx)\n break\n }\n default: {\n const anyT = t as Tokens.Generic\n if (anyT.tokens?.length) {\n const inner = blockToHtml(anyT.tokens, ctx)\n if (inner.trim()) out += inner\n } else if (typeof anyT.raw === 'string' && anyT.raw.trim()) {\n out += `<p>${escapeHtml(anyT.raw.trim())}</p>`\n }\n }\n }\n }\n return out\n}\n\n/**\n * 提取 $...$ / $$...$$ 公式(marked 无内建数学扩展,预扫描后在输出中恢复)。\n * 移除定界符后保留内侧全部字符(含首尾空白)——不 trim。\n * 货币场景保真:`$5`、`$10` 这类 $ 紧跟数字的当普通文字,不当公式定界。\n * $$...$$ 始终以 $$ 开头,不受单 $ 货币规则影响。\n */\nfunction extractFormulas(text: string, ph: Placeholders): { text: string; formulas: { block: boolean; latex: string }[] } {\n const formulas: { block: boolean; latex: string }[] = []\n let out = ''\n let i = 0\n while (i < text.length) {\n const dollar = text.indexOf('$', i)\n if (dollar < 0) { out += text.slice(i); break }\n // \\$ 转义不算定界符。\n if (dollar > 0 && text[dollar - 1] === '\\\\') { out += text.slice(i, dollar + 1); i = dollar + 1; continue }\n const isDouble = text[dollar + 1] === '$'\n const openLen = isDouble ? 2 : 1\n // 货币/变量保真:单 $ 紧跟数字 → 价格($5);紧跟两个大写字母 → 环境变量\n // ($HOME、$PATH)。$E=mc^2$ 不受影响(E 后是 =)。\n const afterOpen = text.slice(dollar + openLen, dollar + openLen + 2)\n if (!isDouble && (/\\d/.test(afterOpen[0] ?? '') || /^[A-Z][A-Z]/.test(afterOpen))) {\n out += text.slice(i, dollar + 1); i = dollar + 1; continue\n }\n const close = text.indexOf(isDouble ? '$$' : '$', dollar + openLen)\n if (close < 0) { out += text.slice(i); break }\n const latex = text.slice(dollar + openLen, close)\n if (!latex) { out += text.slice(i, close + openLen); i = close + openLen; continue }\n // 货币尾保真:单 $ 闭定界后紧跟数字(如 $x$5 实为价格片段)→ 整个当普通文字。\n if (!isDouble && /\\d/.test(text[close + openLen] ?? '')) {\n out += text.slice(i, close + openLen); i = close + openLen; continue\n }\n formulas.push({ block: isDouble, latex })\n out += text.slice(i, dollar) + ph.formula(formulas.length - 1)\n i = close + openLen\n }\n return { text: out, formulas }\n}\n\n// ---------------------------------------------------------------------------\n// 想法正文(纯文本段落 + text-link + hash_tag)\n// ---------------------------------------------------------------------------\n\n/**\n * 扫描纯文本里的 #话题名#,转成 hash_tag <a>。\n * 用 parse5 遍历 DOM 树,只在文本节点内做替换——不触碰属性值,\n * 不会在已有 <a> 内嵌套 <a>,不会双转义。\n */\nfunction injectPinHashTags(html: string): { html: string; count: number } {\n const frag = parseFragment(html)\n let count = 0\n const walk = (parent: { childNodes: P5AnyNode[] }) => {\n for (let i = 0; i < parent.childNodes.length; i++) {\n const node = parent.childNodes[i]\n if (node.nodeName === '#text') {\n const text = (node as DefaultTreeAdapterMap['textNode']).value\n if (!text.includes('#')) continue\n // 文本节点内 #xxx# → 拆分:话题部分变 anchor,其余保留为文本。\n const parts: P5AnyNode[] = []\n let last = 0\n text.replace(/#([^#\\n<>]{1,200})#/g, (whole, name: string, offset: number) => {\n if (offset > last) {\n parts.push({ nodeName: '#text', value: text.slice(last, offset), parentNode: null, nextNode: null, prevNode: null } as unknown as P5AnyNode)\n }\n const topic = `#${name}#`\n const anchor = {\n nodeName: 'a', tagName: 'a', attrs: [\n { name: 'class', value: 'hash_tag' },\n { name: 'data-topic-name', value: topic },\n ],\n childNodes: [{ nodeName: '#text', value: topic }],\n namespaceURI: 'http://www.w3.org/1999/xhtml',\n } as unknown as P5Element\n parts.push(anchor as P5AnyNode)\n count += 1\n last = offset + whole.length\n return whole\n })\n if (count > 0 && parts.length) {\n if (last < text.length) parts.push({ nodeName: '#text', value: text.slice(last), parentNode: null, nextNode: null, prevNode: null } as unknown as P5AnyNode)\n parent.childNodes.splice(i, 1, ...parts)\n i += parts.length - 1\n }\n continue\n }\n // 递归进入子节点,但不进入已生成的 hash_tag anchor(避免嵌套 a)。\n const tag = (node as P5Element).tagName?.toLowerCase()\n if (tag === 'a') continue\n if ('childNodes' in node) walk(node as { childNodes: P5AnyNode[] })\n }\n }\n walk(frag as unknown as { childNodes: P5AnyNode[] })\n // serialize 返回包含 entity 的 HTML;对文本节点中的 # 已处理,\n // 对 hash_tag 的文本内容(#话题名#)保持原样。\n const serialized = frag.childNodes.map(c => serializeNode(c)).join('')\n return { html: serialized, count }\n}\n\nfunction serializeNode(node: P5AnyNode): string {\n if (node.nodeName === '#text') {\n return escapeHtml((node as DefaultTreeAdapterMap['textNode']).value)\n }\n const el = node as P5Element\n const tag = el.tagName?.toLowerCase()\n if (!tag) return ''\n if (VOID_TAGS.has(tag)) {\n const attrs = (el.attrs ?? []).map(a => `${a.name}=\"${escapeAttr(a.value)}\"`).join(' ')\n return `<${tag}${attrs ? ' ' + attrs : ''} />`\n }\n const attrs = (el.attrs ?? []).map(a => `${a.name}=\"${escapeAttr(a.value)}\"`).join(' ')\n const inner = (el.childNodes ?? []).map(c => serializeNode(c)).join('')\n return `<${tag}${attrs ? ' ' + attrs : ''}>${inner}</${tag}>`\n}\n\n// ---------------------------------------------------------------------------\n// 正文图片与上传结果的匹配\n// ---------------------------------------------------------------------------\n\n/**\n * 把 draft.body 中的图片引用按出现顺序与 draft.images 中的 SyncImage 匹配。\n * 匹配键:SyncImage.originalUrl 等于引用 src(允许 file:// 归一化差异);\n * 若未提供 originalUrl,则按顺序消费。无法匹配到已上传 URL → error。\n */\nfunction resolveBodyImages(ctx: Ctx, draft: SyncDraft) {\n const uploaded = draft.images ?? []\n const unused = new Set<number>(uploaded.map((_, i) => i))\n const normalize = (v: string) => {\n let s = v.trim().replace(/^file:\\/\\//i, '').replace(/\\\\/g, '/')\n // 图片 src 与 originalUrl 可能在 %20 等编码形式上不一致;\n // 仅在解码无歧义(往返一致)时归一,避免 %25 → % 这类单向下沉。\n try { if (encodeURIComponent(decodeURIComponent(s)) === s) s = decodeURIComponent(s) } catch { /* keep raw */ }\n return s.toLowerCase()\n }\n for (const ref of ctx.bodyImages) {\n let matched = -1\n for (const i of unused) {\n const img = uploaded[i]\n if (img.originalUrl && normalize(img.originalUrl) === normalize(ref.src)) { matched = i; break }\n }\n if (matched < 0) {\n // 顺序回退:仍按声明顺序取第一个未用条目;其 originalUrl 必须等于引用 src 或为空。\n for (const i of unused) {\n const img = uploaded[i]\n if (!img.originalUrl || normalize(img.originalUrl) === normalize(ref.src)) { matched = i; break }\n }\n }\n if (matched < 0) {\n error(ctx, 'image.unresolved', `正文图片未找到对应的上传结果:${ref.src || '(空 src)'}。请先上传图片后再保存/发布。`)\n continue\n }\n unused.delete(matched)\n ref.resolved = uploaded[matched]\n }\n // 声明了上传结果但正文未引用:不静默丢弃,提示用户检查。\n // pin 的 images 属于独立媒体区(不经正文引用),跳过此检查。\n if (!ctx.pinMode) {\n for (const i of unused) {\n warning(ctx, 'image.unused', `已上传图片未在正文中引用:${uploaded[i].originalUrl || uploaded[i].url}。`)\n }\n }\n}\n\n/** 知乎域白名单(与媒体 owner 一致):^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$ */\nconst ZHIHU_DOMAIN_RE = /^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction isZhihuImageUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'https:' && !u.username && !u.password && ZHIHU_DOMAIN_RE.test(u.hostname)\n } catch { return false }\n}\n\nfunction renderBodyImage(ref: ImageRef, ctx: Ctx): string {\n const img = ref.resolved\n if (!img?.url) return ''\n if (!isZhihuImageUrl(img.url)) {\n error(ctx, 'image.unsafe-url', `图片发布 URL 不是 HTTPS 知乎图片域:${img.url}。`)\n return ''\n }\n const caption = ref.caption || ''\n if (caption.length > SYNC_LIMITS.captionMax) {\n error(ctx, 'image.caption-too-long', `图片注释超过 ${SYNC_LIMITS.captionMax} 字,请缩短后重试。`)\n return ''\n }\n const attrs: string[] = [`src=\"${escapeAttr(img.url)}\"`]\n attrs.push(`data-caption=\"${escapeAttr(caption)}\"`)\n const w = img.width ?? 0\n attrs.push(`data-size=\"${w > 0 && w < 343 ? 'small' : 'normal'}\"`)\n if (img.width !== undefined) attrs.push(`data-rawwidth=\"${img.width}\"`)\n if (img.height !== undefined) attrs.push(`data-rawheight=\"${img.height}\"`)\n if (img.watermark) attrs.push('data-watermark=\"watermark\"')\n // data-original-src 只保留知乎域地址;外部原始链接只用于预览匹配,不写入发布 body。\n if (img.originalUrl && isZhihuImageUrl(img.originalUrl)) {\n attrs.push(`data-original-src=\"${escapeAttr(img.originalUrl)}\"`)\n } else {\n attrs.push('data-original-src=\"\"')\n }\n attrs.push('data-watermark-src=\"\"', 'data-private-watermark-src=\"\"')\n return `<img ${attrs.join(' ')} />`\n}\n\n// ---------------------------------------------------------------------------\n// HTML 输入消毒(format='html',parse5 解析;不用正则充当 HTML 解析器)\n// ---------------------------------------------------------------------------\n\ntype P5Element = DefaultTreeAdapterMap['element']\ntype P5AnyNode = DefaultTreeAdapterMap['childNode']\n\nconst SAFE_TAGS = new Set([\n 'p', 'br', 'h2', 'h3', 'b', 'i', 'ol', 'ul', 'li', 'blockquote', 'hr',\n 'pre', 'code', 'sup', 'img', 'a', 'table', 'tbody', 'tr', 'th', 'td',\n])\nconst DANGEROUS_TAGS = new Set([\n 'script', 'iframe', 'object', 'embed', 'svg', 'style', 'link', 'meta', 'base',\n 'form', 'input', 'button', 'select', 'textarea', 'option', 'video', 'audio',\n 'source', 'track', 'noscript', 'template', 'frame', 'frameset', 'applet',\n])\nconst VOID_TAGS = new Set(['br', 'hr', 'img'])\nconst DANGEROUS_URL = /^\\s*(javascript|vbscript|file|data):/i\n\nfunction attr(el: P5Element, name: string): string {\n return el.attrs?.find(a => a.name.toLowerCase() === name)?.value ?? ''\n}\n\nfunction sanitizeAnchorAttrs(el: P5Element, ctx: Ctx): { ok: boolean; html: string } {\n const dataAttrs = Object.fromEntries((el.attrs ?? []).map(a => [a.name, a.value]))\n const isLinkCard = dataAttrs['data-draft-type'] === 'link-card' && dataAttrs['data-draft-node'] === 'block'\n const isTextLink = dataAttrs['data-draft-type'] === 'text-link'\n const isHashTag = (dataAttrs['class'] ?? '').split(/\\s+/).includes('hash_tag') || (dataAttrs['class'] ?? '').includes('zed-topic')\n const href = (dataAttrs['href'] ?? '').trim()\n if (!href || !isAbsoluteHttpUrl(href) || DANGEROUS_URL.test(href)) {\n error(ctx, 'html.unsafe-link-url', `链接地址不受支持,已拒绝:${href || '(空)'}`)\n return { ok: false, html: '' }\n }\n let attrs = `href=\"${escapeAttr(href)}\"`\n if (isHashTag) {\n const topicName = dataAttrs['data-topic-name'] ?? ''\n attrs = `class=\"hash_tag\" href=\"${escapeAttr(href)}\" data-topic-name=\"${escapeAttr(topicName)}\"`\n const topicId = dataAttrs['data-topic-id']\n if (topicId) attrs += ` data-topic-id=\"${escapeAttr(topicId)}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n if (isLinkCard) {\n attrs += ` data-draft-node=\"block\" data-draft-type=\"link-card\" data-draft-title=\"${escapeAttr(dataAttrs['data-draft-title'] ?? '')}\" data-draft-cover=\"${escapeAttr(dataAttrs['data-draft-cover'] ?? '')}\"`\n if (dataAttrs['data-content-type']) attrs += ` data-content-type=\"${escapeAttr(dataAttrs['data-content-type'])}\"`\n if (dataAttrs['data-content-id']) attrs += ` data-content-id=\"${escapeAttr(dataAttrs['data-content-id'])}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n if (isTextLink || ctx.pinMode) {\n attrs += ` data-insert-way=\"force\" data-draft-node=\"inline\" data-draft-type=\"text-link\" data-draft-title=\"${escapeAttr(dataAttrs['data-draft-title'] ?? '')}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n }\n const title = dataAttrs['data-draft-title']\n if (title) attrs += ` data-draft-title=\"${escapeAttr(title)}\"`\n return { ok: true, html: `<a ${attrs}>{INNER}</a>` }\n}\n\nfunction sanitizeImg(el: P5Element, ctx: Ctx): string {\n const eeimg = attr(el, 'eeimg')\n const src = (attr(el, 'src') ?? '').trim()\n if (DANGEROUS_URL.test(src)) {\n error(ctx, 'html.unsafe-img-url', `图片地址不受支持,已拒绝:${src || '(空)'}`)\n return ''\n }\n if (eeimg === '1' || eeimg === '2') {\n // 公式图片:src 必须是知乎 equation 地址或占位。\n if (!/^(https?:)?\\/\\/www\\.zhihu\\.com\\/equation\\?/i.test(src) && src) {\n warning(ctx, 'formula.invalid-src', '公式图片地址异常,已重建。')\n const latex = attr(el, 'alt')\n return latexToEquationImg(latex, eeimg === '2')\n }\n const alt = attr(el, 'alt')\n return `<img eeimg=\"${eeimg}\" src=\"${escapeAttr(src)}\" alt=\"${escapeAttr(alt)}\" />`\n }\n // 内容图片:src 必须能匹配已上传结果(originalUrl 或 url)。\n const idx = ctx.bodyImages.length\n ctx.bodyImages.push({ src, caption: attr(el, 'data-caption') || attr(el, 'alt') || '' })\n return ctx.ph.img(idx)\n}\n\nfunction sanitizeSup(el: P5Element, ctx: Ctx): { wrapper: [string, string] | null } {\n // 注释(仅文章;提问不支持)。属性:data-text / data-url / data-numero + 固定两值。\n if (attr(el, 'data-draft-type') !== 'reference') {\n warning(ctx, 'unsupported.sup', '非注释语义的 <sup> 已降级为文本。')\n return { wrapper: null }\n }\n if (ctx.type !== 'article') {\n error(ctx, 'reference.unsupported', '提问与想法不支持注释(<sup data-draft-type=\"reference\">)。')\n return { wrapper: null }\n }\n const text = attr(el, 'data-text')\n const url = attr(el, 'data-url')\n if (!text && !url) {\n error(ctx, 'reference.empty', '注释需要 data-text 或 data-url 至少一项非空。')\n return { wrapper: null }\n }\n if (url && !isAbsoluteHttpUrl(url)) {\n error(ctx, 'reference.bad-url', `注释 data-url 不是合法链接:${url}`)\n return { wrapper: null }\n }\n const numero = attr(el, 'data-numero')\n const attrs = `data-text=\"${escapeAttr(text)}\" data-url=\"${escapeAttr(url)}\" data-draft-node=\"inline\" data-draft-type=\"reference\" data-numero=\"${escapeAttr(numero)}\"`\n return { wrapper: [`<sup ${attrs}>`, '</sup>'] }\n}\n\nfunction sanitizeNode(node: P5AnyNode, ctx: Ctx): string {\n if (node.nodeName === '#text') {\n return escapeHtml((node as DefaultTreeAdapterMap['textNode']).value)\n }\n if (node.nodeName === '#comment' || node.nodeName === '#documentType') return ''\n const el = node as P5Element\n const tag = (el.tagName ?? '').toLowerCase()\n if (!tag) return ''\n if (DANGEROUS_TAGS.has(tag)) {\n error(ctx, 'html.unsafe-tag', `危险标签已移除且阻止发布:<${tag}>`)\n return ''\n }\n const children = (el.childNodes ?? []) as P5AnyNode[]\n const inner = () => children.map(c => sanitizeNode(c, ctx)).join('')\n if (!SAFE_TAGS.has(tag)) {\n // 不危险但不受支持:剥掉标签保留子内容(div/span/font/p→文本流)。\n warning(ctx, 'html.unsupported-tag', `不支持的标签 <${tag}> 已降级为纯文本。`)\n return inner()\n }\n switch (tag) {\n case 'a': {\n const r = sanitizeAnchorAttrs(el, ctx)\n if (!r.ok) return '' // 危险链接:丢弃整个锚(文本也含在锚内,不静默透传危险目标)。\n return r.html.replace('{INNER}', inner())\n }\n case 'img': return sanitizeImg(el, ctx)\n case 'sup': {\n const r = sanitizeSup(el, ctx)\n if (!r.wrapper) return inner()\n return r.wrapper[0] + inner() + r.wrapper[1]\n }\n case 'pre': {\n const lang = normalizeCodeLang(attr(el, 'lang'))\n // <pre><code>text</code></pre>:code 是知乎不支持的包装,取内部纯文本;\n // 不能只看直接子文本节点(那样 <pre><code>body</code></pre> 会丢掉全部正文)。\n const collectText = (nodes: DefaultTreeAdapterMap['childNode'][]): string =>\n nodes.map(n => n.nodeName === '#text'\n ? (n as DefaultTreeAdapterMap['textNode']).value\n : collectText((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])).join('')\n const text = collectText(children)\n return `<pre lang=\"${lang}\">${escapeHtml(text)}</pre>`\n }\n case 'table': {\n return `<table data-draft-node=\"block\" data-draft-type=\"table\" data-size=\"normal\" data-row-style=\"normal\">${inner()}</table>`\n }\n case 'thead': case 'tfoot': {\n // 输出只允许 tbody(§4.12):thead/tfoot 行并入主流。\n warning(ctx, 'table.unsupported-section', `<${tag}> 已并入 tbody。`)\n return inner()\n }\n case 'hr': return '<hr />'\n case 'br': return '<br>'\n case 'code': {\n const text = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持行内代码,已降级为文本。'); return text }\n return `<code>${text}</code>`\n }\n case 'b': case 'i': {\n const content = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', `想法正文不支持 <${tag}>,已降级为文本。`); return content }\n return `<${tag}>${content}</${tag}>`\n }\n case 'h2': case 'h3': {\n const content = inner()\n if (ctx.pinMode) { warning(ctx, 'pin.unsupported-rich-text', '想法正文不支持标题,已降级为文本。'); return `<p>${content}</p>` }\n return `<${tag}>${content}</${tag}>`\n }\n case 'ol': case 'ul': case 'li': case 'p': case 'blockquote': case 'tbody': case 'tr': case 'th': case 'td': {\n if (ctx.pinMode && tag !== 'p') {\n warning(ctx, 'pin.unsupported-rich-text', `想法正文不支持 <${tag}>,已降级为文本。`)\n return inner()\n }\n return `<${tag}>${inner()}</${tag}>`\n }\n default:\n return inner()\n }\n}\n\nfunction sanitizeInputHtml(body: string, ctx: Ctx): string {\n const frag = parseFragment(body)\n return (frag.childNodes as P5AnyNode[]).map(c => sanitizeNode(c, ctx)).join('')\n}\n\n/** 行内原始 HTML 消毒:与 format='html' 走同一 parse5 路径,\n * 但只返回内联内容(不包 <p>),供 markdown 行内 html token 使用。 */\nfunction sanitizeInlineHtml(raw: string, ctx: Ctx): string {\n const frag = parseFragment(raw)\n return (frag.childNodes as P5AnyNode[]).map(c => sanitizeNode(c, ctx)).join('')\n}\n\n/** 用 parse5 把 HTML 实体解码为可见字符(< → <)。 */\nfunction decodeEntities(text: string): string {\n const frag = parseFragment(text)\n let out = ''\n const walk = (nodes: DefaultTreeAdapterMap['childNode'][]) => {\n for (const n of nodes) {\n if (n.nodeName === '#text') out += (n as DefaultTreeAdapterMap['textNode']).value\n else walk((n as { childNodes?: DefaultTreeAdapterMap['childNode'][] }).childNodes ?? [])\n }\n }\n walk(frag.childNodes as DefaultTreeAdapterMap['childNode'][])\n return out\n}\n\n// ---------------------------------------------------------------------------\n// 规范化输入草稿\n// ---------------------------------------------------------------------------\n\nfunction normalizeDraft(input: unknown, issues: SyncIssue[]): SyncDraft {\n if (!isRecord(input)) {\n issues.push({ severity: 'error', code: 'input.invalid', message: '草稿输入必须是对象。' })\n return { type: 'article', title: '', body: '', format: 'markdown' }\n }\n const type = (['article', 'question', 'pin'].includes(String(input.type))\n ? input.type\n : 'article') as SyncDraft['type']\n if (input.type !== undefined && input.type !== type) {\n issues.push({ severity: 'error', code: 'input.bad-type', message: `不支持的内容形态:${String(input.type)}。` })\n }\n const format: SyncDraft['format'] = input.format === 'html' ? 'html' : 'markdown'\n const draft: SyncDraft = {\n type,\n title: typeof input.title === 'string' ? input.title : '',\n body: typeof input.body === 'string' ? input.body : '',\n format,\n }\n if (input.images !== undefined) {\n if (!Array.isArray(input.images)) {\n issues.push({ severity: 'error', code: 'input.bad-images', message: 'images 必须是数组。' })\n } else {\n draft.images = input.images\n .filter((i): i is SyncImage => isRecord(i))\n .map(i => ({\n url: typeof i.url === 'string' ? i.url : '',\n originalUrl: typeof i.originalUrl === 'string' ? i.originalUrl : undefined,\n width: typeof i.width === 'number' && Number.isFinite(i.width) ? i.width : undefined,\n height: typeof i.height === 'number' && Number.isFinite(i.height) ? i.height : undefined,\n watermark: i.watermark === true,\n mediaId: typeof i.mediaId === 'string' ? i.mediaId : undefined,\n }))\n .filter(i => i.url || i.originalUrl)\n }\n }\n if (input.linkCard !== undefined && input.linkCard !== null) {\n if (isRecord(input.linkCard) && typeof input.linkCard.url === 'string' && input.linkCard.url.trim()) {\n const lc = input.linkCard\n const lcUrl = lc.url as string\n draft.linkCard = {\n url: lcUrl.trim(),\n title: typeof lc.title === 'string' ? lc.title : undefined,\n cover: typeof lc.cover === 'string' ? lc.cover : undefined,\n contentType: typeof lc.contentType === 'string' ? lc.contentType : undefined,\n contentId: typeof lc.contentId === 'string' ? lc.contentId : undefined,\n isFromForward: lc.isFromForward === true ? true : undefined,\n }\n } else {\n issues.push({ severity: 'warning', code: 'input.bad-linkcard', message: 'linkCard 缺少有效 url,已忽略。' })\n }\n // linkCard.url 是外部链接:只需 http(s) + 无 userinfo(与正文 <a> 同策略);\n // linkCard.cover 是图片:需 https + 知乎图片域(与媒体同策略)。\n if (draft.linkCard) {\n if (!isAbsoluteHttpUrl(draft.linkCard.url)) {\n issues.push({ severity: 'error', code: 'linkcard.url-unsafe', message: `linkCard.url 必须是 http(s) 链接:${draft.linkCard.url}` })\n }\n if (draft.linkCard.cover && !isZhihuImageUrl(draft.linkCard.cover)) {\n issues.push({ severity: 'error', code: 'linkcard.cover-unsafe', message: `linkCard.cover 必须是 HTTPS 知乎图片域链接:${draft.linkCard.cover}` })\n }\n }\n }\n if (input.settings !== undefined && input.settings !== null) {\n if (!isRecord(input.settings)) {\n issues.push({ severity: 'warning', code: 'input.bad-settings', message: 'settings 必须是对象,已忽略。' })\n } else {\n const s = input.settings\n const settings: NonNullable<SyncDraft['settings']> = {}\n if (s.commentPermission !== undefined) settings.commentPermission = String(s.commentPermission) as SyncCommentPermission\n if (s.tableOfContentsEnabled !== undefined) settings.tableOfContentsEnabled = s.tableOfContentsEnabled === true\n if (s.creationStatement !== undefined) settings.creationStatement = String(s.creationStatement) as SyncCreationStatement\n if (Array.isArray(s.topics)) settings.topics = s.topics.filter((t): t is string => typeof t === 'string' && !!t.trim()).map(t => t.trim())\n if (typeof s.ring === 'string' && s.ring.trim()) settings.ring = s.ring.trim()\n if (Object.keys(settings).length) draft.settings = settings\n }\n }\n return draft\n}\n\n// ---------------------------------------------------------------------------\n// prepareZhihuSyncDraft\n// ---------------------------------------------------------------------------\n\nexport function prepareZhihuSyncDraft(input: unknown): SyncPrepared {\n const issues: SyncIssue[] = []\n const draft = normalizeDraft(input, issues)\n const ph = makePlaceholders()\n const ctx: Ctx = { issues, type: draft.type, pinMode: draft.type === 'pin', bodyImages: [], formulas: [], ph }\n\n // ---- 标题(§2):question 自动补「?」并计入长度 ----\n let title = draft.title\n if (draft.type === 'question') {\n title = title.trimEnd()\n if (title && !/[??]\\s*$/.test(title)) title += '?'\n draft.title = title\n }\n const titleChars = [...draft.title].length\n if (/\\n/.test(draft.title)) error(ctx, 'title.multiline', '标题禁止换行。')\n if (draft.type === 'question') {\n if (titleChars === 0) error(ctx, 'title.required', '提问标题必填。')\n if (titleChars > 0 && titleChars < SYNC_LIMITS.questionTitleMin) {\n error(ctx, 'title.too-short', `提问标题 4~51 字(含结尾问号),当前 ${titleChars} 字。`)\n }\n if (titleChars > SYNC_LIMITS.questionTitleMax) {\n error(ctx, 'title.too-long', `提问标题 4~51 字(含结尾问号),当前 ${titleChars} 字。`)\n }\n const questionMarks = (draft.title.match(/[??]/g) ?? []).length\n if (questionMarks > 1) {\n warning(ctx, 'title.multi-question', '标题含多个问号,请确认只表达一个明确问题。')\n }\n } else if (draft.type === 'pin') {\n if (titleChars > SYNC_LIMITS.pinTitleMax) {\n error(ctx, 'title.too-long', `想法标题最多 ${SYNC_LIMITS.pinTitleMax} 字,当前 ${titleChars} 字。`)\n }\n } else {\n if (titleChars === 0) error(ctx, 'title.required', '文章标题必填。')\n if (titleChars > SYNC_LIMITS.articleTitleMax) {\n error(ctx, 'title.too-long', `文章标题最多 ${SYNC_LIMITS.articleTitleMax} 字,当前 ${titleChars} 字。`)\n }\n }\n\n // ---- 正文转换 ----\n // markdown:先 lexer 再逐 text token 提取公式——code/codespan/html token\n // 不参与,避免 ```$x$``` 与行内代码里的 $ 被误判为公式定界符。\n let html = ''\n if (draft.format === 'markdown') {\n const tokens = lexer(draft.body) as Token[]\n html = blockToHtml(tokens, ctx)\n } else {\n html = sanitizeInputHtml(draft.body, ctx)\n }\n\n // 恢复公式占位符(前缀含 prepare 内随机成分,不会与正文碰撞)。\n // 占位符在 escapeHtml 之后仍是纯字母数字下划线,不会被转义破坏。\n const formulaPrefix = ph.formula(0).replace(/0$/, '')\n const formulaRe = new RegExp(`${formulaPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\d+)`, 'g')\n html = html.replace(formulaRe, (_, idx: string) => {\n const f = ctx.formulas[Number(idx)]\n return f ? latexToEquationImg(f.latex, f.block) : ''\n })\n\n // 想法:纯文本扫描 #话题# → hash_tag;文章/提问不处理话题语法。\n let pinTopicCount = 0\n if (draft.type === 'pin') {\n const injected = injectPinHashTags(html)\n html = injected.html\n pinTopicCount = injected.count\n if (pinTopicCount > SYNC_LIMITS.pinTopicsMax) {\n error(ctx, 'topics.too-many', `想法话题最多 ${SYNC_LIMITS.pinTopicsMax} 个,当前 ${pinTopicCount} 个。`)\n }\n }\n\n // 正文图片:按出现顺序匹配上传结果。\n resolveBodyImages(ctx, draft)\n const imgPrefix = ph.img(0).replace(/0$/, '')\n const imgRe = new RegExp(`${imgPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\d+)`, 'g')\n html = html.replace(imgRe, (_, idx: string) => {\n // 想法正文不支持内嵌图片(§5.3):只从 body 中移除占位符,不渲染 img 标签;\n // 已上传的媒体由下方移入独立 media 区。\n if (ctx.pinMode) return ''\n return renderBodyImage(ctx.bodyImages[Number(idx)], ctx)\n })\n\n // 想法:draft.images 全部属于独立媒体区(正文里出现的图片 markdown 不合法)。\n if (draft.type === 'pin' && ctx.bodyImages.length) {\n // pin 正文图片引用不进入 body;把它们移到独立媒体区(顺序在已有 images 之后)。\n const stray = ctx.bodyImages.filter(r => r.resolved?.url)\n if (stray.length) {\n warning(ctx, 'pin.body-image-moved', '想法正文不支持内嵌图片,已将正文图片移入独立媒体区。')\n draft.images = [...(draft.images ?? []).filter(i => !stray.some(r => r.resolved === i)), ...stray.map(r => r.resolved!)]\n }\n }\n\n // ---- 正文长度 ----\n const bodyChars = plainTextLength(html)\n if (draft.type === 'article') {\n if (bodyChars < SYNC_LIMITS.articleBodyMin) {\n error(ctx, 'body.too-short', `文章正文至少 ${SYNC_LIMITS.articleBodyMin} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n if (bodyChars > SYNC_LIMITS.articleBodyMax) {\n error(ctx, 'body.too-long', `文章正文最多 ${SYNC_LIMITS.articleBodyMax} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n } else if (draft.type === 'pin') {\n if (bodyChars > SYNC_LIMITS.pinBodyMax) {\n error(ctx, 'body.too-long', `想法正文最多 ${SYNC_LIMITS.pinBodyMax} 字(纯文本),当前 ${bodyChars} 字。`)\n }\n const hasContent = bodyChars > 0 || (draft.images?.length ?? 0) > 0 || !!draft.linkCard\n if (!hasContent) error(ctx, 'body.required', '想法正文、图片或链接卡片至少需要一项有效内容。')\n // publish-pin.md:content.html 必须非空(validate 允许空正文仅当有媒体/卡片)。\n else if (bodyChars === 0) error(ctx, 'body.required', '想法正文不能为空,请补充正文内容。')\n }\n\n // ---- 想法互斥:独立图片 vs linkCard(publish-pin.md:同时存在即拒绝发布)----\n const pinImages = draft.type === 'pin' ? (draft.images ?? []).filter(i => i.url) : []\n if (draft.type === 'pin') {\n // pin 独立媒体图也必须是 HTTPS 知乎域(预览/发布都会请求)。\n for (const img of pinImages) {\n if (!isZhihuImageUrl(img.url)) {\n error(ctx, 'image.unsafe-url', `独立图片发布 URL 不是 HTTPS 知乎图片域:${img.url}。`)\n }\n }\n if (pinImages.length > SYNC_LIMITS.pinImagesMax) {\n error(ctx, 'images.too-many', `想法独立图片最多 ${SYNC_LIMITS.pinImagesMax} 张,当前 ${pinImages.length} 张。`)\n }\n if (pinImages.length && draft.linkCard) {\n error(ctx, 'pin.media-linkcard-exclusive', '单条想法不支持同时包含独立图片与链接卡片。')\n }\n }\n\n // ---- 发布设置校验(不修改 draft.settings;错误只进 issues)----\n validateSettings(draft, issues, pinImages.length)\n\n return {\n draft,\n html,\n issues,\n canPublish: !issues.some(i => i.severity === 'error'),\n counts: {\n titleChars,\n bodyChars,\n images: draft.type === 'pin' ? pinImages.length : ctx.bodyImages.length,\n topics: draft.type === 'pin' ? pinTopicCount : (draft.settings?.topics?.length ?? 0),\n },\n }\n}\n\nfunction validateSettings(draft: SyncDraft, issues: SyncIssue[], pinImageCount: number) {\n const ctx: Ctx = { issues, type: draft.type, pinMode: draft.type === 'pin', bodyImages: [], formulas: [], ph: { img: () => '', formula: () => '' } }\n const s = draft.settings ?? {}\n\n // 形态-设置项支持矩阵(config.md §7 / publish-*.md)。\n if (draft.type === 'question') {\n for (const [key, label] of [\n ['commentPermission', '评论权限'],\n ['creationStatement', '创作声明'],\n ['tableOfContentsEnabled', '目录'],\n ['ring', '圈子'],\n ] as const) {\n if (s[key as keyof typeof s] !== undefined) warning(ctx, `settings.unsupported-${key}`, `提问不支持${label}配置,发布时将忽略。`)\n }\n }\n if (draft.type === 'article') {\n if (s.ring !== undefined) warning(ctx, 'settings.unsupported-ring', '文章不支持圈子配置,发布时将忽略。')\n }\n if (s.topics?.length) {\n if (draft.type === 'pin') {\n warning(ctx, 'settings.unsupported-topics', '想法话题属于正文(#话题名#),不使用 settings.topics,发布时将忽略。')\n } else {\n const max = draft.type === 'article' ? SYNC_LIMITS.articleTopicsMax : SYNC_LIMITS.questionTopicsMax\n const valid = s.topics.filter(t => strictZhihuUrl(t, /^\\/topic\\/(\\d+)(?:\\/|$)/))\n if (valid.length !== s.topics.length) {\n error(ctx, 'topics.invalid', '话题必须是知乎话题链接(https://www.zhihu.com/topic/{token}/...)。')\n }\n if (s.topics.length > max) {\n error(ctx, 'topics.too-many', `${draft.type === 'article' ? '文章' : '提问'}话题最多 ${max} 个,当前 ${s.topics.length} 个。`)\n }\n }\n }\n if (s.commentPermission !== undefined && draft.type !== 'question') {\n const allowed: readonly string[] = draft.type === 'pin' ? SYNC_PIN_COMMENT_PERMISSIONS : SYNC_ARTICLE_COMMENT_PERMISSIONS\n if (!allowed.includes(s.commentPermission)) {\n error(ctx, 'comment-permission.invalid', `当前形态不支持的评论权限:${String(s.commentPermission)}。`)\n }\n }\n if (s.creationStatement !== undefined) {\n if (draft.type === 'pin') {\n if (s.creationStatement !== 'ai_creation') {\n error(ctx, 'creation-statement.invalid', `想法仅支持 ai_creation 创作声明,当前:${String(s.creationStatement)}。`)\n }\n } else if (draft.type === 'article') {\n if (!(SYNC_ARTICLE_CREATION_STATEMENTS as readonly string[]).includes(String(s.creationStatement))) {\n error(ctx, 'creation-statement.invalid', `不支持的创作声明:${String(s.creationStatement)}。`)\n }\n }\n }\n if (s.ring !== undefined && draft.type === 'pin') {\n if (!strictZhihuUrl(s.ring, /^\\/ring\\/host\\/(\\d+)(?:\\/|$)/)) {\n error(ctx, 'ring.invalid', '圈子必须是知乎圈子链接(https://www.zhihu.com/ring/host/{id})。')\n }\n }\n void pinImageCount\n}\n\n// ---------------------------------------------------------------------------\n// buildZhihuPublishRequest(publish-*.md 字段映射;不二次校验内容字段)\n// ---------------------------------------------------------------------------\n\nexport type ZhihuPublishRequest = {\n type: SyncDraft['type']\n confirmed: true\n confirm_note: string\n content: Record<string, unknown>\n}\n\nexport function buildZhihuPublishRequest(prepared: unknown): ZhihuPublishRequest {\n if (!isRecord(prepared) || prepared.canPublish !== true || !isRecord(prepared.draft)) {\n throw new Error('buildZhihuPublishRequest: 需要 prepareZhihuSyncDraft 返回且 canPublish 为 true 的结果。')\n }\n const draft = prepared.draft as unknown as SyncDraft\n const html = typeof prepared.html === 'string' ? prepared.html : ''\n const s = draft.settings ?? {}\n const base = { type: draft.type, confirmed: true as const, confirm_note: 'confirmed by user after local preview' }\n\n if (draft.type === 'article') {\n const content: Record<string, unknown> = {\n title: draft.title,\n html,\n comment_permission: s.commentPermission ?? 'all',\n table_of_contents_enabled: s.tableOfContentsEnabled ?? false,\n }\n if (s.creationStatement) content.creation_statement = s.creationStatement\n const topics = extractTopicTokens(s.topics ?? [], SYNC_LIMITS.articleTopicsMax)\n if (topics.length) content.topics = topics\n return { ...base, content }\n }\n\n if (draft.type === 'question') {\n const content: Record<string, unknown> = { title: draft.title, html }\n const topics = extractTopicTokens(s.topics ?? [], SYNC_LIMITS.questionTopicsMax)\n if (topics.length) content.topics = topics\n return { ...base, content }\n }\n\n // pin:publish 要求 content.html 非空(publish-pin.md 前置校验)。\n const content: Record<string, unknown> = {\n title: draft.title,\n html,\n comment_permission: s.commentPermission ?? 'all',\n }\n const images = (draft.images ?? []).filter(i => typeof i.url === 'string' && i.url)\n if (images.length) {\n // publish-pin.md:images[].url 优先水印 URL 再原图 URL——SyncImage.url 已按此约定填充。\n content.images = images.map(i => ({ url: i.url }))\n } else if (draft.linkCard) {\n const lc = draft.linkCard\n content.linkcard = {\n data_content_type: lc.contentType ?? 'common_url',\n data_content_id: lc.contentId ?? '0',\n url: lc.url,\n data_draft_title: lc.title ?? '',\n data_draft_cover: lc.cover ?? '',\n is_from_forward: lc.isFromForward ?? false,\n }\n }\n if (s.creationStatement === 'ai_creation') content.creation_statement = 'ai_creation'\n const ring = extractRingId(s.ring)\n if (ring) content.ring = ring\n return { ...base, content }\n}\n\nfunction strictZhihuUrl(raw: string, pathRe: RegExp): string | '' {\n try {\n const u = new URL(raw)\n if (u.protocol !== 'https:' || u.username || u.password || u.port) return ''\n if (u.hostname !== 'www.zhihu.com') return ''\n const m = u.pathname.match(pathRe)\n return m?.[1] ?? ''\n } catch { return '' }\n}\n\nfunction extractTopicTokens(urls: string[], max: number): { topic_id: string; topic_token: string; topic_name: string }[] {\n const out: { topic_id: string; topic_token: string; topic_name: string }[] = []\n const seen = new Set<string>()\n for (const u of urls) {\n const token = strictZhihuUrl(String(u), /^\\/topic\\/(\\d+)(?:\\/|$)/)\n if (!token || seen.has(token)) continue\n seen.add(token)\n out.push({ topic_id: '', topic_token: token, topic_name: '' })\n if (out.length >= max) break\n }\n return out\n}\n\nfunction extractRingId(url: string | undefined): { ring_id: string; ring_name: string } | undefined {\n if (!url) return undefined\n const id = strictZhihuUrl(url, /^\\/ring\\/host\\/(\\d+)(?:\\/|$)/)\n return id ? { ring_id: id, ring_name: '' } : undefined\n}\n","/**\n * 知乎 OpenAPI 发布/媒体上传共享 HTTP 层(HMAC-SHA256 签名)。\n *\n * 契约来源(只读镜像 H:\\Reference\\zhihu-sync-full_v0.1.4,MIT / author zhihu):\n * zhihu-sync/zhihu-publish/reference/publish-openapi.md — POST /openapi/publish\n * zhihu-mediacloud-uploader/mediacloud_uploader/api.py — /openapi/media/* 上传三步\n *\n * 签名:\n * sign_string = app_key:{X-App-Key}|ts:{X-Timestamp}|logid:{X-Log-Id}|extra_info:{X-Extra-Info}\n * X-Sign = Base64(HMAC-SHA256(sign_string, app_secret))\n *\n * 边界:\n * - 请求目标 origin 固定为 openapi.zhihu.com。baseUrl 仅用于同源等价地址\n * (如企业内部镜像);任何非该 origin 的相对/绝对地址一律拒绝——媒体上传\n * commit_address 等同源绝对 URL 也走同一校验。\n * - 不跟随重定向(redirect:'error')。\n * - appSecret / X-Sign / 上游 msg 中的长 base64 串永不进入返回值、错误或日志;\n * 错误消息统一过 sanitizeMessage。\n * - init.headers 只能「添加」业务头(媒体场景名/安全 token/内容 hash 等),\n * 不能覆盖 Content-Type 与任何 X-* 签名头;冲突时抛 INVALID_URL。\n * - 信封解包:body 带 status 字段按发布信封(status!==0 → UPSTREAM_REJECTED,\n * definite);媒体 Apply/Commit 返回扁平 JSON(无 status)时原样返回。\n */\nimport { createHmac, randomUUID } from 'node:crypto'\nimport { buildZhihuPublishRequest } from './sync-content.ts'\n\nexport const ZHIHU_SYNC_DEFAULT_BASE_URL = 'https://openapi.zhihu.com'\nexport const ZHIHU_SYNC_PUBLISH_PATH = '/openapi/publish'\nexport const ZHIHU_SYNC_DEFAULT_TIMEOUT_MS = 30_000\nexport const ZHIHU_SYNC_ALLOWED_ORIGIN = 'https://openapi.zhihu.com'\n\nexport type ZhihuSyncErrorCode =\n | 'TOKEN_MISSING'\n | 'INVALID_URL'\n | 'CANCELLED'\n | 'TIMEOUT'\n | 'NETWORK_ERROR'\n | 'AUTH_REJECTED'\n | 'RATE_LIMITED'\n | 'UPSTREAM_REJECTED'\n | 'HTTP_ERROR'\n | 'BAD_RESPONSE'\n\n/**\n * 发布/媒体调用错误。definite === true 表示已确定「这次请求没有成功」\n * (上游业务拒绝 / 鉴权拒绝 / 限流 / 参数错误 / URL 非法),可安全重试;\n * definite === false 表示结果不确定(超时 / 网络错 / 5xx / 异常响应),\n * service 必须保留 publishing 意图并提示用户核对,避免重复发送。\n */\nexport class ZhihuSyncApiError extends Error {\n constructor(\n readonly code: ZhihuSyncErrorCode,\n message: string,\n readonly definite: boolean,\n readonly httpStatus?: number,\n readonly upstreamStatus?: number,\n readonly upstreamMsg?: string,\n ) {\n super(message)\n this.name = 'ZhihuSyncApiError'\n }\n}\n\nexport type ZhihuSyncTransportOptions = {\n /** 由调用方解析凭证;本层不持久化也不输出。 */\n resolveCredentials: () => Promise<{ appKey: string; appSecret: string }>\n /** 默认 globalThis.fetch;测试注入假 fetcher 验证请求。 */\n fetcher?: typeof fetch\n /** 单次请求超时(毫秒),默认 30s。 */\n timeoutMs?: number\n /**\n * 允许的请求 origin。即使显式传入,也仍要求与请求目标一致且为 HTTPS;\n * 推荐保持缺省 https://openapi.zhihu.com,测试用 fetcher 即可拦截。\n */\n baseUrl?: string\n}\n\nexport type ZhihuSyncRequestInit = {\n method: 'GET' | 'POST' | 'PATCH'\n /** JSON body;自动 stringify 一次。 */\n body?: unknown\n /**\n * 附加业务头(媒体场景名 X-Media-Scene-Name、X-Security-Token、\n * X-Upload-Content-Type / -Hash 等)。不允许覆盖签名/内容类型头。\n */\n headers?: Record<string, string>\n /** 参与签名的 X-Extra-Info;缺省空字符串,即使为空也必须发送该头。 */\n extraInfo?: string\n}\n\nconst SIGNED_HEADER_NAMES = new Set([\n 'content-type',\n 'x-app-key',\n 'x-timestamp',\n 'x-log-id',\n 'x-extra-info',\n 'x-sign',\n])\n\n/**\n * 上游错误消息脱敏。secrets 可能以任意长度/形态出现在上游 msg、HTTP body 或\n * 网络错误 message 中——只做长 base64/hex 替换不足以保护短 secret 或 appKey,\n * 因此对「当前凭据」与「签名值」做精确字符串替换,其余仍按长度折叠。\n */\nfunction sanitizeMessage(value: unknown, secrets: readonly string[] = []): string {\n if (typeof value !== 'string' || !value) return ''\n let out = value\n for (const s of secrets) {\n if (s) out = out.split(s).join('<redacted>')\n }\n return out\n .replace(/[A-Za-z0-9+/=]{24,}/g, '<redacted>')\n .replace(/[0-9a-f]{32,}/gi, '<redacted>')\n .slice(0, 400)\n}\n\nfunction resolveBase(options: ZhihuSyncTransportOptions): URL {\n const raw = options.baseUrl ?? ZHIHU_SYNC_DEFAULT_BASE_URL\n let base: URL\n try {\n base = new URL(raw)\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的 baseUrl:${sanitizeMessage(raw)}`, true)\n }\n // 即使显式传入也只允许官方源;测试应注入 fetcher 而不是放宽目标主机。\n if (base.protocol !== 'https:' || base.origin !== ZHIHU_SYNC_ALLOWED_ORIGIN) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `OpenAPI baseUrl 必须等于 ${ZHIHU_SYNC_ALLOWED_ORIGIN}。`,\n true,\n )\n }\n return base\n}\n\nfunction syncUrl(pathOrUrl: string, base: URL): string {\n let url: URL\n if (/^https?:\\/\\//i.test(pathOrUrl)) {\n try {\n url = new URL(pathOrUrl)\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的请求地址:${sanitizeMessage(pathOrUrl)}`, true)\n }\n } else {\n try {\n url = new URL(pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`, base.origin + '/')\n } catch {\n throw new ZhihuSyncApiError('INVALID_URL', `无效的请求路径:${sanitizeMessage(pathOrUrl)}`, true)\n }\n }\n if (url.protocol !== 'https:' || url.username || url.password) {\n throw new ZhihuSyncApiError('INVALID_URL', '请求地址必须为无账号密码的 HTTPS。', true)\n }\n if (url.origin !== base.origin) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `拒绝向非 OpenAPI 源发起请求:${url.origin}(允许 ${base.origin})。`,\n true,\n )\n }\n return url.toString()\n}\n\nfunction sign(appKey: string, timestamp: string, logId: string, extraInfo: string, secret: string): string {\n const signString = `app_key:${appKey}|ts:${timestamp}|logid:${logId}|extra_info:${extraInfo}`\n return createHmac('sha256', secret).update(signString, 'utf8').digest('base64')\n}\n\nfunction buildHeaders(\n appKey: string,\n timestamp: string,\n logId: string,\n extraInfo: string,\n appSecret: string,\n extraHeaders: Record<string, string> | undefined,\n): Record<string, string> {\n const signed: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-App-Key': appKey,\n 'X-Timestamp': timestamp,\n 'X-Log-Id': logId,\n 'X-Extra-Info': extraInfo,\n 'X-Sign': sign(appKey, timestamp, logId, extraInfo, appSecret),\n }\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n const lower = name.toLowerCase()\n if (SIGNED_HEADER_NAMES.has(lower)) {\n throw new ZhihuSyncApiError(\n 'INVALID_URL',\n `init.headers 不允许覆盖签名或内容类型头:${name}。`,\n true,\n )\n }\n signed[name] = value\n }\n return signed\n}\n\n/**\n * 发起一次签名 OpenAPI 请求并解包响应。\n *\n * 返回规则:\n * - body 是对象且带 status 字段(发布信封):status===0 → 返回 body.data;\n * 其余 → UPSTREAM_REJECTED(definite,消息含 status / 已脱敏 msg)。\n * - body 不带 status(媒体 Apply/Commit 扁平响应):原样返回 body。\n * - 非 2xx:401→AUTH_REJECTED、429→RATE_LIMITED、5xx→HTTP_ERROR(不确定)、\n * 其他 4xx→HTTP_ERROR(definite);若 body 是可解析信封仍按信封处理。\n */\nexport async function syncRequest(\n pathOrUrl: string,\n init: ZhihuSyncRequestInit,\n options: ZhihuSyncTransportOptions,\n signal?: AbortSignal,\n): Promise<unknown> {\n const base = resolveBase(options)\n const url = syncUrl(pathOrUrl, base)\n const credentials = await options.resolveCredentials()\n const appKey = credentials.appKey?.trim()\n const appSecret = credentials.appSecret?.trim()\n if (!appKey || !appSecret) {\n throw new ZhihuSyncApiError('TOKEN_MISSING', '缺少知乎 OpenAPI 凭证(app key / app secret)。', true)\n }\n\n const timestamp = String(Math.floor(Date.now() / 1000))\n const logId = randomUUID().replace(/-/g, '')\n const extraInfo = init.extraInfo ?? ''\n const headers = buildHeaders(appKey, timestamp, logId, extraInfo, appSecret, init.headers)\n\n const fetcher = options.fetcher ?? globalThis.fetch\n if (!fetcher) throw new ZhihuSyncApiError('NETWORK_ERROR', '当前环境没有可用的 fetch。', false)\n\n // 参与脱敏的机密集合:凭据与本次签名值(X-Sign)。\n const secrets = [appKey, appSecret, headers['X-Sign']].filter(Boolean) as string[]\n\n const timeout = options.timeoutMs ?? ZHIHU_SYNC_DEFAULT_TIMEOUT_MS\n const timeoutController = new AbortController()\n const timer = setTimeout(() => timeoutController.abort(), timeout)\n const combined = signal\n ? AbortSignal.any([signal, timeoutController.signal])\n : timeoutController.signal\n\n let response: Response\n let text: string\n let body: unknown\n try {\n // pre-fetch abort(用户主动取消)是确定行为;fetch 已发出后 abort 结果不定。\n if (signal?.aborted) throw new ZhihuSyncApiError('CANCELLED', '请求已取消。', true)\n combined.throwIfAborted()\n response = await fetcher(url, {\n method: init.method,\n headers,\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n redirect: 'error',\n signal: combined,\n })\n // 超时覆盖整个请求:headers 返回不代表结束,body 读取同样计入。\n // response.text() 不接收 abort signal——手动 race 超时;finally 移除监听防泄漏。\n let abortReject!: (e: unknown) => void\n const abortPromise = new Promise<never>((_, reject) => {\n abortReject = () => reject(new DOMException('aborted', 'AbortError'))\n combined.addEventListener('abort', abortReject, { once: true })\n })\n // 未决 reject 被 GC 前无害:race 已结束后 abort 触发只产生未消费拒绝。\n abortPromise.catch(() => {})\n try {\n text = await Promise.race([response.text(), abortPromise])\n } finally {\n combined.removeEventListener('abort', abortReject)\n }\n if (text) body = JSON.parse(text)\n } catch (error) {\n if (error instanceof ZhihuSyncApiError) throw error\n if (signal?.aborted) throw new ZhihuSyncApiError('CANCELLED', '请求已取消。', false)\n if (timeoutController.signal.aborted) {\n throw new ZhihuSyncApiError('TIMEOUT', `请求超时(${Math.round(timeout / 1000)}s)。`, false)\n }\n // JSON 解析失败(body 非 JSON)→ BAD_RESPONSE;其余 fetch/body 错误 → NETWORK_ERROR。\n // 非 JSON 响应结果不定(可能是代理/网关截断),始终非 definite——\n // 上游返回 5xx 时 body 可能是 HTML,同样不能当确定失败。\n if (error instanceof SyntaxError) {\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `响应不是合法 JSON(HTTP ${response!.status})。`,\n false,\n response!.status,\n )\n }\n // 网络错误不透传原始 message——可能含目标主机/凭据线索;统一脱敏。\n throw new ZhihuSyncApiError(\n 'NETWORK_ERROR',\n '网络请求失败,请检查连接后重试。',\n false,\n )\n } finally {\n clearTimeout(timer)\n }\n\n const envelope = body && typeof body === 'object' && !Array.isArray(body)\n ? (body as Record<string, unknown>)\n : undefined\n const hasStatus = envelope !== undefined && 'status' in envelope\n\n if (!response.ok) {\n if (hasStatus && typeof envelope!.status === 'number' && envelope!.status !== 0) {\n throw new ZhihuSyncApiError(\n 'UPSTREAM_REJECTED',\n `发布被服务端拒绝(status=${envelope!.status}):${sanitizeMessage(envelope!.msg, secrets)}`,\n true,\n response.status,\n envelope!.status,\n sanitizeMessage(envelope!.msg, secrets),\n )\n }\n if (hasStatus && typeof envelope!.status !== 'number') {\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `发布响应 status 不是数字(收到 ${typeof envelope!.status})。`,\n false,\n response.status,\n )\n }\n if (response.status === 401) {\n throw new ZhihuSyncApiError('AUTH_REJECTED', '鉴权失败(HTTP 401)。请检查 app key / app secret。', true, 401)\n }\n if (response.status === 429) {\n throw new ZhihuSyncApiError('RATE_LIMITED', '发布频率超限(每人每天最多 50 次)。', true, 429)\n }\n throw new ZhihuSyncApiError(\n 'HTTP_ERROR',\n `HTTP ${response.status}:${sanitizeMessage(envelope?.msg, secrets) || '请求失败'}`,\n response.status >= 400 && response.status < 500,\n response.status,\n undefined,\n sanitizeMessage(envelope?.msg, secrets),\n )\n }\n\n if (hasStatus) {\n if (typeof envelope!.status === 'number' && envelope!.status === 0) {\n return envelope!.data\n }\n // status 非 0 数字 → 确定的业务拒绝;status 非数字(字符串/其他类型)→ 畸形响应。\n if (typeof envelope!.status === 'number') {\n throw new ZhihuSyncApiError(\n 'UPSTREAM_REJECTED',\n `发布被服务端拒绝(status=${envelope!.status}):${sanitizeMessage(envelope!.msg, secrets)}`,\n true,\n response.status,\n envelope!.status,\n sanitizeMessage(envelope!.msg, secrets),\n )\n }\n throw new ZhihuSyncApiError(\n 'BAD_RESPONSE',\n `发布响应 status 不是数字(收到 ${typeof envelope!.status})。`,\n false,\n response.status,\n )\n }\n return body\n}\n\n/** 知乎发布成功 URL:必须是 https 且属于知乎域(与 content 的 ZHIHU_DOMAIN_RE 一致)。 */\nconst ZHIHU_PUBLISH_DOMAIN_RE = /^([a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction isValidPublishUrl(value: string): boolean {\n try {\n const u = new URL(value)\n return u.protocol === 'https:' && !u.username && !u.password && ZHIHU_PUBLISH_DOMAIN_RE.test(u.hostname)\n } catch { return false }\n}\n\n/**\n * 将 prepareZhihuSyncDraft 的结果组装并发送到 /openapi/publish。\n *\n * 发送前不做内容字段二次校验(发布字段唯一事实源是 SyncPrepared);\n * 上层负责:预览确认、持久化 publishing 意图、按 definite 标记结果。\n */\nexport async function publishZhihuSync(\n prepared: unknown,\n options: ZhihuSyncTransportOptions,\n signal?: AbortSignal,\n): Promise<{ url: string; contentToken: string; type: string }> {\n const request = buildZhihuPublishRequest(prepared)\n const data = await syncRequest(ZHIHU_SYNC_PUBLISH_PATH, { method: 'POST', body: request }, options, signal)\n // syncRequest 只在上游 status===0 时返回 data;扁平 {url,content_token} 无 status\n // 不会被当成成功(publish-openapi.md:成功判断 = status===0)。\n const row = (data ?? {}) as Record<string, unknown>\n const contentToken =\n typeof row.content_token === 'string' || typeof row.content_token === 'number'\n ? String(row.content_token)\n : ''\n const url = typeof row.url === 'string' ? row.url : ''\n const type = typeof row.type === 'string' ? row.type : ''\n if (!contentToken || !url) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', '发布成功响应缺少 content_token 或 url。', false)\n }\n if (!isValidPublishUrl(url)) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', `发布成功响应返回了无效的 url:${sanitizeMessage(url, [])}`, false)\n }\n if (type !== request.type) {\n throw new ZhihuSyncApiError('BAD_RESPONSE', `发布响应 type 与请求不一致:${String(type)} ≠ ${request.type}。`, false)\n }\n return { url, contentToken, type }\n}\n","/**\n * 知乎官方媒体上传(仅图片)。协议来源:只读参考\n * zhihu-mediacloud-uploader v0.1.4(mediacloud_uploader/api.py、client.py、cloud/ali.py、\n * watermark_poller.py、image_validator.py),移植为 Node TypeScript 原生 fetch。\n *\n * 流程:本地校验 → ApplyUpload(POST /openapi/media/uploads)→ 阿里 OSS PutObject\n * (自建 HTTPS 直传,OSS v1 签名)→ CommitUpload(PATCH)→ 水印轮询\n * (GET /openapi/media/images/{id}/watermark)。秒传(UPLOAD_SUCCESS)跳过云直传。\n *\n * 安全约束:重定向拒绝;OSS 目的主机仅允许 https 且 *.aliyuncs.com;\n * 凭据、STS、签名绝不进入返回值或错误消息(统一脱敏)。\n */\nimport { createHash, createHmac } from 'node:crypto'\nimport { syncRequest, type ZhihuSyncTransportOptions } from './sync-api.ts'\n\nexport type ZhihuSyncMediaOptions = ZhihuSyncTransportOptions & {\n /** 测试注入点;缺省用 ./sync-api.ts 的共享签名传输。 */\n syncRequest?: typeof syncRequest\n /** 水印轮询节奏(测试可调快)。 */\n watermarkPoll?: {\n intervalMs?: number\n timeoutMs?: number\n sleep?: (ms: number, signal: AbortSignal) => Promise<void>\n }\n}\n\nexport type ZhihuSyncMediaInput = {\n fileName: string\n contentBase64: string\n scene: 'article' | 'question' | 'pin'\n kind?: 'image' | 'video'\n}\n\nexport type ZhihuSyncMediaResult = {\n kind: 'image'\n /** 最终可公开 URL(水印成功时为水印图 URL)。 */\n url: string\n /** 水印前原图 URL(仅水印成功时有值)。 */\n originalUrl?: string\n /** 最终媒资 token:水印成功为服务端 watermark_image_token,否则为 v2-{md5}。 */\n mediaId?: string\n width?: number\n height?: number\n /** 仅当服务端确认返回了不同的水印图 key 时为 true;绝不自拼 URL 伪造。 */\n watermark?: boolean\n warnings?: string[]\n}\n\n// ── 错误 ───────────────────────────────────────────────────────────────────────\n\nexport type ZhihuSyncMediaErrorCode =\n | 'INVALID_INPUT' | 'UNSUPPORTED_TYPE' | 'NEEDS_TRANSCODE' | 'LIMIT_EXCEEDED'\n | 'API_ERROR' | 'AUTH_ERROR' | 'OSS_ERROR'\n | 'UNSUPPORTED_VENDOR' | 'SESSION_EXPIRED' | 'CANCELLED' | 'TIMEOUT' | 'BAD_RESPONSE'\n\nexport class ZhihuSyncMediaError extends Error {\n readonly code: ZhihuSyncMediaErrorCode\n readonly httpStatus?: number\n readonly vendorCode?: string\n constructor(code: ZhihuSyncMediaErrorCode, message: string, extra: { httpStatus?: number; vendorCode?: string } = {}) {\n super(message)\n this.name = 'ZhihuSyncMediaError'\n this.code = code\n if (extra.httpStatus !== undefined) this.httpStatus = extra.httpStatus\n if (extra.vendorCode !== undefined) this.vendorCode = extra.vendorCode\n }\n}\n\n// ── 常量 ───────────────────────────────────────────────────────────────────────\n\nconst PATH_APPLY_UPLOAD = '/openapi/media/uploads'\nconst PATH_COMMIT_UPLOAD = (sessionKey: string) => `/openapi/media/uploads/${sessionKey}`\nconst PATH_IMAGE_WATERMARK = (imageId: string) => `/openapi/media/images/${imageId}/watermark`\n\nconst TEMPLATE_IMAGE = 'image-upload-default'\nconst MEDIA_TYPE_IMAGE = 'image'\nconst PLATFORM_WEB = 'web'\n\nconst UPLOADING = 'UPLOADING'\nconst UPLOAD_SUCCESS = 'UPLOAD_SUCCESS'\nconst UPLOAD_FAIL = 'UPLOAD_FAIL'\n\nconst SIZE_LIMIT_GIF = 15 * 1024 * 1024\nconst SIZE_LIMIT_OTHER = 30 * 1024 * 1024\nconst MAX_LONG_SIDE = 4 * 4096\nconst MAX_TOTAL_PIXELS = 200_000_000\n\nconst WATERMARK_INTERVAL_MS = 500\nconst WATERMARK_TIMEOUT_MS = 30_000\nconst OSS_TIMEOUT_MS = 120_000\n\nconst VENDOR_ALI = 'ali'\n\nconst OSS_EXPIRED_CODES = new Set(['InvalidAccessKeyId', 'SecurityTokenExpired', 'AccessDenied', 'InvalidSecurityToken'])\n\n// ── 工具 ───────────────────────────────────────────────────────────────────────\n\nconst record = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v)\nconst str = (v: unknown): string => (typeof v === 'string' ? v : '')\nconst num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0)\n\n/** 从待抛出消息中抹除一切已知敏感串(STS、对象名等不参与签名但属临时授权信息)。 */\nfunction redact(message: string, secrets: readonly string[]): string {\n let out = message\n for (const s of secrets) {\n if (s && s.length >= 4) out = out.split(s).join('[REDACTED]')\n }\n return out\n}\n\nfunction throwAborted(signal: AbortSignal): never {\n throw new ZhihuSyncMediaError('CANCELLED', '上传已取消。')\n}\n\ntype MediaUrl = { primary: string; backups: string[] }\nfunction parseMediaUrl(raw: unknown): MediaUrl | undefined {\n if (!record(raw)) return undefined\n const backups = Array.isArray(raw.backups) ? raw.backups.filter((b): b is string => typeof b === 'string') : []\n const primary = str(raw.primary) || backups[0] || ''\n return { primary, backups }\n}\n// 可公开图片 URL 只接受知乎真实图片域的 HTTPS 地址——上游若回内网/他域地址,\n// 前端 preview 会变成 SSRF 通道。域名策略与 sync-content 图片引用一致。\nconst PUBLIC_IMAGE_HOST_RE = /^(?:[a-z0-9-]+\\.)*(zhimg\\.com|zhihu\\.com)$/i\nfunction publicImageUrl(u: MediaUrl | undefined): string {\n for (const candidate of [u?.primary, ...(u?.backups ?? [])]) {\n if (!candidate) continue\n try {\n const url = new URL(candidate)\n if (url.protocol !== 'https:' || url.username || url.password) continue\n if (!PUBLIC_IMAGE_HOST_RE.test(url.hostname)) continue\n return candidate\n } catch { continue }\n }\n return ''\n}\nconst firstUrl = publicImageUrl // 兼容命名:语义即「首个合法可公开 URL」\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal.aborted) return reject(new ZhihuSyncMediaError('CANCELLED', '上传已取消。'))\n const timer = setTimeout(() => { cleanup(); resolve() }, ms)\n const onAbort = () => { cleanup(); reject(new ZhihuSyncMediaError('CANCELLED', '上传已取消。')) }\n const cleanup = () => { clearTimeout(timer); signal.removeEventListener('abort', onAbort) }\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n// ── 图片嗅探(只读文件头;不支持的格式明确报错,不静默改写)─────────────────────\n\ntype SniffedImage = { format: string; ext: string; contentType: string; width: number; height: number; gif: boolean }\n\nfunction sniffImage(buf: Uint8Array): SniffedImage {\n const bad = () => new ZhihuSyncMediaError('UNSUPPORTED_TYPE',\n '无法识别图片格式,仅支持 JPEG / PNG / GIF / WebP / BMP;HEIC / AVIF / TIFF 等请先转换为上述格式。')\n if (buf.byteLength < 12) throw bad()\n\n // HEIC / HEIF / AVIF(ISOBMFF ftyp 品牌)——参考实现需转码,这里明确报错。\n if (buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) { // 'ftyp'\n const brand = String.fromCharCode(buf[8], buf[9], buf[10], buf[11]).toLowerCase()\n if (/^(heic|heix|hevc|hevx|mif1|msf1|avif|avis)$/.test(brand)) {\n throw new ZhihuSyncMediaError('NEEDS_TRANSCODE',\n `检测到 ${brand.toUpperCase()} 格式,当前不内置转码,请先转换为 JPEG / PNG / WebP 后再上传。`)\n }\n throw bad()\n }\n\n // PNG\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47\n && buf[4] === 0x0d && buf[5] === 0x0a && buf[6] === 0x1a && buf[7] === 0x0a) {\n if (buf.byteLength < 24) throw bad()\n const w = (buf[16]! << 24 | buf[17]! << 16 | buf[18]! << 8 | buf[19]!) >>> 0\n const h = (buf[20]! << 24 | buf[21]! << 16 | buf[22]! << 8 | buf[23]!) >>> 0\n if (!w || !h) throw bad()\n return { format: 'PNG', ext: '.png', contentType: 'image/png', width: w, height: h, gif: false }\n }\n\n // GIF\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) {\n if (buf.byteLength < 10) throw bad()\n const w = buf[6]! | (buf[7]! << 8)\n const h = buf[8]! | (buf[9]! << 8)\n if (!w || !h) throw bad()\n return { format: 'GIF', ext: '.gif', contentType: 'image/gif', width: w, height: h, gif: true }\n }\n\n // BMP\n if (buf[0] === 0x42 && buf[1] === 0x4d) { // 'BM'\n if (buf.byteLength < 26) throw bad()\n const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n if (dv.getUint32(14, true) < 40) throw bad()\n const w = Math.abs(dv.getInt32(18, true))\n const h = Math.abs(dv.getInt32(22, true))\n if (!w || !h) throw bad()\n return { format: 'BMP', ext: '.bmp', contentType: 'image/bmp', width: w, height: h, gif: false }\n }\n\n // WebP: RIFF....WEBP + VP8 / VP8L / VP8X chunk\n if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46\n && buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {\n if (buf.byteLength < 30) throw bad()\n const four = String.fromCharCode(buf[12], buf[13], buf[14], buf[15])\n if (four === 'VP8X') {\n const w = (buf[24]! | (buf[25]! << 8) | (buf[26]! << 16)) + 1\n const h = (buf[27]! | (buf[28]! << 8) | (buf[29]! << 16)) + 1\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp', width: w, height: h, gif: false }\n }\n if (four === 'VP8L') {\n const bits = buf[21]! | (buf[22]! << 8) | (buf[23]! << 16) | (buf[24]! << 24)\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp',\n width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1, gif: false }\n }\n if (four === 'VP8 ') {\n if (buf[23] !== 0x9d || buf[24] !== 0x01 || buf[25] !== 0x2a) throw bad()\n const w = (buf[26]! | (buf[27]! << 8)) & 0x3fff\n const h = (buf[28]! | (buf[29]! << 8)) & 0x3fff\n if (!w || !h) throw bad()\n return { format: 'WEBP', ext: '.webp', contentType: 'image/webp', width: w, height: h, gif: false }\n }\n throw bad()\n }\n\n // JPEG: FF D8 + SOF marker\n if (buf[0] === 0xff && buf[1] === 0xd8) {\n let off = 2\n while (off + 9 < buf.byteLength) {\n if (buf[off] !== 0xff) { off++; continue }\n let marker = buf[off + 1]!\n while (marker === 0xff && off + 2 < buf.byteLength) { off++; marker = buf[off + 1]! }\n if (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) { off += 2; continue }\n if (off + 3 >= buf.byteLength) break\n const len = (buf[off + 2]! << 8) | buf[off + 3]!\n if (len < 2) break\n const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc\n if (isSof && off + 8 < buf.byteLength) {\n const h = (buf[off + 5]! << 8) | buf[off + 6]!\n const w = (buf[off + 7]! << 8) | buf[off + 8]!\n if (!w || !h) throw bad()\n return { format: 'JPEG', ext: '.jpg', contentType: 'image/jpeg', width: w, height: h, gif: false }\n }\n off += 2 + len\n }\n throw bad()\n }\n\n throw bad()\n}\n\nfunction validateImage(meta: SniffedImage, sizeBytes: number): void {\n const limit = meta.gif ? SIZE_LIMIT_GIF : SIZE_LIMIT_OTHER\n if (sizeBytes > limit) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED',\n `图片大小超限(${(sizeBytes / 1048576).toFixed(1)} MB),上限 ${limit / 1048576} MB${meta.gif ? '(GIF)' : ''},请压缩后重试。`)\n }\n const longSide = Math.max(meta.width, meta.height)\n if (longSide > MAX_LONG_SIDE) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED',\n `图片长边 ${longSide}px 超限,上限 ${MAX_LONG_SIDE}px。`)\n }\n if (meta.width * meta.height > MAX_TOTAL_PIXELS) {\n throw new ZhihuSyncMediaError('LIMIT_EXCEEDED', '图片总像素数超限(上限 2 亿像素)。')\n }\n}\n\n// ── 服务端下发数据解析 ─────────────────────────────────────────────────────────\n\ntype ApplyResponse = {\n state: string\n sessionKey: string\n vendorCode: string\n commitAddress: string\n objectKey: string\n processes: string\n extraParams: Record<string, unknown>\n auth?: { accessKeyId: string; secretAccessKey: string; securityToken: string; expiration: number }\n address?: { endpoint: string; bucketName: string; objectName: string; isCname: boolean }\n}\n\nfunction parseApply(data: unknown): ApplyResponse {\n if (!record(data)) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'ApplyUpload 响应不是对象。')\n const config = record(data.upload_config) ? data.upload_config : {}\n const addressRaw = record(config.address) ? config.address : undefined\n const authRaw = record(data.upload_auth) ? data.upload_auth : undefined\n const media = record(data.upload_media) ? data.upload_media : {}\n return {\n state: str(data.upload_state),\n sessionKey: str(data.session_key),\n vendorCode: str(config.vendor_code),\n commitAddress: str(media.commit_address),\n objectKey: str(media.key),\n processes: str(media.processes),\n extraParams: record(data.extra_params) ? data.extra_params : {},\n auth: authRaw ? {\n accessKeyId: str(authRaw.access_key_id),\n secretAccessKey: str(authRaw.secret_access_key),\n securityToken: str(authRaw.security_token),\n expiration: num(authRaw.expiration),\n } : undefined,\n address: addressRaw ? {\n endpoint: str(addressRaw.endpoint),\n bucketName: str(addressRaw.bucket_name),\n objectName: str(addressRaw.object_name),\n isCname: addressRaw.is_cname === true,\n } : undefined,\n }\n}\n\ntype CommitResponse = {\n result: string\n mediaKey: string\n mediaUrl?: MediaUrl\n width?: number\n height?: number\n}\n\nfunction parseCommit(data: unknown): CommitResponse {\n if (!record(data)) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'CommitUpload 响应不是对象。')\n const meta = record(data.media_meta) ? data.media_meta : undefined\n return {\n result: str(data.upload_results),\n mediaKey: str(data.media_key),\n mediaUrl: parseMediaUrl(data.media_url),\n width: meta ? num(meta.width) || undefined : undefined,\n height: meta ? num(meta.height) || undefined : undefined,\n }\n}\n\n// ── 阿里 OSS PutObject(v1 签名,等价 oss2 StsAuth + Bucket.put_object)─────────\n\n// OSS 主机与 bucket 的最小校验。只接受 aliyuncs.com 下的主机名与合法 bucket 名:\n// 路径、端口、账号密码、反斜杠都可能把直传请求改投到别的主机上,而请求头里带着 STS 凭据。\nconst OSS_HOST_RE = /^(?:[a-z0-9-]+\\.)*aliyuncs\\.com$/\nconst OSS_BUCKET_RE = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/\n\nfunction ossEndpointHost(endpoint: string): string {\n const text = endpoint.trim()\n if (!text) throw new ZhihuSyncMediaError('BAD_RESPONSE', 'OSS endpoint 为空。')\n if (/^http:\\/\\//i.test(text)) throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS endpoint 必须使用 HTTPS。')\n // 只接受裸主机名或 https://主机名。\n const host = text.replace(/^https:\\/\\//i, '').replace(/\\/+$/, '').toLowerCase()\n if (!OSS_HOST_RE.test(host)) {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS endpoint 必须是不带路径、端口与账号密码的 aliyuncs.com 主机名。')\n }\n return host\n}\n\n/**\n * 拼出直传地址,并校验最终 URL:主机名必须与预期的合规主机完全一致,且不得带账号密码。\n * 返回的就是实际发出去的那个 URL——校验与请求必须是同一个地址。\n */\nfunction ossTargetUrl(bucket: string, host: string, encodedKey: string, isCname: boolean): string {\n if (!OSS_BUCKET_RE.test(bucket)) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', 'OSS bucket 名称不合法。')\n }\n const requestHost = isCname ? host : `${bucket}.${host}`\n let target: URL\n try {\n target = new URL(`https://${requestHost}/${encodedKey}`)\n } catch {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS 目标地址不合法。')\n }\n if (target.protocol !== 'https:' || target.username || target.password || target.hostname !== requestHost) {\n throw new ZhihuSyncMediaError('OSS_ERROR', 'OSS 目标地址不合法。')\n }\n return target.toString()\n}\n\nfunction encodeObjectName(name: string): string {\n return name.split('/').map(seg => encodeURIComponent(seg)).join('/')\n}\n\ntype OssCredentials = { accessKeyId: string; secretAccessKey: string; securityToken: string }\n\n/**\n * OSS v1 header 签名:\n * StringToSign = VERB \\n Content-MD5 \\n Content-Type \\n Date \\n x-oss-* \\n CanonicalizedResource\n */\nfunction ossAuthorization(verb: string, contentMd5B64: string, contentType: string, date: string,\n auth: OssCredentials, bucket: string, encodedKey: string): string {\n const canonicalHeaders = `x-oss-security-token:${auth.securityToken}\\n`\n const canonicalResource = `/${bucket}/${encodedKey}`\n const toSign = `${verb}\\n${contentMd5B64}\\n${contentType}\\n${date}\\n${canonicalHeaders}${canonicalResource}`\n const sig = createHmac('sha1', auth.secretAccessKey).update(toSign, 'utf8').digest('base64')\n return `OSS ${auth.accessKeyId}:${sig}`\n}\n\nfunction parseOssErrorCode(xml: string): string {\n const m = /<Code>([^<]+)<\\/Code>/i.exec(xml)\n return m ? m[1]!.trim() : ''\n}\n\nasync function ossPutObject(\n fetcher: typeof fetch,\n auth: OssCredentials,\n address: NonNullable<ApplyResponse['address']>,\n body: Uint8Array,\n contentType: string,\n md5B64: string,\n signal: AbortSignal,\n timeoutMs: number,\n): Promise<{ ok: true } | { ok: false; ossCode: string; status: number }> {\n const host = ossEndpointHost(address.endpoint)\n const encodedKey = encodeObjectName(address.objectName)\n const url = ossTargetUrl(address.bucketName, host, encodedKey, address.isCname)\n const date = new Date().toUTCString()\n const authorization = ossAuthorization('PUT', md5B64, contentType, date, auth, address.bucketName, encodedKey)\n\n const controller = new AbortController()\n const onAbort = () => controller.abort(signal.reason)\n if (signal.aborted) onAbort()\n else signal.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => controller.abort(new Error('oss put timeout')), timeoutMs)\n try {\n const res = await fetcher(url, {\n method: 'PUT',\n redirect: 'error',\n headers: {\n Date: date,\n 'Content-Type': contentType,\n 'Content-MD5': md5B64,\n 'x-oss-security-token': auth.securityToken,\n Authorization: authorization,\n },\n body: body as unknown as BodyInit,\n signal: controller.signal,\n })\n if (res.ok) return { ok: true }\n const text = await res.text().catch(() => '')\n return { ok: false, ossCode: parseOssErrorCode(text), status: res.status }\n } finally {\n clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n }\n}\n\nfunction resolveSyncRequest(options: ZhihuSyncMediaOptions): typeof syncRequest {\n return options.syncRequest ?? syncRequest\n}\n\n// ── 主入口 ─────────────────────────────────────────────────────────────────────\n\nexport async function uploadZhihuSyncMedia(\n input: ZhihuSyncMediaInput,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n): Promise<ZhihuSyncMediaResult> {\n const secrets: string[] = []\n try {\n return await uploadImpl(input, options, signal, secrets)\n } catch (error) {\n if (error instanceof ZhihuSyncMediaError) {\n error.message = redact(error.message, secrets)\n throw error\n }\n throw new ZhihuSyncMediaError('API_ERROR', redact(\n `媒体上传失败:${error instanceof Error ? error.message : String(error)}`, secrets))\n }\n}\n\nasync function uploadImpl(\n input: ZhihuSyncMediaInput,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n secrets: string[],\n): Promise<ZhihuSyncMediaResult> {\n if (signal.aborted) throwAborted(signal)\n if (input.kind !== undefined && input.kind !== 'image') {\n throw new ZhihuSyncMediaError('UNSUPPORTED_TYPE', '当前仅支持图片上传;视频与对象文件未接入。')\n }\n const scene = input.scene\n if (scene !== 'article' && scene !== 'question' && scene !== 'pin') {\n throw new ZhihuSyncMediaError('INVALID_INPUT', `scene 必须是 article / question / pin,收到 ${String(scene)}。`)\n }\n const fileName = (input.fileName.trim().split(/[\\\\/]/).pop() ?? '').trim()\n if (!fileName || [...fileName].some(ch => (ch.codePointAt(0) ?? 0) < 32 || ch.codePointAt(0) === 127)) {\n throw new ZhihuSyncMediaError('INVALID_INPUT', 'fileName 不能为空且不能包含控制字符。')\n }\n const b64 = input.contentBase64.trim()\n // Buffer.from 对非法字符静默跳过,必须先严格校验,避免混入内容被当图片上传。\n if (!b64 || !/^[A-Za-z0-9+/]*={0,2}$/.test(b64) || b64.length % 4 !== 0) {\n throw new ZhihuSyncMediaError('INVALID_INPUT', 'contentBase64 不是合法 base64。')\n }\n const bytes = new Uint8Array(Buffer.from(b64, 'base64'))\n if (!bytes.byteLength) throw new ZhihuSyncMediaError('INVALID_INPUT', '图片内容为空。')\n\n const meta = sniffImage(bytes)\n validateImage(meta, bytes.byteLength)\n\n const md5Hex = createHash('md5').update(bytes).digest('hex')\n const md5B64 = createHash('md5').update(bytes).digest('base64')\n const syncRequest = resolveSyncRequest(options)\n // 提前登记 app 凭据用于错误消息脱敏(transport 会再解析一次,失败由其报错)。\n try {\n const creds = await options.resolveCredentials()\n if (creds?.appKey) secrets.push(creds.appKey)\n if (creds?.appSecret) secrets.push(creds.appSecret)\n } catch { /* 凭据解析失败由 syncRequest 统一报错 */ }\n if (signal.aborted) throwAborted(signal)\n\n const warnings: string[] = []\n const applyBody: Record<string, unknown> = {\n template_name: TEMPLATE_IMAGE,\n media_type: MEDIA_TYPE_IMAGE,\n file_name: fileName,\n file_extension: meta.ext,\n file_size: bytes.byteLength,\n }\n const applyHeaders = {\n 'X-Media-Scene-Name': scene,\n 'X-Media-Upload-Platform': PLATFORM_WEB,\n 'X-Upload-Content-Type': meta.contentType,\n 'X-Upload-Content-Hash': md5Hex,\n }\n\n let apply = parseApply(await syncRequest(PATH_APPLY_UPLOAD, { method: 'POST', headers: applyHeaders, body: applyBody }, options, signal))\n if (apply.auth) secrets.push(apply.auth.accessKeyId, apply.auth.secretAccessKey, apply.auth.securityToken)\n if (signal.aborted) throwAborted(signal)\n\n let mediaKey = ''\n let mediaUrl: MediaUrl | undefined\n let width = meta.width\n let height = meta.height\n\n if (apply.state === UPLOAD_SUCCESS) {\n // 秒传:apply 阶段即完成,extra_params 内嵌 meta 与 url。\n const ep = apply.extraParams\n mediaUrl = parseMediaUrl(ep.url)\n mediaKey = apply.objectKey\n width = num(ep.width) || width\n height = num(ep.height) || height\n } else if (apply.state === UPLOADING) {\n if (apply.vendorCode !== VENDOR_ALI) {\n throw new ZhihuSyncMediaError('UNSUPPORTED_VENDOR',\n `服务端下发未实现的云厂商 ${apply.vendorCode || '(空)'};当前仅支持阿里 OSS。`,\n { vendorCode: apply.vendorCode || undefined })\n }\n if (!apply.auth || !apply.address || !apply.auth.accessKeyId || !apply.auth.secretAccessKey\n || !apply.auth.securityToken || !apply.address.endpoint || !apply.address.bucketName || !apply.address.objectName) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', 'ApplyUpload 缺少 OSS 凭证或地址,无法直传。')\n }\n\n const fetcher = options.fetcher ?? (typeof fetch === 'function' ? fetch : undefined)\n if (!fetcher) throw new ZhihuSyncMediaError('BAD_RESPONSE', '当前环境没有可用的 fetch。')\n\n // OSS 直传;凭证过期码时携带 session_key 重调 apply 刷新一次。\n let oss = await ossPut(fetcher, apply, bytes, meta.contentType, md5B64, signal, options.timeoutMs)\n if (!oss.ok && OSS_EXPIRED_CODES.has(oss.ossCode)) {\n const refreshed = parseApply(await syncRequest(PATH_APPLY_UPLOAD, {\n method: 'POST', headers: applyHeaders,\n body: { template_name: TEMPLATE_IMAGE, media_type: MEDIA_TYPE_IMAGE, session_key: apply.sessionKey },\n }, options, signal))\n if (refreshed.state === UPLOAD_FAIL || !refreshed.sessionKey) {\n throw new ZhihuSyncMediaError('SESSION_EXPIRED', '上传会话已过期,刷新凭证被拒绝。')\n }\n apply = refreshed\n if (apply.auth) secrets.push(apply.auth.accessKeyId, apply.auth.secretAccessKey, apply.auth.securityToken)\n if (!apply.auth || !apply.address) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', '刷新后的 ApplyUpload 缺少 OSS 凭证或地址。')\n }\n oss = await ossPut(fetcher, apply, bytes, meta.contentType, md5B64, signal, options.timeoutMs)\n }\n\n let ossError = ''\n if (!oss.ok) ossError = `OSS PutObject HTTP ${oss.status}${oss.ossCode ? ` code=${oss.ossCode}` : ''}`\n\n // 无论成败都 CommitUpload,保持服务端状态一致。\n const commitBody: Record<string, unknown> = {\n media_type: MEDIA_TYPE_IMAGE,\n client_upload_state: oss.ok ? UPLOAD_SUCCESS : UPLOAD_FAIL,\n }\n if (ossError) commitBody.error_log = ossError\n if (apply.processes) commitBody.processes = apply.processes\n const commit = parseCommit(await syncRequest(apply.commitAddress || PATH_COMMIT_UPLOAD(apply.sessionKey), {\n method: 'PATCH',\n headers: {\n 'X-Media-Scene-Name': scene,\n 'X-Media-Upload-Platform': PLATFORM_WEB,\n 'X-Security-Token': apply.auth?.securityToken ?? '',\n },\n body: commitBody,\n }, options, signal))\n\n if (!oss.ok) throw new ZhihuSyncMediaError('OSS_ERROR', `云端直传失败:${ossError}`, { httpStatus: oss.status })\n if (commit.result !== UPLOAD_SUCCESS) {\n throw new ZhihuSyncMediaError('API_ERROR', `CommitUpload 未成功:${commit.result || '(空结果)'}。`)\n }\n mediaKey = commit.mediaKey || apply.objectKey\n mediaUrl = commit.mediaUrl\n width = commit.width ?? width\n height = commit.height ?? height\n } else {\n throw new ZhihuSyncMediaError('API_ERROR',\n `ApplyUpload 返回 ${apply.state || '(空状态)'}${apply.state === UPLOAD_FAIL ? ',会话被拒绝' : ''}。`)\n }\n\n // 图片成功:媒资 token 与 pier SDK 一致为 v2-{md5}。\n const originalKey = `v2-${md5Hex}`\n if (!mediaUrl || !firstUrl(mediaUrl)) {\n throw new ZhihuSyncMediaError('BAD_RESPONSE', '上传成功但服务端未返回可访问图片 URL。')\n }\n const originalUrl = firstUrl(mediaUrl)\n\n // ── 水印轮询:失败/超时降级原图;仅当 watermark key 与原 key 实际不同才算成功。\n const watermark = await pollWatermark(syncRequest, apply.sessionKey, scene, originalKey, options, signal, warnings)\n if (watermark) {\n return {\n kind: 'image',\n url: firstUrl(watermark.url),\n originalUrl,\n mediaId: watermark.token,\n width, height,\n watermark: true,\n ...(warnings.length ? { warnings } : {}),\n }\n }\n return {\n kind: 'image',\n url: originalUrl,\n mediaId: mediaKey && mediaKey !== apply.objectKey ? mediaKey : originalKey,\n width, height,\n watermark: false,\n ...(warnings.length ? { warnings } : {}),\n }\n}\n\nasync function ossPut(\n fetcher: typeof fetch,\n apply: ApplyResponse,\n bytes: Uint8Array,\n contentType: string,\n md5B64: string,\n signal: AbortSignal,\n timeoutMs: number | undefined,\n): Promise<{ ok: true } | { ok: false; ossCode: string; status: number }> {\n const auth = apply.auth!\n const address = apply.address!\n try {\n return await ossPutObject(fetcher, auth, address, bytes, contentType, md5B64, signal,\n timeoutMs && timeoutMs > 0 ? Math.max(timeoutMs, OSS_TIMEOUT_MS / 4) : OSS_TIMEOUT_MS)\n } catch (error) {\n if (error instanceof ZhihuSyncMediaError) throw error\n const name = (error as { name?: unknown })?.name\n if (name === 'AbortError') {\n if (signal.aborted) throwAborted(signal)\n throw new ZhihuSyncMediaError('TIMEOUT', '云端直传超时。')\n }\n return { ok: false, ossCode: '', status: 0 }\n }\n}\n\ntype WatermarkOk = { token: string; url: MediaUrl }\n\nasync function pollWatermark(\n request: typeof syncRequest,\n imageId: string,\n scene: string,\n originalKey: string,\n options: ZhihuSyncMediaOptions,\n signal: AbortSignal,\n warnings: string[],\n): Promise<WatermarkOk | undefined> {\n if (!imageId) return undefined\n const interval = options.watermarkPoll?.intervalMs ?? WATERMARK_INTERVAL_MS\n const timeout = options.watermarkPoll?.timeoutMs ?? WATERMARK_TIMEOUT_MS\n const doSleep = options.watermarkPoll?.sleep ?? sleep\n const deadline = Date.now() + timeout\n\n while (Date.now() < deadline) {\n if (signal.aborted) throwAborted(signal)\n let data: unknown\n try {\n // 单次请求超时不超过剩余轮询预算,避免多次 30s transport 超时放大总等待。\n const remaining = deadline - Date.now()\n const scoped = { ...options, timeoutMs: Math.max(1, Math.min(options.timeoutMs ?? 30_000, remaining)) }\n data = await request(PATH_IMAGE_WATERMARK(imageId), {\n method: 'GET', headers: { 'X-Media-Scene-Name': scene },\n }, scoped, signal)\n } catch (error) {\n if (signal.aborted) throwAborted(signal)\n const status = (error as { httpStatus?: unknown; status?: unknown })?.httpStatus\n ?? (error as { status?: unknown })?.status\n if (typeof status === 'number' && status >= 400 && status < 500) break // 永久错误:降级\n await doSleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal)\n continue\n }\n if (!record(data)) break\n const state = str(data.state)\n if (state === 'success') {\n const token = str(data.watermark_image_token)\n const url = parseMediaUrl(data.watermark_image_url)\n // 水印必须返回不同的服务端 key 与真实 URL;相同或缺失一律降级,绝不自拼。\n if (url && firstUrl(url) && token && token !== originalKey) {\n if (!url.primary && url.backups[0]) warnings.push('水印图主 URL 为空,已使用备用 URL。')\n return { token, url }\n }\n break\n }\n if (state === 'failed') break\n await doSleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal)\n }\n warnings.push('水印处理未完成,已使用未加水印的原图 URL。')\n return undefined\n}\n","import { randomUUID } from 'node:crypto'\nimport { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\nimport { z } from 'zod'\nimport { prepareZhihuSyncDraft } from './sync-content.ts'\nimport { publishZhihuSync, ZhihuSyncApiError, type ZhihuSyncTransportOptions } from './sync-api.ts'\nimport { uploadZhihuSyncMedia } from './sync-media.ts'\nimport type { SyncDraft } from './sync-types.ts'\n\nexport const ZHIHU_SYNC_CREDENTIAL_REFS = ['ZHIHU_OPENAPI_APP_KEY', 'ZHIHU_OPENAPI_APP_SECRET'] as const\n\nexport type SyncDraftRecord = {\n id: string\n revision: number\n updatedAt: string\n draft: SyncDraft\n publishState?: 'publishing' | 'published' | 'uncertain'\n published?: { url: string; contentToken: string; type: string }\n lastError?: string\n}\n\nconst draftSchema = z.object({\n type: z.enum(['article', 'question', 'pin']), title: z.string().max(10_000),\n body: z.string().max(2_000_000), format: z.enum(['markdown', 'html']),\n images: z.array(z.object({\n url: z.string(), originalUrl: z.string().optional(), width: z.number().positive().optional(),\n height: z.number().positive().optional(), watermark: z.boolean().optional(), mediaId: z.string().optional(),\n }).strict()).max(200).optional(),\n linkCard: z.object({\n url: z.string(), title: z.string().optional(), cover: z.string().optional(),\n contentType: z.string().optional(), contentId: z.string().optional(), isFromForward: z.boolean().optional(),\n }).strict().nullable().optional(),\n settings: z.object({\n commentPermission: z.enum(['all', 'nobody', 'followee', 'censor', 'follower', 'follower_n_days']).optional(),\n creationStatement: z.enum(['spoiler', 'medical_advice', 'fictional_creation', 'contain_finance', 'ai_creation']).optional(),\n tableOfContentsEnabled: z.boolean().optional(), topics: z.array(z.string()).max(100).optional(), ring: z.string().optional(),\n }).strict().optional(),\n}).strict()\n\nconst recordSchema = z.object({\n id: z.string(), revision: z.number().int().positive(), updatedAt: z.string(),\n draft: draftSchema,\n publishState: z.enum(['publishing', 'published', 'uncertain']).optional(),\n published: z.object({ url: z.string(), contentToken: z.string(), type: z.string() }).optional(),\n lastError: z.string().optional(),\n})\n\nexport const zhihuSyncDomainSpec = defineDomain({\n name: 'dsh_zhihu_sync', version: 1,\n tables: { drafts: domainTable<string, SyncDraftRecord>(recordSchema) },\n})\n\nexport interface SyncDraftTable {\n get(id: string): SyncDraftRecord | undefined\n entries(): IterableIterator<[string, SyncDraftRecord]>\n put(id: string, record: SyncDraftRecord): Promise<void>\n}\n\nexport class ZhihuSyncServiceError extends Error {\n constructor(readonly code: string, message: string) { super(message) }\n}\n\nfunction object(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new ZhihuSyncServiceError('bad-request', '请求参数必须为对象。')\n return value as Record<string, unknown>\n}\n\n/** Review tickets bind an exact saved revision. Only the human UI receives them;\n * model tools can prepare content but cannot grant publishing authority. */\nexport function createZhihuSyncService(table: SyncDraftTable, options: ZhihuSyncTransportOptions) {\n const reviews = new Map<string, { revision: number; token: string; expires: number }>()\n const locks = new Map<string, Promise<unknown>>()\n async function locked<T>(id: string, operation: () => Promise<T>): Promise<T> {\n const previous = locks.get(id) ?? Promise.resolve()\n const current = previous.catch(() => {}).then(operation)\n locks.set(id, current)\n try { return await current } finally { if (locks.get(id) === current) locks.delete(id) }\n }\n function get(id: unknown) {\n if (typeof id !== 'string' || !id) throw new ZhihuSyncServiceError('bad-request', '缺少草稿 ID。')\n const record = table.get(id)\n if (!record) throw new ZhihuSyncServiceError('not-found', '草稿不存在。')\n return structuredClone(record)\n }\n function revision(record: SyncDraftRecord, expected: unknown) {\n if (record.revision !== expected) throw new ZhihuSyncServiceError('conflict', '草稿已更新,请重新载入后再操作。')\n }\n function editable(record: SyncDraftRecord) {\n if (record.publishState) throw new ZhihuSyncServiceError('publish-locked', '此草稿已提交发布。请先检查知乎发布结果;修改内容请保存为新草稿。')\n }\n return {\n async call(endpoint: string, payload: unknown, signal: AbortSignal, caller: 'ui' | 'tool' = 'ui'): Promise<unknown> {\n signal.throwIfAborted()\n const body = object(payload)\n if (endpoint === 'sync.list') {\n const drafts = [...table.entries()].map(([, value]) => ({\n id: value.id, revision: value.revision, updatedAt: value.updatedAt,\n title: value.draft.title, type: value.draft.type, publishState: value.publishState,\n published: value.published,\n })).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))\n const offset = body.offset === undefined ? 0 : body.offset\n if (typeof offset !== 'number' || !Number.isSafeInteger(offset) || offset < 0) throw new ZhihuSyncServiceError('bad-request', '草稿分页位置无效。')\n const page = drafts.slice(offset, offset + 100)\n return { drafts: page, total: drafts.length, ...(offset + page.length < drafts.length ? { nextOffset: offset + page.length } : {}) }\n }\n if (endpoint === 'sync.get') return get(body.id)\n if (endpoint === 'sync.media.upload') {\n return uploadZhihuSyncMedia(body as Parameters<typeof uploadZhihuSyncMedia>[0], options, signal)\n }\n if (endpoint === 'sync.publish' && caller !== 'ui') throw new ZhihuSyncServiceError('confirmation-required', 'Publishing requires a human-confirmed UI review; the agent tool can only save and validate drafts.')\n if (!['sync.save', 'sync.prepare', 'sync.publish'].includes(endpoint)) throw new ZhihuSyncServiceError('bad-request', '未知同步操作。')\n const id = endpoint === 'sync.save' && body.id === undefined ? randomUUID() : body.id\n if (typeof id !== 'string' || !id) throw new ZhihuSyncServiceError('bad-request', '缺少草稿 ID。')\n return locked(id, async () => {\n signal.throwIfAborted()\n if (endpoint === 'sync.save') {\n const old = table.get(id)\n if (body.id !== undefined) {\n if (!old) throw new ZhihuSyncServiceError('not-found', '草稿不存在。')\n revision(old, body.expectedRevision)\n editable(old)\n }\n // Saving invalid content is allowed; only the publish boundary requires\n // all checks to pass. No truncation or semantic rewriting occurs here.\n const parsed = draftSchema.safeParse(body.draft)\n if (!parsed.success) throw new ZhihuSyncServiceError('bad-request', '草稿字段格式无效或内容过大,请检查类型、正文、图片与发布设置。')\n const record: SyncDraftRecord = {\n id, revision: (old?.revision ?? 0) + 1, updatedAt: new Date().toISOString(),\n draft: parsed.data,\n }\n await table.put(id, record)\n reviews.delete(id)\n return structuredClone(record)\n }\n const record = get(id)\n revision(record, body.expectedRevision)\n editable(record)\n const prepared = prepareZhihuSyncDraft(record.draft)\n if (endpoint === 'sync.prepare') {\n if (caller === 'tool') return { record, prepared }\n // One bounded, short-lived ticket per draft. Preparing again replaces it.\n for (const [key, review] of reviews) if (review.expires < Date.now()) reviews.delete(key)\n const reviewToken = randomUUID()\n if (prepared.canPublish) reviews.set(id, { revision: record.revision, token: reviewToken, expires: Date.now() + 30 * 60_000 })\n return { record, prepared, reviewToken: prepared.canPublish ? reviewToken : '' }\n }\n const review = reviews.get(id)\n if (body.confirmed !== true || !review || review.token !== body.reviewToken\n || review.revision !== record.revision || review.expires < Date.now()) {\n throw new ZhihuSyncServiceError('confirmation-required', '请重新预览当前草稿并确认发布。')\n }\n if (!prepared.canPublish) throw new ZhihuSyncServiceError('validation-failed', '请先修正校验错误。')\n // Check configuration before recording a possibly sent request. Pin this\n // credential pair for this single operation, never across operations.\n const credentials = await options.resolveCredentials()\n signal.throwIfAborted()\n record.publishState = 'publishing'\n record.updatedAt = new Date().toISOString()\n await table.put(id, record)\n reviews.delete(id)\n // Persist intent before the network call: after process loss it must never\n // look like an unsent draft. There is no upstream idempotency contract.\n try {\n record.published = await publishZhihuSync(prepared, { ...options, resolveCredentials: async () => credentials }, signal)\n record.publishState = 'published'\n } catch (error) {\n if (error instanceof ZhihuSyncApiError && error.definite) {\n delete record.publishState\n record.lastError = error.message\n } else {\n record.publishState = 'uncertain'\n record.lastError = '未能确认发布结果,请检查知乎主页,避免重复发布。'\n }\n }\n record.updatedAt = new Date().toISOString()\n await table.put(id, record)\n return structuredClone(record)\n })\n },\n }\n}\n","import { resolveZhihuCallPolicy, type ZhihuCallPolicyOptions } from './call-policy.ts'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { credentialRef } from '@deepseek-ai/dsh-credentials'\nimport { executeZhihuSearch, ZHIHU_SEARCH_DEFAULT_COUNT } from './search-api.ts'\nimport {\n executeZhihuGlobalSearch, executeZhihuHotList, executeZhihuAsk, executeZhihuKnowledgeSearch,\n normalizeRecallScopes, ZHIHU_ASK_MODELS, ZHIHU_ASK_DEFAULT_MODEL,\n} from './operations.ts'\nimport { listZhihuKnowledgeBases, uploadZhihuKnowledgeFile, ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES } from './zhihu-knowledge.ts'\nimport { type ZhihuClientOptions, type ZhihuSearchExecuted, ZhihuSearchError } from './zhihu-client.ts'\nimport { createZhihuUsageRecorder, resolveDays, zhihuUsageDomainSpec, type ZhihuUsageRecorder } from './usage.ts'\nimport { ZHIHU_RPC_CHANNEL, ZHIHU_CREDENTIAL_REF, ZHIHU_SEARCH_EVENT, type ZhihuRpcResult } from './contracts.ts'\nimport { registerHostRpc, type HostRpcContext } from './host-rpc.ts'\nimport { bindZhihuWebSearch } from './web-search-provider.ts'\nimport { createZhihuSyncService, zhihuSyncDomainSpec, ZHIHU_SYNC_CREDENTIAL_REFS, ZhihuSyncServiceError } from './sync-service.ts'\nimport { executeZhihuOpenPlatform, resolveZhihuOpenPlatformOperation, ZHIHU_OPEN_PLATFORM_APIS, zhihuOpenPlatformResultCount } from './open-platform.ts'\n\nexport const name = '@klarkxy/dsh-zhihu'\nexport const inject = ['connection', 'credentials', 'storageDomain', 'webServer'] as const\nexport type ZhihuService = {\n call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult>\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n dispose(): Promise<void>\n usageSummary(days: unknown): Promise<{ days: unknown[] }>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n syncCall?: (endpoint: string, payload: unknown, signal: AbortSignal, caller: 'ui' | 'tool') => Promise<unknown>\n}\n\ntype Host = Context & HostRpcContext & {\n credentials: { resolve: (ref: ReturnType<typeof credentialRef>) => Promise<{ value: string } | undefined> }\n}\nconst fail = (code: string, message: string, details: Record<string, unknown> = {}): ZhihuRpcResult => ({ ok: false, error: { code, message, details } })\n\n/** One shared service for direct UI RPC and optional Tool wrappers. */\nexport function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify: (event: ZhihuSearchExecuted) => void = () => {}): ZhihuService {\n // Serialize read-modify-write counters; concurrent tools must not lose increments.\n let pendingUsage = Promise.resolve()\n const lifetime = new AbortController()\n const active = new Set<Promise<unknown>>()\n const run: ZhihuService['run'] = (execute, signal) => {\n const combined = AbortSignal.any([signal, lifetime.signal])\n const request = Promise.resolve().then(() => { combined.throwIfAborted(); return execute(combined) })\n active.add(request)\n return request.finally(() => active.delete(request))\n }\n const record = (event: ZhihuSearchExecuted) => {\n pendingUsage = pendingUsage.then(() => usage.record(event)).catch(() => {})\n try { notify(event) } catch { /* observer failure does not change a request */ }\n }\n const service = {\n toolOptions: { ...options, onExecuted: record },\n async usageSummary(days: unknown) { await pendingUsage; return { days: await usage.read(resolveDays(days)) } },\n async call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult> {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n const client = { ...options, signal }\n let counted = false\n const operation = resolveZhihuOpenPlatformOperation(endpoint)\n const quotaId = operation ? ZHIHU_OPEN_PLATFORM_APIS[operation].quotaId : undefined\n const metering = quotaId ? { quotaId } : {}\n try {\n if (endpoint === 'policy.status') return { ok: true, value: resolveZhihuCallPolicy(options.env, options.callPolicy) }\n if (endpoint === 'usage.summary') { await pendingUsage; return { ok: true, value: { days: await usage.read(resolveDays(body.days)) } } }\n const query = typeof body.query === 'string' ? body.query : ''\n if (['search', 'global.search', 'ask', 'knowledge.search'].includes(endpoint) && (!query.trim() || query.length > 4000)) {\n return fail('bad-request', '查询不超过 4000 字符')\n }\n let value: unknown\n let results = 0\n counted = true\n if (operation) {\n counted = operation !== 'quota'\n const result = await executeZhihuOpenPlatform(operation, payload === undefined ? {} : payload, client)\n value = result; results = zhihuOpenPlatformResultCount(result)\n } else if (endpoint === 'search') {\n const result = await executeZhihuSearch(query, typeof body.count === 'number' ? body.count : ZHIHU_SEARCH_DEFAULT_COUNT, client)\n value = result; results = result.items.length\n } else if (endpoint === 'global.search') {\n const result = await executeZhihuGlobalSearch(query, typeof body.count === 'number' ? body.count : 10,\n body.searchDb === 'realtime' || body.searchDb === 'static' ? body.searchDb : 'all', client)\n value = result; results = result.items.length\n } else if (endpoint === 'hot.list') {\n const result = await executeZhihuHotList(typeof body.limit === 'number' ? body.limit : 10, client)\n value = result; results = result.items.length\n } else if (endpoint === 'ask') {\n const model = (ZHIHU_ASK_MODELS as readonly unknown[]).includes(body.model) ? body.model as typeof ZHIHU_ASK_MODELS[number] : ZHIHU_ASK_DEFAULT_MODEL\n value = await executeZhihuAsk(query, model, client); results = 1\n } else if (endpoint === 'knowledge.search') {\n const result = await executeZhihuKnowledgeSearch(query, typeof body.limit === 'number' ? body.limit : 5, normalizeRecallScopes(body.recallScopes), client)\n value = result; results = result.items.length\n } else if (endpoint === 'knowledge.bases') {\n const result = await listZhihuKnowledgeBases(client); value = result; results = result.bases.length\n } else if (endpoint === 'knowledge.upload') {\n const contentBase64 = typeof body.contentBase64 === 'string' ? body.contentBase64 : ''\n if (!contentBase64) throw new Error('缺少文件内容。')\n if (contentBase64.length > Math.ceil(ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(contentBase64)) {\n return fail('bad-request', '文件内容无效或超过 20 MB')\n }\n const upload = await uploadZhihuKnowledgeFile({\n fileName: typeof body.fileName === 'string' ? body.fileName : '',\n data: new Uint8Array(Buffer.from(contentBase64, 'base64')),\n knowledgeBaseId: typeof body.knowledgeBaseId === 'string' && body.knowledgeBaseId.trim() ? body.knowledgeBaseId.trim() : undefined,\n }, client)\n value = { upload }; results = 1\n } else {\n counted = false\n return fail('bad-request', `unknown endpoint ${endpoint}`)\n }\n if (counted) record({ ok: true, results, ...metering })\n return { ok: true, value }\n } catch (error) {\n if (counted) record({ ok: false, results: 0, ...metering })\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const code = error instanceof ZhihuSearchError ? error.code.toLowerCase().replaceAll('_', '-') : 'internal'\n const details: Record<string, unknown> = {}\n if (error instanceof ZhihuSearchError) {\n for (const key of ['status', 'upstreamCode', 'retryAfter', 'retryable'] as const) {\n if (error[key] !== undefined) details[key] = error[key]\n }\n }\n return fail(code, error instanceof Error ? error.message : '知乎请求失败', details)\n }\n },\n }\n return {\n ...service,\n run,\n async call(endpoint, payload, signal) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n try { return await run(combined => service.call(endpoint, payload, combined), signal) }\n catch (error) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n throw error\n }\n },\n async dispose() {\n lifetime.abort()\n await Promise.allSettled([...active])\n await pendingUsage\n },\n }\n}\n\nexport async function apply(ctx: Context, config: ZhihuCallPolicyOptions = {}): Promise<void> {\n const host = ctx as Host\n const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec)\n const service = createZhihuService({\n callPolicy: config,\n resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value,\n }, createZhihuUsageRecorder(domain.table('daily')), event => {\n const emit = ctx.emit as unknown as (name: string, event: ZhihuSearchExecuted) => void\n emit.call(ctx, ZHIHU_SEARCH_EVENT, event)\n })\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'zhihu.usageDomainClose')\n const syncDomain = await ctx.storageDomain.open(zhihuSyncDomainSpec)\n const sync = createZhihuSyncService(syncDomain.table('drafts'), {\n resolveCredentials: async () => {\n const [key, secret] = await Promise.all(ZHIHU_SYNC_CREDENTIAL_REFS.map(ref => host.credentials.resolve(credentialRef(ref))))\n if (!key?.value.trim() || !secret?.value.trim()) throw new ZhihuSyncServiceError('sync-credentials-missing', '请在「设置 → 知乎」中填写发布 App Key 和 App Secret。')\n return { appKey: key.value.trim(), appSecret: secret.value.trim() }\n },\n })\n service.syncCall = (endpoint, payload, signal, caller) => service.run(combined => sync.call(endpoint, payload, combined, caller), signal)\n ctx.effect(() => async () => { await service.dispose(); await syncDomain.close() }, 'zhihu.syncDomainClose')\n ctx.provide('zhihu', service)\n ctx.effect(() => registerHostRpc(host, ZHIHU_RPC_CHANNEL, async (endpoint, payload, signal) => {\n if (!endpoint.startsWith('sync.')) return service.call(endpoint, payload, signal)\n try { return { ok: true, value: await service.syncCall!(endpoint, payload, signal, 'ui') } }\n catch (error) {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n return fail(error instanceof ZhihuSyncServiceError ? error.code : 'sync-failed', error instanceof Error ? error.message : '知乎同步失败。')\n }\n }))\n bindZhihuWebSearch(ctx, service)\n}\n"],"mappings":"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"}
|