通过 WorkBuddy 已连接的 ClawBot 微信 bot 通道(weixinClawBot / ilink bot,非微信客服号)主动向老板微信推送文本/图片/文件/视频。当用户要求"通过 ClawBot 主动推送/给老板发微信/微信主动推送测试/微信独立消息/发图片给老板/发文件给老板"时使用。协议严格对齐腾讯官方插件 @tencent-weixin/openclaw-weixin@2.4.6(src/api + src/messaging + src/cdn)。
编程
Weixinclaw Proactive Push
试用通过 WorkBuddy 已连接的 ClawBot 微信 bot 通道(weixinClawBot / ilink bot,非微信客服号)主动向老板微信推送文本/图片/文件/视频。当用户要求"通过 ClawBot 主动推送/给老板发微信/微信主动推送测试/微信独立消息/发图片给老板/发文件给老板"时使用。协议严格对齐腾讯官方插件 @tencent-weixin/openclaw-weixin@2.4.6(src/api + src/messaging + src/cdn)。
它能做什么
通过 WorkBuddy 已连接的 ClawBot 微信 bot 通道(weixinClawBot / ilink bot,非微信客服号)主动向老板微信推送文本/图片/文件/视频。当用户要求"通过 ClawBot 主动推送/给老板发微信/微信主动推送测试/微信独立消息/发图片给老板/发文件给老板"时使用。协议严格对齐腾讯官方插件 @tencent-weixin/openclaw-weixin@2.4.6(src/api + src/messaging + src/cdn)。
技能文档
📦 本 skill 源码仓库:https://github.com/NoahEleven/weixinclaw-proactive-push (公开,欢迎 clone / 提 Issue)
🔒 隐私红线(务必遵守):本 skill 运行时只读本机凭据、不写出任何密钥、日志不打印 token 明文。 分享 / 备份 / 提交时,严禁携带以下文件(含 bot 完整凭证 / 老板微信 id):
~/.workbuddy/settings.json(含完整botToken、userId)~/.workbuddy/claw-state/weixin/(cursor 游标内含 bot 凭证镜像get_updates_buf) 本仓库只含SKILL.md+send.js,接收方用自己的settings.json即可,无需你提供任何密钥。
微信(ClawBot)主动推送(weixinClawBot / ilink bot)
背景
WorkBuddy 迁移自 OpenClaw。微信主动推送能力其实一直活着,藏在
~/.workbuddy/settings.json 的 claw/users//channels/weixinClawBot 里——
它是 ClawBot 的微信 bot 通道(ilink bot,polling 模式),对接 https://ilinkai.weixin.qq.com。
⚠️ 这不是微信客服号:微信客服号走公众平台客服消息 API(48 小时会话限制), 而本通道是 WorkBuddy / ClawBot 内置的 bot 通道,走 ilink bot API,可随时主动推送,不受客服号限制。
协议实现严格对齐腾讯官方插件 @tencent-weixin/openclaw-weixin@2.4.6
(npm 包,含 src/api/api.ts、src/api/types.ts、src/messaging/*、src/cdn/*)。
关键常量、请求头、item 结构、AES 加密、错误码检查均与官方保持一致。
⚠️
openclaw-weixin-cli不需要装(那是 OpenClaw 的通道安装器,对 WorkBuddy 无用)。
何时用
- "通过 ClawBot 主动推一条消息给老板"(文本默认空 context_token,无需激活;详见下方机制专节)
- "微信主动推送测试""给老板发条微信 bot 消息"
- "发张图/发个文件/发段视频给老板"(context_token 自动读取,详见机制专节)
- 定时任务把结果主动推到老板微信(ClawBot bot 消息,非对话气泡)
怎么跑
脚本自动读 settings.json 取凭据与收件人,向老板微信发消息:
NODE="C:/Users//.workbuddy/binaries/node/versions/22.22.2/node.exe" # 将 换成你的 Windows 用户名
SKILL="C:/Users//.workbuddy/skills/weixinclaw-proactive-push/send.js" # ⚠️ 必须指到 send.js;只传目录会 MODULE_NOT_FOUND(无 package.json main)
# 文本(默认空 context_token,无需激活;详见下方机制专节)
"$NODE" "$SKILL" "🦐 你的消息内容"
# 从文件读长文本
"$NODE" "$SKILL" --text-file 内容.txt
# 图片 + 可选说明(context_token 自动读取)
"$NODE" "$SKILL" --image 截图.png --caption "看这张图"
# 文件 + 文件名 + 可选说明(context_token 自动读取)
"$NODE" "$SKILL" --file 报告.pdf --name "周报.pdf" --caption "请查阅"
# 视频(context_token 自动读取)
"$NODE" "$SKILL" --video clip.mp4
# 显式指定 token(极端兜底:手动传入一个已知有效的 context_token)
"$NODE" "$SKILL" "回复内容" --context
- 不传参数 → 发送默认测试文案(文本)。
- 跑的时候若因沙箱拦截网络,加
dangerouslyDisableSandbox: true(或在弹窗点允许)。 - 成功标志:终端打印
[weixinclaw] OK,老板微信里会收到一条 ClawBot bot 消息(不是对话气泡)。
凭据从哪来(settings.json)
claw/users//channels/weixinClawBot = {
"enabled": true,
"channelId": "<你的 bot channelId,从你的 settings.json 读取>",
"botToken": "<完整 botToken 串,含 accountId: 前缀;脚本自动从你的 settings.json 读取,无需手填>", // 完整 token = Bearer 凭据
"baseUrl": "https://ilinkai.weixin.qq.com", // apiBaseUrl
"accountId": "<你的 bot accountId,从你的 settings.json 读取>",
"userId": "<老板的 im.wechat id = to_user_id;脚本自动从你的 settings.json 读取>", // 老板的 im.wechat id = to_user_id
"connectionMode": "polling"
}
🔒 以上字段均为接收方自己的
settings.json内容,无需分享者提供。脚本只读取、不写出。
🔴 关键机制:context_token(2026-07-09 定稿)
文本默认空 token;只有媒体才带 cursor buf
- 文本推送:
msg.context_token默认传【空串】即可发送,无需读 cursor、无需激活。 这就是为什么会话闲置十几小时(例如昨晚到今早)文本仍能正常推送——空 token 对文本长期有效。 - 媒体推送(图片 / 文件 / 视频):必须带非空
context_token, 唯一来源是 cursor 游标里的get_updates_buf(见下)。空 token 发媒体会ret:-2。 - 任何被拒(
ret:-2)都要激活:文本平时用空 token 就能发,一旦文本也被拒, 大概率是底层 bot id / 凭证已轮换,同样需要先给 ClawBot 发消息激活后重跑(详见排错表)。
媒体的 context_token 来源:cursor 游标里的 get_updates_buf
- host 实时把 ilink 接收会话游标写进
~/.workbuddy/claw-state/weixin/_im.bot.cursor.json的get_updates_buf字段。 - 该字段值是 base64 编码的 protobuf;我们把整段 base64 串原样当作
context_token用 (不是解码出来的 botToken 串,也不是里面的 accountId 段)。
✅❌ 读取铁律(违反任何一条都会 ret:-2,这是"编码读取方式"的要害)
- 以 UTF-8 文本读
cursor.json→ ②JSON.parse→ ③ 取get_updates_buf字段的【字符串值】 → ④ 原样作为context_token。- ❌ 严禁对这串做任何变换:base64 解码、重新编码、trim、URL-decode、转义、换行、截断、大小写改写。 base64 串里全是
A-Za-z0-9+/=字符,JSON 安全,直接原样塞进context_token字段即可。- ❌ 不要"聪明地"解析 / 改写 / 重组这个串——它就是个不透明令牌,服务端的校验逻辑你是看不见的,任何本地变换都只会让它失效。
- ✅ 直接用本仓库的
send.js(已封装以上全部),不要手工拼请求——手工拼最容易在读取这步翻车。
- 凭证稳定、不要误判:
botToken/accountId段(形如 ``)约 1 天才轮换一次,期间一直有效、不变是正常的,它不是失效信号,别拿它判断 buf 是否过期。 - 文本无需激活:文本走空 token,会话闲置多久都能发(实测跨十几小时);只有出现
ret:-2时才需激活。 - 媒体激活一次可长期使用:媒体带 cursor buf,只要此前已激活过且没收到
ret:-2,就可持续推送,不需要每次推送前都重新发消息。 - 文本被拒 = id 变了,也要激活:文本平时空 token 能发;一旦文本
ret:-2,大概率底层 bot id 轮换,同样先发消息激活再重跑。 - ⚠️ 未激活时反复直接重跑必定再次
ret:-2——请务必先完成"发消息激活"这个动作,再重跑命令。 - 实测:媒体用 buf 当
context_token+ 完整botToken当 Bearer ⇒{}成功,且不读 context-token.json、无需轮询。 - 编码细节:
get_updates_buf解码后为约 78 字节 protobuf,含botId(varint) + 账号标识 + botToken 明文段(accountId@im.bot:secret)。
🔒
get_updates_buf内嵌了 bot 凭证副本,绝不要把它粘贴到聊天、Issue、或随仓库提交。
im.bot.cursor.json 文件的更新机制(务必分清"文件刷新"和"buf 轮换"两件事)
文件路径:~/.workbuddy/claw-state/weixin/_im.bot.cursor.json
(`` = bot accountId 段,如同一个 bot 换了凭证/会话,文件名的这段会变,会出现新的一份 cursor 文件)。
① 文件何时被创建
- 该文件由 host(WorkBuddy 后台的 ilink bot 长轮询)维护。通道必须先建立过会话才会有这个文件—— 即老板/用户曾在微信给 ClawBot 发过消息、host 侧建立了接收游标。
- 若目录里没有任何
*_im.bot.cursor.json:让老板/用户先在微信给 ClawBot 发一条消息,host 才会写出该文件。
② 文件 mtime(修改时间)——host 实时刷新,别拿它当判断依据
- host 在每一轮长轮询写盘游标时都会更新该文件的 mtime,非常频繁,且与你是否发消息无关(是 host 侧实时行为,不是你触发的)。
- ⚠️ 因此 绝不能用 mtime("多久没变 / 刚变过")来判断 buf 是否新鲜、是否需要刷新——mtime 一直在变,但
get_updates_buf的值未必跟着变。这是最容易误判的点。
③ get_updates_buf 内容(真正的 token)——稳定,靠"激活"刷新,不靠时间猜
- 这才是我们要的 token。它在 bot 凭证/会话有效期内保持稳定(
accountId段约 1 天轮换一次,期间不变属正常)。- 判断是否需要刷新,唯一依据是「实际推送是否被服务端拒绝(
ret:-2)」:- 被拒=当前 buf 暂时不可发送 → 让老板/用户在微信给 ClawBot 发一条消息激活(host 随即刷新游标),再重跑
send.js自动读最新 buf 重发即可。 - 只要不被拒,激活一次即可在较长时间内反复推送(实测跨十几小时仍能成功),不需要每次推送前都发消息激活。
- ⚠️ 不要把 mtime 变化、或"buf 值有没有变"当成判断条件——这两者都不决定可发送性,纯属干扰。
- 被拒=当前 buf 暂时不可发送 → 让老板/用户在微信给 ClawBot 发一条消息激活(host 随即刷新游标),再重跑
- 判断是否需要刷新,唯一依据是「实际推送是否被服务端拒绝(
send.js收到-2会【清晰报错并退出】,不内置轮询等待("等待刷新"退步逻辑已彻底删除);由你手动激活后重跑命令即可。
send.js 的取值优先级
文本:ctx = --context 显式传入 > 【空串】(默认,无需 cursor / 激活)
媒体:ctx = --context 显式传入 > 读最新 _im.bot.cursor.json 的 get_updates_buf(默认走这条)
(媒体的 get_updates_buf 为空、或任何推送被拒 ret:-2(含文本被拒=id 变了)时:
先让老板/用户自己在微信给 ClawBot 发一条消息激活(host 随即刷新游标),再重跑 send.js;
仍失败再走 --context 显式兜底。)
🔴 关键坑(必看,否则必失败)
- Bearer token 必须用完整
botToken串(含accountId:前缀),即<完整 botToken 串(含 accountId: 前缀),从你的 settings.json 读取>。 只取冒号后的短串会返回errcode:-14 session timeout,推送失败。 - 协议常量对齐官方
@tencent-weixin/openclaw-weixin@2.4.6(不要再用旧值):channel_version = "2.4.6"(早期实现误用cbc-1.0.0,现已对齐官方)iLink-App-ClientVersion = 132102(=buildClientVersion("2.4.6"))iLink-App-Id = "bot",bot_agent = "OpenClaw"
- 文本默认空
context_token即可;媒体推送必须带有效context_token(见上节),否则ret:-2拒收。文本若也被拒ret:-2,大概率 bot id 轮换,需激活。
发送契约(对齐官方 ilink bot 协议)
- 端点:
POST {baseUrl}/ilink/bot/sendmessage - 请求头(完整,缺一不可):
Authorization: Bearer <完整botToken>AuthorizationType: ilink_bot_tokenX-WECHAT-UIN: <随机4字节uint32的base64>iLink-App-Id: botiLink-App-ClientVersion: 132102Content-Type: application/json
- Body(文本):
{
"msg": {
"from_user_id": "",
"to_user_id": "",
"client_id": "<16位随机hex>",
"message_type": 2,
"message_state": 2,
"item_list": [{ "type": 1, "text_item": { "text": "消息内容" } }],
"context_token": "<从 cursor 读取的 get_updates_buf>"
},
"base_info": { "channel_version": "2.4.6", "bot_agent": "OpenClaw" }
}
item_list类型:1=文本/2=图片/4=文件/5=视频(text 超 4000 字自动分片)。- 成功判定:
sendmessage返回{}(host 视ret缺失为成功);ret非零(如-2)即失败。 - 会话存活自检:
POST {baseUrl}/ilink/bot/getupdates返回无errcode即健康。
富媒体流程(图片/文件/视频)
- context_token 自动从 cursor 游标读取(详见上方机制专节),无需手动准备。没有会
ret:-2。 - 生成 16 字节随机 AES 密钥
aesKey,aesKeyHex = aesKey.toString('hex')。 POST /ilink/bot/getuploadurl,body:filekey, media_type(1=IMAGE/2=VIDEO/3=FILE), to_user_id, rawsize, rawfilemd5, filesize(密文长), aeskey(aesKeyHex), no_need_thumb。- 取响应
upload_full_url(或旧版upload_param拼https://novac2c.cdn.weixin.qq.com/c2c/upload?encrypted_query_param=...&filekey=...)。 POST密文(AES-128-ECB(PKCS7) 加密后的文件)到该 URL,Content-Type: application/octet-stream。- 取响应头
x-encrypted-param作为下载令牌encrypt_query_param。 - 构造 item:
media = { encrypt_query_param, aes_key: base64(aesKeyHex), encrypt_type: 1 }, 图片加mid_size,文件加file_name+len,视频加video_size。 POST /ilink/bot/sendmessage,msg.context_token= 有效 token。
排错
| 现象 | 原因 | 解决 |
|---|---|---|
errcode:-14 session timeout | 用了短 token | 改用完整 botToken 串 |
FAILED: API ret=-2 | 会话已失效 / 底层 bot id 已轮换。文本平时用空 token 可长期发送,一旦文本也 -2 大概率是 id 变了;媒体则是 cursor buf 失效 | 【必须】先到微信给 ClawBot 发一条任意消息(激活 host 的发送游标),激活后再重跑本命令,send.js 自动读最新 buf 重发。⚠️ 未激活时反复直接重跑必定再次 ret:-2,请务必先完成激活动作;不要拿 mtime 或"buf 值变没变"当判断条件;send.js 收到 -2 会清晰报错并退出(不内置轮询);仍失败再显式 --context 兜底 |
| 网络超时 / fetch 失败 | 默认沙箱拦截出网 | 加 dangerouslyDisableSandbox: true 或点允许 |
| 通道找不到 | settings.json 路径/字段变了 | 按上文"凭据从哪来"手动核对路径 |
| 走错通道 | 误用 MCP 的 weixin/wecom | 确认用 weixinClawBot(ClawBot),不是 MCP connector |
| 富媒体上传失败 | getuploadurl 未返回 url / CDN 缺 x-encrypted-param | 检查 botToken 完整、网络可达 CDN |
| 文本类文件(.txt/.md)中文乱码 | 源文件 UTF-8 无 BOM,微信/Windows 按 GBK 解码 | send.js 已自动为文本类文件补 UTF-8 BOM,无需手动处理 |
文件不存在: ... | 传入的媒体路径错误 | 检查 --image/--file/--video 指向的文件路径 |
缺少 botToken / 缺少 userId | settings.json 的 weixinClawBot 配置不完整 | 按"凭据从哪来"补全字段 |
相关技能
通过 pushplus HTTP API,用 curl 即可向微信、ClawBot、邮箱、webhook、短信、App 等渠道推送消息。
企业微信群机器人工具与服务端 API 指南。Use for: (1) 用附带的零依赖 CLI 向企微群发通知——文本/markdown/图片/文件, (2) CI/监控/日报自动化推送, (3) 企业微信服务端 API(通讯录/应用消息/客户联系)开发指导。WeCom (WeChat Work) group-ro...
Use when configuring OpenClaw chat channels to send an immediate acknowledgement before the main reply starts. Supports Feishu and DingTalk, includes bilingu...
微信AI助手 — 主动发消息 + 自动回复 / WeChat AI assistant — send messages & auto-reply
WeCom Bot (work.weixin.qq.com). Use this skill for ANY WeCom Bot request — reading, creating, updating, and deleting data. Whenever a task involves WeCom Bot, use this skill instead of calling the API directly.