wechat-publish · v0.1.0 · 2026-09-30 · sha256 7c16ca6d8451b054

wechat-publish v0.1.0A

Immutable. This exact content never changes and is served at /api/v1/blob/7c16ca6d8451b054.

---
name: wechat-publish
description: 本地预览 Markdown 文章的公众号排版(含微信暗色模式),一键上传图片和封面并同步到公众号草稿箱。用于“预览公众号排版”“发到公众号草稿”“同步草稿箱”“调公众号样式”。
license: MIT
disable-model-invocation: true
metadata:
  author: simonwong
  version: "0.1.0"
---

# WeChat Publish

把一篇 Markdown 文章转成公众号正文 HTML:本地预览排版,并同步到公众号草稿箱。脚本在 `scripts/`,唯一一套正文样式在 `assets/theme.css`。命令都在 `scripts/` 目录下运行。

## 准备

1. `scripts/node_modules` 不存在时,先在 `scripts/` 运行 `bun install`。
2. 发布需要凭证:环境变量 `WECHAT_APP_ID`、`WECHAT_APP_SECRET`,可选 `WECHAT_AUTHOR`(草稿作者)、`WECHAT_OPEN_COMMENT=0`(账号没有留言权限时关掉留言);也可以写进 `~/.config/wechat-publish/.env`,环境变量优先。只预览时不需要凭证。
3. 第一次发布前运行 `bun cli.ts check`,确认凭证、IP 白名单、素材和草稿接口权限。失败时把错误提示原样转告用户。

## 按请求选模式

- **预览**:在后台运行 `bun cli.ts preview <文章.md>`,把输出的地址告诉用户。
  - 没有浏览器可打开时(Linux、远程环境)加 `--no-open`。
  - 默认端口 4321。端口上已经在预览同一篇文章时,沿用原来的服务,命令直接退出;端口被别的程序占用时,会自动换一个端口,以输出的地址为准。
  - 页面左栏是原文,右栏默认显示 375px 宽的正文,可切换亮色和微信暗色,也可切到消息卡片视图。
  - 原文上方列出写法提示,比如标题超长、一级标题和文章标题重复、公众号会删掉的 `<video>`、mermaid 图、无意义的图注。提示只提醒、不改正文,要不要改写由用户决定。
  - 用户保存 md 或 `theme.css` 后页面自动刷新。md 改名或移动后,用新路径重新运行 preview。
  - 页面上的「发布到草稿」按钮执行的就是发布模式。
- **发布**:运行 `bun cli.ts publish <文章.md>`,按输出的 JSON 告诉用户:新建还是更新了草稿,上传了几张图片,封面是否重传。`warnings` 不为空时一并转告(比如没有设置作者)。
- **调样式**:运行 `bun cli.ts preview`(不带文章)打开样例 `assets/sample.md`,样例预览不能发布。
  - 样式只改 `assets/theme.css`。改之前先读文件头的注释:写了哪些 CSS 能生效,以及暗色下颜色怎么选。颜色、字号、间距优先改 `:root` 里的变量。
  - 样例覆盖不到的元素,补进 `sample.md`。
  - 改完让用户在亮色和暗色下都看一遍效果。

## 发布会改什么

- **md 原文**:本地图片(`![[x.png]]` 或相对路径)和外链图片会被上传,原文里的图片改写成 `![说明](mmbiz 地址)`,`coverImage` 换成 mmbiz 地址。`![[x.png|300]]` 的尺寸不保留。已经是 mmbiz 地址的图片会跳过。除此之外 frontmatter 不改。
- **上传前检查**:所有图片先读进来检查格式,有一张不行就整篇不上传。超过 1MB 的 png、jpg 在 macOS 上用 `sips` 压缩后再传;gif 走永久素材接口。`<img>` 标签、引用式图片 `![x][ref]`、嵌入的笔记和 pdf 会直接报错,要用户改成 `![说明](地址)`。
- **草稿**:按 md 的真实路径在 `~/.config/wechat-publish/state.json` 记录草稿 ID 和封面素材 ID。同一路径再次发布,就更新同一篇草稿;草稿已经发表或被删掉时,新建一篇。md 改名或移动后再发布,会新建草稿。同一篇文章同时发布两次时,第二次直接报错。
- **字段**:`title` 和 `coverImage` 必填,`coverImage` 写成一行字符串(`"[[cover.png]]"` 要加引号)。`summary` 用作摘要。
- **封面裁剪**:`coverImage` 是 3.35:1 的拼接图(codex-wechat-cover 的默认输出,比如 2010×600)时,消息列表用左边 2.35:1,转发卡片和次条用右边 1:1。其他比例由公众号居中裁剪。预览页的消息卡片视图会按同样的裁剪显示。
- **终点**:流程只到草稿箱为止。草稿接口不支持原创声明,发布后提醒用户:在公众号后台勾选原创再群发。勾选后如果再同步草稿,发表前要确认原创还在。

正文渲染规则:

- frontmatter 的 `title` 不进正文。
- 链接处理参照 doocs/md:
  - 只有 `mp.weixin.qq.com` 的链接保留可点。
  - 其他外链显示为「文字[n]」,文末「引用链接」按「[n] 标题:网址」列出。标题取链接的 title,没有就用链接文字。标题和网址都相同的链接共用编号。
  - 链接文字就是网址时原样显示。`#锚点`、`./a.md` 这类不是网址的链接只显示文字。
- 列表符号渲染成文字(无序 `•`、有序 `1.`),由主题的 `.bullet`、`.number` 控制样式;公众号里原生列表符号改不了颜色和大小。
- 独占一段或独占一行的图片用 alt 作图注,alt 为空就不显示图注。
- 单个换行不换行,和 Obsidian 的 `strictLineBreaks` 一致;中文之间的换行直接去掉,不留空格。
- `**` 紧贴中文标点时也能加粗,和 Obsidian 一致。
- `%%批注%%` 和 HTML 注释不进正文,里面的图片也不上传。