Files
qihuaiflow/docs/wechat-ilink-refactor.md
qihu-bot 894eeff65a
CI / gate (push) Has been cancelled
docs: 微信Bot iLink协议重构方案 v1.0
分析微信官方iLink(ClawBot)协议完整能力,对比平台现状,识别5项差距。核心:媒体内联发送(CDN AES-128-ECB加密)替代文本链接降级。分5阶段实施(P0-P2)。
2026-08-15 17:14:07 +08:00

13 KiB
Raw Permalink Blame History

微信 Bot iLink 协议重构方案

版本: v1.0 | 日期: 2026-08-15 | 作者: 七虎AI 技术团队 背景: 参考微信官方 iLink (ClawBot) 协议完整解析,对平台现有个人微信 Bot 实现做差距分析并重构 关联: im_bot_user.py(前台用户级)、admin/im_bot_admin.py(后台平台级)、im_llm.py(EngineCore 薄壳)


1.1 定位

iLink(智联)是腾讯微信 2026 年官方开放的个人微信机器人通信协议,对外产品名 ClawBot。这是唯一合规的个人微信 Bot 通道,有《微信 ClawBot 功能使用条款》法律背书。

维度 逆向 iPad/PC Hook(旧灰产) iLink Bot API(官方合规)
合规性 违反微信用户协议 官方开放,签署使用条款
封号风险 极高,大号易永久封禁 正常使用无封号风险
协议稳定性 微信升级即失效 服务端 API,版本可控
登录方式 模拟设备/劫持会话 微信扫码授权,临时 token
通信方式 模拟心跳,触发风控 长轮询,无需公网 IP

1.2 核心架构(三层)

上层(逻辑): 业务代码 —— 消息收发、AI 应答、工具调用
中层(文件): CDN 媒体加解密 —— AES-128-ECB + PKCS7
底层(通道): iLink 长轮询 —— 消息通道(ilinkai.weixin.qq.com)

两个核心域名:

域名 用途
https://ilinkai.weixin.qq.com 所有 API 请求
https://novac2c.cdn.weixin.qq.com/c2c 图片/语音/视频媒体文件

1.3 七个 API 端点

端点 方法 功能 关键参数
/ilink/bot/get_bot_qrcode GET 获取登录二维码 ?bot_type=3
/ilink/bot/get_qrcode_status GET 轮询扫码状态 ?qrcode=xxx
/ilink/bot/getupdates POST 长轮询收消息(35s hold) { get_updates_buf }
/ilink/bot/sendmessage POST 发送消息 { msg: { context_token, item_list } }
/ilink/bot/getuploadurl POST 获取 CDN 上传地址 { filekey, md5, len }
/ilink/bot/getconfig POST 获取配置 { base_info }
/ilink/bot/sendtyping POST 发送"正在输入"状态 { to_user_id, typing_ticket }

1.4 鉴权机制

Authorization: Bearer {bot_token}       # 扫码登录获取
AuthorizationType: ilink_bot_token       # 固定值
X-WECHAT-UIN: {随机uint32 → base64}      # 每次请求换新,防重放攻击

1.5 消息机制

  • 长轮询:get_updates_buf 是游标/位置书签,每次请求必须用返回的新值替换旧值,否则重复收消息死循环
  • context_token:每条消息的"身份证",回复必须原样回带,否则不关联到正确对话窗口
  • 不能主动推送:必须由用户先发消息触发
  • 消息类型:type 1文本 / 2图片 / 3语音 / 4文件 / 5视频
  • 语音自带 ASR:VoiceItem 含 text 字段,微信已做好语音转文字

二、现状分析

2.1 已实现能力(im_bot_user.py)

能力 实现 状态
扫码登录 get_bot_qrcode + get_qrcode_status 轮询 ✅ 完整
长轮询收消息 getupdates + get_updates_buf 游标推进 ✅ 完整
发消息 sendmessage + context_token 回带 ✅ 完整
鉴权 ilink_bot_token + X-WECHAT-UIN 随机防重放 ✅ 完整
文本分段 WECHAT_TEXT_MAX_LEN=1500 超长分段 ✅ 完整
会话过期检测 ret=-14/-2 区分过期/参数错误 ✅ 完整
用户级隔离 前台用户各自绑定,im_bot_wechat_account 按 user_id 隔离 ✅ 完整
token 持久化 bot_token/ilink_bot_id 存 PG ✅ 完整
对话历史 _log_chat + 镜像到 AI 聊天窗 ✅ 完整
异常日志 接收器异常写 /tmp/wechat_receiver_err.log ✅ 完整
双 Claw 单引擎 im_llm.py 薄壳复用 EngineCore ✅ 完整

2.2 差距分析(核心问题)

# 能力 当前状态 影响 优先级
1 媒体内联发送 ❌ 降级为纯文本链接(🖼️ 图片已生成,点击查看:URL) 图片/视频不能内联显示,用户体验差 🔴 P0
2 sendtyping ❌ 未实现 AI 回复等待期无"正在输入"提示 🟡 P1
3 语音消息 ❌ 未提取 VoiceItem ASR text 语音消息无法应答 🟡 P1
4 Session Guard 增强 ⚠️ -14 仅标记 expired,无主动通知 会话过期用户无感知,需手动发现重扫 🟡 P1
5 CDN 下载解密 ❌ 未实现(收图片/语音/文件无法解密) 无法处理用户发来的图片/文件 🟢 P2

2.3 核心痛点详述:媒体内联发送

当前实现(_wechat_send_media):

def _wechat_send_media(...):
    # iLink 对 type=2/5 媒体 item 返回 ret=-2 invalid arguments
    # 降级为纯文本链接
    caption = "🖼️ 图片已生成,点击查看:" + relay_url
    return _wechat_send_message(...)  # 发文本

根本原因:iLink 媒体消息不是直接传 URL,而是需要走 CDN 上传加密链路:

  1. 下载 AGNES 生成的图片字节
  2. 用 getuploadurl 获取微信 CDN 上传地址
  3. AES-128-ECB 加密图片字节
  4. 上传到 novac2c.cdn.weixin.qq.com
  5. 发消息时带 download_param + aes_key(type=2 图片 / type=5 视频)

当前代码缺少整个 CDN 加密链路,所以只能降级为文本链接。


三、重构方案

3.1 总体设计

用户微信 ──(文本/图片/语音)──> iLink getupdates
                                    │
                                    ▼
                        ┌── im_bot_user.py 接收循环 ──┐
                        │  提取 text / VoiceItem ASR  │
                        │  (P1) 语音转文字            │
                        └──────────┬─────────────────┘
                                   ▼
                        EngineCore._send_message_core
                        (双 Claw 单引擎,复用 LLM/MCP)
                                   │
                    ┌──────────────┼──────────────┐
                    ▼                             ▼
              文本回复                        媒体回复(__media__)
                    │                             │
                    │              ┌──────────────┴──────────────┐
                    │              │  (P0) 媒体内联发送链路      │
                    │              │  1. 下载 AGNES 图片字节     │
                    │              │  2. getuploadurl 拿地址     │
                    │              │  3. AES-128-ECB 加密       │
                    │              │  4. 上传微信 CDN           │
                    │              │  5. sendmessage type=2/5   │
                    │              └──────────────┬──────────────┘
                    ▼                             ▼
              sendmessage (文本)          sendmessage (媒体 item)
              + (P1) sendtyping 前置

3.2 分阶段实施

Phase 1(P0):媒体内联发送 —— 核心

目标:图片/视频真正内联显示在微信,替代当前文本链接降级。

新增模块 app/api/im_ilink_media.py:

"""
iLink CDN 媒体加解密 —— 参考微信官方 iLink 协议
核心:AES-128-ECB + PKCS7 加密上传,下载解密
"""
import hashlib, os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

def _aes_ecb_encrypt(plaintext: bytes, key: bytes) -> bytes:
    """AES-128-ECB + PKCS7 加密"""
    padder = padding.PKCS7(128).padder()
    padded = padder.update(plaintext) + padder.finalize()
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    enc = cipher.encryptor()
    return enc.update(padded) + enc.finalize()

def _aes_ecb_decrypt(ciphertext: bytes, key: bytes) -> bytes:
    """AES-128-ECB 解密"""
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    dec = cipher.decryptor()
    padded = dec.update(ciphertext) + dec.finalize()
    unpadder = padding.PKCS7(128).unpadder()
    return unpadder.update(padded) + unpadder.finalize()

def upload_media_to_wechat_cdn(bot_token: str, baseurl: str, media_bytes: bytes, filekey: str) -> dict:
    """六步上传流程:
    1. 计算 MD5
    2. 生成随机 AES-128 key (16字节)
    3. POST /ilink/bot/getuploadurl → upload_param
    4. AES-128-ECB 加密文件
    5. POST novac2c.cdn.weixin.qq.com/c2c/upload
    6. 返回 download_param + aes_key
    """
    md5 = hashlib.md5(media_bytes).hexdigest()
    aes_key = os.urandom(16)
    # ... getuploadurl 调用
    # ... 加密 + 上传
    return {"download_param": "...", "aes_key": aes_key.hex()}

修改 _wechat_send_media:

def _wechat_send_media(baseurl, bot_token, to_user_id, context_token, media_type, url):
    # 1. 下载 AGNES 图片字节(走 HK 代理,trust_env=False)
    media_bytes = _download_media(url)
    # 2. 上传微信 CDN + AES 加密
    cdn = upload_media_to_wechat_cdn(bot_token, baseurl, media_bytes, filekey)
    # 3. 发媒体消息(type=2 图片 / type=5 视频)
    item = {
        "type": 2 if media_type == "image" else 5,
        "image_item": {  # 或 video_item
            "media": cdn["download_param"],
            "aes_key": cdn["aes_key"],
        }
    }
    return _wechat_post_msg(baseurl, bot_token, to_user_id, context_token, [item])

依赖:cryptography 库(需加入 requirements.txt)

Phase 2(P1):sendtyping 正在输入

def _wechat_send_typing(baseurl, bot_token, to_user_id, typing_ticket):
    """发消息前先发'正在输入'状态,改善等待体验"""
    return _post(baseurl + "/ilink/bot/sendtyping",
                 {"to_user_id": to_user_id, "typing_ticket": typing_ticket})

# 接收循环中,调用 _qihua_reply 前:
_wechat_send_typing(baseurl, bot_token, sender, typing_ticket)
  • typing_ticket 从 getconfig 获取,按用户缓存 24h

Phase 3(P1):语音消息 ASR

def _wechat_extract_text(msg):
    """扩展:支持语音消息自带 ASR 结果"""
    for it in (msg.get("item_list") or []):
        if it.get("type") == 1:  # 文本
            ...
        elif it.get("type") == 3:  # 语音
            voice = it.get("voice_item", {})
            if voice.get("text"):  # 微信已做好语音转文字
                parts.append(voice["text"])
    ...

Phase 4(P1):Session Guard 增强

def _mark_wechat_expired(account_id):
    """增强:标记 expired 的同时,主动给用户发站内通知/邮件提醒重扫"""
    # 1. 标记 expired + 清 token(现有逻辑)
    # 2. 停掉接收线程
    # 3. 触发站内通知:'您的微信 Bot 会话已过期,请重新扫码绑定'
    # 4. 记录 audit log

Phase 5(P2):接收侧媒体解密

def _download_wechat_media(msg):
    """接收用户发来的图片/语音/文件:
    1. GET novac2c.cdn.weixin.qq.com/c2c/download?encrypted_query_param=xxx
    2. 用消息里的 aes_key 解密
    """

3.3 技术要点

要点 说明
AES 模式 AES-128-ECB(微信指定,非标准 CBC;一次性随机 key + HTTPS 保证安全)
密钥长度 16 字节(128-bit)
填充 PKCS7
密文大小 ceil((plaintext_size+1)/16)*16
媒体域名 `novac2c.cdn.weixin.qq.com/c2c/upload
错误码 0成功 / -14会话过期 / -2参数错误

3.4 风险与规避

风险 规避
ECB 模式理论不安全 每文件一次性随机 key + HTTPS,风险可控
媒体上传超时 大图/视频走异步,超时重试 + 降级文本链接兜底
cryptography 依赖 需在容器 venv 安装,纳入 requirements.txt
iLink 媒体协议细节不明确 保留文本链接降级 fallback,逐步灰度

四、实施计划

Phase 内容 优先级 预估工作量 依赖
P0 媒体内联发送(CDN AES 加密) 🔴 高 2-3 天 cryptography 库
P1 sendtyping 正在输入 🟡 中 0.5 天 无
P1 语音 ASR 🟡 中 0.5 天 无
P1 Session Guard 增强 🟡 中 1 天 站内通知
P2 接收侧媒体解密 🟢 低 2 天 P0 经验复用

五、验收标准

  • P0:微信里生成的图片/视频内联显示,不再是文本链接
  • P1:AI 回复前微信显示"正在输入"
  • P1:发语音消息能正确应答(ASR 文本进入 LLM)
  • P1:会话过期时用户收到明确重扫通知
  • 双 Claw 单引擎不回归(tests/test_dual_claw.py 绿)
  • pytest + vitest 全绿

六、参考资料

  • 微信官方 iLink (ClawBot) 协议(CSDN: 2401_88147261 / HiWangWenBing / bytenote)
  • OpenClaw 框架 @tencent-weixin/openclaw-weixin 增强:Session Guard / Config Cache / SILK 转码 / CDN 加解密
  • 平台现状代码:app/api/im_bot_user.py、app/api/admin/im_bot_admin.py、app/api/im_llm.py