公众号文章自动化(wechat-article-automation)
把 Markdown / 纯文本内容排成可直接粘进微信公众号编辑器的富文本,并生成带「复制公众号富文本」按钮的本地预览页。核心解决「复制粘贴后排版丢失」这一真实痛点。
第一步(必须先做)
node scripts/env-check.mjs
输出 "ok": true → 环境就绪;输出 "ok": false → 按 JSON 里 error.hint 的指引处理。
一期能力
render — 内容转微信富文本
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
--input |
文件 | 是 | Markdown 或纯文本路径(含中文 / 空格需引号) |
--template |
长文 / 清单 / 日报 | 否 | 视觉模板,默认长文 |
--output |
文件 | 否 | 输出 HTML,默认 <输入同名>.wechat.html |
node scripts/render.mjs --input "./drafts/article.md" --template 清单
输出一份 JSON(ok、输出路径、<span 与 </span> 标签计数断言结果)。
preview — 生成复制预览页
node scripts/preview.mjs --input "./drafts/article.wechat.html" --output "./drafts/article.preview.html"
打开生成的 .preview.html,点「复制公众号富文本」,回到公众号编辑器粘贴即可。
二期能力
topic — 主题词离线生成草稿骨架
零联网、零凭据。给一个主题词,自动铺好标题备选、各级小标题、每段引导占位和写作建议,渲染成带占位的微信富文本,正文由你填。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
--topic |
字符串 | 是* | 主题词或一句话,如「露营装备选购」(可用 --input 文本文件的首行代替) |
--template |
长文 / 清单 / 日报 | 否 | 视觉模板,默认长文 |
--output |
文件 | 否 | 输出 HTML,默认 <主题词>.wechat.html |
node scripts/topic.mjs --topic "露营装备选购" --template 清单
输出的草稿里,【引导段占位】 【本段占位】 这类中文括号文字是给你替换的提示,不是正文。写完后跑 preview.mjs 即可复制。
> 二期暂不做「AI 真写稿」:那需要你自备 LLM 的 API key(联网 + 凭据),与「零凭据」定位冲突。当前是离线骨架,任何环境可跑;真写稿留作后续可选增强。
extract — 网页链接 / 本地文件重排
把网页或本地 HTML/Markdown 的正文抽出来,重排成微信富文本。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
--url |
链接 | 是* | 网页链接(http/https);只抓该 URL,不访问其他地址、不外传数据 |
--input |
文件 | 是* | 本地文件:.html/.htm 走正文抽取,.md/.txt 直接解析 |
--template |
长文 / 清单 / 日报 | 否 | 视觉模板,默认长文 |
--output |
文件 | 否 | 输出 HTML,默认 <输入同名>.wechat.html |
node scripts/extract.mjs --url "https://example.com/article" --template 长文
node scripts/extract.mjs --input "./drafts/saved-page.html"
抽取只做正文重排、不声明原创;落地前请自行核对来源与授权。抓取失败(403 / 超时 / 非 http(s))会给出可读错误而非臆测。
三期能力
push — 推送到草稿箱(可选 BYO 凭据增强)
前置条件:认证公众号权限 + AppID/Secret + 服务器 IP 白名单;个人订阅号没有草稿箱接口。凭据走环境变量 WECHAT_APPID / WECHAT_SECRET(或 --appid / --secret 覆盖),绝不硬编码、不随技能分发。本脚本只写入草稿箱,不发表。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
--input |
文件 | 是 | render / topic / extract 产出的 .wechat.html |
--cover |
文件 | 二选一 | 本地封面图(draft/add 必填 thumb_media_id) |
--cover-url |
链接 | 二选一 | 封面图网址(自动下载后上传) |
--title |
字符串 | 否 | 标题(缺省取正文首个标题块) |
--author |
字符串 | 否 | 作者名 |
--digest |
字符串 | 否 | 摘要 |
--content-source-url |
链接 | 否 | 阅读原文链接 |
--token-file |
文件 | 否 | token 缓存,默认 .wechat_token.json |
node scripts/push.mjs --input "./drafts/article.wechat.html" --cover "./drafts/cover.jpg"
输出 media_id 与「已写入草稿箱(非发表)」提示。令牌失效(40001/42001)自动删缓存重取一次;封面上传瞬时故障(53402)自动等 10 秒重试一次。
verify — 草稿箱字节级验证
push 拿到 media_id 后,回读 draft/get 比对推送前后结构与文本,断言微信重序列化后样式仍存活。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
--media-id |
字符串 | 是 | push 返回的 media_id |
--input |
文件 | 是 | 推送前的原稿 .wechat.html(用于比对) |
--token-file |
文件 | 否 | 同 push 的 token 缓存 |
node scripts/verify.mjs --media-id <MEDIA_ID> --input "./drafts/article.wechat.html"
通过则 ok:true;结构(span/section 计数)或文本不匹配则 ok:false,建议人工核对草稿箱实际内容。
为什么这样排(技术真相)
微信编辑器会重新序列化文档,样式丢失的根因是标签结构而非 CSS 属性。本技能采用微信原生结构(根 <section> + 块级 <span style="display:block"> + 每个文本节点 <span leaf="">),因此 linear-gradient、border-radius、box-shadow、rgba 都能原样存活。
另一个易踩的坑:每个块级元素都必须显式声明 text-align(正文用 left、标题用 center)。因为 Chromium 下未设置 text-align 的节点计算值为 start,而微信《编辑器插件开发规范》§1.6 把 start/end 判为「非标准值」并逐段告警「文字对齐异常」(left/center/right/justify 才合法)。渲染器对此有强制断言——任何块缺 text-align 直接报错,所以无需你手动检查。详细规范见 references/wechat-dom.md。
发布前自检(可选)
粘贴前想先确认是否违反微信内容规范,可用微信官方本地检测 CLI 对产出的 .wechat.html 跑一遍:
git clone https://github.com/wechatjs/verify-article-structure-spec.git
cd verify-article-structure-spec/cli
PUPPETEER_SKIP_DOWNLOAD=true npm install --legacy-peer-deps
PUPPETEER_EXECUTABLE_PATH="<本机 chrome.exe 路径>" npm run check "<你的文章>.wechat.html"
输出 isValid: true(无违规) 即通过。该步骤需联网安装、且会启动本机 Chrome,非必需——本技能产出的结构本身已按规范生成,装这一步只是多一道官方口径的保险。
暂不支持 / 后续版本
- 主题词 AI 真写稿:需自备 LLM 的 API key(联网 + 凭据),与「零凭据」定位冲突,留作后续可选增强;当前二期仅提供离线骨架。
- 草稿箱一键推送的「自动发表」:三期已实现「写入草稿箱(push)+ 回读验证(verify)」。但发表是公开动作,不在本技能内自动执行;需要发表请在公众号后台手动点,或另接发布流程。个人订阅号无草稿箱接口,仍只能用「复制富文本」通道。
合规
- 内置互动诱导词一票否决(点赞 / 关注 / 转发 / 收藏 / 留言 / 评论区…),生成内容若触发会提示改写。
- 本技能不内置任何题材模板,用户自备内容,本身不涉及题材合规风险;但用户自带内容仍需自行把关。
红线
- 默认写出新文件,绝不覆盖用户源文件;覆盖须经确认。
- 默认零联网、数据不出本机;仅当用户显式给定网页链接做抽取时,才对该 URL 发起一次请求,不访问其他地址、不向外发送任何数据。
- 不硬编码任何凭据。
- 不绑定任何用户私人题材、素材、配色、文案。