# 这篇文章是 AI 自己排版发布的：给 DeepSeek Harness 写一个公众号草稿插件

markdown 转公众号草稿要过两道坎：样式必须内联、正文图必须走微信自己的上传接口。这篇讲清楚这两道坎，以及一个把很多人挡在门外的错误码顺序问题。

---

你现在看到的标题字号、代码块配色、表格边框线，都不是在公众号后台点出来的。

我在编辑器里写完 markdown，对 agent 说了一句话：

> 把 ~/posts/ai-typeset-this.md 用 grace 主题发到公众号草稿箱，封面用 ~/posts/img/cover.png。

它渲染了文章、建好了草稿。我只做了最后一步：打开后台，看一眼，点发送。



这件事能成，是因为三天前发生的一件事。

## 三天，12 万 star



8 月 13 日，DeepSeek 开源了 **DeepSeek Harness**，一个跑在自己机器上的编程 agent。MIT 协议，代码全公开。



上面这张截图是我写这篇时截的，11.9 万 star。等我把正文写完再刷一次，**12.5 万**。

三天。

但真正让我坐不住的不是 star 数，是它的架构——**everything is a plugin**。

不是「我们预留了扩展接口」那种客气话。是整个 agent 本身就由插件拼起来：模型、工具、技能、会话、沙箱、存储、循环、调度，连界面都是插件。

**意思是，你想让它多干一件事，你自己就能加。**

不用等官方排期，不用提 issue 求人，不用 fork 整个项目。写个包，发到 npm，一行命令装上。

社区反应有多快？8 月 16 日我数了一下：插件市场收录 **1585** 个，awesome 列表 **842** 条。

三天，一千五百多个插件。

（这两个数字每天都在变，写下来只是当天的快照。）

我看着这个数字，想了个问题：**还有什么是没人做的？**

## 一个没人碰的方向

我翻了一遍这些插件。

给 agent 换皮肤的、加记忆的、管会话的、接 Telegram 的、接飞书的、接钉钉的，热闹得很。

但绝大多数，是**给 agent 自己加功能**。

真正伸手出去、连到一个外部服务、替你干完一件具体活的，少得出奇。

微信相关的插件有好几个，我一个个点开——全是**把 agent 接进微信聊天**，让你能用手机指挥它干活。

方向是反的。

**没有一个是帮你往公众号发文章的。**

而我每周都在被公众号排版折磨。

## 为什么公众号排版这么烦

如果你试过「markdown 转 HTML 再粘进公众号编辑器」，大概率失败过。

原因有两个，都不明显。

### 第一，编辑器只认元素上的 style 属性

`` 块、外链样式表、class 名，粘进去的一瞬间全部丢弃。

你本地预览得好好的文章，进了编辑器就是一坨没格式的纯文字。

正确做法是把每条 CSS 规则**算出来**，写进对应元素：

```html
<!-- 你以为可以这样 -->
h2 { color: #0F4C81; }
<h2>小标题</h2>

<!-- 实际必须这样 -->
<h2 style="color:#0F4C81;font-size:20px;">小标题</h2>
```

一篇文章几百个元素。手工做？不现实。

### 第二，正文图片必须来自微信自己的上传接口

这条比想象中绕，而且很多人第一次都会踩错接口。

正文里的每一张图，URL 必须是调用微信「上传图文消息内的图片获取 URL」接口（`/cgi-bin/media/uploadimg`）之后、由微信返回的那个地址。任何其他来源的图片链接都会被过滤掉。

注意判断标准是**「这个 URL 是不是 uploadimg 返回的」**，不是「域名是不是 `mmbiz.qpic.cn`」。该接口目前返回的确实是这个域名，但官方文档从没对域名做过承诺，写死域名去校验是给自己埋雷。

还有一个容易搞混的地方：

| 用途 | 接口 |
|---|---|
| 正文里的图 | `/cgi-bin/media/uploadimg` |
| 封面（缩略图） | `/cgi-bin/material/add_material` |

两者不能互换。正确顺序是：正文每张图先过 `uploadimg`，把返回的 url 回填进 content 的 `img src`；封面单独走 `add_material` 拿 `media_id`，填进草稿的 `thumb_media_id`。

**这个过滤是静默的。** `draft/add` 照样返回 `errcode 0`，错误码表里没有任何一条对应「图片被吃掉了」。

但别指望群发之后才发现——微信是在服务端处理这段 content 的，草稿阶段就该验收：打开后台草稿箱看一眼，或者用 `draft/get` 把 content 读回来对一遍。图片没上去的表现是那个位置空白，不是浏览器那种裂图图标。

用自己图床的，一样中招。

## 所以我搓了一个

叫 `dsh-wechat-mp`。

它就干上面那几件事：

| 做什么 | 怎么做 |
|---|---|
| 样式内联 | 每条 CSS 规则算成字面量，写进元素的 `style` |
| 图片搬运 | 正文图走 `uploadimg`，封面走 `add_material`，各自换成微信返回的地址 |
| 建草稿 | 组装标题、作者、封面，提交到草稿箱 |

装起来一行：

```bash
dsh plugin --profile web add dsh-wechat-mp
```

**它不群发。**

只到草稿箱为止，发送这一步永远留给人。我不想要一个能自己往几千人手机上推东西的程序。

## 那条「个人号没权限」的传闻，是怎么来的

动手之前我做过调研，网上的说法是：**未认证的个人订阅号，大概率调不了草稿箱接口。**

我信了，所以插件特意做成两层：排版那半边不碰任何接口——就算你的号没权限，照样能拿到排好版的 HTML 自己粘。

然后我拿自己的号真测了一次。未认证的个人订阅号，能用。草稿箱、素材接口，全都能调。

后来我去翻了官方文档，发现这个传闻的来源比「大家没细看错误码」要具体得多：

微信在 2025 年 7 月回收了**发布能力** `freepublish/*` 系列对个人主体、企业主体未认证账号的调用权限。而这一系列里那个最常用的接口 `freepublish/submit`，**官方名字就叫「发布草稿」**。

「发布草稿」被传成了「草稿箱」。

而草稿箱接口 `draft/*` 从来不在回收名单里。官方文档对 `draft/add`、`material/add_material` 标的适用范围一直是可用——**不是官方说不行、我实测打了脸，是官方一直没说不行，传的人把两个接口搞混了。**

这也正好解释了这套流程为什么成立：API 只负责把文章送进草稿箱，最后点发布那一下在后台手动完成——被回收的恰好是我绕开的那个接口。

## 一条把顺序搞反的报错

第一次调接口，报错是这个：

```text
errcode 40164: invalid ip 203.0.113.42, not in whitelist
```

IP 白名单没配。这个我知道，去后台加上就行。

但**关键在于它发生在哪一步**。

我原以为白名单是拦具体业务接口的：你调草稿箱，它检查你的 IP。

实际上，微信在你**换取 access_token** 的时候就查来源 IP——`cgi-bin/token`，以及任何直接传 AppSecret 的接口，都在这一步被拦下。

后果很要命：**白名单没配好之前，你没法用 API 去验证权限。** 所有请求都卡在换凭据这一步，那个真正代表「接口未授权」的 `48001` 根本没机会出现。你看到的永远是一个跟权限毫无关系的错误。

所以顺序应该是：**先把出口 IP 加进白名单，再判断权限。** 顺序反了，你测的是白名单，不是权限。

更省事的做法是根本别用调接口来试权限——直接去后台看「接口权限与额度」，哪些接口已获得、哪些没有，一目了然。

一个提醒：白名单校验覆盖到哪些接口，官方文档自己两页口径并不一致，一页说覆盖 AppSecret / access_token 的全部服务端调用，另一页只说覆盖取凭据接口。工程上按更严的那个口径处理——把所有出网调用的 IP 都加白，别赌。

## 边界与注意事项

**AppSecret 和 IP 白名单已经搬家了。** 微信把「开发接口管理／基本配置」从公众平台迁到了微信开发者平台，现在的路径是 `我的业务 → 公众号/服务号 → 基础信息 → 开发密钥`。同一个后台的 `接口管理 → 接口权限与额度` 能查到你账号的真实接口权限，比任何流传的权限表都准。

**家宽是动态 IP。** 换了还得重加，这个没办法。两个缓解办法：白名单支持 IP 段（形如 `172.0.0.1/24`），运营商地址段固定时可以按段加；但如果你在 CGNAT 后面、压根没有独享的公网 IPv4，那不是「换了要重加」的问题，是加不了，得换个有固定出口的环境跑。

**它只到草稿箱。** 这是设计选择，不是没做完。任何能自动群发的东西，我都不想留在自己机器上。

**主题 CSS 来自 [doocs/md](https://github.com/doocs/md)**，那个很多人在用的公众号编辑器，所以排出来的样子你可能眼熟。用它的主题请遵守其许可证。

**文中的数字都是快照。** star 数、插件数量每天都在变，看到这篇时早已不是这个数。

## 你也可以搓一个

代码是开源的：

- 仓库：**github.com/coolbreezecoin/dsh-wechat-mp**
- npm：`dsh-wechat-mp`

前后不到两周。真正花时间的不是排版逻辑，是搞清楚微信接口到底怎么回事：哪个接口对应哪种图、哪个错误码真正意味着什么、以及上面那个把人带偏的顺序。

文档里都没有，只能撞。

但我想说的重点其实不是这个插件，是**现在搓一个真的不难了**。

三天一千五百个插件，不是因为大家突然变强了，是因为门槛塌了：agent 帮你读文档、写代码、跑测试，你负责判断方向对不对。

你每天在忍受的那个重复劳动，大概率也没人做过。

*这篇文章的排版和草稿由 `dsh-wechat-mp` 生成。哪里排得不对，那是我的 bug。*

---

本文同步自凉风Lab。原文与后续更新：https://liangfenglab.com/guides/ai-typeset-this/
