Install
openclaw skills install @noaheleven/weixinclawbot通过 WorkBuddy 已连接的 ClawBot 微信 bot 通道(weixinClawBot / ilink bot,非微信客服号)主动向老板微信推送文本/图片/文件/视频。当用户要求"通过 ClawBot 主动推送/给老板发微信/微信主动推送测试/微信独立消息/发图片给老板/发文件给老板"时使用。协议严格对齐腾讯官方插件 @tencent-weixin/openclaw-weixin@2.4.6(src/api + src/messaging + src/cdn)。
openclaw skills install @noaheleven/weixinclawbot📦 本 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即可,无需你提供任何密钥。
WorkBuddy 迁移自 OpenClaw。微信主动推送能力其实一直活着,藏在
~/.workbuddy/settings.json 的 claw/users/<uid>/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 无用)。
脚本自动读 settings.json 取凭据与收件人,向老板微信发消息:
NODE="C:/Users/<USER>/.workbuddy/binaries/node/versions/22.22.2/node.exe" # 将 <USER> 换成你的 Windows 用户名
SKILL="C:/Users/<USER>/.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 <context_token>
dangerouslyDisableSandbox: true(或在弹窗点允许)。[weixinclaw] OK,老板微信里会收到一条 ClawBot bot 消息(不是对话气泡)。claw/users/<uid>/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内容,无需分享者提供。脚本只读取、不写出。
msg.context_token 默认传【空串】即可发送,无需读 cursor、无需激活。
这就是为什么会话闲置十几小时(例如昨晚到今早)文本仍能正常推送——空 token 对文本长期有效。context_token,
唯一来源是 cursor 游标里的 get_updates_buf(见下)。空 token 发媒体会 ret:-2。ret:-2)都要激活:文本平时用空 token 就能发,一旦文本也被拒,
大概率是底层 bot id / 凭证已轮换,同样需要先给 ClawBot 发消息激活后重跑(详见排错表)。get_updates_buf~/.workbuddy/claw-state/weixin/<ACCOUNT_ID>_im.bot.cursor.json 的 get_updates_buf 字段。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 段(形如 <ACCOUNT_ID>)约 1 天才轮换一次,期间一直有效、不变是正常的,它不是失效信号,别拿它判断 buf 是否过期。ret:-2 时才需激活。ret:-2,就可持续推送,不需要每次推送前都重新发消息。ret:-2,大概率底层 bot id 轮换,同样先发消息激活再重跑。ret:-2——请务必先完成"发消息激活"这个动作,再重跑命令。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/<ACCOUNT_ID>_im.bot.cursor.json
(<ACCOUNT_ID> = bot accountId 段,如同一个 bot 换了凭证/会话,文件名的这段会变,会出现新的一份 cursor 文件)。
① 文件何时被创建
*_im.bot.cursor.json:让老板/用户先在微信给 ClawBot 发一条消息,host 才会写出该文件。② 文件 mtime(修改时间)——host 实时刷新,别拿它当判断依据
get_updates_buf 的值未必跟着变。这是最容易误判的点。③ get_updates_buf 内容(真正的 token)——稳定,靠"激活"刷新,不靠时间猜
accountId 段约 1 天轮换一次,期间不变属正常)。
ret:-2)」:
send.js 自动读最新 buf 重发即可。send.js 收到 -2 会【清晰报错并退出】,不内置轮询等待("等待刷新"退步逻辑已彻底删除);由你手动激活后重跑命令即可。文本:ctx = --context 显式传入 > 【空串】(默认,无需 cursor / 激活)
媒体:ctx = --context 显式传入 > 读最新 <ACCOUNT_ID>_im.bot.cursor.json 的 get_updates_buf(默认走这条)
(媒体的 get_updates_buf 为空、或任何推送被拒 ret:-2(含文本被拒=id 变了)时:
先让老板/用户自己在微信给 ClawBot 发一条消息激活(host 随即刷新游标),再重跑 send.js;
仍失败再走 --context <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 轮换,需激活。POST {baseUrl}/ilink/bot/sendmessageAuthorization: Bearer <完整botToken>AuthorizationType: ilink_bot_tokenX-WECHAT-UIN: <随机4字节uint32的base64>iLink-App-Id: botiLink-App-ClientVersion: 132102Content-Type: application/json{
"msg": {
"from_user_id": "",
"to_user_id": "<boss的im.wechat 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 即健康。ret:-2。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。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 <token> 兜底 |
| 网络超时 / 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 配置不完整 | 按"凭据从哪来"补全字段 |