# 微信公众号发布:换机器还原指南 > **这是可选功能,默认关闭,需要额外安装。** 插件本体**不含**它:真正干活的是仓库 > `tools/wechat/` 下的 opencli adapter + Browser Bridge 浏览器扩展。**不安装/不开启, > 插件的其他功能完全不受影响**(不注入发布相关提示词、不往 wiki 写发布规范文档)。 > > 目的:在**另一台机器**上无缝还原「TiddlyWiki 笔记 → 微信公众号」的发布能力。 > 本机(开发机)已验证全程可跑;本文按顺序照做即可复现。 > > 关键结论先讲:**整套发布走浏览器自动化,不依赖公众号服务端 API**——因为 > 2025-07 起官方已回收个人主体账号的「发布能力」接口权限(详见 §5)。 --- ## 0. 它是可选功能:需要什么、不影响什么 | | 说明 | |---|---| | **默认状态** | **关闭**(`wechat.enabled` 默认 `false`) | | **开启方式** | DSH 设置 →「TiddlyWiki 知识库」→「可选功能:微信公众号发布」→ 勾选 → 保存配置 | | **开启后有什么变化** | ① 注入提示词多一行「发布前先读发布元数据规范」;② 启动时把该规范文档写进 wiki(同名不覆盖)。**仅此两项**——不会安装任何东西、不会启用任何后台服务 | | **不开启会怎样** | 提示词里没有发布相关文字;wiki 里不会出现「发布元数据规范」;其他功能一切照旧 | | **插件会替你做安装吗** | **不会**。opencli 与浏览器扩展必须你手动装(见 §1–§2)——这属于插件外部的工具链 | | **要额外装什么** | ① opencli(npm 包)② Browser Bridge 浏览器扩展 ③ 浏览器登录公众号 | | **凭据如何保存** | 不保存任何凭据:复用浏览器已有登录态(cookie)。脚本不接触 AppSecret | > 一句话:**开关只控制「插件要不要配合这个流程」,不负责装工具链。** --- ## 1. 一分钟总览 ``` TiddlyWiki 笔记(tiddler) │ 标题 ▼ DSH 的 POST /dsh-tiddlywiki/render ← TW 自己渲染成语义 HTML(含代码高亮) │ 纯语义 HTML(无内联样式) ▼ wechat-html.js decorate() ← 补内联样式(微信唯一认的形式) │ 带内联样式的 HTML ▼ opencli + Browser Bridge 扩展 ← 驱动你已登录的浏览器 │ 填标题/作者/摘要 + insertHTML 写正文 + 传图 + 设封面 + 存草稿 ▼ 公众号草稿箱(mp.weixin.qq.com) │ (可选 --publish) ▼ 点「发表」→ ⚠️ 管理员扫码确认 → 发布成功 ``` **一条命令**(在第二台机器上,装好后): ```bash opencli weixin publish-note "笔记标题" --trace retain-on-failure -f json ``` --- ## 1. 需要安装/准备的东西 按顺序做;**最后一步才是打开插件里的开关**(开关只影响提示词与文档,装不上工具链)。 | # | 项目 | 怎么装 | 验证 | |---|---|---|---| | 1 | **Node.js ≥ 22** | 官网安装包 | `node -v` | | 2 | **DSH** + **dsh-tiddlywiki 插件** | 本仓库的插件(含 wiki) | `curl http://127.0.0.1:3080/dsh-tiddlywiki/status` | | 3 | **opencli** | `npm install -g @jackwener/opencli` | `opencli --version` | | 4 | **Browser Bridge 扩展** | 见 §2(二选一) | `opencli doctor` 显示 `Extension: connected` | | 5 | **Chrome/Edge + 公众号登录** | 浏览器登录 mp.weixin.qq.com | 能进后台首页 | | 6 | **本仓库的 adapter** | `node tools/wechat/install-wechat-adapters.mjs` | 脚本自检输出 ✔ | | 7 | **打开插件开关** | 设置 →「TiddlyWiki 知识库」→「可选功能:微信公众号发布」勾选 → 保存配置 | 提示词预览里出现「发布元数据规范」一行 | > **opencli 版本建议 ≥ 1.8.7**(本方案在该版本实测通过;1.7.x 的 `browser` > 子命令语法不同,且内置 weixin adapter 不完整)。 > > **新版 npm 的 allowScripts 机制会拦 opencli 的 postinstall 脚本**(安装时打 > warning):**无害,可忽略**——postinstall 只装 bash/zsh/fish 补全(Windows 用不上), > adapters 随 npm 包自带,功能不受影响。 > > 第 7 步之前,插件不会注入任何发布相关提示词、也不会往 wiki 写「发布元数据规范」。 > 也就是说:**没装好工具链时开关可以一直关着,不影响任何其他功能。** ### 自动化的终点:草稿箱(2026-09-17 与用户确认的策略) **adapter 负责到「草稿落盘」为止**:填标题/作者/摘要、写正文、传图、设封面、存草稿。 **发表由人工在后台完成**——点「发表」时平台会弹确认对话框(原创声明、作者、留言 设置等)以及最后的管理员扫码;原创声明涉及账号权益,人工点最稳妥。`--publish` 选项保留(best-effort:点「发表」后轮询等扫码),但**不处理中途弹窗**,遇到弹窗 会一直等到超时。 --- ## 2. 安装 Browser Bridge 扩展(关键前置) 扩展**不在 npm 包里**,必须单独装。二选一: **方式 A — Chrome Web Store(推荐)** 1. 打开 https://chromewebstore.google.com/detail/opencli/ildkmabpimmkaediidaifkhjpohdnifk 2. 点「添加至 Chrome」 **方式 B — GitHub Releases(Web Store 打不开时)** 1. 到 https://github.com/jackwener/opencli/releases 下载 `opencli-extension-v*.zip` 2. 解压到某个固定目录 3. 浏览器打开 `chrome://extensions` → 开启右上角「开发者模式」 4. 点「加载已解压的扩展程序」→ 选择解压出的目录 > ⚠️ GitHub 的 ext release **经常落后于 Web Store**(2026-09-17 实测只到 > `ext-v1.0.21`,Web Store 已是 1.0.24)。**扩展版本以 Chrome Web Store 为准**; > GitHub 方式只作 Web Store 打不开时的离线兜底。 **验证**: ```bash opencli doctor # 期望: [OK] Daemon: running ... # [OK] Extension: connected (vX.Y.Z) # [OK] Connectivity: connected # Everything looks good! ``` 若报 `Extension: not connected`:确认浏览器在运行、扩展已启用;再 `opencli daemon stop` 后重跑 `opencli doctor`(daemon 会自动重启)。 --- ## 3. 装 adapter 并试跑 ```bash cd <本仓库> node tools/wechat/install-wechat-adapters.mjs ``` 脚本会把 `tools/wechat/*.js` 复制到 `~/.opencli/clis/weixin/`(Windows:`C:\Users\<你>\.opencli\clis\weixin\`)。 这些是**私有 adapter**,放在该目录即可被 opencli 自动发现,**无需任何构建**。 然后: ```bash # ① 先干跑:只做 TW 渲染 + 排版装饰,导出预览,不碰微信 opencli weixin publish-note "某篇笔记标题" --preview ./out --trace retain-on-failure # → 用浏览器打开 ./out.html 肉眼确认排版 # (注意:--preview 仍会继续执行发布流程;只想预览时看文件即可,或 Ctrl-C) # ② 真发到草稿箱 opencli weixin publish-note "某篇笔记标题" --trace retain-on-failure -f json # ③ 带封面(公众号封面必填,否则草稿显示"内容不完整") opencli weixin publish-note "某篇笔记标题" --cover ./cover.png --trace retain-on-failure -f json # ④ 直接发表(⚠️ 会真发文章,需管理员扫码) opencli weixin publish-note "某篇笔记标题" --publish --trace retain-on-failure -f json # ⑤ 正文多图(v0.23.2+):TW 笔记内嵌 [img[...]] 经 /render 变成 data URI, # publish-note 传不了(微信存草稿时过滤非 mmbiz 图)——用 publish-note-imgs: opencli weixin publish-note-imgs "某篇笔记标题" --images <图片目录> --trace retain-on-failure -f json opencli weixin publish-note-imgs "某篇笔记标题" --images "01.png|02.png|03.png" -f json # · --images 传目录时按文件名排序 = 正文图片出现顺序(封面图排第一) # · 也可用 | 分隔的路径列表精确指定顺序 # · 流程:先逐张上传 CDN → 按 DOM 顺序重写正文 src → insertHTML → 选封面 → 存草稿 # · 发表前同样读 pub-state / no-publish(只告警不阻断),--publish 可直接发表 ``` ### 3.6 在 TiddlyWiki 里点按钮发布(v0.23.3,日常推荐) 不想敲命令时,用**笔记工具栏的「发布到公众号」按钮**(嵌入式 TW 面板、右侧栏 tab、原生编辑弹窗里都在): 1. 设置页勾选「可选功能:微信公众号发布」并保存 → 重启 dsh web 后,启动 seed 会把按钮插件 `$:/plugins/dsh/wechat-publish` 写进 wiki(老 wiki 也可在设置页「初始化」区单独写它)。 2. 打开任意笔记 → 工具栏点「发布到公众号」→ 按钮**先预检**(opencli 跑不跑得起来、adapter 缺哪些文件), 再弹确认框(自动列出 `no-publish` / `pub-state` 警告)→ 点「开始存草稿」。 3. 宿主进程起一个**后台任务**,覆盖层每 2 秒显示状态与日志尾部。**只到草稿箱为止,不自动发表。** 按钮背后的三条路由(同源;写操作有方法 + CSRF 守卫;`wechat.enabled` 关着时一律 403): | 路由 | 方法 | 作用 | |---|---|---| | `/dsh-tiddlywiki/wechat/ready` | GET | 预检:`opencli --version` 能否跑通、adapter 缺哪些文件 | | `/dsh-tiddlywiki/wechat/publish` | POST | 起任务(body `{title, adapter?}`);单并发,第二次调用 409 | | `/dsh-tiddlywiki/wechat/publish/status` | GET | 轮询任务(`?id=`;不带 id 取最新/正在跑的那个) | 可配项(设置页目前只暴露 `enabled`,其余写进配置 tiddler `$:/plugins/dsh-tiddlywiki/config` 的 `wechat` 块,保存即生效): | 键 | 默认 | 说明 | |---|---|---| | `wechat.enabled` | `false` | 总开关;关着时按钮不写入、三条路由 403 | | `wechat.adapter` | `publish-note` | 换成 `publish-note-imgs` 走正文内嵌多图(需该 adapter 已装) | | `wechat.command` | `opencli` | CLI 路径(装了别名、不在 PATH 时用) | | `wechat.token` | 空 | 非空时要求请求头 `x-wechat-publish-token`(按钮会自动带上) | | `wechat.dsn` | 空 | adapter 回连 DSH 的基址;留空按请求端口推导 `http://127.0.0.1:<端口>/dsh-tiddlywiki` | | `wechat.endpoint` | 空 | **只被 TW 侧读**:覆盖按钮请求的基址(默认 `location.origin + /dsh-tiddlywiki`);反向代理/远程访问场景用 | ⚠️ 宿主用 **`--title-file`**(v0.23.3 新增参数)把标题经 UTF-8 文件交给 adapter:标题不进 argv, 否则 Windows 上 `opencli` 的 `.cmd` shim 会把 `&`/`|`/`^` 当命令分隔符,中文标题还会被 cmd 的 代码页解码成乱码。位置参数 `
` 内的 `` 不能套用行内代码样式(粉底 + 内边距会很难看),
`wechat-html.js` 里用 `preDepth` 特判。
---
## 8. 排错
| 现象 | 原因 / 处理 |
|---|---|
| `Navigation rejected` | 忘了 `--trace retain-on-failure` |
| `AUTH_REQUIRED` / 提示登录 | 浏览器里 `mp.weixin.qq.com` 登录态过期 → 重新扫码登录 |
| `Extension: not connected` | 扩展没装/没启用,或浏览器没运行 → 见 §2 |
| `Page.fileChooserOpened not received` | 不应再出现(已改用 DataTransfer);若出现说明源码被改回 setFileInput |
| 找不到笔记 | tiddler 标题要**完全精确**(含空格/标点);`opencli weixin publish-note "标题"` |
| `/render 返回 HTTP 404` | 标题不存在;先用 `tiddlywiki_search` 或 TW 面板确认 |
| 无法连接 DSH | `--dsn` 默认 `http://127.0.0.1:3080/dsh-tiddlywiki`;DSH 没跑或端口不同就改它 |
| 草稿显示「内容不完整」 | 缺封面 → 传 `--cover`(或用 publish-note-imgs 从正文第一张自动设) |
| 图片没上传 | 单图 > 8MB(DataTransfer 限制)→ 先压缩 |
| 发表卡住超时 | 没扫码,或扫码没完成 → 调大 `--timeout`;若卡在原创声明/留言等确认弹窗 → 推荐流程本就到草稿箱为止,到后台人工点发表(见 §1、§4.4) |
| `stale page identity` | 命令执行中标签页被手工关闭/导航了。**草稿不丢**——重开编辑页续作:`https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&type=77&appmsgid=&idx=0&token=`(token 用 `opencli browser wx eval 'location.href.match(/token=(\d+)/)[1]'` 从后台首页取) |
| 封面警告「设置失败」但草稿其实有封面 | 已知**误报**(校验时机太早);以草稿箱实际显示为准(2026-09-17 实测:`list_ex` 接口 `cover` 字段已是 mmbiz 地址)。v0.23.2 起改为轮询校验 |
| 用 eval 诊断后台状态时读到「未实名」「未设置头像和名称」等提示 | ⚠️ 后台 DOM 里常残留**不可见的历史 toast 节点**——查询必须过滤可见性(`offsetHeight > 0`),勿把残留文案当实时状态(2026-09-17 踩过:据此误判账号被平台拦截,实际账号正常并成功发表含原创声明)。同理,判断原创声明是否生效以草稿箱/发表记录为准,勿只看侧栏文案 |
| 需要核实草稿是否落盘 | 后台 ajax:`/cgi-bin/appmsg?action=list_ex&type=77&orderby=create_time&token=&f=json&begin=0&count=5`(在 mp.weixin.qq.com 页面上下文执行;`cover`/`digest`/`update_time` 一目了然);发表记录:`/cgi-bin/appmsgpublish?sub=list&...&f=json`(`publish_page.publish_list[].publish_info` 里是 JSON 字符串,含 `content_url`) |
---
## 9. 安全与隐私
- **不存任何公众号凭据**:全程复用浏览器已有登录态(cookie),脚本不接触 AppSecret。
- **不落盘密钥**:adapter 只读本地图片路径,不写任何 token。
- **低频使用**:不做规避风控的行为伪装;默认走「发表」(不推送粉丝、不占额度)。
- wiki 里的配置 tiddler 与本方案无关——本方案零配置。
---
## 10. 参考
- [opencli](https://github.com/jackwener/opencli) — 把网站变成 CLI,复用浏览器登录态
- [微信「发布能力」文档(2025-07 权限回收)](https://developers.weixin.qq.com/doc/subscription/guide/product/publish.html)
- [微信「新增草稿」draft/add](https://developers.weixin.qq.com/doc/subscription/api/draftbox/draftmanage/api_draft_add.html)
- [微信服务端 API 调用说明(IP 白名单)](https://developers.weixin.qq.com/doc/subscription/guide/dev/api/)
- 设计文档:`docs/plans/2026-09-17-wechat-publish-design.md`