docs: weixin ClawBot bridge protocol and examples

This commit is contained in:
AskaEth committed 2026-08-21 18:15:20 +08:00
commit 4ea3957da9
13 files changed
+2158

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
# 03 — 消息与媒体处理
> 本文档覆盖:五种消息类型的结构、媒体文件的 CDN 传输、AES-128-ECB 加解密、语音的特殊性。
> 媒体部分的字段级细节来自官方包逆向与第三方适配器实现,标注 ⚠️ 处建议对照官方包源码或实测确认。
## 1. 消息类型速览
| `item.type` | 类型 | 入站 | 出站 | 关键字段 |
|---|---|---|---|---|
| 1 | 文本 | ✅ | ✅ | `text_item.text` |
| 2 | 图片 | ✅ | ✅ | CDN 引用 + aes_key |
| 3 | 语音 | ✅(附转写) | ⚠️ 存疑 | silk 编码,`voice_item.text` 为 ASR 文本 |
| 4 | 文件 | ✅ | ✅ | CDN 引用 + aes_key |
| 5 | 视频 | ✅ | ⚠️ | CDN 引用 + aes_key |
> 关于出站语音/视频:早期协议解析称「仅文本」或「bot 暂不能主动发语音」,但 OpenAkita 等适配器后续实现了图片/文件/语音的 CDN+AES 出站。说明协议能力在逐步开放或官方未完整公开,**以你的账号实测为准**。
## 2. 文本消息
### 2.1 入站
```json
{
"item_list": [
{ "type": 1, "text_item": { "text": "你好" } }
]
}
```
一条消息的 `item_list` 可能含多个 item(图文混排等场景),处理时应遍历而不是只取 `[0]`。
### 2.2 出站
```json
{
"msg": {
"to_user_id": "...@im.wechat",
"client_id": "uuid",
"message_type": 2,
"message_state": 2,
"context_token": "...",
"item_list": [
{ "type": 1, "text_item": { "text": "回复内容" } }
]
}
}
```
注意:
- **微信侧不渲染 Markdown**。如果你的 AI 后端输出 Markdown,发送前应转纯文本(OpenAkita 的做法),或至少把 `**`、`` ` ``、标题符号等剥掉
- 超长文本考虑分片发送(多条 `sendmessage`)。Java SDK 提供 `sendTextStream(...)` 做分片——注意这只是「把长文本切成多条消息」,不是 LLM 流式输出
- 每条消息带唯一 `client_id` 防重
## 3. 媒体传输总流程
微信侧所有媒体文件不直接走 iLink API 的 JSON body,而是走独立 CDN:
```
CDN 域名: https://novac2c.cdn.weixin.qq.com/c2c
加密方式: AES-128-ECB
```
### 3.1 出站(Bot → 用户)完整流程
```
1. 生成随机 AES-128 key(16 字节)
2. plaintext = 原始文件字节
3. encrypted = AES-128-ECB-Encrypt(plaintext, key)
4. POST /ilink/bot/getuploadurl → 拿到 CDN 预签名上传 URL
5. PUT encrypted 到 CDN URL
6. POST /ilink/bot/sendmessage,item 中携带:
- CDN 引用参数(上传后返回/约定的 key)
- aes_key(base64 编码的 16 字节 key),供接收方解密
```
### 3.2 入站(用户 → Bot)完整流程
```
1. getupdates 收到媒体类 item(含 CDN 引用与密钥信息)
2. 从 CDN 下载 encrypted 字节
3. plaintext = AES-128-ECB-Decrypt(encrypted, key)
4. 交给业务层(存盘 / 送多模态模型 / ASR 等)
```
### 3.3 关于解密密钥的来源 ⚠️
- **出站**:key 由发送方(你)随机生成,随消息的 `aes_key` 字段(base64)带给接收方——这部分明确。
- **入站**:OpenAkita 的实现笔记记载「加密密钥从 Token 中提取(base64 解码后取后 16 字节)」。即入站媒体解密密钥与 `bot_token` 派生相关。⚠️ 此细节来源单一,实现时若解密失败,请对照 `@tencent-weixin/openclaw-weixin` 官方包 `src/cdn/` 目录的源码确认 key 的实际派生方式。
### 3.4 AES-128-ECB 实现要点
- ECB 模式无 IV,块大小 16 字节,需要 PKCS#7 padding(具体 padding 方案以官方包源码为准)
- 各语言实现:
- Node.js:`crypto.createCipheriv('aes-128-ecb', key, null)`
- Python:`Crypto.Cipher.AES.new(key, AES.MODE_ECB)`(pycryptodome)
- Java:`Cipher.getInstance("AES/ECB/PKCS5Padding")`
## 4. 语音消息的特殊性
- 入站语音是 **silk 编码**(微信语音专用格式)
- 好消息:`voice_item.text` 直接附带微信侧的 **ASR 转写文字**——如果你的场景只需要文本,直接用转写结果,不必自己解码 silk
- 如果需要原始音频做二次处理(自己的 ASR、声纹等),需 silk 解码。社区方案:
- `pilk`(Python silk 解码绑定)
- 或 silk → wav → ffmpeg 转任意格式
- 出站语音:⚠️ 开放状态存疑(见第 1 节)
## 5. 图片消息
- 入站:CDN 下载 + AES 解密得到原图(jpg/png 等),可直接送多模态模型
- 出站:按 3.1 流程加密上传
- OpenAkita 的实践经验:图片入站后由消息网关自动下载预处理;文件和视频**不自动下载**,由 Agent 按需主动拉取(节省带宽和存储)——这个策略值得借鉴
## 6. 文件与视频
- item type 4(文件)、5(视频),链路同为 CDN + AES
- 建议默认不自动下载大文件,在消息里记录元信息(文件名、大小、CDN 引用),业务层需要时再拉
- 注意给用户反馈:大文件处理慢时先发一条文本「收到文件 xxx,处理中」
## 7. 消息规范化建议(Bridge 内部模型)
不管后端是什么 AI 项目,建议在 Bridge 层把 iLink 消息规范化成统一内部模型,例如:
```json
{
"channel": "wechat-ilink",
"msg_id": "<client_id 或服务端 id>",
"direction": "inbound",
"peer_id": "o9cq809L...@im.wechat",
"context_token": "CK3E...",
"received_at": "2026-08-19T10:00:00Z",
"parts": [
{ "kind": "text", "text": "看这个图" },
{ "kind": "image", "cdn_ref": "...", "local_path": "/tmp/xxx.jpg", "mime": "image/jpeg" },
{ "kind": "voice", "transcript": "语音转写文字", "duration_ms": 3200 }
]
}
```
好处:
- AI 后端不感知 iLink 协议细节,以后加别的 IM 通道(Telegram/飞书等)复用同一模型
- 多 item(图文混排)天然支持
- 媒体下载策略(立即/惰性)在 Bridge 层统一控制
## 8. 常见坑
1. **只取 `item_list[0]`** → 图文混排丢内容。遍历整个 list。
2. **Markdown 直接发** → 用户看到一堆 `*` 和 `#`。发送前转纯文本。
3. **长文本一条发** → 可能被截断或体验差。按字数/段落分片,带不同 `client_id`。
4. **入站媒体密钥搞错** → 解密出一堆乱码还以为是 bug。先确认 key 派生方式(见 3.3)。
5. **语音自己跑 ASR** → 浪费算力,`voice_item.text` 已有微信转写。
6. **大文件自动下载** → 用户发个 100MB 视频你的磁盘/带宽就炸了。惰性下载 + 大小上限。
## 参考资料
- [微信 Bot API 技术解析 — 3.6 媒体文件 AES-128-ECB](https://github.com/hao-ji-xing/openclaw-weixin/blob/main/weixin-bot-api.md)
- [OpenAkita IM 通道指南 — 微信媒体处理与收发矩阵](https://github.com/openakita/openakita/blob/main/docs/im-channels.md)
- [openclaw-weixin-sdk (Java) — MediaUploadService / CdnUploader / CdnMediaDownloader](https://github.com/openclaw-java/openclaw-weixin-sdk)
- 腾讯官方协议包 `src/cdn/` 目录(字段细节的最终依据):`npm pack @tencent-weixin/openclaw-weixin` 下载查看