dsh-wechat-push
DeepSeek Harness 插件:把 HTML 正文与图片直接投递到微信公众号草稿箱。
它可以与dsh-wechat-html(markdown → 微信可用 HTML)结合使用。两个插件互相独立:
本插件不排版——直接给它 HTML 文本或文件即可,所以不装排版插件也能用。
它永远不群发:草稿建好之后,仍然要人在公众平台后台点发送。
安装
克隆仓库后,在仓库根目录运行:
dsh plugin --profile web add file:.
本插件要求 Node.js 18 或更高版本。运行时还需要由 DeepSeek Harness 提供
@deepseek-ai/dsh-credentials、@deepseek-ai/dsh-tools 和
@deepseek-ai/dsh-typert-protocol 三个宿主依赖。
凭据
API 工具读两个凭据引用名。配置里放的是名字,值留在 dsh 的凭据提供方,所以这份配置可以放心提交。
| 引用名 | 是什么 |
|---|---|
WECHAT_MP_APPID |
公众号的 AppID |
WECHAT_MP_SECRET |
公众号的 AppSecret |
任何 dsh 能解析凭据的地方都行——进程环境变量、项目 .env、$DSH_HOME/.env、$DSH_HOME/.credentials.yaml:
export WECHAT_MP_APPID=wx...
export WECHAT_MP_SECRET=...
两个值都在 公众平台 → 设置与开发 → 基本配置。AppSecret 只显示一次;重新生成会让其它 系统手里的 token 立刻失效。
没有凭据时这几个工具不会挂载(它们只在凭据提供方被组合进来后才注册),而不是挂出一堆注定失败的入口。
工具
mp_upload_image —— 写
上传一张本地 jpg/png(< 1MB),返回正文里必须使用的 mmbiz.qpic.cn 地址。
微信会剥掉所有其它来源的图片,所以文章里每张本地图都要过这一步。
mp_create_draft —— 写
从 HTML 建草稿。正文可以是文件(html_path)或直接是文本(html)——二选一。
HTML 通常来自 mp_render,但任何内联样式的 HTML 都可以。
| 参数 | 说明 |
|---|---|
html_path / html |
二选一:正文文件,或正文 HTML 文本 |
title |
必填,≤ 64 字符 |
cover |
封面:{media_id, path}。优先直接引用 media_id,它为空时才上传 path |
cover_image / thumb_media_id |
旧参数,分别等价于 cover.path / cover.media_id,为兼容已有调用保留 |
author / digest / source_url |
可选;source_url 是「阅读原文」的链接 |
images |
每张正文图一条:优先直接引用 {token, url},url 为空时按 {token, path} 上传;两者都为空时跳过该图并继续。也可以用 src 代替 token |
missing_image_policy |
缺少 url/path 时的策略:warn(默认)、keep、remove 或 error |
upload_local_images |
额外扫描 HTML 里写成本地路径的 img src 并上传,默认 false |
base_dir |
上面那种相对路径的基准目录,默认取 html_path 所在目录 |
submitted_path |
只给 html 文本时,把提交后的正文另存一份的位置 |
没有出现在 images 里的未解析占位 token 仍会被拒绝;但如果调用方明确给了该图的
images 条目,只是没有 url 和 path,则保留 HTML 原状并继续创建草稿。封面仍是必需的:
已有 cover.media_id 时直接引用,
否则必须给出 cover.path 本地路径上传。两者同时给出时不会重复上传。
因为支持 {token, path},一篇文章可以一次调用连图带正文全部上去:
{
"html_path": "./content/article.html",
"title": "示例文章",
"cover": {
"path": "./content/cover.png"
},
"images": [
{ "token": "dsh-mp-image-0", "path": "./content/image-01.png" }
]
}
给 html_path 时,提交后的正文会另存为 <html_path>.submitted.html——填完 token 之后它
和渲染出的文件就不再一样了。保存失败不会把已创建的草稿判成失败,但结果会返回明确警告。
mp_list_drafts —— 只读
最近的草稿,新的在前。用来确认草稿真的落地了。
「发公众号」按钮(Web UI)
每条助手回复后面有一个 发公众号 按钮。一次点击就把那条回复排版(用配置的模板) 并放进草稿箱——不必先存成文件再让 agent 发布。
标题取消息的第一个标题行,或第一行正文,截到 64 字符。
按钮从 markdown 起步,所以它需要排版插件 dsh-wechat-html:装了就走
ctx.get('wechatHtml') 服务渲染;没装则明确告诉你缺什么,而不是静默失败。
按钮和工具共用同一套 API 代码,两条路不会走偏。
按钮没有地方选封面,所以必须先配好已有素材 ID 或本地路径:
- id: wechat-push
config:
defaultCoverMediaId: '' # 有已上传素材时填它,优先使用
defaultCover: './content/cover.png'
两者都没配的话,一次点击会告诉你该设哪个键。
消息里的外链图片会继续保留,但结果会警告它可能被微信过滤。正文图片、本地图片、封面、字段校验和
重复提交保护都与 mp_create_draft 共用同一条发布流程。
审批
写操作默认不弹提示。这里不会发布——草稿仍然需要人打开后台按发送——所以一次无人值守的 运行留下的是待删的草稿,而不是读者已经看到的推送。
想开提示:
- id: wechat-push
config:
requireApproval: true
它们随后走 dsh 的审批缝(tools/pre-execute)并 fail closed:没有组合进审批通道时,
调用被拒而不是被悄悄放行。
开启审批后,「发公众号」按钮也不会绕过这条规则:按钮会拒绝写入并提示改用受审批保护的
mp_create_draft 工具。
当 agent 无人值守地跑在一个永久素材配额要紧的账号上时值得打开:删掉草稿并不会把封面 占掉的坑还回来。
配置
全部可选;写在 profile 的 cordis.patch.yml 里,按行 id wechat-push 改:
- id: wechat-push
config:
appIdRef: WECHAT_MP_APPID
appSecretRef: WECHAT_MP_SECRET
tokenCacheDir: '' # '' → 系统临时目录
baseUrl: https://api.weixin.qq.com
defaultAuthor: ''
defaultCover: '' # 「发公众号」按钮需要它
defaultCoverMediaId: '' # 已有素材 ID;非空时按钮直接引用,不上传 defaultCover
requireApproval: false
补丁层会整块替换某一行的 config,所以想保留的键都要写全。
已知坑
IP 白名单(errcode 40164)
每次 API 调用都必须来自 公众平台 → 基本配置 → IP白名单 里列出的地址。家宽的地址会变, 所以昨天能用的配置今天可能就失败。错误信息会直接说这件事。
access_token 是按账号唯一的(errcode 45009)
微信每个账号只发一个 token,有效期两小时,每天发放次数有上限,而且新的一次发放会让上一个失效。 如果别的系统(CMS、某个微信 SDK、同事的脚本)在用同一个账号,你们会互相把对方顶掉。 本插件把 token 缓存在磁盘上、提前五分钟刷新,并把并发刷新收敛成一次发放。它对另一个系统无能为力。
账号类型
未认证的个人订阅号也能用草稿箱和素材接口。 这是权限最受限的一种账号,且是拿真实账号验证过的。 它不能做的是「通过 API 群发」——而本插件本来就不群发,所以那条限制碰不到。 如果某个接口确实没开放,微信会回 errcode 48001, "api unauthorized",插件会用大白话说清楚。
注意 IP 白名单是在取 token 时就校验的,早于任何接口。所以白名单没配会每次都是 40164, 它不告诉你任何权限信息。
图片
media/uploadimg 只收 jpg/png 且要小于 1MB;插件会在花掉一次 API 调用之前先在本地检查。
封面走的是另一个接口(永久 thumb 素材),插件替你处理了。
License
MIT。
No comments yet. Be the first to write one.