Skip to content

fix(cli): embed local images as data URI so each platform uploads via…#196

Open
jssyy wants to merge 1 commit into
wechatsync:v2from
jssyy:fix/cli-local-image-datauri
Open

fix(cli): embed local images as data URI so each platform uploads via…#196
jssyy wants to merge 1 commit into
wechatsync:v2from
jssyy:fix/cli-local-image-datauri

Conversation

@jssyy

@jssyy jssyy commented Jun 13, 2026

Copy link
Copy Markdown

… its own publish() pipeline# fix(cli): 同步本地图片改用 data URI,让各平台经自身 publish() 管线上传

背景 / 问题

用 CLI 同步带本地图片的文章时,部分平台图片上传失败:

  • 今日头条(toutiao)图片上传响应解析失败 → 最终 图片uri非法,请重新上传,同步失败。
  • 哔哩哔哩(bilibili):图片上传失败(缺 Origin/Referer 头与 csrf)。
  • 知乎、掘金等接口"干净"的平台正常。

复现:

wechatsync sync article.md -p toutiao   # article.md 里是 ![](images/xxx.png) 本地图

根因

CLI 的 sync 命令对本地图片采用 processLocalImages:把图片预上传到第一个目标平台当图床,走的是各适配器的uploadImage(blob)。问题:

  1. 这条裸上传路径没有进入适配器 publish() 的上下文——拿不到头条所需的页面内反爬签名(a_bogus/msToken),也没套上 B站 publish()withHeaderRules 加的 Origin/Referer,于是这些平台上传被拒。
  2. 仓库里其实已有更稳的 convertImagesToDataUri(源码注释标注为「推荐方式:让各平台适配器自己处理图片上传,确保图片存储在目标平台的图床」),但 sync 命令未启用它。

改动

packages/cli/src/index.tssync 命令处理本地图片处:把 processLocalImages(预上传到首个平台)替换为 convertImagesToDataUri(内嵌 data URI,交给各平台 publish() 自行上传)。

   if (localImages.length > 0) {
-    // 使用第一个目标平台作为图床
-    const imageHost = platforms[0]
-    console.log(chalk.bold(`发现 ${localImages.length} 张本地图片,上传到 ${imageHost}...`))
-    const imageResult = await processLocalImages(parsed.content, fileDir, bridge, imageHost)
-    if (imageResult.uploadedCount > 0) {
-      if (parsed.format === 'markdown') {
-        processedMarkdown = imageResult.content
-        processedHtml = markdownToHtml(imageResult.content)
-      } else {
-        processedHtml = imageResult.content
-      }
-    }
-    console.log(`图片上传完成: ${imageResult.uploadedCount} 成功, ${imageResult.failedCount} 失败`)
+    // 内嵌 data URI,交给各平台适配器自己的 publish() 上传到各自图床。
+    // 这样每个平台都走自己 publish() 里的签名/Header 管线(头条 a_bogus/msToken、
+    // B站 Origin/Referer+csrf 等),反爬平台也能传图,且各平台图片各存自己图床,避免跨站盗链。
+    console.log(chalk.bold(`发现 ${localImages.length} 张本地图片,转为内嵌(各平台自传图床)...`))
+    const imageResult = convertImagesToDataUri(parsed.content, fileDir)
+    if (imageResult.convertedCount > 0) {
+      if (parsed.format === 'markdown') {
+        processedMarkdown = imageResult.content
+        processedHtml = markdownToHtml(imageResult.content)
+      } else {
+        processedHtml = imageResult.content
+      }
+    }
+    console.log(`本地图片内嵌完成: ${imageResult.convertedCount} 成功, ${imageResult.failedCount} 跳过`)
   }

convertImagesToDataUriprocessLocalImages 均为本文件已有函数;本改动仅切换调用,无新增依赖。)

为什么这样修

把图片以 data URI 内嵌进文章内容后,每个平台的 publish() 会在自己的 processImages 流程里把这些图上传到该平台自己的图床——也就是走和「浏览器扩展一键同步按钮」完全相同的、带平台签名/Header 的上传管线。因此:

  • 头条等需要页面内签名的平台,CLI 也能成功传图(此前只有扩展按钮能成)。
  • B站经 publish() 时已套用 withHeaderRules,无需额外补丁即可成功。
  • 各平台图片落在各自图床,避免「所有平台引用首个平台图床」带来的跨站盗链/失效风险。

测试

本地 pnpm --filter @wechatsync/cli build 后,用本地 CLI 同步含 4 张本地图片的 Markdown:

  • -p toutiao:✅ 图片成功上传到头条图床,草稿正常生成(此前失败)。
  • -p zhihu:✅ 正常。
  • B站经同一 publish() 管线,图片走 withHeaderRules 路径上传。

取舍

  • 每个平台各自上传一份图片(相比"预传到一个平台再共享 URL"会多几次上传),换来的是跨平台稳健各平台自托管图片,整体更可靠。
  • 文章内容携带 base64,桥接传输体积变大;本地 WebSocket 传输,影响可忽略。

相关观察(可选,另一处独立缺口)

performImageUploadpackages/extension/src/mcp/client.ts)调用的裸 adapter.uploadImage(blob) 路径,对 B站而言缺少 publish() 里的 withHeaderRules/csrf。若希望该独立路径也可用,可在 BilibiliAdapter 覆盖一个自带 header 规则与 csrf 的 uploadImage(blob)。在本 PR 的 data URI 方案下,CLI 不再走该路径,故非必需。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant