# 02 — iLink 协议完整参考 > 本文档是对 iLink Bot API 的完整整理,供实现协议层时对照查阅。 > 来源:腾讯官方包 `@tencent-weixin/openclaw-weixin` 的逆向分析、Java SDK 实现、多个第三方适配器实践。字段级细节以实测为准。 ## 1. 基本信息 | 项 | 值 | |---|---| | API 根地址 | `https://ilinkai.weixin.qq.com` | | CDN 地址 | `https://novac2c.cdn.weixin.qq.com/c2c` | | 协议 | HTTPS + JSON | | 收消息模式 | HTTP 长轮询(无 Webhook,无需公网 IP) | | 鉴权 | `Authorization: Bearer ` | ## 2. 端点总表 | Endpoint | Method | 功能 | 需要鉴权 | |---|---|---|---| | `/ilink/bot/get_bot_qrcode` | GET | 获取登录二维码(`?bot_type=3`) | 否 | | `/ilink/bot/get_qrcode_status` | GET | 轮询扫码状态(`?qrcode=xxx`) | 否 | | `/ilink/bot/getupdates` | POST | 长轮询收消息(核心) | 是 | | `/ilink/bot/sendmessage` | POST | 发送消息(文本/图片/文件/视频/语音) | 是 | | `/ilink/bot/getconfig` | POST | 获取配置(typing_ticket 等) | 是 | | `/ilink/bot/sendtyping` | POST | 发送「正在输入」状态 | 是 | | `/ilink/bot/getuploadurl` | POST | 获取 CDN 预签名上传地址 | 是 | ## 3. 通用请求头 登录后所有请求的固定头: ```http Content-Type: application/json AuthorizationType: ilink_bot_token Authorization: Bearer X-WECHAT-UIN: ``` - `X-WECHAT-UIN`:每次请求生成一个随机 uint32 → 转十进制字符串 → base64 编码。每次都不一样,起防重放作用。 - 扫码登录阶段(`get_qrcode_status`)有实现带 `iLink-App-ClientVersion: 1` 头。 ## 4. 登录流程(一次性扫码绑定) ### 4.1 时序 ``` 开发者 iLink 服务器 微信用户 │── GET get_bot_qrcode ──▶│ │ │◀── { qrcode, url } ─────│ │ │ │◀──── 用户扫码 ───────│ │── GET get_qrcode_status ─▶│ (长轮询/定时轮询) │ │◀── { status:"confirmed", │ │ │ bot_token, ... } ──│ │ │ ▼ 持久化 bot_token,后续所有请求 Bearer 鉴权 ``` ### 4.2 获取二维码 ```http GET /ilink/bot/get_bot_qrcode?bot_type=3 ``` 响应: ```json { "qrcode": "xxx", "qrcode_img_content": "https://liteapp.weixin.qq.com/q/..." } ``` - `qrcode`:QR 标识符,轮询状态时用 - `qrcode_img_content`:二维码图片 URL,可在终端渲染(如 `qrcode-terminal`)或浏览器打开给用户扫 - `bot_type=3` 是官方包源码中硬编码的值,含义未公开,照抄即可 ### 4.3 轮询扫码状态 ```http GET /ilink/bot/get_qrcode_status?qrcode= ``` 状态流转:`wait` → `scaned` → `confirmed`(注意官方拼写是 `scaned`)。 确认后响应(关键凭据): ```json { "status": "confirmed", "bot_token": "44b9b8ec@im.bot:060000b7...", "ilink_bot_id": "44b9b8ec@im.bot", "ilink_user_id": "o9cq809L...@im.wechat", "baseurl": "https://ilinkai.weixin.qq.com" } ``` - **`bot_token` 是之后所有 API 调用的鉴权凭据,一次扫码长期有效**(具体有效期官方未公布,社区观察为长期;过期会收到 errcode=-14,需重新扫码) - 建议把 `bot_token`、`ilink_bot_id`、`ilink_user_id`、`baseurl` 全部持久化 - 轮询建议间隔 1s,整体超时给 5~8 分钟 ## 5. 收消息:长轮询 ### 5.1 请求 ```http POST /ilink/bot/getupdates ``` ```json { "get_updates_buf": "<上次返回的游标,首次为空字符串>", "base_info": { "channel_version": "1.0.2" } } ``` ### 5.2 响应 ```json { "ret": 0, "msgs": [ /* WeixinMessage[],见第 7 节 */ ], "get_updates_buf": "Ck1...", "longpolling_timeout_ms": 35000 } ``` ### 5.3 机制要点 - 服务器 **hold 住连接最多约 30~35 秒**(`longpolling_timeout_ms` 会告知),有新消息立即返回,无消息超时返回空 `msgs` - HTTP 客户端超时必须设得比 `longpolling_timeout_ms` 大(建议 +10s 余量) - **`get_updates_buf` 是同步游标**(类似 Telegram getUpdates 的 offset): - 每次请求带上次返回的值 - **收到响应后立刻持久化**,否则重启后会重复收消息 - 返回中没有该字段时保持旧值 - HTTP 超时/网络错误视为「本轮无新消息」,直接重试,不要中断循环 ### 5.4 失败恢复与退避(来自 Java SDK 的实战策略) | 情况 | 处理 | |---|---| | `errcode = -14`(会话过期 / token 失效) | 该账号进入暂停窗口(SDK 默认 1 小时),期间停止轮询和发送;提示用户重新扫码 | | 其他失败 | 2s 后快速重试;连续失败 3 次后进入 30s 退避 | | HTTP 超时 | 视为无新消息,立即进入下一轮 | | 响应带 `longpolling_timeout_ms` | 动态调整下一轮轮询超时 | ## 6. 发消息 ```http POST /ilink/bot/sendmessage ``` ```json { "msg": { "to_user_id": "o9cq809L...@im.wechat", "client_id": "unique-id-123", "message_type": 2, "message_state": 2, "context_token": "CK3E...", "item_list": [ { "type": 1, "text_item": { "text": "你好!我是你的 AI。" } } ] } } ``` 字段说明: | 字段 | 说明 | |---|---| | `to_user_id` | 收件人,取入站消息的 `from_user_id` | | `client_id` | 客户端生成的去重 ID(如 UUID),防止重发 | | `message_type` | `2` = Bot 发出的消息(入站用户消息是 `1`) | | `message_state` | `2` = FINISH 完成态(非流式)。协议还有 NEW(0?) → GENERATING → FINISH 的三段式状态,可用于流式分片发送(Java SDK 的 `sendTextStream` 即分片发送,但注意**那不是 LLM token 流**,而是把长文本切成多条消息) | | `context_token` | **必填**,从要回复的那条入站消息原样取出 | | `item_list` | 消息体,文本见上;媒体 item 构造见 03 文档 | ## 7. 消息结构(WeixinMessage) 入站消息核心字段: ```json { "from_user_id": "o9cq800kum_xxx@im.wechat", "to_user_id": "e06c1ceea05e@im.bot", "message_type": 1, "message_state": 2, "context_token": "AARzJWAFAAABAAAAAAAp...", "item_list": [ { "type": 1, "text_item": { "text": "你好" } } ] } ``` ### 7.1 ID 格式规律 - 用户 ID:`xxx@im.wechat` - Bot ID:`xxx@im.bot` ### 7.2 常量表 | 字段 | 值 | 含义 | |---|---|---| | `message_type` | 1 | 用户消息(入站) | | `message_type` | 2 | Bot 消息(出站) | | `message_state` | 2 | FINISH(完整消息) | | `item.type` | 1 | 文本(`text_item.text`) | | `item.type` | 2 | 图片(CDN 加密存储) | | `item.type` | 3 | 语音(silk 编码,`voice_item.text` 附 ASR 转写文字) | | `item.type` | 4 | 文件附件 | | `item.type` | 5 | 视频 | > 媒体类 item 的详细字段(CDN 引用、aes_key 等)见 [03-messages-and-media.md](03-messages-and-media.md)。 ## 8. context_token:对话关联的核心 整个协议里最关键、最容易踩坑的细节: 1. **每条入站消息都带 `context_token`**,回复时必须原样带上,否则消息无法关联到正确的对话窗口 2. 每条入站消息都应刷新本地保存的 token(Java SDK 的做法:`FileContextTokenStore` 按对话持久化,每条入站消息更新),这样主动发消息时也有最新 token 可用 3. 有资料称:**用户最后一条消息超过 24 小时后,bot 的消息可能送达不到**;在窗口内可用 `context_token` 主动追发消息。做定时推送类功能时必须考虑这个窗口(⚠️ 建议实测确认) ## 9. 输入中状态(typing) ``` POST /ilink/bot/getconfig → 获取 typing_ticket POST /ilink/bot/sendtyping → 发送「正在输入」 ``` 用途:AI 后端处理耗时较长时,先发个 typing 让用户知道 bot 在干活。非必需,第一版可以不做。 ## 10. 错误处理备忘 | 现象 | 含义 | 处理 | |---|---|---| | `errcode = -14` | 会话/token 过期 | 暂停轮询与发送,走重新扫码流程 | | `ret != 0` | 业务错误 | 记录完整响应,按退避策略重试 | | HTTP 层超时 | 长轮询正常超时或网络抖动 | 直接重试 | | 重复收到同一条消息 | `get_updates_buf` 没更新/没持久化 | 检查游标保存逻辑;应用层按消息 ID 去重兜底 | ## 11. 协议层 Checklist(实现自查) - [ ] 扫码登录:`get_bot_qrcode` → 渲染二维码 → 轮询 `get_qrcode_status` 到 `confirmed` - [ ] 持久化:`bot_token` / `ilink_bot_id` / `ilink_user_id` / `baseurl` / `get_updates_buf` / 每对话 `context_token` - [ ] 请求头:`Authorization`、`AuthorizationType`、随机 `X-WECHAT-UIN` - [ ] 长轮询循环:超时 > 服务端 timeout、游标即时保存、异常不中断 - [ ] 回复带 `context_token`、带唯一 `client_id` - [ ] errcode=-14 的暂停与重新登录路径 - [ ] 退避重试策略 ## 参考资料 - [微信 Bot API 技术解析(含裸调 demo.mjs)](https://github.com/hao-ji-xing/openclaw-weixin/blob/main/weixin-bot-api.md) - [WeChat ClawBot iLink 协议解析](https://www.chihao.li/blog/zh/wechat-clawbot-ilink-protocol) - [openclaw-weixin-sdk (Java) — 失败恢复与退避策略一节](https://github.com/openclaw-java/openclaw-weixin-sdk) - [OpenAkita 微信适配器说明](https://github.com/openakita/openakita/blob/main/docs/im-channels.md)