wx-bot Linux 微信一键部署
这个交付包不包含 SDK 源码,面向 Ubuntu 或 Debian x86_64 主机。默认快路径直接拉取已经构建好的 Linux 桌面基础镜像,本机只构建 SDK 薄层和 Portal,不再重复安装 Ubuntu、Xfce、noVNC 与微信。
一条命令安装
curl -fsSL 'https://botdoc.zhxapp.com/downloads/install-linux.sh?v=20260723-search2' | bash
安装器会检查系统、磁盘、Docker Compose 和端口,缺少 Docker 时从 Docker 官方软件源安装,自动生成 Portal 与 VNC 随机密码,然后拉取基础镜像并启动服务。默认安装目录是 ~/wx-bot-linux,已有 .env、微信登录态、SDK 配置和设备身份不会被覆盖。
安装成功后查看 Portal 账号与密码:
cd ~/wx-bot-linux
grep -E '^(PORTAL_USER|PORTAL_PASSWORD)=' .env
本机打开:
http://127.0.0.1:6180/
远程服务器推荐通过 SSH 隧道访问,不需要把 SDK API 或 VNC 直接暴露到公网:
ssh -L 6180:127.0.0.1:6180 <user>@<server-ip>
然后在自己的浏览器打开 http://127.0.0.1:6180/。
主机要求
- Ubuntu 22.04/24.04 或当前 Debian,x86_64/amd64。
- 至少 2 核 CPU、4 GB 内存和 12 GB 可用磁盘;长期运行建议 4 核、6 GB 内存和 20 GB 可用磁盘。
- 能访问文档下载站、Docker Hub、
ghcr.io 和腾讯微信官方下载地址。
- Docker Desktop 的 WSL 环境也可以使用,但生产部署建议独立 Linux 主机。
仅检查环境,不修改主机:
cd ~/wx-bot-linux
./install.sh --check
为什么构建更快
默认 Dockerfile 以版本化 GHCR 镜像为基础:
GHCR 基础镜像: Ubuntu + Xfce + noVNC + Linux 微信
本地薄层: wx-bot SDK .deb + 启动脚本
独立 Portal: 登录认证 + 扫码桌面 + SDK 配置向导
基础镜像更新由 GitHub Actions 单独完成,并固定微信安装包的 SHA-256。客户部署或升级 SDK 时只重建薄层,通常不需要重新下载系统依赖和 200 MB 以上的微信安装包。
如果所在网络不能稳定访问 GHCR,可将镜像同步到自己的仓库,并在 .env 中替换:
WXBOT_BASE_IMAGE=registry.example.com/wx-bot-linux-base:wechat4.1.1.8-ubuntu24.04-amd64-r1
基础镜像拉取失败时,安装器默认自动使用 Dockerfile.full 完整构建,保证安装流程仍可完成。设置 WXBOT_FULL_BUILD_FALLBACK=0 可关闭自动兜底;设置 WXBOT_FORCE_FULL_BUILD=1 可直接跳过 GHCR。
手工下载安装
下载 wx-bot-linux-deploy.zip 及同目录下的 .sha256 文件,校验后解压:
sha256sum -c wx-bot-linux-deploy.zip.sha256
unzip wx-bot-linux-deploy.zip
cd wx-bot-linux-deploy
./install.sh --install
部署包结构:
wx-bot-linux-deploy/
install.sh
Dockerfile
Dockerfile.base
Dockerfile.full
docker-compose.yml
docker-compose.full.yml
.env.example
portal/
packages/wx-bot-client_xxx_amd64.deb
scripts/
网络访问与安全
一键安装默认生成 WXBOT_BIND_HOST=127.0.0.1,只允许本机或 SSH 隧道访问。需要从可信局域网或 WSL 宿主机直接访问时,编辑 .env:
WXBOT_BIND_HOST=0.0.0.0
修改后运行 ./install.sh --upgrade。这只会发布带认证的 Portal;原始 noVNC、VNC 和 SDK API 仍绑定到 127.0.0.1。应使用主机防火墙限制 Portal 来源,并在公网入口前配置 HTTPS。
只有可信的外部 Agent Console 确实需要直连 SDK 时,才单独设置 WXBOT_API_BIND_HOST=0.0.0.0 并将 5080 端口限制到它的来源 IP。日常扫码和观察微信不需要公开 6080 或 5901。不要将 .env、Portal 密码、授权码或其他密钥提交到仓库或发到公开渠道。
日常运维
查看运行状态和日志:
cd ~/wx-bot-linux
docker compose ps
docker compose logs --tail=200 portal wx-bot-linux
下载最新交付包并升级:
curl -fsSL 'https://botdoc.zhxapp.com/downloads/install-linux.sh?v=20260723-search2' | bash -s -- --upgrade
只停止并移除本套容器,保留 .env 和全部命名卷:
cd ~/wx-bot-linux
./install.sh --uninstall
重新运行 ./install.sh --install 会重新连接保留的数据。安装器不会执行 docker compose down -v。
完整构建兜底
GHCR 暂时不可用、需要审计全部构建过程或准备自己的基础镜像时,可以使用完整构建文件:
cd ~/wx-bot-linux
docker compose -f docker-compose.yml -f docker-compose.full.yml build --pull wx-bot-linux
docker compose -f docker-compose.yml -f docker-compose.full.yml up -d
完整构建会重新下载 Ubuntu 依赖和腾讯 Linux 微信,耗时与网络开销明显更高。默认微信包版本为 4.1.1.8 amd64,SHA-256 固定为 c9765e87ee5133bf4bb50d585c1814fafd995e3fb0da62c5ed07259b43dada7b;腾讯更新安装包后应同时更新 URL、校验值和基础镜像版本标签。
SDK 包
交付包默认已经带一个 SDK Linux 安装包,位于 packages/ 目录,文件名类似:
packages/wx-bot-client_0.1.1781404988_amd64.deb
交付包中应只保留一个当前版本的 SDK .deb。也可以不放本地文件,改用私有下载地址:
# 编辑 .env,并把下面占位地址替换成实际的私有下载地址:
WXBOT_DEB_URL=https://example.invalid/wx-bot-client_xxx_amd64.deb
打开部署向导
浏览器会先进入 Portal 登录页。使用 .env 中的 PORTAL_USER 和 PORTAL_PASSWORD 登录。登录状态使用签名后的 HttpOnly Cookie,页面、配置接口和内嵌 VNC WebSocket 都会在 Nginx 层校验。
原始 noVNC 调试入口默认仅限容器宿主机:
http://<host-ip>:6080/vnc.html
SDK API 默认仅限容器宿主机:
http://<host-ip>:5080/status
部署向导
默认入口:
http://<host-ip>:6180/
部署向导只保留两个交付必需流程:
- 右侧内嵌 Linux 微信桌面,用于首次扫码登录。
- 左侧填写 SDK 授权服务、activation code,并选择自动检测到的微信数据目录。
页面默认是“只观察”模式,避免误操作微信桌面。扫码或手工处理微信登录时切换到“允许操作”。
SDK 状态区会持续显示当前阶段:等待配置、等待微信登录、正在启动、已激活并运行、授权未生效或 SDK 离线。同时回显脱敏后的 activation code、API 连通状态、消息采集/发送开关以及群消息入队范围的实际运行状态。
交付镜像不会预置 activation code,config.example.json 中该字段保持为空,部署方需要在 Portal 中自行填写。Docker named volume 会持久化已保存的配置,因此仅重建镜像不会清除旧 activation code;这是已有实例的正常行为,不代表 code 被打进镜像。
首次配置
- 打开
http://<host-ip>:6180/。
- 使用
.env 中配置的 portal 用户名和密码登录。
- 右侧桌面默认是“只观察”模式;需要点击微信时切换到“允许操作”,然后用手机微信扫码登录。
- 微信登录并完成数据初始化后,点击“重新读取”,选择自动检测到的微信账号数据目录。
- 填写授权服务地址和 activation code,确认
self_wxid,设置消息采集/发送开关和群消息入队范围,然后点击“保存并启动 SDK”。
- 等待 SDK 状态变为“已激活并运行”,并确认 API 在线、消息采集/发送状态及群消息范围符合预期。
SDK 后台服务会等待配置完整。首次保存后会自动启动;授权码、账号目录、发送后端等核心配置实际发生变化时,启动脚本会通过 SHA-256 检测变化并自动重启 SDK 子进程。相同内容重复保存不会重启。配置文件路径:
/home/wechat/.config/wx-bot-client/config.json
保存配置只会重启 sdk 子进程,不会重建容器,也不会重启 Linux 微信或清除登录态。镜像会给 SDK 主程序授予受限的 CAP_SYS_PTRACE,用于首次从当前微信进程提取数据库密钥。
最少需要配置:
{
"activation_code": "...",
"auth_base_url": "https://auth.example.com",
"wechat_data_dir_linux": "/home/wechat/Documents/xwechat_files/<account>",
"self_wxid_linux": "<wxid>",
"sender_backend": "auto",
"group_require_at_me": false
}
group_require_at_me=false 让全部群消息入队;设为 true 时只让明确 @ 当前账号的群消息入队。Portal 中点击“全部群消息”或“仅 @我”会立即保存并通过运行时接口热生效,不需要重启 SDK;同时也会同步写入旧字段 ingest_group_mentions_only,便于兼容已有 SDK 配置。
这个选项只影响群聊。私聊和群聊是否采集由 ingest_start_paused 对应的“消息采集”总开关统一控制,SDK 当前没有独立的“仅关闭私聊采集”配置:
- “全部群消息”:消息采集开启时,私聊正常入队,全部群消息也会入队。
- “仅 @我”:消息采集开启时,私聊仍正常入队;其他成员发来的群消息仅在明确
@ 当前账号时入队,当前账号自己发出的群消息仍会保留在队列中。
Linux 微信可能在数据库重建或重新解密后从较小的消息序号重新开始。SDK 会按完整账号目录名隔离采集游标,并在已初始化账号发生序号回退时仅恢复最近 5 分钟、最多 20 条受支持消息。首次升级到账号级游标时会以当前消息为基线,不会批量重放历史消息;升级前已经漏掉的旧消息不会自动补发,新收到的私聊和群聊会继续进入全局消息流。
如需排障时手工重启 SDK 服务:
docker compose exec wx-bot-linux supervisorctl restart sdk
SDK 状态与设备换绑身份
portal 的 SDK 状态区会显示配置、微信登录、SDK API、授权、消息采集/发送开关和群消息入队范围的实际状态。常见阶段包括“等待配置”“等待微信登录”“正在启动”“已激活并运行”“授权未生效”和“SDK 离线”。
“设备换绑身份”区域会在 SDK 初始化身份后显示两项值:
device_id
device_public_key_sha256
授权提示已绑定其他设备或需要换绑时,完整复制这两项并提交给有权限处理换绑的管理员。不要删除命名卷、重建身份或公开发送授权码;否则新身份可能与待处理的换绑申请不一致。
使用 SDK GUI 配置向导
通常不需要使用 SDK GUI;推荐使用 6180 部署向导写配置。如果确实想在 SDK GUI 里填授权码和选择目录,先停止后台 SDK:
docker compose exec wx-bot-linux supervisorctl stop sdk
然后在同一个 noVNC 桌面里启动 GUI:
docker compose exec -u wechat wx-bot-linux \
env DISPLAY=:1 HOME=/home/wechat XDG_RUNTIME_DIR=/tmp/runtime-wechat \
wx-bot-client
GUI 配好后,再启动后台服务:
docker compose exec wx-bot-linux supervisorctl start sdk
agent-console 连接方式
如果 agent-console 和这个容器在同一个 compose/network 里:
WXBOT_SDK_URL=http://wx-bot-linux:5080
如果 agent-console 在宿主机或另一个容器里,通过宿主机端口访问:
WXBOT_SDK_URL=http://<host-ip>:5080
数据持久化
compose 默认持久化:
wxbot-linux-home: 微信登录态、微信数据、SDK 配置、SDK 数据和设备身份
wxbot-linux-logs: supervisor 日志
Linux SDK 会将每个微信账号的解密库、图片和密钥缓存隔离到 ~/.local/share/wx-bot-sdk/accounts/<完整账号目录名>/。统一消息队列仍保存在 SDK 数据根目录并保持全局递增;采集游标也存放在这个全局 queue.db 中,但使用 account:<完整账号目录名>:<消息表> 作为隔离键。这样 Agent Console 等上游服务切换账号后可以继续沿用原 cursor,同时不会复用上一个账号的 keys.json。
升级 SDK(保留微信登录态)
升级时不要重新下载并覆盖整个 deploy 目录,也不要删除 Docker 命名卷。先把 packages/ 中的旧 SDK .deb 替换为新文件。
运行中原地升级(推荐)
下面的方式只更新并重启 SDK 子进程,不会重建主容器,也不会重启 Linux 微信、Xfce 或 VNC:
cd wx-bot-linux-deploy
mkdir -p ../sdk-package-backup
mv packages/*.deb ../sdk-package-backup/
cp /path/to/new/wx-bot-client_xxx_amd64.deb packages/
SDK_DEB=$(find packages -maxdepth 1 -name '*.deb' | head -n 1)
SDK_NAME=$(basename "$SDK_DEB")
docker compose cp "$SDK_DEB" "wx-bot-linux:/tmp/$SDK_NAME"
docker compose exec -u root wx-bot-linux dpkg -i "/tmp/$SDK_NAME"
docker compose exec wx-bot-linux supervisorctl restart sdk
docker compose exec wx-bot-linux supervisorctl status wechat sdk
docker compose exec wx-bot-linux dpkg-query -W wx-bot-client
更新后回到 Portal,确认 SDK 状态为“已激活并运行”,并检查消息采集、发送和群消息范围。packages/ 中保留的新 .deb 会在下次主动重建镜像时继续使用。
20260716.145236.d454f0ad 及之后的 SDK 在授权心跳超时后,如果服务端判定旧的一次性租约已重放或失效,会清理旧租约并自动重新登录。无需再通过人工重启 SDK 恢复 session lease missing。
20260717.122350.58dd4ad7 及之后的 SDK 将文本入站与图片解密、群成员同步拆分处理,并对损坏会话表做隔离和数据库快照校验。发送接口入队后会立即唤醒发送线程,避免额外等待一个轮询周期。
20260722-search1 版本增强了 Linux 微信会话定位。SDK 使用完整会话名搜索,并直接抓取微信独立搜索弹窗;只有数据库中的唯一会话头像在连续两帧中稳定匹配时才会点击。点击后还会等待搜索弹窗关闭,并连续两帧确认目标头像位于已选中的会话行。缺少头像、头像被多个会话共用、匹配不稳定或未真正进入目标会话时,发送会停止,不会用 Enter 或盲点第一项兜底。复用短期会话缓存前也会核验当前选中会话,避免人工切换后发错;正常发送每条消息前不会重复截图,因此只通过 VNC 观察不会增加发送延迟。
20260723.074313.5adc93d0 修复了 20260722-search1 对 wmctrl -lGx 窗口类列的解析错误。该错误会让 SDK 已经正确搜索到联系人或群聊后,仍无法识别微信的独立搜索弹窗,并在三次安全重试后停止发送。修复版恢复了搜索弹窗识别,同时保留唯一头像、两帧稳定、点击后会话验收以及禁止盲点兜底的保护逻辑。
维护窗口重建镜像
需要把新 SDK 固化进主镜像时,再在维护窗口执行:
cd wx-bot-linux-deploy
docker compose build wx-bot-linux
docker compose up -d --no-deps wx-bot-linux
docker compose restart portal
wxbot-linux-home 卷会继续挂载到新容器,因此微信登录态、SDK 配置和设备身份都会保留,但主容器和微信进程会短暂重启。最后重启 portal 只是让代理重新识别更新后的 SDK 容器,不会删除登录数据。
新版 .deb 的 postinst 会在每次安装或原地升级后重新授予并验证 CAP_SYS_PTRACE;容器入口也会在每次启动时再次恢复该 capability。
不要在升级时运行 docker compose down -v。 -v 会删除保存微信登录态和设备身份的命名卷,导致微信需要重新扫码,并可能触发设备换绑。
注意
- 这是桌面应用容器,不是纯 headless 服务。微信运行在 Xvfb/Xfce 桌面里,通过 noVNC 查看和登录。
- VNC 是同一个桌面的远程控制入口。只观察不会改变微信焦点;用户手动点击微信时,可能会和 SDK 发送自动化抢焦点。
- 容器需要
SYS_PTRACE 和 seccomp=unconfined,用于 Linux 微信数据库 key 提取。SDK 二进制同时使用文件 capability,确保 wechat 用户运行时能读取当前容器内的微信进程。
- 微信下载地址通过
WECHAT_DEB_URL 配置。默认值来自腾讯 Linux 微信下载入口对应的 x86_64 deb;如果官方地址变化,改 .env 即可。
维护方发布基础镜像
基础镜像不是在每次提交时自动覆盖。维护方在确认微信版本、安装包校验值和镜像版本后,手工运行 GitHub Actions 的 Linux base image workflow。流水线会拒绝覆盖已有版本标签,生成 SBOM 与 provenance,并检查公开基础镜像中不存在 /opt/wx-bot-client 或 wx-bot-client 命令。
首次推送后还必须在 GitHub Package Settings 中将 wx-bot-linux-base 设为 Public,再从没有 GitHub 登录信息的环境验证:
DOCKER_CONFIG="$(mktemp -d)" \
docker pull ghcr.io/zhx8702/wx-bot-linux-base:wechat4.1.1.8-ubuntu24.04-amd64-r1
公开基础镜像包含腾讯 Linux 微信二进制。在正式公开分发前,维护方需要确认对应许可与分发方式;如果不能确认,应只公开桌面运行环境,并让客户在本地从腾讯官方地址安装微信。发布完成后记录镜像 digest,正式客户版本优先把 WXBOT_BASE_IMAGE 固定到 image@sha256:<digest>。
SDK API 接口文档
用途
本文档说明 wx-bot-encry 对外暴露的 SDK 侧 HTTP API,用于外部系统和本地工具接入。
典型调用方:
agent-console
- 本地管理工具
- 内部 webhook worker
- 可信的本地自动化脚本
注意:这是 SDK 侧接口,不是 agent-console 的代理接口。
基础地址
默认本地地址:
http://127.0.0.1:5080
常见默认值:
- host:
127.0.0.1
- port:
5080
- content type:
application/json
注意事项
- 本接口面向可信本地集成设计。
- 当前 SDK API 自身不强制 bearer-token 鉴权。
- 已为本地工具启用 CORS。
- SSE 接口是长连接。
- 队列和事件负载中的大部分时间戳为 Unix 秒。
- 群聊
session_id 通常以 @chatroom 结尾。
健康检查与运行状态
GET /ping
简单存活探测。
响应示例:
pong
GET /status
返回 SDK 运行状态、授权状态、能力列表以及当前解析后的配置摘要。
响应示例:
{
"status": "running",
"auth_active": true,
"capabilities": ["fetch_messages", "dispatch_messages"],
"paused": {
"ingest_paused": false,
"send_paused": false
},
"config": {
"auth_server": "https://auth.example.com",
"api_endpoint": "http://127.0.0.1:5080",
"wechat_data_dir": "C:\\Users\\...\\xwechat_files\\xxx",
"decrypted_dir": "C:\\...\\data\\decrypted",
"self_wxid": "wxid_xxx",
"my_names": ["bot"],
"group_require_at_me": false,
"group_capture_mode": "all_group_messages"
},
"queue": {
"max_inbound_id": 120,
"max_event_id": 8,
"max_stream_id": 188,
"outbound_stats": {
"pending": 3,
"sent": 42
}
}
}
队列字段说明:
max_inbound_id: 旧版入站消息队列的最新游标
max_event_id: 旧版类型事件队列的最新游标
max_stream_id: 统一 /stream 事件流的最新游标
POST /control/pause
暂停或恢复 SDK worker。
请求体:
{
"ingest": true,
"send": false
}
字段说明:
ingest: 可选布尔值,控制消息采集 worker
send: 可选布尔值,控制发送 worker
GET /control/status
返回当前暂停状态。
消息进入
GET /messages
从 SDK 本地队列拉取入站消息。
查询参数:
cursor: 整数,默认 0
limit: 整数,默认 100,最大 500
请求示例:
curl "http://127.0.0.1:5080/messages?cursor=0&limit=100"
响应示例:
{
"messages": [
{
"id": 101,
"msg_svr_id": "1234567890",
"session_id": "123456@chatroom",
"session_name": "测试群",
"sender_wxid": "wxid_xxx",
"sender_name": "张三",
"msg_text": "你好",
"msg_type": "text",
"image_path": null,
"recv_ts": 1776681000,
"mentioned_me": true,
"at_wxids": ["zhx870255124"],
"mention_mode": "metadata",
"is_self_sent": false,
"created_ts": 1776681001
}
],
"cursor": 101,
"count": 1
}
@ 提及相关字段:
mentioned_me: SDK 判断当前账号是否被提及
at_wxids: 从微信 msgsource.atuserlist 中解析出的被 @ 目标
mention_mode: metadata 或空字符串
is_self_sent: 该消息是否由当前登录微信账号发出
当前 SDK 行为:
- 群消息优先使用微信数据库元数据
source -> <atuserlist> 判断 @。
- 如果没有 @ 元数据,则返回
mentioned_me=false。
- 私聊消息始终返回
mentioned_me=false。
- 自己发送的消息也会进入队列,便于审计和回放;消费方应使用
is_self_sent 避免回复循环。
GET /messages/stream
入站消息 SSE 流。
兼容说明:
- 这是旧版兼容接口。
- 新接入建议使用
GET /stream。
/messages/stream 只推送入站消息,不包含成员、授权、运行时等事件。
查询参数:
cursor: 整数,默认 0
heartbeat: 心跳秒数,默认 15,范围 5-60
请求示例:
curl -N "http://127.0.0.1:5080/messages/stream?cursor=0"
消息帧格式:
data: {"id":101,"session_id":"123456@chatroom",...}
心跳格式:
: heartbeat 1776681001
统一事件流
GET /stream
SDK 侧统一 SSE 事件流。
这是新接入的主要订阅接口。它把旧的消息流和事件流合并为一个游标空间、一条 SSE 连接。
当前事件类型包括:
message.received
message.media.ready
group.member.joined
group.member.left
message.delivery.succeeded
message.delivery.failed
auth.revoked
runtime.warning
查询参数:
cursor: 整数,默认 0
heartbeat: 心跳秒数,默认 15,范围 5-60
event_type: 可选,精确事件类型过滤
session_id: 可选,会话过滤
请求示例:
curl -N "http://127.0.0.1:5080/stream?cursor=0"
消息帧格式:
id: 123
event: message.received
data: {"id":123,"event_id":"stream:123","event_type":"message.received",...}
心跳格式:
: heartbeat 1776681001
消费方约定:
- 持久化最后处理成功的整数
id。
- 重连时带上
cursor=<last_id>。
- 将
id 视为 SDK 侧权威回放游标。
/messages 和 /events 仅作为旧版拉取兼容接口保留。
message.media.ready 是图片消息的补充更新事件。图片消息首次进入队列时本地文件可能还未解析完成,稍后解析成功后会再次发出该事件。消费方应把它合并到原始图片消息上,而不是当作新的用户聊天轮次。
示例:
{
"event_type": "message.media.ready",
"message": {
"id": "1234567890",
"type": "image",
"text": "[图片]",
"image_path": "images/abc.png",
"recv_ts": 1777000000
},
"media": {
"type": "image",
"image_path": "images/abc.png",
"ready_ts": 1777000012
}
}
消息发送
POST /send
向本地发送队列写入一条出站消息。
文本消息示例:
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"sender_name": "客服",
"mention_sender": false,
"msg_type": "text",
"text": "你好,这是一条测试消息"
}
图片消息示例:
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"sender_name": "客服",
"mention_sender": false,
"msg_type": "image",
"image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png"
}
成功响应:
{
"queued": true,
"id": 88
}
校验规则:
session_id 必填。
- 文本消息需要
text 或 reply_text。
- 图片消息需要
image_path 或 image_url。
mention_sender 可选,仅对群聊有意义。
- 如果
mention_sender=true,SDK 发送 worker 会在最终文本前拼接 @sender_name。
可选上下文字段:
sender_wxid: 原始发送人 wxid
reply_to_msg_svr_id: 该回复对应的上游消息 id
session_kind: group 或 private
image_url: 跨机器投递图片时使用的远程图片 URL
source_message: 原始入站消息快照对象
delivery: 投递调试和审计元数据
idempotency_key / command_id: 可选稳定出站命令键
这些额外字段会写入 SDK 出站队列,并可通过 GET /queue/messages 再次读取。文本消息中,如果 source_message 带有明确引用信息,例如 is_quote、refermsg 或 quote_text,当前 SDK 会在实际发送到微信时附加轻量引用预览。图片消息中,如果只提供了 image_url,SDK 会先下载到本地缓存目录,再调用微信桌面发送逻辑。
如果顶层或 delivery 内提供了 idempotency_key、command_id 或 outbound_idempotency_key,重复请求会返回既有出站队列行 id,而不会创建新的发送任务。没有显式幂等键的普通消息保持历史追加队列行为。
POST /send/batch
一次请求写入多条出站消息。
请求示例:
{
"messages": [
{
"session_id": "wxid_xxx",
"session_name": "李四",
"sender_name": "客服",
"mention_sender": false,
"msg_type": "text",
"text": "第一条"
},
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"sender_name": "客服",
"mention_sender": true,
"msg_type": "text",
"text": "第二条"
}
]
}
POST /send/envelope
写入一条结构化出站 envelope,适合第三方系统集成。
该接口提供比旧版扁平 /send 更完整的协议结构,但内部仍写入同一张 SDK 出站队列表。对于文本消息,如果 source_message 包含引用元数据,实际发送时可能会渲染轻量引用后缀。
请求示例:
{
"target": {
"session_id": "123456@chatroom",
"session_name": "测试群",
"session_kind": "group"
},
"sender": {
"wxid": "wxid_customer",
"name": "张三"
},
"content": {
"msg_type": "image",
"image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png"
},
"reply": {
"mention_sender": true,
"reply_to_msg_svr_id": "1234567890"
},
"source_message": {
"message_id": "1234567890",
"sender_wxid": "wxid_customer"
},
"delivery": {
"channel": "wechat",
"command_id": "wxbot-reply-12345",
"provider": "third-party-bridge"
},
"metadata": {
"trace_id": "trace-demo"
}
}
校验规则:
target.session_id 必填。
content.msg_type 必须是 text 或 image。
- 文本 envelope 需要
content.text。
- 图片 envelope 需要
content.image_path 或 content.image_url。
target.session_kind 如提供,必须是 group 或 private。
成功响应:
{
"queued": true,
"id": 99,
"protocol": "envelope",
"normalized": {
"session_id": "123456@chatroom",
"session_name": "测试群",
"session_kind": "group",
"sender_name": "张三",
"sender_wxid": "wxid_customer",
"msg_type": "image",
"image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png",
"mention_sender": true,
"reply_to_msg_svr_id": "1234567890"
}
}
POST /send/envelope/batch
一次请求写入多条结构化出站 envelope。
请求体数组键支持 messages 或 envelopes。
请求示例:
{
"messages": [
{
"target": {
"session_id": "123456@chatroom",
"session_name": "测试群",
"session_kind": "group"
},
"sender": {
"wxid": "wxid_customer",
"name": "张三"
},
"content": {
"msg_type": "text",
"text": "第一条结构化消息"
},
"reply": {
"mention_sender": true,
"reply_to_msg_svr_id": "1234567890"
}
}
]
}
成功响应:
{
"results": [
{
"queued": true,
"id": 100,
"protocol": "envelope",
"normalized": {
"session_id": "123456@chatroom",
"session_name": "测试群",
"session_kind": "group",
"sender_name": "张三",
"sender_wxid": "wxid_customer",
"msg_type": "text",
"mention_sender": true,
"reply_to_msg_svr_id": "1234567890"
}
}
],
"count": 1
}
会话与队列元数据
GET /sessions
列出从已解密微信数据库映射出的已知会话。
响应示例:
{
"sessions": [
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"kind": "group"
},
{
"session_id": "wxid_xxx",
"session_name": "李四",
"kind": "private"
}
],
"count": 2
}
GET /queue/stats
返回本地出站队列状态计数。
响应示例:
{
"pending": 3,
"sent": 42,
"failed": 1
}
GET /queue/messages
从 SDK 本地 SQLite 队列库返回出站队列明细。
该接口主要供 agent-console 查看 SDK 本地待发送队列,而不是只展示聚合计数。
查询参数:
status: 可选,空值表示不过滤
limit: 整数,默认 100,范围 1-500
请求示例:
curl "http://127.0.0.1:5080/queue/messages?status=pending&limit=50"
响应示例:
{
"items": [
{
"id": 7,
"session_id": "123456@chatroom",
"session_name": "测试群",
"sender_name": "客服",
"sender_wxid": "wxid_customer",
"mention_sender": 1,
"reply_to_msg_svr_id": "1234567890",
"session_kind": "group",
"reply_text": "你好",
"image_path": "",
"image_url": "",
"msg_type": "text",
"source_message": {
"message_id": "1234567890"
},
"delivery": {
"channel": "wechat",
"command_id": "wxbot-reply-12345"
},
"idempotency_key": "wxbot-reply-12345",
"status": "pending",
"error": "",
"attempt_count": 0,
"claimed_ts": 0,
"lease_until_ts": 0,
"created_ts": 1776681000,
"sent_ts": null
}
],
"count": 1
}
说明:
mention_sender 在本地队列表中以 0/1 保存。
- 排序为
created_ts DESC, id DESC。
- 如果队列库尚未初始化,SDK 返回空列表而不是
404。
GET /debug/trigger-config
返回当前群消息采集调试开关状态。
该接口供 agent-console 确认群消息是否仅限 @ 机器人时进入队列,还是允许所有群消息进入队列。
响应示例:
{
"group_require_at_me": false,
"group_capture_mode": "all_group_messages",
"my_names": ["bot", "机器人"],
"config_path": "C:\\Users\\...\\config.json"
}
语义:
group_require_at_me=true: 其他成员发来的群消息只采集明确 @ 机器人的内容;机器人账号自己发出的群消息仍会采集。
group_require_at_me=false: 采集所有群消息。
该字段不影响私聊;接收总开关开启时,私聊仍会正常采集。
- group_capture_mode: 同一状态的人类可读描述。
POST /debug/trigger-config
更新运行时群消息采集调试开关,并写回 config.json。
请求体:
{
"group_require_at_me": false
}
成功响应:
{
"group_require_at_me": false,
"group_capture_mode": "all_group_messages",
"my_names": ["bot", "机器人"],
"config_path": "C:\\Users\\...\\config.json",
"saved": true
}
校验错误示例:
{
"error": "group_require_at_me required"
}
运维说明:
- 该开关只影响后续群消息扫描。
- 默认推荐值是
true。
- 设置为
false 主要用于排查意外自动回复触发问题。
GET /logs
读取近期 SDK 日志。
查询参数:
响应示例:
{
"lines": [
"[19:40:44] [main] starting HTTP API on 127.0.0.1:5080"
],
"count": 1
}
图片
GET /images/<path>
返回 SDK 图片基础目录下的已解密图片文件。
通常配合入站图片消息中的 image_path 使用。
群成员名册
GET /ext/roster/groups
从已解密的 contact.db 列出权威群会话。
响应示例:
{
"ok": true,
"sessions": [
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"kind": "group",
"count": 0,
"last_ts": null,
"owner": "wxid_owner",
"avatar": {
"big_head_url": "https://wx.qlogo.cn/mmhead/.../0",
"small_head_url": "https://wx.qlogo.cn/mmhead/.../132",
"head_img_md5": "",
"cache_md5": "b83ce6640fbeb97ce5001790546f451e",
"cache_url": "/ext/roster/avatars/2106355209%40chatroom",
"cached": true,
"size": 5463,
"content_type": "image/jpeg",
"update_time": 1753248121
}
}
],
"count": 1
}
GET /ext/roster/groups/<session_id>/members
返回权威群成员名册以及消息历史统计。
响应示例:
{
"ok": true,
"session_id": "123456@chatroom",
"source_kind": "authoritative_group_roster",
"is_membership_roster": true,
"message_scope": "text_only",
"source_note": "候选列表来自已解密 contact.db 的真实群成员名册;发言列表仅表示当前保留消息中的历史发言统计。",
"candidates": [
{
"wxid": "wxid_xxx",
"name": "张三",
"alias": "",
"remark": "",
"nick_name": "张三",
"avatar": {
"big_head_url": "https://wx.qlogo.cn/mmhead/.../0",
"small_head_url": "https://wx.qlogo.cn/mmhead/.../132",
"head_img_md5": "",
"cache_md5": "ce210c0289fc727dcbf116d581373cee",
"cache_url": "/ext/roster/avatars/wxid_xxx",
"cached": true,
"size": 4773,
"content_type": "image/jpeg",
"update_time": 1753242004
},
"msg_count": 18,
"first_ts": 1776000000,
"last_ts": 1776681000,
"has_history": true
}
]
}
GET /ext/roster/avatars/<username>
从已解密的 head_image.db 返回缓存头像图片字节。样本运行数据中 image_buffer 为普通图片字节,因此接口会按探测到的 Content-Type 直接返回,例如 image/jpeg 或 image/png。
当群或成员响应中的 avatar.cached 为 true 时,使用 avatar.cache_url 取头像。
成员事件与 Webhook 订阅
GET /events
拉取 SDK 类型事件。
查询参数:
cursor: 整数,默认 0
limit: 整数,默认 100,最大 500
event_type: 可选
session_id: 可选
当前重要事件类型:
group.member.joined
group.member.left
GET /events/stream
类型事件 SSE 流。
兼容说明:
- 这是旧版兼容接口。
- 新接入建议使用
GET /stream。
/events/stream 不包含入站消息正文。
请求示例:
curl -N "http://127.0.0.1:5080/events/stream?cursor=0"
消息帧格式:
id: 12
event: group.member.joined
data: {"id":12,"event_type":"group.member.joined",...}
GET /event-subscriptions
列出 SDK 侧类型事件 webhook 订阅。
查询参数:
event_type: 可选
session_id: 可选,空字符串表示该事件类型的全局订阅
POST /event-subscriptions
创建或更新一个类型事件 webhook 订阅。
请求示例:
{
"event_type": "group.member.joined",
"session_id": "123456@chatroom",
"target_url": "https://example.com/webhooks/wxbot-member-events",
"enabled": true
}
响应示例:
{
"ok": true,
"subscription": {
"id": 3,
"event_type": "group.member.joined",
"session_id": "123456@chatroom",
"target_url": "https://example.com/webhooks/wxbot-member-events",
"enabled": 1
}
}
DELETE /event-subscriptions/<subscription_id>
按数字 id 删除订阅。
群成员设置
GET /group-members/settings/<session_id>
读取 SDK 侧单群成员事件行为设置。
响应示例:
{
"session_id": "123456@chatroom",
"welcome_enabled": 1,
"welcome_template": "欢迎 {{member_name}} 加入群聊",
"welcome_mention": 0,
"updated_ts": 1776681000
}
POST /group-members/settings/<session_id>
更新群成员加入/离开相关设置。
请求示例:
{
"welcome_enabled": true,
"welcome_template": "欢迎 {{member_name}}",
"welcome_mention": false
}
用途:
Persona 提取支持
POST /ext/persona/messages
直接从已解密微信消息数据库中,采集某个会话内某个成员的人设提取消息样本。
该接口供 agent-console 的 persona 提取或类似工具使用。
请求体:
{
"session_id": "123456@chatroom",
"target_wxid": "wxid_xxx",
"target_name": "张三",
"days_limit": 90,
"max_messages": 2000
}
规则:
days_limit = 0 表示不限制时间范围。
max_messages = 0 表示不限制消息数量。
- 当前实现只采集文本消息。
响应示例:
{
"ok": true,
"session_id": "123456@chatroom",
"target_wxid": "wxid_xxx",
"target_name": "张三",
"source_kind": "wechat_decrypted_message_db",
"message_scope": "text_only",
"full_extract": false,
"days_limit": 90,
"max_messages": 2000,
"count": 128,
"messages": [
{
"sender_wxid": "wxid_xxx",
"sender_name": "张三",
"text": "你好",
"timestamp": "2026-04-20 10:00:00",
"ts": 1776650400
}
],
"first_timestamp": "2026-01-20 10:00:00",
"last_timestamp": "2026-04-20 10:00:00"
}
群报告
GET /ext/reports/subscriptions
列出所有群报告订阅。
POST /ext/reports/subscriptions
创建或更新一个群报告订阅。
请求示例:
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"daily_enabled": true,
"monthly_enabled": false,
"daily_hour": 9,
"monthly_day": 1,
"tz": "Asia/Shanghai"
}
DELETE /ext/reports/subscriptions/<session_id>
删除一个群报告订阅。
GET /ext/reports/preview/<session_id>
预览一份报告,不发送。
查询参数:
report_type: daily 或 monthly
session_name: 可选覆盖值
GET /ext/reports/messages/<session_id>
返回一份日报或月报时间窗口背后的原始聊天记录。
查询参数:
report_type: daily 或 monthly
session_name: 可选覆盖值
date: 可选 YYYY-MM-DD,仅日报使用
year_month: 可选 YYYY-MM,仅月报使用
响应示例:
{
"ok": true,
"session_id": "123456@chatroom",
"session_name": "测试群",
"report_type": "daily",
"period": "2026-04-20",
"start_ts": 1776614400,
"end_ts": 1776700800,
"count": 2,
"messages": [
{
"ts": 1776650400,
"timestamp": "2026-04-20 10:00:00",
"sender_wxid": "wxid_xxx",
"sender_name": "张三",
"msg_type": "text",
"text": "你好"
},
{
"ts": 1776650500,
"timestamp": "2026-04-20 10:01:40",
"sender_wxid": "wxid_yyy",
"sender_name": "李四",
"msg_type": "image",
"text": "[图片]"
}
]
}
说明:
- 日报不传
date 时,默认前一天。
- 月报不传
year_month 时,默认上个月。
- 该接口返回原始记录,不返回
/ext/reports/preview 使用的汇总文本。
POST /ext/reports/send
生成并入队发送一份报告。
请求示例:
{
"session_id": "123456@chatroom",
"session_name": "测试群",
"report_type": "daily"
}
重要行为:
- 报告内容从已解密微信数据库生成。
- 不依赖
agent-console 对话轮次。
- 如果需要原始记录而不是摘要文本,请使用
GET /ext/reports/messages/<session_id>。
只读查询扩展
POST /ext/query/read
对 SDK 本地 SQLite 数据库执行受限只读 SQL 查询。
该接口用于减少内部扩展时反复修改 SDK 代码,同时保留安全边界。
允许的数据库:
限制:
- 只允许
SELECT 或 WITH 查询。
- 不允许分号分隔的多语句。
- 不允许写入或修改 schema。
limit 上限为 500。
请求示例:
{
"database": "message",
"sql": "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
"limit": 20
}
响应示例:
{
"ok": true,
"database": "message",
"db_path": "C:\\...\\message_0.db",
"columns": ["name"],
"rows": [
{"name": "Name2Id"},
{"name": "Msg_xxxxx"}
],
"count": 2,
"limit": 20,
"truncated": false
}
建议:
- 稳定产品功能优先使用专用接口。
/ext/query/read 适合内部调试、数据检查或临时扩展场景。
建议接入顺序
典型外部系统接入流程:
- 调用
GET /status。
- 拉取
GET /sessions 或 GET /ext/roster/groups。
- 订阅
GET /stream。
- 通过
POST /send 或 POST /send/envelope 发送出站消息。
- 通过
/event-subscriptions 管理群 webhook。
- 通过
/ext/reports/* 管理群报告。
旧版兼容:
GET /messages、GET /messages/stream、GET /events 和 GET /events/stream 仍可用于阶段性迁移。
- 新消费方应以
/stream 作为主要入站消息和事件通道。
已知数据语义
- 群成员候选列表来自已解密
contact.db。
- Persona 提取样本来自已解密
message_0.db。
- 报告生成来自已解密
message_0.db。
- 出站队列状态来自 SDK 本地队列库。
兼容提醒
新增 SDK 接口时,应同步更新:
api/server.py
docs/README.md
- 本文档
这样下游调用方才能与实际 SDK 协议保持一致。
统一 Stream API
用途
本文档定义 wx-bot-encry 计划采用的统一 SDK 事件流协议。
它面向上游系统集成,尤其是 agent-console。这样消费方可以先适配稳定协议,同时 SDK 实现逐步对齐该协议。
该事件流用于替代当前拆分的两类流:
GET /messages/stream
GET /events/stream
统一为一个端点:
迁移期间,旧端点仍可继续保留以兼容已有调用方;新集成建议直接使用 GET /stream。
设计目标
- 所有 SDK 入站事件共用一个游标。
- 所有事件类型共用一条 SSE 连接。
- 不同来源事件使用稳定一致的 envelope。
- 断线重连后支持按游标回放。
- 出站发送命令仍保留在 HTTP API 上,例如:
POST /send
POST /send/batch
POST /send/envelope
POST /send/envelope/batch
统一事件流只负责 SDK -> consumer 推送,不替代发送命令接口。
端点
GET /stream
SDK 事件统一 SSE 流。
请求示例:
curl -N "http://127.0.0.1:5080/stream?cursor=0"
查询参数
cursor: 整数,默认 0
表示返回 id > cursor 的事件。
limit: 整数,默认 100,最大 500
如果后续支持有限拉取模式,该参数用于控制返回数量;标准 SSE 消费通常可以省略。
heartbeat: 心跳秒数,默认 15,范围 5-60
event_type: 可选
精确事件类型过滤,例如 message.received。
session_id: 可选
限定只返回某个会话的事件。
SSE 帧格式
普通事件帧:
id: 123
event: message.received
data: {"id":123,"event_id":"stream:123","event_type":"message.received",...}
心跳帧:
: heartbeat 1777000000
消费方应当:
- 持久化最后处理成功的整数
id。
- 使用
cursor=<last_id> 重连。
- 忽略心跳帧。
通用事件 Envelope
/stream 上的每个事件都应遵循相同的顶层结构。
{
"id": 123,
"event_id": "stream:123",
"event_type": "message.received",
"occurred_ts": 1777000000,
"occurred_at": "2026-04-21T21:30:00+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": null,
"member": null,
"operator": null,
"message": null,
"media": null,
"delivery": null,
"auth": null,
"runtime": null,
"raw": {},
"meta": {}
}
通用字段
id: 整数,统一事件流的单调递增游标。
event_id: 字符串,稳定事件标识,适合消费方做幂等处理。
event_type: 字符串,事件类型。
occurred_ts: Unix 秒。
occurred_at: SDK 本地时区下的 RFC3339 时间字符串。
source: 固定为 wxbot-sdk。
session: 会话上下文。
sender: 入站用户消息事件中存在。
member: 成员加入/离开事件中存在。
operator: 能识别操作人时存在。
message: 入站消息事件中存在。
media: 延迟媒体解析完成事件中存在。
delivery: 出站投递结果事件中存在。
auth: 授权状态事件中存在。
runtime: 运行时告警事件中存在。
raw: 来源相关原始负载快照。
meta: 额外调试或兼容元数据。
对象结构
session
{
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
}
sender
{
"id": "wxid_user",
"name": "张三"
}
member
{
"id": "wxid_new_member",
"name": "李四"
}
operator
{
"id": "wxid_operator",
"name": "王五"
}
message
{
"id": "1234567890",
"type": "text",
"text": "你好",
"image_path": "",
"mentioned_me": false,
"at_wxids": [],
"mention_mode": "",
"is_self_sent": false,
"recv_ts": 1777000000
}
语义:
mentioned_me 是 SDK 的最终 @ 判断。
at_wxids 来自微信 msgsource.atuserlist,前提是该元数据存在。
mention_mode="metadata" 表示 SDK 使用了解析后的微信元数据。
- 空
mention_mode 表示没有可用 @ 元数据。
is_self_sent=true 表示该记录由当前登录微信账号发出,并被纳入审计/回放。
delivery
{
"outbound_id": 88,
"command_id": "cmd_001",
"status": "succeeded",
"msg_type": "text",
"reply_to_msg_svr_id": "1234567890",
"attempt_count": 1,
"error": "",
"sent_ts": 1777000010
}
{
"type": "image",
"image_path": "images/abc.png",
"ready_ts": 1777000012
}
投递状态语义:
status="succeeded" 表示 SDK 发送 worker 已完成一个出站项。
status="retry" 表示一次发送尝试失败,但该队列项仍可继续重试。
status="failed" 表示 SDK 认为该出站项已终态失败。
attempt_count 是产生当前投递事件的尝试次数。
auth
{
"status": "revoked",
"reason": "device revoked",
"detail": "session inactive"
}
runtime
{
"level": "warning",
"code": "decrypt.missing_db",
"message": "message_0.db not found",
"detail": ""
}
事件类型
message.received
SDK 从已解密微信数据库采集到一条新的入站消息时产生。
示例:
{
"id": 101,
"event_id": "stream:101",
"event_type": "message.received",
"occurred_ts": 1777000000,
"occurred_at": "2026-04-21T21:30:00+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": {
"id": "wxid_user",
"name": "张三"
},
"member": null,
"operator": null,
"message": {
"id": "1234567890",
"type": "text",
"text": "你好",
"image_path": "",
"mentioned_me": true,
"at_wxids": ["zhx870255124"],
"mention_mode": "metadata",
"is_self_sent": false,
"recv_ts": 1777000000
},
"delivery": null,
"auth": null,
"runtime": null,
"raw": {
"msg_svr_id": "1234567890"
},
"meta": {
"legacy_source": "inbound_queue"
}
}
SDK 首次采集到图片消息时,本地媒体文件可能还没有解析完成。稍后成功解析出图片后,会产生该事件。
消费方应把它视为对原始 message.id 的更新,而不是新的聊天消息。
{
"id": 102,
"event_id": "stream:102",
"event_type": "message.media.ready",
"occurred_ts": 1777000012,
"occurred_at": "2026-04-21T21:30:12+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": {
"id": "wxid_user",
"name": "张三"
},
"member": null,
"operator": null,
"message": {
"id": "1234567890",
"type": "image",
"text": "[图片]",
"image_path": "images/abc.png",
"mentioned_me": true,
"at_wxids": ["zhx870255124"],
"mention_mode": "metadata",
"is_self_sent": false,
"recv_ts": 1777000000
},
"media": {
"type": "image",
"image_path": "images/abc.png",
"ready_ts": 1777000012
},
"delivery": null,
"auth": null,
"runtime": null,
"raw": {
"msg_svr_id": "1234567890",
"inbound_id": 88
},
"meta": {
"legacy_source": "inbound_queue",
"update_kind": "media_ready"
}
}
group.member.joined
SDK 判断有新用户加入群聊时产生。
{
"id": 201,
"event_id": "stream:201",
"event_type": "group.member.joined",
"occurred_ts": 1777000100,
"occurred_at": "2026-04-21T21:31:40+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": null,
"member": {
"id": "wxid_new_member",
"name": "李四"
},
"operator": {
"id": "wxid_inviter",
"name": "王五"
},
"message": null,
"delivery": null,
"auth": null,
"runtime": null,
"raw": {},
"meta": {}
}
group.member.left
SDK 判断有成员离开群聊时产生。
{
"id": 202,
"event_id": "stream:202",
"event_type": "group.member.left",
"occurred_ts": 1777000200,
"occurred_at": "2026-04-21T21:33:20+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": null,
"member": {
"id": "wxid_left_member",
"name": "赵六"
},
"operator": null,
"message": null,
"delivery": null,
"auth": null,
"runtime": null,
"raw": {},
"meta": {}
}
message.delivery.succeeded
一个 SDK 出站队列项真正发送成功后产生。
{
"id": 301,
"event_id": "stream:301",
"event_type": "message.delivery.succeeded",
"occurred_ts": 1777000300,
"occurred_at": "2026-04-21T21:35:00+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": null,
"member": null,
"operator": null,
"message": null,
"delivery": {
"outbound_id": 88,
"command_id": "cmd_001",
"status": "succeeded",
"msg_type": "text",
"reply_to_msg_svr_id": "1234567890",
"attempt_count": 1,
"error": "",
"sent_ts": 1777000300
},
"auth": null,
"runtime": null,
"raw": {},
"meta": {}
}
message.delivery.failed
一个 SDK 出站队列项进入失败状态,或某次发送尝试失败且 SDK 希望暴露错误时产生。
{
"id": 302,
"event_id": "stream:302",
"event_type": "message.delivery.failed",
"occurred_ts": 1777000310,
"occurred_at": "2026-04-21T21:35:10+08:00",
"source": "wxbot-sdk",
"session": {
"id": "123456@chatroom",
"name": "测试群",
"kind": "group"
},
"sender": null,
"member": null,
"operator": null,
"message": null,
"delivery": {
"outbound_id": 89,
"command_id": "cmd_002",
"status": "failed",
"msg_type": "image",
"reply_to_msg_svr_id": "",
"attempt_count": 3,
"error": "window not found",
"sent_ts": 0
},
"auth": null,
"runtime": null,
"raw": {},
"meta": {}
}
说明:
delivery.status 可以是 retry 或 failed。
retry 表示队列项仍留在本地出站队列中,SDK 之后可能继续重试。
failed 表示 SDK 已停止重试该出站项。
auth.revoked
SDK 授权失效,运行能力即将被阻断时产生。
{
"id": 401,
"event_id": "stream:401",
"event_type": "auth.revoked",
"occurred_ts": 1777000400,
"occurred_at": "2026-04-21T21:36:40+08:00",
"source": "wxbot-sdk",
"session": null,
"sender": null,
"member": null,
"operator": null,
"message": null,
"delivery": null,
"auth": {
"status": "revoked",
"reason": "device revoked",
"detail": "signed session rejected by auth server"
},
"runtime": null,
"raw": {},
"meta": {}
}
runtime.warning
非致命运行时问题,消费方需要感知时产生。
{
"id": 501,
"event_id": "stream:501",
"event_type": "runtime.warning",
"occurred_ts": 1777000500,
"occurred_at": "2026-04-21T21:38:20+08:00",
"source": "wxbot-sdk",
"session": null,
"sender": null,
"member": null,
"operator": null,
"message": null,
"delivery": null,
"auth": null,
"runtime": {
"level": "warning",
"code": "decrypt.missing_db",
"message": "message_0.db not found",
"detail": ""
},
"raw": {},
"meta": {}
}
游标语义
- 游标是全局 stream 游标,不是按事件类型分别计数。
- 消费方应把
id 视为权威回放游标。
- 使用最后看到的
cursor 重连时,不应重复回放更旧事件。
- 多种事件类型会自然交错出现。
过滤规则
如果提供 event_type,只推送匹配该类型的事件。
如果提供 session_id,只推送该会话下的事件。
过滤应在统一流排序确定后执行。
迁移说明
计划迁移方向:
- 继续保留
/messages、/messages/stream、/events、/events/stream,用于过渡期兼容。
- 引入
/stream 作为主要订阅端点。
- 将出站投递结果事件加入统一事件流。
- 将
agent-console 等上游消费方迁移为只追踪一个 stream 游标。
agent-console 消费建议
推荐初始订阅行为:
- 连接
GET /stream?cursor=<stored_cursor>。
- 持久化每个已处理事件的
id。
- 至少处理以下事件类型:
-
message.received
- group.member.joined
- group.member.left
- message.delivery.succeeded
- message.delivery.failed
- auth.revoked
- runtime.warning
- 出站发送仍通过 HTTP 命令接口完成。
版本与更新
这里记录 wx-bot SDK 的正式发布历史。版本号用于客户识别,内部 BUILD_ID 仅用于问题追踪。
当前尚未登记正式版本。正式发版后,这里会显示稳定版下载、校验值和完整变更记录。