weixin_clawbot

pub.dev source License

一个 单包双 SDK(Dart + Flutter)package,通过微信扫码绑定 ClawBot(基于 OpenClaw iLink Bot API),并在应用中接收/发送微信消息。

  • Dart 服务器 / CLI:直接依赖,无需 Flutter SDK —— 适合后端自动收发消息、下载入站图片。
  • Flutter 移动端 / 桌面端 / Web:同一依赖;开箱即用的扫码绑定 Widget 见 example/lib/qr_login_widget.dart,可整文件复制到你的应用。

源码仓库:https://github.com/ubuntu2204/weixin_clawbot 协议实现参考腾讯官方插件 Tencent/openclaw-weixin(详见文末"参考与致谢")。

灵感来源:@claw-lab/wxclawbot-cli 和 openclaw-weixin-cli。


功能

功能 描述
🔗 微信扫码绑定 获取 QR 码 → 微信扫描 → 自动保存凭证
📥 接收消息 HTTP 长轮询 (getupdates),消息以 Stream<WeixinMessage> 形式推送
📤 发送消息 发送文本消息,自动携带 context_token
💾 凭证持久化 可插拔 AccountStorage:原生为 ~/.weixin_clawbot/accounts.json 文件,Web 为 localStorage,可注入自定义后端
🖼 接收图片/媒体 从微信 CDN 下载并 AES-128-ECB 解密入站图片(与官方 openclaw-weixin 一致)
🧩 开箱即用 Widget QrLoginWidget / showQrLoginDialog(见 example/lib/qr_login_widget.dart,可复制复用)

快速开始

1. 添加依赖

dependencies:
  weixin_clawbot: ^0.3.0

2. 前置要求

使用此 package 前,需要先在 OpenClaw 中安装微信插件并完成一次命令行登录,以确认账号可用:

npx -y @tencent-weixin/openclaw-weixin-cli@latest install

插件会在 https://ilinkai.weixin.qq.com 上为你的机器人注册一个账号,本 package 通过该地址的 REST API 工作。

3. 扫码绑定

import 'package:weixin_clawbot/weixin_clawbot.dart';

final clawbot = WeixinClawbot();

// 纯 Dart / 任意平台:监听事件流驱动绑定流程
await for (final event in clawbot.startQrLogin()) {
  if (event is QrReadyEvent) {
    print('请用微信扫描:${event.qrContent}'); // 交给任意二维码渲染器展示
  }
  if (event is QrConfirmedEvent) {
    // account.defaultTo 即绑定时扫码的微信用户 ID,可直接作为 toUserId 使用
    clawbot.connect(event.account).listen((msg) {
      print('收到消息:${msg.textContent}');
    });
    break;
  }
}

Flutter 应用可以直接复制 example 的登录对话框 / Widget(内置配对数字输入框):

// import 'qr_login_widget.dart'; // 复制 example/lib/qr_login_widget.dart 后
final account = await showQrLoginDialog(context: context, clawbot: clawbot);
if (account != null) {
  clawbot.connect(account).listen((msg) => print(msg.textContent));
}

4. 接收消息

// 从持久化存储中恢复已绑定账号(应用重启后自动重连)
final account = await clawbot.loadAccount();
if (account != null) {
  clawbot.connect(account).listen((WeixinMessage msg) {
    if (msg.isFromUser) {
      print('[${msg.fromUserId}]: ${msg.textContent}');
    }
  });
}

5. 接收图片

图片消息会完整解析 image_item(CDN 下载参数 + AES 密钥)。下载并解密一张图片:

clawbot.connect(account).listen((msg) {
  final image = msg.imageItem;
  if (image != null) {
    final bytes = await clawbot.downloadImage(image); // 已解密的原始图片字节
    // 例如:Image.memory(bytes)
  }
});

实现与官方 openclaw-weixin 插件一致:优先使用 media.full_url,否则请求 {cdn}/download?encrypted_query_param=...(默认 CDN 为 https://novac2c.cdn.weixin.qq.com/c2c),再按 AES-128-ECB + PKCS7 解密。 image_item.aeskey(hex)与 media.aes_key(base64)两种密钥形态均支持。 Flutter Web 上 CDN 请求走 <proxyBaseUrl>/cdn 前缀,配套代理服务器已支持。

6. 发送消息

// 发给绑定时确定的微信用户(account.defaultTo)
final result = await clawbot.sendText(
  text: '你好,来自 Flutter!',
  toUserId: account.defaultTo,  // 扫码绑定时自动写入
);

// 也可以在收到对方消息后,直接回复其 fromUserId
final result = await clawbot.sendText(
  text: '已收到你的消息',
  toUserId: incomingMsg.fromUserId,
);

if (!result.ok) {
  print('发送失败:${result.error}');
}

提示:context_token 由轮询器在收到消息时自动缓存,发送时无需手动传递。 若尚未收到过对方消息(冷启动主动发送),需确保 account.defaultTo 不为空,否则会返回错误。


纯 Dart / 服务端使用

0.3.0 起核心包不再依赖 Flutter,Dart 服务器可直接调用(pub.dev 已声明 Dart 支持):

import 'package:weixin_clawbot/weixin_clawbot.dart';

Future<void> main() async {
  final clawbot = WeixinClawbot();
  ClawBotAccount? account =
      await clawbot.loadAccount(); // ~/.weixin_clawbot/accounts.json
  if (account == null) {
    // 首次使用:打印 QR 内容,用任意二维码渲染(终端/网页)完成扫码
    await for (final event in clawbot.startQrLogin()) {
      if (event is QrReadyEvent) print('QR: ${event.qrContent}');
      if (event is QrConfirmedEvent) break;
    }
    account = await clawbot.loadAccount();
  }

  clawbot.connect(account!).listen((msg) async {
    if (!msg.isFromUser) return;
    // 收到图片:下载并解密
    final image = msg.imageItem;
    if (image != null && image.isDownloadable) {
      final bytes = await clawbot.downloadImage(image);
      // bytes = 已解密原始图片字节
    }
    // 回复文本
    await clawbot.sendText(text: '已收到', toUserId: msg.fromUserId);
  });
}

服务器场景建议为 AccountStore 注入自己的存储后端(如数据库),而不是默认文件:

class DbAccountStorage implements AccountStorage {
  @override
  Future<List<String>> read() async => /* 从 DB 读取 JSON 字符串列表 */;
  @override
  Future<void> write(List<String> entries) async => /* 写回 DB */;
}

final clawbot = WeixinClawbot(store: AccountStore(storage: DbAccountStorage()));

Flutter Web(CORS 代理)

浏览器不允许直接访问跨域 API,需通过本地代理服务器转发:

# 在 example 目录下启动代理
dart run bin/proxy_server.dart

然后在构建/调试时传入代理地址:

flutter run -d chrome --dart-define=PROXY_URL=http://localhost:3001

代码中创建 WeixinClawbot 时设置 proxyBaseUrl:

final clawbot = WeixinClawbot(
  proxyBaseUrl: kIsWeb ? 'http://localhost:3001' : null,
);

API 参考

WeixinClawbot

主门面类,管理账号与连接。

方法 说明
loadAccount() 返回第一个持久化账号,无则返回 null
loadAllAccounts() 返回所有持久化账号
startQrLogin() 返回 Stream<QrLoginEvent>,驱动 QR 扫码绑定流程
connect(account) 启动长轮询,返回 Stream<WeixinMessage>
sendText({text, toUserId?, accountId?}) 向指定用户发送文本消息
disconnect(accountId) 停止指定账号的长轮询
logout({accountId?}) 删除持久化账号并停止轮询(null 则清除全部)
dispose() 停止所有轮询并释放 HTTP 连接池

ClawBotAccount

字段 说明
id / botId 机器人在 iLink 平台的用户 ID
token Bearer 鉴权令牌
baseUrl 服务器分配的 API 地址(可能与默认地址不同)
defaultTo 绑定时扫码的微信用户 ID(扫码登录后自动写入,可直接用作 toUserId)
contextToken 最近一条入站消息携带的 context token,用于主动推送通知

QrLoginEvent 子类型

类型 说明
QrReadyEvent(qrContent) QR 码内容就绪,传给 QrImageView 显示
QrScannedEvent 用户已在微信扫码,等待手机端确认
QrVerifyCodeRequiredEvent(isRetry) 手机显示配对数字,需调用 submitVerifyCode 提交
QrVerifyCodeBlockedEvent 配对数字错误次数过多,流程终止
QrAlreadyBoundEvent 该 ClawBot 已绑定(本机已存其凭据),无需重复扫码
QrConfirmedEvent(account) 登录成功,账号已自动持久化
QrExpiredEvent QR 码超时过期,需重新获取
QrErrorEvent(error, stackTrace) 网络或服务端错误

配对数字(need_verifycode):部分账号扫码后微信会在手机上显示一组数字, 需要把它输入回应用(官方 CLI 是终端输入,Flutter 端由 QrLoginWidget 内置输入框处理):

final session = await clawbot.startQrLoginSession();
session.events.listen((event) {
  if (event is QrVerifyCodeRequiredEvent) {
    // 从输入框拿到数字后提交(QrLoginWidget 已内置此交互)
    session.submitVerifyCode(userInput);
  }
  if (event is QrConfirmedEvent) {
    clawbot.connect(event.account);
  }
});

WeixinMessage

属性 说明
fromUserId 发送方 iLink 用户 ID
toUserId 接收方 iLink 用户 ID
textContent 第一条文本/语音转文字内容(便捷 getter)
isFromUser true 表示来自真实用户(messageType == 1)
contextToken 回复所需的 token,轮询器自动缓存,无需手动管理
createTimeMs 消息创建时间戳(毫秒级 Unix 时间)
items 消息体列表,支持文本、图片、语音、文件、视频

API 速率限制

  • 每个机器人账号约 7 条 / 5 分钟,服务端限制,所有客户端共享。
  • 错误码 -2:触发频率限制,等待 5–10 秒后重试。
  • 错误码 -14:会话已过期,需重新扫码登录(showQrLoginDialog)。

常见问题

Q:绑定成功后页面仍显示"未绑定"?
startQrLogin 会在 QrConfirmedEvent 到达监听器之后异步保存账号, 因此 onLoggedIn 触发时账号状态已正确更新。如果遇到此问题,请确认使用的是最新版本。

Q:发送消息返回"No recipient"?
toUserId 必须显式提供,或通过 account.defaultTo(绑定时自动写入)获取。 若 defaultTo 为空,说明服务端未在登录响应中返回 ilink_user_id, 可先让对方发一条消息,轮询器会自动从入站消息中提取并缓存收件人 ID。

Q:Flutter Web 下请求失败(CORS 错误)?
需要启动本地代理服务器(见上方"Flutter Web"章节)。


工作原理

Flutter App
    │
    ├─ GET /ilink/bot/get_bot_qrcode     → 获取 QR 码内容(bot_type=3)
    ├─ GET /ilink/bot/get_qrcode_status  → 轮询扫码状态(wait / scaned / confirmed)
    │       ↑ 微信用户扫码并在手机端确认
    ├─ POST /ilink/bot/getupdates        → 长轮询接收消息(服务端持有连接 ~35s)
    └─ POST /ilink/bot/sendmessage       → 发送消息(附带 context_token 可触发通知)

Base URL: https://ilinkai.weixin.qq.com  (登录后可能切换到账号专属域名)
Auth:     Authorization: Bearer <bot_token>

参考与致谢

本 package 的协议实现大量参考了腾讯官方的 OpenClaw 微信通道插件:

  • Tencent/openclaw-weixin(npm: @tencent-weixin/openclaw-weixin)——官方 OpenClaw 微信 channel 插件。本项目遵循其消息收发协议(getupdates 长轮询、sendmessage、context_token)、CDN 媒体下载参数(encrypt_query_param / full_url)与 AES-128-ECB + PKCS7 媒体加解密逻辑,包括 image_item.aeskey(hex)与 media.aes_key(base64)两种密钥形态的处理。
  • @claw-lab/wxclawbot-cli 与 openclaw-weixin-cli——第三方 CLI 实现,协议细节参考。

感谢以上开源项目。

许可证

MIT

Libraries

weixin_clawbot
WeChat ClawBot package (pure Dart).