Files
weixin_ClawBot_Bridge/docs/03-messages-and-media.md

161 lines
6.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 下载查看