WeBridge 服务端长轮询接入
本文档版次为 8080,沿用 8079 的业务接口约定;当前插件主版本为 8.0.80,本次构建为 8.0.80-2。用户已确认现有全部业务功能在微信 8.0.80 验证通过;下文保留旧版本的具体真机记录。新增的设置页检查更新仍需单独完成真机弹窗与浏览器下载联调。示例中的当前版本和构建标记用于说明格式,排错时以设备实际返回为准。
本文适用插件 8.0.80。选择本交互模式后,仅阅读本文即可完成配置、鉴权、命令调用、事件接收及结果处理;所有业务参数、返回与重试规则均在本篇。本地 HTTP 专用能力的限制及本模式替代方式也在本文说明。
目录
接入与基础协议
- 1. 这个模式是什么
- 2. 你要实现什么
- 3. 通信约定
- 4. 五个接口总览
- 5.
POST /agent/register - 6.
POST /agent/state - 7.
POST /agent/event - 8.
POST /agent/poll - 9.
POST /agent/command_result - 10. 公共字段
- 11. 能力清单
业务命令
- 12. 命令参数与返回
- 12.1 参数通用规则
- 12.2 命令总表
- 12.3 消息发送与操作
- 12.4 联系人查询与好友添加
- 12.5 群昵称、群名、公告与群二维码
- 12.6 红包与转账
- 12.7 个人资料设置与地区代码
- 12.8 状态查询与后台保活
- 12.9 本地 HTTP 专用入口与本模式限制
- 13. 好友备注、星标和黑名单
- 14. 创建群聊、直接加人和发送入群邀请
- 15. 入群邀请:查询、静默接受与探针
- 16. 群管理员、群免打扰与个人二维码
- 17. 媒体下载与上传
- 18. 公众号:搜索、列表、关注/取关与历史文章
- 19. 朋友圈:调用、结果与后续操作
- 20. 收藏:分页读取、发送与删除
事件与运行
附录
1. 这个模式是什么
插件作为 HTTP 客户端,主动向你的服务端发请求:
| 方向 | 谁发起 | 路径 |
|---|---|---|
| 插件 → 你 | 插件 | /agent/register、/agent/state、/agent/event、/agent/poll、/agent/command_result |
| 你 → 插件 | 只能通过 poll 的响应体(你不能主动连插件) | — |
优点:你的服务端只要有普通 HTTP 就能对接,不需要 WebSocket。
服务端没有命令时保持 poll 等待,有命令时立即结束等待并返回;插件处理完当前批次及结果上报后才会继续 poll。命令延迟还受排队、业务处理、上报和网络影响(详见第 22 节)。
和"插件内置 HTTP 接口"的关系:不冲突。你仍然可以同时通过手机上那个 HTTP 端口主动调插件。不同模式的选型对比可另看《接入总览与模式选择》,不影响按本文独立接入。 和"WebSocket"的关系:两者互斥,插件同一时刻只用一种传输模式。长轮询是默认模式。
2. 你要实现什么
5 个 POST 接口,路径固定为:
{你在设置页填的基础地址}/agent/register
{你在设置页填的基础地址}/agent/state
{你在设置页填的基础地址}/agent/event
{你在设置页填的基础地址}/agent/poll
{你在设置页填的基础地址}/agent/command_result
| 接口 | 你要做的事 |
|---|---|
/agent/register |
记录设备与能力,返回 200 |
/agent/state |
记录状态,返回 200 |
/agent/event |
处理事件,返回 200 |
/agent/poll |
可以 hold 住请求,有命令时返回命令数组 |
/agent/command_result |
记录结果,返回 200 |
请求和响应体都是 JSON。 除 poll 的响应体之外,其余接口的响应体内容插件不解析——只认 HTTP 状态码。
2.1 设置页配置与授权
| UI 名称 | 默认值 | 取值范围 | 影响 |
|---|---|---|---|
插件总开关 |
关闭 | 开关 | 需要先获得有效授权,再开启插件;协议中的接收、发送、转发有效状态均跟随此总开关 |
通信模式 |
长轮询 | 长轮询 / WebSocket | 决定用哪套协议。切换不需要重启微信,插件会停掉旧链路、启动新链路 |
服务端地址 |
空(显示"未配置") | 任意 URL | 唯一地址来源。为空时长轮询链路不启动 |
本地 HTTP 端口 |
19088 |
1024–65535 |
只影响插件内置 HTTP 接口,与本协议无关 |
轮询间隔 |
20 秒 |
1–300 |
只影响 poll 超时上限,不控制拉取频率(见 22.2) |
发送测试事件 |
— | 动作 | 造一条测试事件验证连通性 |
断线后启用暂存 |
开启 | 开关 | 关闭后离线事件直接丢弃 |
最大暂存条数 |
500 |
1–5000 |
超限丢最旧的 |
最大暂存时间 |
2 分钟 |
0–任意(界面按分钟填) |
0 或留空 = 不过期 |
当前暂存数量 |
— | 只读 | 实时查看积压量 |
一键清空暂存 |
— | 动作 | 清空本地队列 |
后台保活 |
关闭 | 开关 | 控制本插件音频、定位及后台检查;关闭后两个子开关保留选择并置灰 |
音频保活 |
开启 | 开关 | 保活总开关开启后允许在后台播放,录音 / 通话中让出 |
持续定位保活 |
关闭 | 开关 | 从微信前台启动、后台延续;需宿主定位声明及定位授权,可与音频同时开启 |
掉线提醒 |
关闭 | 开关 | 本地掉线提醒,无额外后台执行能力 |
插件总开关的状态关系:开启总开关时会把底层接收/发送/转发/模拟配置设为开启;这四种有效状态均跟随插件总开关与授权,界面没有独立入口。
长轮询请求和失败重试只维持通信,依赖微信进程获得执行时间,不能替代 iOS 后台保活。“后台保活”与插件总开关分开;其关闭后 audio_enabled/location_enabled 仍可能为 true,实际停止应核对运行字段,见 保活诊断与测试。
plugin_configured_enabled 表示保存的总开关,plugin_enabled 表示总开关与有效授权同时成立。当前设置页没有独立的接收、发送和转发开关;本文业务段落中的“发送/写入开关”等均指相应有效状态,不是要求另找一个设置项。服务端接收本协议请求时,按设备和账号保存状态;auth_status 描述插件授权结果,不是调用方自定义的 API Token。
3. 通信约定
3.1 地址怎么写
在插件设置页的「服务端地址」里填基础地址:
| 你填写的 | 插件实际请求的 |
|---|---|
http://192.168.0.100:8000 |
http://192.168.0.100:8000/agent/poll |
http://192.168.0.100:8000/api |
http://192.168.0.100:8000/api/agent/poll |
https://your-server.com |
https://your-server.com/agent/poll |
地址规范化规则:
| 规则 | 说明 |
|---|---|
不要带 /agent/xxx |
你即使填了 http://h:8000/agent/poll,末尾的 agent/poll 会被自动剥掉,变成 http://h:8000 |
| 去掉查询串和锚点 | http://h:8000/api?token=1#x → http://h:8000/api |
| 支持路径前缀 | /api 这类前缀会保留 |
| 地址为空 | 长轮询链路不启动,只会在日志里写"未配置长轮询HTTP接口",并每 5 秒重试一次 |
基础地址只填到业务路径前缀,插件会自动拼接
/agent/*;不要填某一个具体接口。
3.2 成功与失败怎么判定
插件只看 HTTP 状态码:
| 状态码 | 插件的理解 |
|---|---|
200~299 |
✅ 成功 |
| 其他 | ❌ 失败,触发重试 |
插件不解析响应体里的业务字段(唯一的例外是 poll 要看 commands 数组)。
所以:你的响应体里写什么 ok 字段都没用,返回正确的状态码才管用。
3.3 请求头
每个请求都是 POST,带:
Content-Type: application/json
Accept: application/json
3.4 超时
| 接口 | 插件等待超时 |
|---|---|
register / state / event / command_result |
15 秒 |
poll |
max(15 秒, 轮询间隔 + 10 秒),默认间隔 20 秒时为 30 秒 |
⚠️ 注意
poll超时有个 15 秒下限。如果你的服务端 hold 时间超过这个值,插件会判定超时并触发退避重试。 想让 hold 更久,需要同时调大设置页里的「轮询间隔」。
3.5 连接数
插件对 poll 请求限制为单连接(同一时刻只有一个 poll 在飞),对事件上报用另一套连接池。
实际行为:事件上报是串行单飞的——一次只发一条,成功了才发下一条。
4. 五个接口总览
| 接口 | 何时被调用 | 响应体要求 |
|---|---|---|
/agent/register |
插件启动后、地址变更后 | 可空 {};非 2xx 会阻断后续流程并重试 |
/agent/state |
register 成功后立即一次 | 可空 {};失败也继续,不重试 |
/agent/event |
微信里发生事件时 | 可空 {};非 2xx 会重试 |
/agent/poll |
循环调用 | 必须返回 {"commands": [...]} 或 {} |
/agent/command_result |
每条命令执行完后 | 可空 {};非 2xx 会重试 |
调用顺序(严格):
register → state → poll → (执行命令)→ command_result → poll → …
5. POST /agent/register
何时调用
插件启动后立即调用;服务端地址被修改后也会在下一轮立即重新 register。
请求示例
{
"device_id": "webridge_device_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"session_id": "sess_1775000000123_a1b2c3d4e5f6",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1775000000123,
"capabilities": ["send_message", "send_image", "…"],
"state": { "…": "…" }
}
| 字段 | 类型 | 恒有 | 说明 |
|---|---|---|---|
| 公共字段 | — | ✅ | device_id / session_id / account_id / runtime_version / build_marker / ts_ms,见第 10 节 |
capabilities |
string[] | ✅ | 插件能力清单,共 118 项,见第 11 节 |
state |
object | ✅ | 状态快照,20 个字段,见 6.2 |
你该做什么
- 用
device_id作为设备主键(长期稳定) - 用
session_id关联本次运行(每次插件启动都会变) - 记录
capabilities - 返回
200(响应体可以是{},插件不看)
失败后果
非 2xx → 阻断。插件不会继续发 state 和 poll,而是按退避策略重试 register。
⚠️ 这是唯一一个"失败了就卡住"的接口。你的服务端如果 register 有 bug,插件会一直停在这里。
6. POST /agent/state
何时调用
register 成功后立即调用一次,之后不再调用。
请求示例
{
"device_id": "webridge_device_xxxx",
"session_id": "sess_1775000000123_a1b2c3d4e5f6",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1775000001123,
"state": { "…": "…" }
}
与 register 的唯一区别:顶层不带 capabilities。state 对象的字段定义相同,但两次采样值可能变化。
6.2 state 对象的完整字段表
register 和 state 的状态快照使用同样的 20 个字段;各次请求会重新采集,字段值可能变化。
| # | 字段 | 类型 | 说明 |
|---|---|---|---|
| 1 | is_logged_in |
bool | 微信是否已登录 |
| 2 | account_id |
string | 当前账号 wxid |
| 3 | auth_authorized |
bool | 插件授权是否有效 |
| 4 | auth_status |
string | 授权状态,取值见下 |
| 5 | auth_expires_at_ms |
number | 授权到期时间(毫秒时间戳) |
| 6 | auth_expires_at |
string | 授权到期时间(可读文本),无期限时为 长期有效,取不到时为 -- |
| 7 | plugin_configured_enabled |
bool | 配置层面的插件开关 |
| 8 | plugin_enabled |
bool | 实际生效的插件总开关(= 配置值 且 授权有效) |
| 9 | receive_enabled |
bool | 接收上报开关(实际等同总开关) |
| 10 | send_enabled |
bool | 发送开关(实际等同总开关) |
| 11 | forward_enabled |
bool | 转发开关(实际等同总开关) |
| 12 | log_capture_enabled |
bool | 日志采集开关 |
| 13 | cached_message_count |
number | 本地缓存的消息条数 |
| 14 | cached_red_packet_count |
number | 本地缓存的红包条数 |
| 15 | cached_room_snapshot_count |
number | 本地缓存的群成员快照数 |
| 16 | pending_event_queue_count |
number | 当前待上报的事件条数(离线积压量) |
| 17 | oldest_pending_event_age_ms |
number | 最老待上报事件距今的毫秒数(按捕获时间算) |
| 18 | event_uploader_running |
bool | 事件上报器是否在运行 |
| 19 | event_upload_inflight |
bool | 当前是否有事件正在上报 |
| 20 | last_hook_selector |
string | 最近命中的内部钩子标识(排障用) |
auth_status 的取值集合:
| 取值 | 含义 |
|---|---|
authorized |
已授权 |
not_found |
从未拿到过服务端结论 |
not_logged_in |
微信未登录 |
account_mismatch |
缓存属于另一个账号 |
auth_assertion_expired |
鉴权断言过期 |
expired |
授权到期 |
disabled |
服务端禁用 |
bad_request / server_error / signature_error / stale_request / replay_rejected |
鉴权失败类 |
network_error |
网络层失败,尚无结论 |
实用字段:
pending_event_queue_count+oldest_pending_event_age_ms是用于观察事件积压的字段;它们不能精确统计已丢条数。poll本身不携带state,持续监控时定期下发bridge_status查询。
失败后果
失败也继续。 state 失败不会阻断流程,不会重试,也不计入失败计数。插件会直接进入 poll。
⚠️ 这是一个真实的行为差异:register 失败会卡住,state 失败不会。所以如果你的服务端只处理了 register 而 state 一直 500,插件不会给你任何反馈,你会静默地收不到状态信息。
7. POST /agent/event
何时调用
微信中发生事件时(收到消息、红包、转账、好友申请、群成员变动等)。
请求示例
{
"device_id": "webridge_device_xxxx",
"session_id": "sess_xxx",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1775000000123,
"event_name": "message",
"event_id": "msg:7307587870543245070",
"captured_at_ms": 1775000000100,
"enqueued_at_ms": 1775000000120,
"upload_started_at_ms": 1775000000121,
"body": { "…": "…" }
}
| 字段 | 类型 | 恒有 | 说明 |
|---|---|---|---|
| 公共字段 | — | ✅ | 见第 10 节 |
event_name |
string | ✅ | 事件类型,见第 21 节 |
event_id |
string | ⚠️ | 去重键。格式规则见下,绝大多数情况都有 |
captured_at_ms |
number | ✅ | 事件被捕获的时刻(毫秒) |
enqueued_at_ms |
number | ✅ | 事件进入发送队列的时刻(毫秒) |
upload_started_at_ms |
number | ✅ | 本次上报真正开始的时刻(毫秒) |
body |
object | ✅ | 事件内容,见第 21 节 |
💡 长轮询独有:事件比 WebSocket 模式多一个
upload_started_at_ms。三段时延分析: -captured_at_ms → enqueued_at_ms:插件内部处理耗时 -enqueued_at_ms → upload_started_at_ms:在本地队列里排队等待的耗时 -upload_started_at_ms → 你收到的时间:网络传输耗时这是长轮询模式相比 WebSocket 的一个优势——排障时能把"插件慢"和"网络慢"分开。
event_id 的格式规则
判定按固定优先级短路,命中第一条就返回:
| 优先级 | 条件 | event_id 格式 |
|---|---|---|
| 0 | 事件为 transfer_refund_result 且 refund_result_id 非空 | transfer_refund:<refund_result_id> |
| 1 | 事件是红包且红包 ID 非空 | red_packet:<红包ID> |
| 2 | 事件是好友申请且申请 ID 非空 | friend_request:<申请ID> |
| 3 | 事件是转账且转账 ID 非空 | transfer:<转账ID> |
| 4 | 有 message_id 或 msg_id 非空 |
msg:<消息ID> |
| 5 | 事件名以 group_member_ 开头且群 ID、成员 ID 非空 |
group_member:<事件名>:<群ID>:<成员ID>:<变动前>:<变动后> |
| 6 | 都不满足 | 当前长轮询请求信封可能不含 event_id;队列内部的 evt: 兜底未透出 |
由此推出的实用结论:
| 事件类型 | 实际 event_id 形态 |
|---|---|
| 普通消息 | msg:<消息ID> |
| 拍一拍 | msg:<消息ID>(消息 ID 取不到时退化为 msg:pat_<毫秒>) |
| 群成员加入/离开/昵称变更 | msg: 开头,形如 msg:group_member_join:<群ID>:<成员ID>:<毫秒>。因为这类事件必然带消息 ID,会先命中优先级 4,优先级 5 实际上用不到 |
| 红包 | red_packet:<红包ID>;红包 ID 本身形如 rp_msg_<消息ID> / rp_send_<发送ID> / rp_<毫秒> |
| 转账 | transfer:<转账ID> |
| 转账退还结果 | transfer_refund:<refund_result_id>;独立结果 ID,不能按 transfer_id 去重 |
| 好友申请 | friend_request:<申请ID>(申请 ID 形如 fr_msg_<消息ID> 或 fr_<加密用户名>_<ticket>_<场景>) |
⚠️ 实战建议:不要把
event_id当格式化字符串去解析(别试图从中提取消息 ID 或群 ID)。它是一个不透明的去重键,格式可能随版本变化。需要具体字段请从body里取。
你该怎么做:按 event_id 去重,返回 200。
失败后果
非 2xx → 重试。重试延迟见第 23 节。
事件用磁盘队列暂存,进程重启后会恢复继续补发。详见第 24 节。
8. POST /agent/poll
服务端通过此接口的响应下发命令。
何时调用
插件循环调用。每轮 poll 结束后立即发起下一轮,中间不等待。
请求示例
{
"device_id": "webridge_device_xxxx",
"session_id": "sess_1775000000123_a1b2c3d4e5f6",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1775000002345,
"capabilities": ["send_message", "send_image", "…"],
"last_result_request_id": "req-send-message-001"
}
| 字段 | 类型 | 恒有 | 说明 |
|---|---|---|---|
| 公共字段 | — | ✅ | 见第 10 节 |
capabilities |
string[] | ✅ | 与 register 里同一份清单 |
last_result_request_id |
string | ⚠️ | 仅当本次运行已产出过至少一条命令结果时才出现 |
⚠️
last_result_request_id的语义容易被误解: 它表示"最近一次已生成结果的 request_id",而不是"最近一次已成功上报的 request_id"。 也就是说:如果那条结果上报失败了、正在退避重试,这个字段里已经出现它了。 不要用它判断"服务端是否已收到结果"——请以command_result接口实际收到为准。
响应:这是唯一必须返回正确结构的接口
有命令时:
{
"commands": [
{
"request_id": "req-send-message-002",
"command_name": "send_message",
"args": {
"receiver": "wxid_friend",
"content": "你好"
}
}
]
}
没有命令时,返回下面任意一种都行:
{ "commands": [] }
{}
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
commands |
array | — | 命令数组。缺失、为空数组、或不是数组,插件都按"没有命令"处理 |
commands[].request_id |
string | ✅ | 你自己生成的唯一标识,插件会在结果里原样回传 |
commands[].command_name |
string | ✅ | 命令名,见第 12 节 |
commands[].args |
object | — | 命令参数;不传或传空对象都行 |
一次可以返回多条,插件会逐条串行执行。
关于"长轮询 hold"
你可以把请求 hold 住,直到有命令要下发或快超时为止。规则:
| 项目 | 值 |
|---|---|
| 插件等待上限 | max(15 秒, 轮询间隔 + 10 秒),默认 30 秒 |
| 建议 hold 时长 | 略小于上述上限,例如上限 30 秒时 hold 25 秒左右 |
如果你 hold 到超时:插件判定失败 → 退避重试 → 产生额外延迟。所以 hold 时长要留安全余量。
⚠️ 一个容易踩的坑:如果你的服务端立即返回空
{}(不做 hold),插件会立刻再发一次 poll——形成高频空转。虽然功能正常,但会产生大量无用请求和电量消耗。 请务必实现 hold,否则长轮询就退化成了"高频轮询"。
失败后果
非 2xx → 计入失败计数 → 退避重试。已取到的待执行命令不会丢失(见第 22 节)。
9. POST /agent/command_result
何时调用
每执行完一条命令后。注意:插件会等这条结果上报成功,才执行下一条命令。
请求示例
{
"device_id": "webridge_device_xxxx",
"session_id": "sess_xxx",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1775000003456,
"request_id": "req-send-message-002",
"command_name": "send_message",
"ok": true,
"duration_ms": 320,
"result": { "receiver": "wxid_friend" },
"error": ""
}
| 字段 | 类型 | 恒有 | 说明 |
|---|---|---|---|
| 公共字段 | — | ✅ | 见第 10 节 |
request_id |
string | ✅ | 与下发时一致 |
command_name |
string | ✅ | 命令名 |
ok |
bool | ✅ | 是否成功 |
duration_ms |
number | ✅ | 插件内排队与业务执行耗时(毫秒) |
result |
object | ✅ | 恒为对象;无内容时是 {} |
error |
string | ✅ | 失败原因;成功时是空串 "",不是缺失 |
判断成功
friend_pay_name 单次命令等待终态后回包,详见阻塞查询;群二维码见 get_chatroom_qrcode。
ok 表示本次命令是否执行成功,不要用 error 是否为空或 result 是否有内容代替它。 朋友圈首次命令ok=true仅表示受理,必须按result.operation_id等待moments_result或继续查询;详见本篇完整流程。对于资料设置,ok:true 表示已提交或当前值已一致,仍需核对 result.accepted、result.local_verified;拍一拍的首次 ok:true 只表示受理,最终成功须查询探针并确认 last_operation.status:"succeeded" 和 server_confirmed:true。详见资料设置命令。
duration_ms 的精确含义
从命令进入共享执行队列之前开始计时,到业务返回为止,包含在插件内的排队和业务等待;不包含前一条命令尚未结束时本条仍留在批次里的等待。
它不包含: - 从你的 poll 响应发出到插件收到的网络时间 - 结果回传给你的网络时间
对包含下载/转码的命令(如 send_voice、send_video、send_emoji),这个值可能达到数秒甚至更长。
ts_ms 的精确含义
ts_ms 是结果信封被生成的那一刻,不是发送成功的时刻。
⚠️ 一个真实存在的现象:结果上报失败时,插件会重发同一个结果信封。重发时
ts_ms仍是原来生成时的值,所以你会看到一个"明显早于当前时间"的ts_ms。这是正常的,不是时钟错误。
失败时 result 里有什么
不是所有失败都是空 result。 部分命令失败时仍会返回诊断信息:
| 命令 | 失败时 result |
|---|---|
| 大部分发送类命令 | {} 空对象 |
send_contact_card、send_location、location_geocode_probe |
透传业务结果;ok、error 移至外层 |
send_xml |
透传底层结果 |
friend_pay_name |
保留 account_id、pay_name_last_char、pay_name_status,具体原因在外层 error |
receive_red_packet / red_packet_detail |
带业务诊断信息(例如"已被领完") |
receive_transfer |
保留精简的编号、accepted、confirmed、status、callback_received、ret_code;ok/error 在外层,实际查询确认时另有 native_status |
refund_transfer / get_transfer_status |
保留 accepted、confirmed、status、native_status、ret_code、error_code 与编号;ok/error 在外层 |
room_members / friends / friends_all / groups_all |
空对象,error 里是原因(room_members 会带 room_id) |
kick_group_member |
带 room_id、member_ids |
rename_group / set_group_self_nickname(同一命令) |
带 room_id、new_name、clears_group_nickname |
set_group_name |
带 room_id、new_name、accepted、local_verified、already_named、local_name;具体原因在 error |
get_group_notice |
带 room_id、cached_notice_found、summary、html、html_cached;具体原因在 error |
upload_message_image |
带上传诊断字段 |
| 插件被禁用时(非白名单命令) | 带 auth_status、authorized |
结论:失败时不要假设 result 是空的,把它记进日志会大幅加快排错。
失败后果
非 2xx → 退避重试。插件会一直重发同一条结果,并暂停执行后面的命令,直到上报成功。
⚠️ 这意味着:如果你的
/agent/command_result一直返回 500,插件会卡在这一条命令上,后续命令全部不执行。 这是最常见的一类"插件好像死了"的误判。
10. 公共字段
以下字段出现在 register、state、event、poll、command_result 全部五个请求中,含义与生成规则完全相同。
下表包含本模式所需的全部公共字段;不需要另查其他协议文档。
| 字段 | 类型 | 恒有 | 说明 |
|---|---|---|---|
device_id |
string | ✅ | 设备唯一标识,格式 webridge_device_<uuid>。首次启动生成后持久化到磁盘,插件重装前不变、重启不变 |
session_id |
string | ✅ | 本次运行会话标识,格式 sess_<毫秒时间戳>_<12位十六进制>。每次插件进程启动重新生成 |
account_id |
string | ✅ | 当前登录的微信 wxid。取值回退链:运行时当前用户名 → 账号信息的当前用户 → 账号信息的 wxid;都取不到时为空串 |
runtime_version |
string | ✅ | 插件完整版本号,包含构建号,示例为 8.0.80-2 |
build_marker |
string | ✅ | 构建标记,当前为 webridge_build_20261106_settings_update_8080 |
ts_ms |
number | ✅ | 该帧/该请求生成时刻的 Unix 毫秒时间戳 |
各接口携带情况
| 接口 | 公共字段 | capabilities |
state |
|---|---|---|---|
register |
✅ 全部 | ✅ | ✅(20 字段) |
state |
✅ 全部 | ❌ | ✅(20 字段,同一份) |
poll |
✅ 全部 | ✅ | ❌ |
event |
✅ 全部 | ❌ | ❌ |
command_result |
✅ 全部 | ❌ | ❌ |
11. 能力清单
capabilities 是插件当前声明支持的命令集合,当前共 118 项。现有业务功能已由用户在微信 8.0.80 验证通过;切换到其他微信版本时仍需核对实际运行能力:
send_message,
send_signal,
send_pat,
receive_red_packet,
red_packet_detail,
receive_transfer,
refund_transfer,
get_transfer_status,
clear_gender_region,
profile_update,
profile_gender,
profile_region,
profile_signature,
profile_pat_suffix,
profile_region_codes,
moments_list,
moments_user,
moments_detail,
moments_refresh,
moments_like,
moments_unlike,
moments_comment,
moments_delete_comment,
moments_publish,
moments_delete,
moments_permissions_query,
moments_permissions_update,
moments_operation,
moments_probe,
moments_media_download,
profile_nickname,
send_image,
upload_message_image,
download_message_media,
upload_message_media,
upload_file_to_wechat_cdn,
message_media_probe,
send_emoji,
send_voice,
send_video,
send_video_from_url,
send_link_card,
send_xml,
send_contact_card,
send_location,
location_geocode_probe,
send_music_card,
send_file,
send_file_from_url,
forward_message,
revoke_message,
delete_friend,
delete_session,
quit_group,
create_group,
add_group_member,
invite_group_member,
kick_group_member,
kick_group_members,
rename_group,
set_group_name,
get_group_notice,
get_chatroom_qrcode,
get_self_qrcode,
group_admin_operate,
set_group_msg_silence,
set_friend_remark,
get_group_invitation,
accept_group_invitation,
group_invitation_probe,
friend_request_probe,
search_contact,
official_accounts,
official_account_search,
official_account_follow,
official_account_unfollow,
official_account_articles,
add_friend,
add_room_friend,
friend_add_probe,
accept_friend_request,
room_members,
account_status,
bridge_status,
device_status,
friends,
friends_all,
friend_pay_name,
groups_all,
quote_build_probe,
signal_send_probe,
pat_send_probe,
red_packet_probe,
red_packet_detail_probe,
transfer_probe,
clear_gender_region_probe,
profile_probe,
profile_region_probe,
profile_pat_suffix_probe,
profile_nickname_probe,
image_send_probe,
emoji_send_probe,
voice_send_probe,
video_send_probe,
video_send_url_probe,
link_send_probe,
file_send_probe,
file_send_url_probe,
revoke_probe,
delete_friend_probe,
quit_group_probe,
invite_group_member_probe,
kick_group_member_probe,
rename_group_probe,
set_group_name_probe,
send_livephoto,
livephoto_probe,
livephoto_diagnostics
当前分发器可执行但尚未列入 capabilities 的业务命令为 favorites_list、favorites_send、favorites_delete、set_friend_star、set_friend_blacklist;另有兼容别名 group_invite_member、group_invite_member_probe、set_group_nickname、set_group_self_nickname、set_group_nickname_probe、set_group_self_nickname_probe、update_nickname、update_nickname_probe 未声明。后端若只按清单过滤会误判不支持;按本篇命令表、别名表及实际插件构建处理这些缺口。清单只代表声明,不能代替实际业务能力判断;用户已确认现有业务功能在微信 8.0.80 验证通过。
三个重要提醒
- 以插件上报的清单为准。 它随插件版本变化,请以每次
register实际收到的为准。 - 声明清单仍有上文列出的业务及兼容别名缺口。 历史构建也曾漏声明实况照片等命令;不要仅凭版本号或清单缺省判断可调用范围。
capabilities会随每次register和poll重复携带,插件运行期间内容不变。
12. 命令参数与返回
12.1 参数通用规则
| 规则 | 说明 |
|---|---|
| 类型转换宽松 | 该传字符串时传了数字会被自动转成字符串;该传数字时传了数字字符串会被解析为数字 |
| 布尔值 | 接受 1/0、true/false、yes/no、y/n,大小写不敏感 |
| 字符串 | 自动去掉首尾空白与换行 |
| 单值→数组 | 部分数组参数接受单个值并自动包装 |
receiver 别名 |
部分命令还接受 toUser、to、chatId、target。新接入请只用 receiver |
收藏命令也使用严格类型校验,并且不接受目标别名,详见收藏用法。公众号五项同样采用严格类型校验,未知字段报错,见公众号。
资料设置命令与资料预校验探针采用严格类型校验;签名保留原始空白,拍一拍使用微信原生文本处理规则,详见本节资料管理说明。
⚠️ 宽松转换是陷阱:类型写错不会报错。把数字参数写成
"abc"会被当作0,而不是返回参数错误。
12.2 命令总表
消息发送
| 命令 | 用途 | 必需参数 | 可选参数 |
|---|---|---|---|
send_message |
发送文本(支持 @ / 引用 / @所有人) | receiver, content |
quote_message_id, mention_user_list |
send_image |
发送图片 | receiver, image_path |
— |
send_emoji |
发送表情 | receiver, source |
emoji_path(别名) |
send_voice |
发送语音 | receiver, source |
duration_ms |
send_video |
发送视频 | receiver, source |
max_size_mb, max_size_bytes |
send_video_from_url |
发送视频(URL 入口) | receiver, source |
max_size_mb, max_size_bytes |
send_livephoto |
发送实况照片 | receiver, video_path |
image_path, still_image_time_ms, prefered_hevc |
send_link_card |
发送链接卡片 | receiver, title, url |
description, thumb_url, thumb_path, source_user_name, source_display_name |
send_music_card |
发送音乐卡片 | receiver, title |
desc, url, data_url, song_album_url, app_id, app_name, song_lyric |
send_file |
发送文件 | receiver, file_path |
— |
send_file_from_url |
发送文件(URL) | receiver, file_url |
— |
send_contact_card |
发送联系人原生名片 | receiver, contact_id |
无 |
send_location |
发送单次位置 | receiver, latitude, longitude |
title, address |
location_geocode_probe |
指定坐标执行微信原生反查,不发送消息 | latitude, longitude |
无 |
send_xml |
发送原始 appmsg XML | receiver, xml, type |
forward(默认 false) |
send_signal |
发送小信号 | receiver |
— |
send_pat |
发送拍一拍 | receiver |
room_id(群聊需要) |
消息操作
| 命令 | 用途 | 必需参数 | 可选参数 |
|---|---|---|---|
forward_message |
转发消息 | receiver, message_id |
— |
revoke_message |
撤回消息 | message_id |
— |
upload_message_image |
已废弃,保留兼容;改用 upload_message_media |
message_id |
upload_url(默认业务地址加 /agent/upload_media) |
download_message_media |
准备消息媒体,详见完整用法 | message_id(字符串) |
media_type、timeout_seconds(默认 60,1~180) |
upload_message_media |
准备并上传消息媒体,详见完整用法 | message_id(字符串) |
upload_url、media_type、timeout_seconds(默认 60,1~180) |
upload_file_to_wechat_cdn |
上传普通文件到微信 CDN,详见完整用法 | file_path / file_url 二选一;任务 ID 取外层 request_id |
timeout_seconds(默认 120,1~180) |
message_media_probe |
读取消息媒体或 CDN 原任务状态 | message_id / request_id 二选一,放在 args |
— |
联系人与群组
| 命令 | 用途 | 必需参数 | 可选参数 |
|---|---|---|---|
official_account_search |
联网搜索公众号首批候选 | keyword |
— |
official_accounts |
已关注公众号分页列表 | — | type、keyword、limit、offset |
official_account_follow |
关注公众号,单次等待结果 | id |
— |
official_account_unfollow |
取消关注,可能清理本地会话和消息 | id |
— |
official_account_articles |
按篇数合并历史文章,详见公众号 | id |
limit(默认 100,1–1000)、cursor |
friends |
好友列表(分页),含添加时间 | — | limit(默认 100)、offset(默认 0)、keyword |
friend_pay_name |
阻塞查询好友或群成员支付姓名末字,见完整流程 | id |
refresh(默认 false) |
friends_all |
全部普通好友,含添加时间及支付姓名缓存 | — | keyword |
groups_all |
全部群聊 | — | keyword |
room_members |
群成员列表 | room_id |
— |
get_group_notice |
读取本机缓存的群公告 | room_id |
— |
get_chatroom_qrcode |
阻塞获取指定群二维码,返回 URL/可选图片与原始限制 | room_id |
— |
group_admin_operate |
添加/移除管理员、转让群主,见完整说明 | room_id、oper_type、member_ids |
— |
set_group_msg_silence |
设置当前账号的群免打扰 | room_id、silence |
— |
get_self_qrcode |
获取当前登录账号二维码 | —(args:{}) |
— |
get_group_invitation |
查询入群邀请 | message_id(字符串) |
— |
accept_group_invitation |
静默接受入群邀请 | message_id(字符串) |
— |
group_invitation_probe |
只读邀请探针 | message_id(字符串) |
stage |
search_contact |
搜索普通个人联系人 | keyword |
— |
add_friend |
搜索结果添加好友或发送验证申请 | contact_ref |
say_content |
add_room_friend |
通过共同群添加成员好友 | room_id, wxid |
say_content |
friend_add_probe |
只读检查搜索/群添加链路 | — | contact_ref, room_id, wxid |
accept_friend_request |
同意好友申请 | encrypt_username, ticket |
request_id, scene, say_content |
set_friend_remark |
设置/清空好友备注,详见好友设置 | id、remark(字符串,可为空) |
— |
set_friend_star |
设置/取消好友星标,详见好友设置 | id、starred(JSON 布尔) |
— |
set_friend_blacklist |
加入/移出黑名单,详见好友设置 | id、blacklisted(JSON 布尔) |
— |
delete_friend |
删除好友 | id |
— |
quit_group |
退出群聊 | id |
— |
create_group |
创建普通群聊 | 信封 request_id,member_ids(至少2位其他好友) |
member_id(单人无法达到建群范围) |
add_group_member |
直接添加群成员 | 信封 request_id、room_id,member_id/member_ids二选一 |
reason |
invite_group_member |
发送入群邀请 | 信封 request_id、room_id、至少一个成员字段 |
member_id、member_ids及兼容别名可合并 |
kick_group_member |
踢出群成员 | room_id + (member_id 或 member_ids) |
wxid, wxids |
kick_group_members |
同上(别名) | 同上 | 同上 |
rename_group |
修改我在本群的群昵称(不是群名称) | room_id |
new_name(可传空串表示清空), nickname, room_name, group_name |
set_group_self_nickname |
同上(语义正确的别名) | 同上 | 同上 |
set_group_nickname |
同上(别名) | 同上 | 同上 |
set_group_name |
修改整个群聊的名称 | room_id, new_name |
— |
⚠️ 重名陷阱:
accept_friend_request的args.request_id是好友申请的业务 ID,与命令信封上的request_id(用于结果关联)同名但完全是两回事。你有两个request_id出现在不同层级,别搞混。
会话管理
| 命令 | 用途 | 必需参数 | 可选参数 |
|---|---|---|---|
delete_session |
删除会话 | id |
— |
红包与转账
| 命令 | 用途 | 必需参数 |
|---|---|---|
receive_red_packet |
领取红包 | red_packet_id |
red_packet_detail |
红包详情 | red_packet_id |
receive_transfer |
确认收款 | transfer_id/message_id 至少一项 |
refund_transfer |
退还待收取转账 | transfer_id/message_id 至少一项 |
get_transfer_status |
查询转账资金状态,不执行收款或退还 | transfer_id/message_id 至少一项 |
资料管理
| 命令 | 用途 | 必需参数 |
|---|---|---|
clear_gender_region |
清除性别地区 | 无 |
profile_nickname |
修改账号昵称 | nickname(别名 new_nickname、name) |
profile_update |
批量设置性别、地区、签名 | 至少一项 sex、region_code/region、signature |
profile_gender |
设置或清空性别 | sex:整数 0/1/2 |
profile_region |
设置或清空地区 | region_code 或 region 二选一 |
profile_signature |
设置或清空签名 | signature;空串清空 |
profile_pat_suffix |
设置或清空拍一拍后缀 | pat_suffix;空串清空 |
profile_region_codes |
只读查询国家、省、市地区代码 | 可选 parent_code;省略或空串查国家 |
profile_probe |
当前资料与候选参数预校验 | {} 或资料设置参数 |
profile_region_probe |
地区代码查询探针 | 同 profile_region_codes |
profile_pat_suffix_probe |
后缀限制、候选校验及最近操作状态 | {} 查状态;可传 pat_suffix 预校验 |
收藏
| 命令 | 用途 | 参数 |
|---|---|---|
favorites_list |
收藏列表,最新在前的游标分页 | 可选 limit、refresh、cursor |
favorites_send |
发送指定收藏到好友或群 | fav_id、receiver;业务请求标识取外层 request_id |
favorites_delete |
删除指定收藏,等待服务器确认 | fav_id;业务请求标识取外层 request_id |
完整参数、结果和重试规则见本篇收藏用法。
朋友圈
| 命令 | 用途 | 参数 |
|---|---|---|
moments_list |
获取好友朋友圈时间线与历史 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_user |
获取指定用户朋友圈与可见期限 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_detail |
联网获取一条朋友圈详情 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_refresh |
服务端主动刷新朋友圈并回调 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_like |
点赞指定朋友圈 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_unlike |
取消朋友圈点赞 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_comment |
评论或回复指定朋友圈 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_delete_comment |
删除朋友圈评论 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_publish |
发布文字、图文或视频朋友圈 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_delete |
删除本人已发布朋友圈 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_permissions_query |
查询朋友圈好友、期限或单帖权限 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_permissions_update |
设置朋友圈好友、期限或单帖权限 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_operation |
查询朋友圈异步操作结果 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_probe |
朋友圈运行时/媒体诊断探针 | 完整参数、返回和后续调用见本篇朋友圈完整用法 |
moments_media_download |
准备图片/视频并主动上传后端 | sns_id、media_id;远程模式加upload,详见本篇完整用法 |
状态查询
| 命令 | 用途 | 参数 |
|---|---|---|
account_status |
账号状态 | 无 |
bridge_status |
插件完整诊断快照 | 无 |
device_status |
设备运行状态 | 无 |
这三个命令是插件被禁用时仍可执行的唯一三个。其余命令在插件禁用时会返回
ok:false且错误文案为plugin is disabled(已授权)或authorization required(未授权),result里带auth_status和authorized。
调试探针
以下命令主要用于返回运行时和候选参数诊断;是否触发资源准备或原生查询以对应说明为准,不将诊断成功当作业务完成。建议在正式调用某个能力前先跑一次对应探针,确认它在你的微信版本上可用:
friend_add_probe、quote_build_probe、signal_send_probe、pat_send_probe、red_packet_probe、red_packet_detail_probe、transfer_probe、clear_gender_region_probe、profile_probe、profile_region_probe、profile_pat_suffix_probe、profile_nickname_probe、image_send_probe、emoji_send_probe、voice_send_probe、video_send_probe、video_send_url_probe、link_send_probe、file_send_probe、file_send_url_probe、revoke_probe、delete_friend_probe、quit_group_probe、invite_group_member_probe、kick_group_member_probe、rename_group_probe、set_group_name_probe、friend_request_probe、livephoto_probe、livephoto_diagnostics
⚠️ 两点必须知道: 1. 探针名不要带
/debug/前缀。命令通道用的是上面这些裸名字;写成/debug/xxx_probe(带斜杠)会得到unsupported command_name。 2. "不真正执行操作"成立,但"无副作用"不成立。部分探针会做真实的网络请求或文件操作:表情/视频/语音探针会发 HTTP 请求探测远程资源并可能下载落盘;文件探针会清理上传目录;视频探针会创建目录并清理缓存。
12.3 消息发送与操作
表情发送与转发补充: send_emoji转换前按当前微信动态配置检查大小,实际限制见emoji_send_probe的limits.max_file_size/max_dimension,不能把静态默认5MiB写死。字节有效上限为原生两种配置较大值与20MiB插件保护上限的较小值,超限明确失败。
forward_message转发表情时,在同一命令内检查/下载原生资源,再转发并等待目标消息最终发送确认,无需另加下载命令,也不需要先打开手机对话。下载、发送各最多等待30秒。下载失败不转发;已提交但确认超时会返回“结果未知,请勿自动重试”,不能将失败回包当作对方一定未收到。HTTP、长轮询、WS共用该实现;成功字段保持既有简洁结构。
send_message
{ "receiver": "wxid_friend" }
传了 quote_message_id 时多一个 quote_message_id 字段;有 @ 时多一个 mention_user_list 数组。
@ 相关规则:
mention_user_list也接受别名at_user_list;at_user(单个 wxid)会被并入名单最前面content里可用{{mention:wxid}}占位符,在正文任意位置插入 @- 即使不传
mention_user_list,只要正文里有{{mention:wxid}},插件也会自动提取并走 @ 链路 - ⚠️ 占位符里的 wxid 解析不出备注名或昵称时,整条消息会被拒绝发送
- @所有人:
mention_user_list: ["notify@all"],仅群聊可用。notify@all是协议标记不是 wxid,不要展开成全部成员 - 插件不会自动往
content里补@所有人,要在界面上看到这四个字必须自己写进content - 是否允许发送仍受微信群主/管理员权限限制,插件不绕过微信校验
send_image
{
"resolved_image_path": "/var/mobile/.../photo.jpg",
"receiver": "wxid_friend",
"downloaded": false
}
send_emoji
用途:发送表情(gif / png / jpg / jpeg 均可)。
大小限制读取当前微信动态配置,实际值见emoji_send_probe命令的limits.max_file_size/max_dimension。字节上限为两种原生配置较大值与20MiB保护上限的较小值;静态默认5MiB不能替代真机配置。转换前和读取数据后均检查,超限明确报错,不提交发送。
args 字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
receiver |
string | ✅ | 接收方 |
source |
string | ✅ | 本地路径或远程 URL(推荐用这个字段名) |
emoji_path |
string | — | source 的兼容别名 |
说明:
- 远程图片会自动下载到插件沙盒,缓存 1 天后自动清理。
- 不是 gif 的图片会被转换成微信表情格式。
成功时的 result:
{
"receiver": "wxid_friend",
"source": "https://example.com/demo.png",
"source_type": "remote_url",
"emoji_path": "/var/mobile/.../demo.gif",
"resolved_emoji_path": "/var/mobile/.../demo.gif",
"downloaded": true,
"emoji_md5": "…",
"message_type": 47,
"message_type_name": "emoji"
}
send_voice
{
"receiver": "wxid_friend",
"source": "https://example.com/audio.mp3",
"source_type": "remote_url",
"resolved_local_path": "…",
"resolved_pcm_path": "…",
"resolved_wechat_aud_path": "…",
"duration_ms": 3240,
"downloaded": true,
"message_type": 34,
"message_type_name": "voice"
}
source兼容别名:voice_url、voice_path、audio_url、audio_path、video_url、video_path、aud_url、aud_pathduration_ms兼容voice_length_ms、voice_duration_ms;推荐使用数字毫秒值- 非
.aud文件会被自动转码(提取音频 → PCM → SILK),耗时较长,后端等待业务结果需留足时间;poll 仅投递命令,其网络超时与业务等待期限分别设置
send_video
{
"source": "…",
"source_type": "remote_url",
"resolved_video_path": "…",
"resolved_thumb_path": "…",
"receiver": "…",
"video_duration": 12.5,
"generated_thumb": true,
"downloaded": true,
"max_size_bytes": 10485760,
"max_size_mb": 10,
"within_size_limit": true,
"remote_size_known": true,
"remote_size_bytes": 8123456
}
- 默认大小上限
10 MiB;max_size_mb优先级高于max_size_bytes source也接受video_url、video_path
send_livephoto
args 必填字符串 receiver、video_path(手机路径或手机可访问的 URL);可选字符串 image_path、数字 still_image_time_ms、布尔 prefered_hevc(默认 false)。prefered_hevc 就是此拼法。命令通道会转换数字/布尔字符串,新接入仍应发送原生 JSON 类型。
成功时的 result:
{"video_path":"/var/mobile/example.mov","image_path":"/var/mobile/example.jpg","receiver":"filehelper"}
send_link_card
args 必填 receiver、title、url;可选 description、thumb_url、thumb_path、source_user_name、source_display_name,均为字符串。缩略图和跳转链接须为手机可访问的资源。
成功时的 result:
{"receiver":"wxid_friend","title":"标题","url":"https://example.com","thumb_url":""}
send_music_card
用途:发送音乐卡片。注意:这个接口内部走的是转发链路(relay_* 字段会体现在响应里),不是直接构造消息。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
receiver |
string | ✅ | 接收方 |
title |
string | ✅ | 歌曲名 |
desc |
string | — | 描述 |
url |
string | — | 音乐链接 |
data_url |
string | — | 音频数据地址 |
song_album_url |
string | — | 封面图 |
app_id |
string | — | 来源 App ID |
app_name |
string | — | 来源 App 名称 |
song_lyric |
string | — | 歌词;兼容 songlyric、songLyric |
别名表(这个接口为了兼容历史调用方,别名特别多):
| 主字段 | 兼容别名 |
|---|---|
receiver |
toUser、to、chatId、target |
desc |
description |
url |
musicUrl、music_url |
data_url |
dataurl、dataUrl |
song_album_url |
songalbumurl、songAlbumUrl、thumbUrl、thumb_url |
app_id |
appId |
app_name |
appName |
song_lyric |
songlyric、songLyric |
重要:url、data_url、song_album_url、app_id、app_name 这五个字段不传会用内置默认值兜底(默认指向一个示例音乐)。也就是说缺参数不会报错,会发出去一张指向默认歌曲的卡片。要发自己的音乐,这五个字段请全部显式传入。
args 示例(URL 和应用信息均为占位值,需替换):
{
"receiver": "filehelper",
"title": "歌曲标题",
"desc": "歌手 - 专辑",
"url": "https://music.example.com/song/1",
"data_url": "https://media.example.com/song/1.mp3",
"song_album_url": "https://media.example.com/song/1.jpg",
"app_id": "wx_example_music_app",
"app_name": "音乐应用"
}
成功时的 result:
{
"receiver": "wxid_friend",
"title": "歌曲名",
"desc": "",
"url": "https://music.example.com/song",
"dataurl": "...",
"songalbumurl": "...",
"app_id": "wx...",
"app_name": "网易云音乐",
"songlyric_length": 0,
"relay_forward": true,
"relay_chat": "filehelper",
"relay_delete_staged": true
}
songlyric_length 为实际歌词的 UTF-16 长度;未提供歌词时为 0。
relay_*字段说明该卡片是通过"先发到文件传输助手再转发"的链路实现的。如果你在文件传输助手里看到过残留,属正常现象,插件会清理。
send_file
{
"resolved_file_path": "…",
"receiver": "…",
"file_name": "report.pdf",
"file_size": 204800,
"extension": "pdf"
}
send_file_from_url 会额外返回 source_url 和 downloaded。
send_contact_card
发送已有联系人的原生名片。receiver 是收件人或群,contact_id 可传被推荐联系人的真实 ID,或本机已有好友的完整微信号(alias),不能传群 ID。微信号精确匹配,匹配不唯一则拒绝;不按昵称、备注模糊选择,不自动联网搜索陌生人。成功 result.contact_id 统一返回解析后的真实 user_name。头像、昵称等取微信联系人资料,不接受自定义头像或昵称。
{"request_id":"card-001","command_name":"send_contact_card","args":{"receiver":"filehelper","contact_id":"wxid_example"}}
成功 result:{"submitted":true,"receiver":"filehelper","contact_id":"wxid_example","message_type":42}。普通名片类型 42,OpenIM / OpenIM 客服按原生判断为 66 / 67。submitted:true 仅表示原生提交,不能视为服务器确认送达;名片发送已由用户在微信 8.0.80 验证通过,特殊联系人仍按微信实际返回处理。
send_location
发送单次位置消息。必填 receiver、latitude、longitude;经纬度必须是有限 JSON 数值,范围分别 -90~90、-180~180,不接受数字字符串、布尔值、null。选填字符串 title、address,空白串按省略处理,null 为参数错误。
{"request_id":"location-001","command_name":"send_location","args":{"receiver":"filehelper","latitude":23.012345,"longitude":113.123456}}
有非空标题直接使用;只传地址时地址也用作标题。两者均省略时执行微信原生反查,最多等 15 秒;优先采用带方向的地理描述,再尝试地址或 POI 名称,不能保证得到指定商户名称。需精确文案时传 title。经纬度同时为 0 会被原生反查拒绝,显式标题可跳过反查。插件原样使用坐标,不做坐标系转换,外部 GPS 输入须先对点。
成功 result 包含 submitted:true、receiver、message_type:48、latitude、longitude、最终 title、address、title_source。来源为 provided、address 或 wechat_geocode。失败时外层 ok:false、error 为中文说明,result 保留 error_code 和反查诊断;反查失败、超时、无可用文字或账号/登录会话变化均不提交消息。成功只表示原生提交,尚未确认送达。
位置等待使用后台执行,不占用心跳和收包队列。send_location 与普通发送命令一样使用已有 request_id 结果缓存;缓存存在时重复 ID 会回放结果,重试应使用新的 ID,并先确认上次是否已经提交。名片和位置发送均遵循授权、插件启用及有效发送状态。
location_geocode_probe
实际测试指定坐标的原生反查,不发送消息、不需要收件人,不受发送开关限制;仍需授权、启用插件并登录微信。
{"request_id":"geocode-001","command_name":"location_geocode_probe","args":{"latitude":23.012345,"longitude":113.123456}}
result 保留以下诊断(流程未到达的字段可能缺省):
| 字段 | 说明 |
|---|---|
probe / message_sent |
true / false |
request_invoked / callback_received |
原生入口是否调用、是否收到本次回调;调用入口也可能命中缓存,不等于发起网络请求 |
selector_signature / signature_supported |
原生签名及 ABI 检查,通过时为 true |
native_params |
正常默认 type:1, scene:0, language_scene:0 |
elapsed_ms / timeout_seconds |
耗时、15 秒等待上限 |
native_result_class |
原生结果类型 |
title / address / title_source |
与发送共用规则选出的文字 |
fields |
poi_name、nearest_poi_name、rough_address、rough_address_with_direction、address_description、city、route、detail,可能为空 |
native_error_domain / native_error_code / native_error_message |
原生失败时的 NSError 信息 |
error_code |
INVALID_COORDINATE、NOT_LOGGED_IN、NATIVE_UNAVAILABLE、GEOCODE_FAILED、GEOCODE_EMPTY、GEOCODE_TIMEOUT、ACCOUNT_CHANGED 等 |
探针和坐标发送共用反查实现,完成、失败和超时都会清理本次回调及扩展注册,迟到回调不会发送消息。探针每次执行,不复用通用 request_id 结果缓存。建议先检查回调与原生地址字段,再执行位置发送。位置发送及反查功能已由用户在微信 8.0.80 验证通过,接收端展示以实际微信界面为准。
send_signal
{
"receiver": "…",
"cgi": 976,
"event_flag": 5,
"client_msg_id": 4976413716,
"create_time": 1777500000
}
send_pat
{ "receiver": "…", "room_id": "…", "is_group": true, "scene": 0, "selector": "…" }
forward_message / revoke_message
forward_message 必填字符串 receiver、message_id,需开启转发功能;revoke_message 必填字符串 message_id,受微信自身撤回时限约束。成功时的 result 分别为:
{"message_id":"1234567890123456789","receiver":"filehelper"}
{"message_id":"1234567890123456789"}
下载/发送确认和结果未知时的处理见本节表情转发说明;失败时检查外层 ok、error,不能因为未拿到确认就自动重发。
12.4 联系人查询与好友添加
联系人摘要对象
friends、friends_all、groups_all、room_members 使用相同的基础联系人字段,并按场景增加好友或群成员字段。本模式好友数据位于成功结果的 result.items[],群成员数据位于 result.members[]。
| 字段 | 类型 | 出现条件 | 含义 |
|---|---|---|---|
account_id |
string | 恒有 | 内部微信 ID(wxid);群聊时为群 ID。好友接口的 id 通常使用此值 |
display_name |
string | 恒有 | 好友展示名依次取备注、昵称、微信 ID;群成员场景可使用群内显示名 |
remark |
string | 恒有 | 当前账号给对方设置的备注;无备注或取不到时为空串 |
alias |
string | 恒有 | 对方设置的微信号;未设置或取不到时为空串,与内部 wxid 分开使用 |
nick_name |
string | 恒有 | 微信昵称(群聊时为群名);取不到时为空串 |
avatar_url |
string | 恒有 | 头像图片地址;取不到时为空串。JSON 中的 \/ 解析后就是 / |
sex |
int | 恒有 | 0 未知、1 男、2 女;取不到时为 0 |
province |
string | 恒有 | 本地资料中的省份;没有有效值时为空串 |
city |
string | 恒有 | 本地资料中的城市;没有有效值时为空串 |
signature |
string | 恒有 | 微信个性签名;未设置或取不到时为空串 |
contact_found |
bool | 恒有 | 是否在本地找到联系人对象;不能据此判断对方是否删除了你 |
is_starred |
bool | 可读取状态时 | 当前账号是否将对方设为星标;false 为未设星标,未知时省略字段 |
is_blacklisted |
bool | 可读取状态时 | 当前账号是否将对方加入黑名单;false 为未拉黑,未知时省略字段 |
add_time |
int / null | 好友列表恒有 | 微信资料页使用的添加时间,Unix 秒级时间戳;没有有效记录或读取失败为 null |
add_time_text |
string / null | 好友列表恒有 | 同一添加时间的公历文本,固定北京时间(UTC+8)、24 小时制,格式 YYYY-MM-DD HH:mm:ss;add_time=null 时同为 null |
pay_name_last_char |
string | 好友列表恒有 | 微信支付脱敏姓名末尾可见的一个组合字符,例如“明”;没有有效结果时为空串,不提供完整姓名 |
pay_name_status |
string | 好友列表恒有 | 支付姓名查询状态,取值见下表;好友列表只读取缓存 |
is_owner |
bool | 群成员场景,可读取时 | 是否群主;未知时省略字段 |
is_admin |
bool | 群成员场景,可读取时 | 是否管理员;未知时省略字段 |
is_self |
bool | 群成员场景,可读取时 | 是否当前登录账号;未知时省略字段 |
空值与省略规则:
- 普通字符串的
""表示未设置或本地没有取得有效值;不能据此推断对方资料一定为空。 add_time与add_time_text总是成对返回:有值时分别为秒级整数和 UTC+8 文本,未知时均为null。不以0、空串或 1970 年日期代替未知,也不使用本地添加流程时间兜底。is_starred、is_blacklisted及群成员角色字段可能整个省略。字段缺失表示未知,不能按false处理。
添加时间来自微信当前保存的记录,与微信资料页使用的字段一致;不承诺删除后重加时仍是终身首次添加时间。文本不随手机时区、语言或日历设置变化。两个好友查询及同名命令均无需新增请求参数。
支付姓名状态:
pay_name_status |
含义 |
|---|---|
not_queried |
尚未查询;姓名末字为空,不代表未实名 |
querying |
排队或查询中,尚无最终结果 |
ok |
已取得有效姓名末字,pay_name_last_char 非空 |
unavailable |
微信业务响应成功,但没有可用末字;不能据此判断未实名或被单删 |
failed |
查询失败 |
friends、friends_all 本身不发起支付姓名查询,需要时调用 friend_pay_name;其结果不会改变昵称、备注或 display_name。
完整好友条目示例(虚构数据,共 17 个字段):
{
"account_id": "wxid_example",
"display_name": "示例备注",
"remark": "示例备注",
"alias": "wechat_example",
"nick_name": "示例好友",
"avatar_url": "",
"sex": 0,
"province": "",
"city": "",
"signature": "",
"contact_found": true,
"is_starred": false,
"is_blacklisted": false,
"add_time": 1704067200,
"add_time_text": "2024-01-01 08:00:00",
"pay_name_last_char": "",
"pay_name_status": "not_queried"
}
时间未知时的字段节选:
{"add_time": null, "add_time_text": null}
friends
result.items[] 为好友摘要,每条恒含 add_time 与 add_time_text,无需新增参数;完整字段与示例见联系人摘要对象。
{
"keyword": "",
"limit": 100,
"offset": 0,
"total": 256,
"has_more": true,
"items": [ "…" ]
}
翻页时不断增大 offset,直到 has_more 为 false。
friends_all
一次性返回关键词过滤后的普通好友。result.items[] 的字段、添加时间时区及空值规则与 friends 相同。
{
"keyword": "",
"total": 256,
"friend_count": 256,
"official_account_count": 12,
"work_account_count": 0,
"system_account_count": 4,
"chatroom_count": 38,
"stranger_count": 0,
"cached_contact_count": 310,
"items": [ "…" ]
}
items只包含关键词过滤后的普通好友;total、friend_count与items长度一致。公众号、企业微信、系统号、群聊和陌生人只参与各自分类计数,不混入好友数组。cached_contact_count是未经过滤的原始缓存联系人条数。 六类联系人计数均经过关键词过滤;cached_contact_count保留原始缓存条数。
groups_all
{ "keyword": "", "total": 38, "items": [ "…" ] }
无分页字段。
room_members
{
"room_id": "…",
"room_name": "…",
"room_remark": "",
"current_user": "wxid_self",
"owner_user_name": "wxid_owner",
"admin_count": 2,
"admin_user_names": [ "…" ],
"admin_members": [ "…" ],
"self_is_owner": false,
"self_is_admin": false,
"member_count": 24,
"members": [ "…" ]
}
失败结果仍使用命令信封,例如 {"ok":false,"result":{"room_id":"…"},"error":"…"};不要因失败而丢弃 result.room_id。
friend_pay_name:阻塞查询好友或群成员支付姓名末字
单次调用等待微信回调后返回最终结果,不返回 querying 中间结果,无需再次查询任务状态。微信主线程保持异步运行。
这里的“实名相关查询”指微信支付脱敏姓名的末字查询,不提供完整实名,也不能用查询结果判断对方是否已实名认证。
id必填,使用好友列表或room_members返回的个人account_id。好友直接查询;非好友自动查找双方均在其中的普通微信群,无需传room_id。不支持自己、群 ID、公众号、企业微信或系统号。refresh为布尔值,默认false。当前好友或共同群场景有缓存时立即返回终态(包括已缓存的失败);true强制刷新或失败重试,并等待本次结果。同一目标、同一场景正在查询时合并等待;关系或群上下文变化后重新查询。- 自动查找依赖本地群列表和成员资料;未找到可用共同群或资料未就绪时返回
failed和中文原因。存在多个共同群时选择一个已核实的普通群,不轮询所有群发起支付查询。 - 原生查询串行执行,最多 32 个待处理请求;30 秒超时包含排队时间,接口等待保护上限约 31 秒。调用方超时建议至少 40 秒。
- 仅查询转账前信息,不提交金额或付款确认。结果只保留在当前账号会话内存中,账号或联系人服务会话变化后清空。
pay_name_status |
HTTP ok/命令外层 ok |
含义 |
|---|---|---|
ok |
true / 1 |
已取得有效的姓名末字,pay_name_last_char 非空 |
unavailable |
true / 1 |
微信业务响应成功,但没有可用末字;不能据此判断未实名或被单删 |
failed |
false / 0 |
参数、账号、兼容、网络、业务或超时失败,error 给出原因 |
friends、friends_all 只附带好友场景缓存,本身不发起支付查询,群内非好友不会因此加入好友列表。列表仍可能显示 not_queried 或正在处理的 querying;单人查询接口和命令只返回上表中的终态。支付姓名字段不改变昵称、备注或 display_name,不提供完整姓名。
自动选择共同群的功能已由用户在微信 8.0.80 验证通过。
下发一次命令:
{
"commands": [
{
"request_id": "pay-name-001",
"command_name": "friend_pay_name",
"args": {
"id": "wxid_example",
"refresh": false
}
}
]
}
插件等到终态后,使用同一 request_id 通过 POST /agent/command_result 上报结果:
{
"request_id": "pay-name-001",
"command_name": "friend_pay_name",
"ok": true,
"result": {
"account_id": "wxid_example",
"pay_name_last_char": "明",
"pay_name_status": "ok"
},
"error": ""
}
不再下发状态查询命令;业务失败时外层 ok=false、error 给出原因,result 保留目标好友和姓名状态。外层身份字段 account_id 是登录账号,result.account_id 才是目标好友。
主动添加好友
命令 search_contact、add_friend、friend_add_probe 的参数放在 args,结果使用既有 request_id/command_name/ok/result/error 信封;业务字段在 result,顶层 ok/error 不再重复放入 result。
下发搜索和添加的命令主体示例(放入 /agent/poll 响应的 commands 数组):
{"request_id":"search_demo_1","command_name":"search_contact","args":{"keyword":"示例微信号"}}
{"request_id":"add_demo_1","command_name":"add_friend","args":{"contact_ref":"fc_example_ref","say_content":"你好,我是某某"}}
添加结果信封示例:
{
"request_id":"add_demo_1",
"command_name":"add_friend",
"ok":true,
"result":{
"status":"request_sent","username":"example_user","is_friend":false,
"contact_ref":"fc_example_ref","native_ret":0,"stage":"send_verification",
"request_submitted":true,"event_id":null
},
"error":""
}
以下为三个命令共用的业务字段说明;结果示例均采用本模式命令信封,ok/error 位于外层,业务字段位于 result。
主动添加使用两步调用:先搜索获得 contact_ref,再用该引用提交验证消息。支持普通个人联系人;公众号、群、OpenIM 和当前账号不提供可添加的引用。friends.keyword 仍只过滤本地好友列表。
搜索参数为 {"keyword":"示例微信号"},keyword 必须是 1–256 个字符的字符串。支持微信实际允许搜索的微信号、手机号等,不保证任意原始 wxid 可搜索。搜索成功示例(依据用户提供的 HTTP 真机回包整理,身份和头像链接已替换):
{
"request_id": "search_demo_1",
"command_name": "search_contact",
"ok": true,
"result": {
"status": "found",
"contacts": [
{
"contact_ref": "fc_example_ref",
"username": "example_user",
"nick_name": "示例昵称",
"alias": "example_alias",
"avatar_url": "https://example.com/avatar/132",
"avatar_hd_url": "https://example.com/avatar/0",
"sex": 1,
"country": "",
"province": "",
"city": "",
"signature": "",
"supported": true,
"is_friend": true,
"source": "wechat_id"
}
],
"expires_in": 300,
"native_ret": 0,
"stage": "search",
"request_submitted": true,
"event_id": 42,
"request_cgi": 106,
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0
},
"error": ""
}
contacts[] 同时返回本次搜索取得的公开资料,无需先加好友,也不会额外发起资料查询:
| 字段 | 类型 | 说明 |
|---|---|---|
avatar_url |
string | 头像链接;微信未返回时为空字符串 |
avatar_hd_url |
string | 大图头像链接;微信未返回时为空字符串 |
sex |
integer | 性别:0 未知、1 男、2 女;未取得时为 0 |
country |
string | 国家/地区,沿用微信返回值;未取得时为空字符串 |
province |
string | 省份,沿用微信返回值;未取得时为空字符串 |
city |
string | 城市,沿用微信返回值;未取得时为空字符串 |
signature |
string | 个性签名;未取得时为空字符串 |
以上字段在每个候选中均返回,是否有值取决于微信本次回包及对方公开资料;地区是个人资料填写值,不代表实时位置。它们不改变 supported 或 contact_ref 的规则。
country、province、city、signature 为空字符串时,表示本次搜索未取得对应资料,不影响 status:found;仅凭该结果无法区分对方未设置资料,还是微信搜索回包未提供。
is_friend 来自本地普通好友判断。为 true 时无需再发好友申请,正常调用 add_friend 会返回 already_friend。supported:true 表示候选类型符合当前添加流程,不代表对方是陌生人;已有好友也可能同时返回 supported:true 和 contact_ref。
source 为 wechat_id、phone 或 qq,由微信搜索匹配类型决定。username 是微信原生返回的标识,可能是加密标识,不能一律当成稳定 wxid。多候选由调用方选择;supported:false 的候选没有 contact_ref,不能提交添加。确认未找到时返回 ok:true,status:not_found,contacts:[];传输错误、解析错误和业务失败返回 ok:false,不能当成“用户不存在”。
添加参数:
{"contact_ref":"fc_example_ref","say_content":"你好,我是某某"}
contact_ref 必填,只能使用本账号本次登录会话的搜索结果;say_content 可省略,默认为空字符串,不接受数字或 null。输入会去除首尾空白,并按微信原生长度规则校验;过长时返回 INVALID_SAY_CONTENT,不会悄悄截断发送。不能直接传 username、scene、opcode 或票据替代引用。好友添加遵循插件发送开关。
添加成功示例:
{
"request_id": "add_demo_1",
"command_name": "add_friend",
"ok": true,
"result": {
"status": "request_sent",
"username": "example_user",
"is_friend": false,
"contact_ref": "fc_example_ref",
"native_ret": 0,
"stage": "send_verification",
"request_submitted": true,
"event_id": 43,
"request_cgi": 137
},
"error": ""
}
| status | ok | 含义及下一步 |
|---|---|---|
already_friend |
true | 本地联系人服务确认已经是普通好友,本次未发添加请求,native_ret:null |
request_sent |
true | 微信确认验证申请发送成功,等待对方同意;不能当成已加好友 |
added |
true | 微信添加成功且原生后处理完成,本地普通好友判断通过 |
pending_sync |
true | 微信添加成功且原生后处理已运行,本地好友状态尚未确认;稍后通过 friends/friends_all 核对 |
needs_user_action |
false | 微信要求人工处理或预检查失败;在微信内查看,不自动确认或绕过限制 |
timeout |
false | 30 秒内未取得最终结果,结果不确定;先核对好友状态,不自动重发 |
failed |
false | 参数、会话、运行时或微信请求失败,读取 error_code、error、native_ret |
引用在插件内存保留 300 秒,最多 50 个;这个时间是本地缓存策略,不代表微信票据寿命。重启、退出登录或账号/上下文变化后必须重新搜索。相同引用及相同规范化消息的并发调用共享操作,完成后在引用有效期内复用结果;消息不同返回 CONTACT_REF_CONFLICT。同目标的另一引用正在添加时返回 ADD_IN_PROGRESS。超时后的相同引用仍返回原结果,不会自动重发。跨引用、跨重启和超过缓存期不承诺去重,后端也应避免重复下发。
两项操作均在当前请求内等待,插件操作期限为 30 秒,调用方建议至少等待 40 秒。native_ret 未取得微信业务结果时为 null;不能把 null 当 0。搜索成功或微信添加成功为 0,部分未找到搜索结果保留微信 -4。stage 为 preparing、search、precheck、add_contact 或 send_verification。request_submitted 只表示搜索/添加请求获原生受理(预检查不单独计入),不代表服务器已成功处理;request_cgi 为当前请求/最近回包的 CGI(搜索 106、添加 137、补取联系人 182、预检查 11568);搜索的 event_id 为提交返回的 ID,添加为最近原生回调的 ID,尚无回包时为 null。它们仅用于诊断。
常见错误码:INVALID_KEYWORD、INVALID_PARAMETERS、INVALID_SAY_CONTENT、SEND_DISABLED、ACCOUNT_UNAVAILABLE、ACCOUNT_CHANGED、CONTACT_REF_EXPIRED、CONTACT_REF_CONFLICT、ADD_IN_PROGRESS、CANDIDATE_CACHE_FULL、TOO_MANY_REQUESTS、NATIVE_API_UNAVAILABLE、REQUEST_NOT_STARTED、PRECHECK_NOT_STARTED、PRECHECK_FAILED、WECHAT_INTERACTION_REQUIRED、WECHAT_REQUEST_FAILED、SEARCH_FAILED、WECHAT_TRANSPORT_FAILED、RESPONSE_INCOMPLETE、RESPONSE_INVALID、FRIEND_OPERATION_TIMEOUT。微信返回普通 -44 时最多一次进入原生发送验证消息入口;交互限制和预检查失败不会自动继续。
探针接受 {} 或 {"contact_ref":"fc_example_ref"}。它不发送搜索/好友申请,返回 search_runtime、add_runtime(ready 及各 selector 的 compatible/encoding)、logged_in、send_enabled、cached_candidates、pending_operations、contact_ref_valid、expires_in、has_antispam_ticket、reference_status 和 last_operation。last_operation 只保留最近一次操作的状态、阶段、返回码和受理信息;不包含账号、关键词、验证消息或原始票据。出现 NATIVE_API_UNAVAILABLE 时先调用探针定位不兼容项。
已取得用户提供的 HTTP 真机搜索成功回包,确认本次已有好友场景返回头像、大图头像、性别和 is_friend:true,地区及签名为空。用户现已确认搜索与添加好友功能在微信 8.0.80 验证通过;资料字段是否有值仍以微信本次回包为准,人工处理和超时按本节状态规则处理。
-44 和发送状态的判断:stage:add_contact 表示直接添加阶段;request_submitted:true 只表示原生请求受理,不能据此认定验证申请已经发出。新增 verification_attempted 表示是否进入过发送验证消息入口;只有最终 status:request_sent 才表示微信确认验证申请发送成功。旧包在 add_contact 返回 needs_user_action,-44 时可能尚未进入验证消息阶段。
交互条件按现有原生链路判断:spam 提示标题、正文及 ctx 同时非空才要求人工确认;仅存在空的 verifyUserSpamInfo 对象或仅有 ctx,不会拦住普通 -44 的一次验证消息转换。微信原生可处理的 XML 提示、预检查失败及其他明确限制仍停止自动继续。
新增脱敏 failure_detail,保留最近一次错误分类,即使随后发送验证成功也可能保留此前的 verification_required:reason 为 spam_challenge、native_error_dialog、error_xml_unreadable、precheck_failed、verification_required 或 wechat_error;native_ret 为该次错误码;并提供 has_spam_info、has_spam_title、has_spam_detail、has_context、spam_challenge、has_error_xml、xml_show_type、xml_handled、xml_unreadable。不返回原始 ctx、XML、票据或提示文本。最终结果以外层 status/native_ret 为准,不能把历史 failure_detail 当成最终失败。
friend_add_probe 的 last_operation 同时返回 verification_attempted 和 failure_detail;群场景也可在 room_candidate.last_result 查看。该判定和诊断同时用于 add_friend 与 add_room_friend,不新增接口。
成功回包按实际请求区分:CGI137 的 opcode 1(直接添加)仍要求联系人标识;opcode 2(发送验证消息)允许回包缺少用户名,插件继续执行微信原生申请记录保存和成功回调,收到 contactSendRequestOk: 后才返回 request_sent。不会仅凭 native_ret:0 合成成功。旧包在 send_verification 阶段可能将这种正常回包误报为 RESPONSE_INCOMPLETE,并把已取得的 0 丢成 null;该旧结果不能证明服务器拒绝申请,应先核对状态,避免重复发送。
取得 CGI137 回包后,新增可选脱敏 response_detail:native_ret 为本次业务返回码,request_opcode 为实际请求操作码(无法读取时为 null),has_response_username 表示回包用户名是否非空。添加结果、探针 last_operation 及群候选 last_result 均可查看。若微信返回 0 后插件本地校验或后处理失败,外层仍保留 native_ret:0,须同时检查 ok/status/error_code;0 不代表插件已完成全部处理。没有本次回包时不提供该详情;探针未发生操作时为 {}。详情不包含用户名、消息正文或票据。
添加群成员好友
命令为 add_room_friend,参数放在 args;放入 /agent/poll 响应的 commands 数组:
{"request_id":"room_add_demo_1","command_name":"add_room_friend","args":{"room_id":"123456789@chatroom","wxid":"wxid_example_member","say_content":"你好,我们在同一个群里"}}
结果沿用 request_id/command_name/ok/result/error 信封。以下结果示例采用本模式命令信封:外层保留 ok/error,其余业务字段位于 result。friend_add_probe 同样接受 args.room_id 和 args.wxid,且不发送申请。
已知共同群和成员原始 wxid 时,使用独立的 add_room_friend。搜索添加仍为 search_contact → add_friend,不把 wxid 塞进搜索流程,也不要求调用方提供手机号、微信号、scene 或票据。
请求参数:
{"room_id":"123456789@chatroom","wxid":"wxid_example_member","say_content":"你好,我们在同一个群里"}
room_id、wxid必填,均为 1–256 字符的字符串,不含空白。只支持普通@chatroom群和个人联系人,暂不支持 OpenIM、公众号、群联系人或本人。wxid使用/room_members/room_members成员结果的account_id(原始成员标识),也可使用已取得的原始 wxid;不能传昵称或群昵称。room_id必须是该成员所在的共同群。say_content可省略,默认为空;仅接受字符串,会去除首尾空白,沿用add_friend的原生长度校验,过长拒绝发送。
插件先复用当前账号状态、群联系人和群成员列表入口,确认本人和目标都在该群;再按原生群资料链路请求 CGI182,携带目标和 fromChatRoom,按用户名匹配本次响应的群成员票据。只有取得有效联系人和票据后,才将群 ID、scene 14 和完整联系人交给 add_friend 共用的原生预检/添加执行器;普通 -44 最多一次转换为发送验证消息。调用方无需点击页面,也无需传递原始票据。列表缺失或成员关系未就绪会失败,不自动同步整个群。
命令结果格式示例(省略公共字段,身份字段为示意值):
{
"request_id": "room_add_demo_1",
"command_name": "add_room_friend",
"ok": true,
"result": {
"status": "request_sent",
"source": "room",
"room_id": "123456789@chatroom",
"username": "wxid_example_member",
"is_friend": false,
"native_ret": 0,
"stage": "send_verification",
"contact_fetch_submitted": true,
"has_group_member_ticket": true,
"request_submitted": true,
"request_cgi": 137,
"event_id": 42
},
"error": ""
}
status 沿用搜索添加的 already_friend、request_sent、added、pending_sync、needs_user_action、timeout、failed;request_sent 仅表示微信确认申请已发送。已有好友不取群票据、不发添加请求。native_ret:null 表示没有可用的微信业务结果。群路径不返回 contact_ref;前置校验失败可能只有公共错误字段。
stage 增加 room_contact。contact_fetch_submitted 仅表示群资料请求获原生受理,has_group_member_ticket 仅表示本次取得的票据非空,二者都不表示好友申请成功。request_submitted 仍仅表示共享添加链路获原生受理,不包含群资料请求和独立预检查。request_cgi/event_id 为当前或最近回包诊断,CGI182 为群资料、11568 为预检查、137 为添加。发生超时、回包不完整或后处理失败时,先核对好友状态,不自动重发。
同一登录会话、同一群、同一成员、相同规范化文字,在首次请求创建起 300 秒内共享在途操作或复用最终结果(包括失败和超时);不同文字返回 ROOM_ADD_CONFLICT。最多保留 50 个群操作记录;共享在途上限 16 个。同一目标经另一群或搜索路径正在添加时返回 ADD_IN_PROGRESS。跨群、跨重启、切换账号或超过窗口不承诺去重,后端应记录操作结果,避免重复下发。整个流程单次等待,操作期限 30 秒,调用方超时建议至少 40 秒。
群相关错误包括 INVALID_PARAMETERS、ROOM_NOT_FOUND、ROOM_MEMBERS_UNAVAILABLE、NOT_IN_ROOM、CANNOT_ADD_SELF、TARGET_NOT_IN_ROOM、UNSUPPORTED_CONTACT、ROOM_CONTACT_FETCH_FAILED、ROOM_CONTACT_NOT_FOUND、GROUP_MEMBER_TICKET_MISSING、ROOM_ADD_CONFLICT;通用运行时、发送开关、切号、超时、预检及微信限制错误沿用 add_friend。GROUP_MEMBER_TICKET_MISSING 明确表示未发起好友申请,不会退回无来源 wxid 添加。
排障继续使用只读 friend_add_probe(HTTP POST /debug/friend_add_probe),可传:
{"room_id":"123456789@chatroom","wxid":"wxid_example_member"}
新增 room_runtime(群链路签名 ready/checks)、room_preflight(本地成员校验结果或明确错误)、room_candidate(cached/status/has_contact/has_group_member_ticket/last_result);last_result 为该群成员缓存的完整业务结果,仅在调用过添加后存在。last_operation 增加 source:search|room,仍为脱敏诊断。探针不联网取资料、不发送申请,不返回原始票据。首次使用先检查 add_runtime.ready 和 room_runtime.ready,再检查 room_preflight.ok;添加失败后用同一参数查看阶段与原始返回码。
群内添加好友已由用户在微信 8.0.80 验证通过;受限/人工处理和重复请求仍按本节约定处理。
delete_friend / delete_session / quit_group
{ "id": "…" }
12.5 群昵称、群名、公告与群二维码
kick_group_member
{ "room_id": "…", "member_ids": ["wxid_a", "wxid_b"], "member_count": 2 }
member_id 与 member_ids 可同时传,自动合并去重。
rename_group / set_group_self_nickname(同一命令的两个名字)
修改当前账号在该群里的群昵称(微信里「我在本群的昵称」),不是修改群名称。
{ "room_id": "…", "new_name": "我在本群的新昵称" }
new_name 允许空串,表示清空群昵称、恢复默认(显示微信昵称):
{ "room_id": "…", "new_name": "" }
返回:{"room_id": "…", "new_name": "", "clears_group_nickname": true}。整个字段缺失才报 new_name is required。
set_group_name
修改整个群聊的名称。与上面的群内个人昵称命令不同,room_id 必须是以 @chatroom 结尾的字符串,new_name 必须是去除首尾空白后非空的字符串;不支持传空串清空群名。
下发时使用 command_name: "set_group_name",args 示例:
{ "room_id": "123456@chatroom", "new_name": "新的群聊名称" }
成功时,命令结果信封的 result 示例:
{
"room_id": "123456@chatroom",
"new_name": "新的群聊名称",
"accepted": true,
"local_verified": true,
"already_named": false,
"local_name": "新的群聊名称"
}
accepted 只表示微信底层接受;local_verified 表示本机群联系人的 nick_name 已读回目标值。ok: true 时 local_verified 仍可能为 false。如果本地群名已是目标值,返回 ok: true、already_named: true、accepted: false、local_verified: true,不重复提交。以上结果不代表其他成员端已经同步。
失败时结果信封的 ok: false,error 为具体原因,result 保留请求字段和状态字段。只读探针命令 set_group_name_probe 的 args 只需 room_id;它不修改群名,需检查结果中的 set_group_name_ready,不能只看信封的 ok。
get_group_notice
args 只需 {"room_id":"123456@chatroom"}。成功时 command_result.result 示例:
{
"room_id": "123456@chatroom",
"cached_notice_found": true,
"summary": "微信提供的文本摘要",
"html": "<p>公告正文</p>",
"html_cached": true
}
summary 只是微信提供的文本摘要;html 是本地缓存文件中的完整原始 HTML。正文文件未缓存或无法读取时,html 为 null、html_cached 为 false,仍可能有摘要。本地未读到公告项时,cached_notice_found 为 false,summary 和 html 均为 null;这不能证明群里没有公告。接口不触发下载或发布,缓存也可能不是最新内容。请求失败时看结果信封的 ok: false 和 error。
get_chatroom_qrcode:阻塞获取指定群二维码
每次调用按微信原生协议创建独立请求,通过当前登录会话的 EventService 发起取码,在同一次调用中等待微信本次响应;不返回查询中状态,不需要任务 ID。同群并发共用正在处理的请求,完成后再次调用会重新取码。最多 32 个不同群的待处理请求,约 30 秒超时,接口等待保护上限约 31 秒;调用方超时建议至少 40 秒。
唯一参数 room_id 必须是实际群 ID 字符串,例如 example@chatroom,不能传群名。当前账号须能找到该群联系人,最终权限以微信响应为准。
| 字段 | 类型 | 含义 |
|---|---|---|
room_id |
string | 目标群 ID |
status |
string | ok / restricted / failed / timeout |
qr_code_url |
string / null | 本次响应里的二维码内容,即扫码承载的入群链接;不是 PNG 下载地址 |
has_qrcode |
bool / 0或1 | 是否取得 URL 或可解码图片;受限提示图也会为真,不代表可扫码入群 |
has_image |
bool / 0或1 | 是否含可解码图片,可能是受限提示图 |
image_base64 |
string / null | 服务器返回图片经解码后统一编码的 PNG base64,不含 data URI 前缀 |
image_mime_type |
string / null | 有图片时为 image/png |
qrcode_status |
uint32 / null | 微信二维码原始状态;不是 BOOL,缺失时不补 0 |
restricted |
bool / 0或1 / null | 已知二维码状态是否非零;未知时为 null |
footer_wording |
string | 微信本次返回的说明,可能包含有效期或限制文字,不自行推算 |
wechat_ret |
int32 / null | 微信业务原始错误码 |
wechat_error_message |
string | 微信 baseResponse.errMsg.string 原文,可能为空 |
message_type |
uint32 / null | 微信网络消息类型,1 视作失败 |
transport_error_type / transport_error_code |
int32 / null | 微信传输错误分类及原码,保留负数 |
error_code |
string | 失败或受限时的插件错误分类 |
request_submitted |
bool / 0或1 | 本次原生事件服务是否返回非零事件 ID,不等于服务器业务成功 |
request_cgi |
uint32 / null | 本次请求 CGI:普通群 168、OpenIM 890,准备前失败为 null |
event_id |
uint32 / null | 本次已受理的原生事件 ID,仅用于诊断,无需用它轮询 |
status=ok 表示本次业务响应成功且取得二维码内容,不能保证扫码立即入群。restricted 对应微信非零二维码状态,业务 ok=false,error 提供中文分类说明;具体原因可能在可读文本字段或返回图片中;即使有图或 URL 也不作为普通成功处理。没有恢复具体状态枚举,不写死“1=超员、2=开启验证”。网络失败、微信 ret != 0、响应不完整和超时均返回失败信息,不返回旧缓存的码。
人数阈值由微信配置控制(静态样本默认 200,客户端判断人数达到阈值);开启管理员进群验证也不一定禁止取码,新管理方式可以扫码申请审批。因此不按人数或验证开关在本地直接拒绝,以本次微信原码与文案为准。
普通群可能只有 URL,没有服务器图片;调用方可用 qr_code_url 自行生成二维码,无需手机下载再上传。接口不会读取微信磁盘缓存,也不返回本地文件路径。微信保存的完整分享卡片还会组合群头像、群名和有效期提示,本接口不拼装该卡片。
OpenIM 按微信原生群类型判断分流至 CGI890;该分支可能只有图片,没有 URL,也没有二维码状态,此时 qrcode_status、restricted 为 null。仍校验本次原始业务响应,不能依赖原生固定为 0 的完成通知。
常见 error_code:INVALID_ROOM_ID、ACCOUNT_UNAVAILABLE、ACCOUNT_CHANGED、ROOM_NOT_FOUND、NATIVE_API_UNAVAILABLE、TOO_MANY_REQUESTS、REQUEST_NOT_STARTED、NATIVE_REQUEST_FAILED、RESPONSE_INVALID、WECHAT_REQUEST_FAILED、RESPONSE_INCOMPLETE、QRCODE_RESTRICTED、QRCODE_TIMEOUT。REQUEST_NOT_STARTED 仅在原生事件服务返回事件 ID 为 0 时使用,不能据此推断群人数或验证限制。request_submitted=true 仅表示微信事件服务已受理,不代表服务器成功;若之后返回 QRCODE_TIMEOUT,表示尚未取得本次最终响应。
中文原因取自微信可读字段:footer_wording 和 wechat_error_message。受限时优先使用非空 footer_wording 作为 error;没有该文案时返回通用中文“微信返回二维码受限状态”,并原样保留 wechat_error_message。微信可能将具体原因直接写在返回图片里,两个文本字段均为空;本接口不做图片文字识别,也不根据 qrcode_status=1 等原始值硬编码具体原因。
用户提供的普通群真机响应已确认:请求受理并收到 wechat_ret=0,qrcode_status=1,返回 PNG,qr_code_url=null,footer_wording、wechat_error_message 均为空;人工查看图片文字为“该群已开启进群验证,只可通过邀请进群”。这只说明该次图片的含义,不是所有状态值 1 的固定映射。
has_qrcode、has_image、restricted、request_submitted 可能序列化为布尔值或数字 0/1,restricted 还可能为 null。has_image=1、has_qrcode=1 只表示有返回内容,不保证图片是可用的入群码。业务是否成功应先检查 ok 和 status。
群二维码功能已由用户在微信 8.0.80 验证通过;取码成功不保证立即入群,实际扫码仍受群设置及微信返回限制。上述受限图片记录不构成固定状态码映射,也不能据此扩大 OpenIM 支持范围。
受限响应示例(节选;群 ID 和事件 ID 为占位值,实际 image_base64 是 PNG Base64 字符串,本文省略图片内容):
{
"request_id": "group-qr-001",
"command_name": "get_chatroom_qrcode",
"ok": false,
"result": {
"room_id": "example@chatroom",
"status": "restricted",
"qr_code_url": null,
"has_qrcode": 1,
"has_image": 1,
"image_mime_type": "image/png",
"qrcode_status": 1,
"restricted": 1,
"footer_wording": "",
"wechat_ret": 0,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0,
"request_submitted": 1,
"request_cgi": 168,
"event_id": 42,
"error_code": "QRCODE_RESTRICTED"
},
"error": "微信返回二维码受限状态"
}
下发一次命令:
{
"commands": [
{
"request_id": "group-qr-001",
"command_name": "get_chatroom_qrcode",
"args": {
"room_id": "example@chatroom"
}
}
]
}
最终结果示例(省略公共身份与时间字段):
{
"request_id": "group-qr-001",
"command_name": "get_chatroom_qrcode",
"ok": true,
"result": {
"room_id": "example@chatroom",
"status": "ok",
"qr_code_url": "https://weixin.qq.com/g/example",
"has_qrcode": true,
"has_image": false,
"image_base64": null,
"image_mime_type": null,
"qrcode_status": 0,
"restricted": false,
"footer_wording": "",
"wechat_ret": 0,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0,
"request_submitted": true,
"request_cgi": 168,
"event_id": 42
},
"error": ""
}
ok、error 在命令信封外层,result 保留原始状态、错误码与文案。调用方等待最终命令结果的期限建议至少 40 秒;长轮询的 /agent/poll 请求仅用于命令投递,其超时不等于命令完成期限。两个查询按账号会话合并在途请求,支付姓名复用终态缓存,群二维码每次重新取码,不使用仅按 request_id 保存的通用旧结果。长轮询按原有批次顺序等待本条结果并上报成功后继续下一条。
12.6 红包与转账
确认收款
receive_transfer 参数 transfer_id、message_id 均为 string,至少一项非空;推荐两个都传且对应同一笔转账。结果使用本模式的 command_result 信封,ok、error 位于外层,精简业务字段位于 result。进入收款业务后的失败同样保留这些字段;插件/发送开关、权限或队列等前置拒绝沿用通用失败信封,不保证业务字段齐全。
result 字段 |
类型 | 说明 |
|---|---|---|
transfer_id、message_id |
string | 本笔转账与消息编号,未能解析时可能为空字符串 |
status |
string | 本次收款状态;见下方说明 |
accepted |
boolean | 已进入现有收款流程的状态标记;不能代替 confirmed 判断到账 |
confirmed |
boolean | 是否已确认本笔收款成功 |
callback_received |
boolean | 是否收到匹配本次操作的确认收款回包;独立查询完成确认时也可能为 false |
ret_code |
integer / null | 确认收款回包返回码;没有取得合法返回码时为 null,不以 0 代填 |
native_status |
integer(可选) | 仅在真实资金状态查询完成本次收款操作时出现;不根据 callback_ok 推测为 2001 |
收到正常确认收款回包的结果示例(省略公共设备/会话字段):
{
"request_id": "receive_example_1",
"command_name": "receive_transfer",
"ok": true,
"result": {
"transfer_id": "transfer_example",
"message_id": "message_example",
"status": "callback_ok",
"accepted": true,
"confirmed": true,
"callback_received": true,
"ret_code": 0
},
"error": ""
}
命令等待原生回包约 5 秒。status="confirm_invoked" 表示已发起收款、等待已超时,返回外层 ok=true,业务字段为 accepted=true, confirmed=false, callback_received=false, ret_code=null,不能认定到账。callback_received 仅是辅助信号;匹配实际确认收款回包且成功时为 callback_ok,或独立查询取得原生状态 2001 后为 confirmed,才会得到外层 ok=true, result.confirmed=true。
收到回包却缺少合法返回码时,失败结果示例:
{
"request_id": "receive_example_1",
"command_name": "receive_transfer",
"ok": false,
"result": {
"transfer_id": "transfer_example",
"message_id": "message_example",
"status": "callback_unknown",
"accepted": true,
"confirmed": false,
"callback_received": true,
"ret_code": null
},
"error": "转账回调缺少状态码,收款结果未知,请勿自动重试"
}
callback_unknown 或等待超时应先用 get_transfer_status 查询,原生状态 2001 才确认已收取;不要直接重试。接收与退还同笔互斥,未知结果先查终态。
业务结果不再包含整份 content/content_xml、native_url、callback、pay_info_probe 或 runtime_probe。排错可使用本模式已有的 transfer_probe 命令,args 传 transfer_id/message_id,或通过 bridge_status 读取完整诊断;探针和诊断快照不能代替业务成功判断。
转账:退还与查询状态
命令:refund_transfer 退还待收取转账;get_transfer_status 只查询服务器资金状态,均已加入 capabilities。退还要求插件和发送开关开启;查询不受发送开关限制,仍需通过本模式配置、鉴权和插件全局校验。
服务端放入 POST /agent/poll 响应的命令项示例;查询时只把 command_name 改为 get_transfer_status,使用新的外层 request_id。poll 响应的其余配置字段按本文第 8 节:
{
"commands": [
{
"request_id": "refund_example_1",
"command_name": "refund_transfer",
"args": {
"transfer_id": "transfer_example",
"message_id": "message_example"
}
}
]
}
结果沿本模式 command_result 外壳,ok、error 放外层,其他基础字段(包括 error_code)保留在 result;下面省略公共设备/会话字段。失败也要读取 result,不能从 diagnostics 猜测结果。
{
"request_id": "refund_example_1",
"command_name": "refund_transfer",
"ok": true,
"result": {
"transfer_id": "transfer_example",
"message_id": "message_example",
"accepted": true,
"confirmed": false,
"status": "refund_pending",
"native_status": null,
"ret_code": null,
"error_code": ""
},
"error": ""
}
{
"request_id": "query_example_1",
"command_name": "get_transfer_status",
"ok": true,
"result": {
"transfer_id": "transfer_example",
"message_id": "message_example",
"accepted": false,
"confirmed": false,
"status": "waiting_receive",
"native_status": 2000,
"ret_code": 0,
"error_code": "",
"can_refund": true,
"status_description": "等待收款",
"native_status_name": "waiting_receive"
},
"error": ""
}
{
"request_id": "refund_example_1",
"command_name": "refund_transfer",
"ok": true,
"result": {
"transfer_id": "transfer_example",
"message_id": "message_example",
"accepted": true,
"confirmed": true,
"status": "refunded",
"native_status": 2002,
"ret_code": 0,
"error_code": ""
},
"error": ""
}
{
"request_id": "refund_example_1",
"command_name": "refund_transfer",
"ok": false,
"result": {
"transfer_id": "",
"message_id": "",
"accepted": false,
"confirmed": false,
"status": "failed",
"native_status": null,
"ret_code": null,
"error_code": "CACHE_NOT_FOUND"
},
"error": "转账上下文已失效,请使用本次运行收到的转账消息"
}
本轮状态(2026-11-05):用户此前已确认普通微信群内指定给自己的个人转账可领取;在 0.8.79-79 交付后,用户回传同笔转账查询 waiting_receive / native_status=2000 / can_refund=true,以及退还后 refunded / native_status=2002 / accepted=true / confirmed=true / ret_code=0,群内个人转账的查询和退还正常路径已实测通过。接口参数及结果结构不变,群 ID 从原消息读取,无需调用方补传。标准 Ubuntu 构建通过,新包为 0.8.79-79;旧包 0.8.79-78 的查询/退还仍保留群限制。此次回包未单独覆盖 WebSocket/长轮询外层收发、结果事件、重复操作、断网与账号切换,也未重新回传接收结果。
范围与前置条件:本轮源码支持当前登录账号作为收款人的普通微信个人转账(app_msg_type=2000),包括普通微信群内他人指定给当前账号的个人转账;不包括群收款/AA、企业互通群、任一方为 @openim 的交易、已收取后的退款或部分金额退还。群内转账须能读取原消息的个人付款人、接收人及群 ID,并确认本地群联系人属于普通微信群;群资料缺失或类型无法确认时仍拒绝。查询与退还都依赖本次运行收到的原转账消息,并要求缓存包含付款人、交易编号和已明确读取的非负整数有效期(允许 0,不等同于字段缺失);本版不提供按历史交易号恢复上下文的入口。上下文按当前账号保存在内存中,最多 120 笔;重启、账号会话变化或淘汰后可能返回 CACHE_NOT_FOUND,持久化 ID 不能恢复该缓存。
请求参数:两个接口使用同一组参数,无其他业务输入;不能补传付款人、金额、交易号或有效期绕过原消息核验。
| 参数 | 类型 | 必填与默认值 | 说明 |
|---|---|---|---|
transfer_id |
string | 与 message_id 至少一项非空;无默认值 |
原转账事件的转账 ID,按字符串原样保存 |
message_id |
string | 与 transfer_id 至少一项非空;无默认值 |
原转账消息 ID,按字符串原样保存 |
可以省略未提供的字段;不得传数字、null、数组或对象。去除首尾空白后两个字段都为空时失败;同时提供时必须指向同一笔,否则 ID_MISMATCH。
结果字段:下表说明业务结果,外层封装按本节本模式的示例读取。进入业务服务后固定返回这些基础字段;通道鉴权、插件禁用等更早的拒绝可采用本模式通用错误外壳,不能假设总有业务字段。
| 字段 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 本次调用是否按该操作的约定完成;查询成功或退还已进入等待都可能为 true,不代表已退还 |
transfer_id |
string | 解析到的转账 ID;前置失败可能为空串 |
message_id |
string | 解析到的消息 ID;前置失败可能为空串 |
accepted |
boolean | 退还动作已进入原生提交阶段;查询恒为 false。true 时即使发生异常,也不能排除已发出交易 |
confirmed |
boolean | 已取得原生终态 2001/2002/2003;不是退还成功专属标志 |
status |
string | 规范化业务状态,见下表 |
native_status |
integer 或 null | 微信原生资金状态;未取得为 null,未知数值保留原值 |
ret_code |
integer 或 null | 微信回包错误码;未取得为 null;非零时结合 error_code 判断 |
error_code |
string | 业务错误码;成功时为空字符串,不省略 |
error |
string | 错误说明;成功时为空字符串 |
查询结果还可能返回 can_refund(boolean)、status_description(string)和 native_status_name(string,微信回包原生名称,可能为空);微信错误回包可能带 wx_error_type(integer 或 null;回包未提供时为 null)。can_refund=true 仅表示服务器状态待收取、普通交易、双方匹配且无同笔未决收款/退还占用;不包含发送开关,也不保证后续风控允许退还。
status |
native_status |
confirmed |
含义与处理 |
|---|---|---|---|
waiting_receive |
2000 | false | 等待收款;查询不会主动收款或退还 |
received |
2001 | true | 已收取;本版不能再退还,退还调用返回 NOT_REFUNDABLE |
refunded |
2002 | true | 唯一确认已退还的资金状态;退还预检发现已退还时 ok=true, accepted=false |
expired |
2003 | true | 已过期;退还调用返回 NOT_REFUNDABLE |
unknown |
2004/2005/其他原值 | false | 含义未核实;查询保留原值,退还预检返回 NOT_REFUNDABLE 且不提交。已提交后的确认查询遇到未知/非终态时保持 refund_pending |
refund_pending |
null | false | 退还已提交,约 5 秒短等待结束仍无终态;继续查询 |
refund_unknown |
通常 null | false | 提交后出现异常或无法验证回包;不能直接认定失败或自动重提 |
failed |
null 或已取得原值 | 由原值决定 | 参数、前置或微信回包错误;必须一起读取 accepted、error_code 和 ret_code |
成功判定与等待:只有 status="refunded" 且 native_status=2002 才代表已退还。ok=true、accepted=true、HTTP/命令传输成功或收到事件,均不能单独作为退还成功判据。两个新操作在当前请求/命令中短等待约 5 秒;排队、主线程调度和网络时间另计,客户端建议至少等待 15 秒。预检/查询超时且未提交时返回 QUERY_TIMEOUT, accepted=false;已提交但仍无终态时返回 refund_pending, accepted=true。提交后的确认查询可能返回 WECHAT_ERROR, accepted=true,仍须继续查询,不能认定退还确定失败。
重试与互斥:同一笔转账的接收与退还互斥,未决操作会返回 TRANSFER_BUSY;结果未知时保留占用,查询到终态后可回收占用。退还请求被微信明确拒绝才解除该退款提交的占用。重复调用不是按 request_id 持久幂等:ID 用于定位同笔转账,通道 request_id 只关联命令结果。网络超时、refund_pending、refund_unknown 或 accepted=true 的失败,都应先调用状态查询,不要改 ID、重启或自动重提退还/收款。未决保护不保证跨进程或跨账号会话延续。
业务错误码:失败保留结构化结果,不要只记录错误文字。
error_code |
说明 |
|---|---|
INVALID_ARGUMENT |
编号类型错误,或两个编号都为空白;本次未提交退还。 |
CACHE_NOT_FOUND |
本次运行没有该转账上下文,可能已重启或被淘汰;不能补传交易信息绕过。 |
ID_MISMATCH |
两个编号未指向同一笔转账。 |
NOT_LOGGED_IN |
当前微信账号未登录。 |
NOT_RECEIVER |
当前账号不是该转账的接收方。 |
UNSUPPORTED_TRANSFER |
群收款/AA、企业互通等不支持的交易,或无法确认群资料/普通群类型;普通微信群内指定给当前账号的个人转账不因来自群而拒绝。 |
INCOMPLETE_CONTEXT |
原消息缺少付款人、交易编号或可确认的非负整数有效期;明确读取到 0 可以使用,字段缺失仍拒绝。 |
UNSUPPORTED_RUNTIME |
当前微信运行时的请求关联或回包能力未就绪。 |
TRANSFER_BUSY |
同笔转账存在未决收款/退还操作;先查询,不要重提。 |
CAPACITY_REACHED |
未决操作容量已满;先查询已有操作。 |
ACCOUNT_CHANGED |
等待期间账号或账号会话发生变化;旧操作不能在新账号继续。 |
WRITE_DISABLED |
插件或发送开关不允许退还;查询不受发送开关限制。 |
REQUEST_NOT_CAPTURED |
未捕获到可以安全关联的微信请求;根据 accepted 判断是否已进入提交。 |
NATIVE_EXCEPTION |
调用微信发生异常;accepted=true 时结果未知,禁止直接重提。 |
INVALID_RESPONSE |
微信回包格式不完整或无法关联;提交后按未知处理。 |
WECHAT_ERROR |
微信返回错误;读取 ret_code 和可选 wx_error_type。提交后的确认查询报错不等于退还确定失败。 |
IDENTITY_MISMATCH |
服务器交易双方与消息上下文不一致;预检时不会提交,accepted=true 时则表示退还已提交、后续确认结果未知,先查询。 |
NOT_REFUNDABLE |
服务器确认当前状态不能退还;保留真实 status/native_status。 |
QUERY_TIMEOUT |
约 5 秒内未取得查询/预检结果,本次未提交退还。 |
MAIN_THREAD_CALL |
内部调用线程不符合等待要求;应通过本模式的正常入口调用。 |
验证范围(2026-11-04):用户确认验证通过。已回传普通个人转账查询由 waiting_receive / native_status=2000 / can_refund=true 到退还后 refunded / native_status=2002 / accepted=true / confirmed=true 的真机结果,并提供接收转账 callback_ok / ok=true / accepted=true / confirmed=true 的成功回包。正常查询、接收与退还业务链路已有历史真机记录。用户现已确认转账相关功能在微信 8.0.80 验证通过;重复操作、网络异常与账号切换仍按本节状态和重试规则处理。
退款完成/失败/未知时还可能收到 transfer_refund_result 事件;事件不可替代主动查询。
12.7 个人资料设置与地区代码
clear_gender_region
{ "fields_cleared": ["sex", "country", "province", "city"], "restart_recommended": true }
资料设置与地区代码查询
上述 9 个命令由长轮询与 WebSocket 的共享分发器执行,复用已通过 HTTP 真机测试的原生资料链。资料写命令要求发送开关开启;地区查询和探针不要求发送开关。均受命令通道的插件授权/启用检查。头像仍延后,没有头像写命令。
写入参数:
| 命令 | args 示例 |
|---|---|
profile_gender |
{"sex":1} |
profile_region |
{"region_code":"CN_Guangdong_Shenzhen"} |
profile_region |
{"region":{"country":"CN","province":"Guangdong","city":"Shenzhen"}} |
profile_signature |
{"signature":"今天也要开心"} |
profile_update |
{"sex":2,"region_code":"CN_Guangdong_Shenzhen","signature":"今天也要开心"} |
profile_update |
{"sex":0,"region_code":"","signature":""}(清空三项) |
profile_pat_suffix |
{"pat_suffix":"的小脑袋"};空字符串清空 |
资料字段采用严格 JSON 类型:sex 只接受整数 0/1/2,拒绝布尔值、小数(包括 1.0)、字符串、null 和越界值。文本必须是字符串,未知字段拒绝。未提供的资料字段保持原值;单字段命令只接受各自字段。地区整组替换,省市缺失就清空;子级不能脱离父级。region_code 和 region 不可同时传,空代码或空对象清空地区。
签名保留原始空白,微信原生加权计数最多 30:ASCII UTF-16 单元权重 1,其他单元权重 2,总权重/2 向上取整;例如 30 中文或 60 ASCII 可通过。超限拒绝、不截断。拍一拍不能放进 profile_update,需独立提交;其文本按微信原生规则处理空白/换行、计数和禁止字符。限制动态读取,本次真机上限 10、禁止字符 <>/,以 profile_pat_suffix_probe 的 capabilities.max_length 和 capabilities.forbidden_characters 为准。
地区代码查询:
profile_region_codes,args:{}或{"parent_code":""}:查询国家/地区列表。- 从
result.items[].code取得国家代码,如CN,传{"parent_code":"CN"}查询省级。 - 从省级列表取得
CN_Guangdong,传{"parent_code":"CN_Guangdong"}查询市级。 - 取得完整代码
CN_Guangdong_Shenzhen后,用profile_region设置region_code。
result 示例(仅展示一项):
{
"parent_code": "CN_Guangdong",
"count": 1,
"items": [{"code":"CN_Guangdong_Shenzhen","name":"深圳","parent_code":"CN_Guangdong","type":2}],
"selector_probe": {}
}
count 为实际列表数量;code 是完整代码,name 随微信语言变化,type 取不到时为 null。不要拼中文名称;叶节点或不存在的父代码可能返回空列表。profile_region_probe 复用同一查询能力。
提交与完成状态:
- 性别/地区/签名结果的
status为submitted或unchanged;accepted是微信受理,local_verified是本地读回,server_confirmed:false。最终展示以微信为准。失败时result保留受理状态和读回值,错误信息在信封顶层error。submitted_with_error可能已入队,先核对状态再处理。 - 拍一拍立即返回
result.status:"pending"、operation_id、数字event_id、requested、normalized、length、max_length等;顶层ok:true只表示提交受理。duration_ms不包含后续等待微信服务器的时间。 - 随后用 新的
request_id下发profile_pat_suffix_probe、args:{},检查result.last_operation。status:"succeeded"且server_confirmed:true才是服务器确认;failed查看内部error;unknown表示 120 秒无回调或提交后异常,不要盲目重试。pending期间拒绝第二项修改。 - 结果只通过探针查询,不会再主动推送一条拍一拍完成的
command_result。相同request_id会复用已缓存的初次结果,即使原操作后来完成也仍可能拿到pending;每次状态查询必须生成新的 ID。 - 只保留最近一次拍一拍操作,重启微信后记录消失。用
operation_id/event_id对照本次结果;该数字event_id是微信 oplog ID,与消息事件的去重键不同。
profile_probe 和 profile_pat_suffix_probe 可传候选参数预校验,返回 validation.valid、normalized、error、writes_performed:false,不会提交。探针顶层 ok:true 只说明查询执行完毕,应继续检查 profile_ready、pat_suffix_ready 或 validation.valid。
下发示例:
{
"commands": [
{
"request_id": "req-region-codes-001",
"command_name": "profile_region_codes",
"args": {
"parent_code": "CN_Guangdong"
}
}
]
}
拍一拍状态查询时更换 request_id 和 command_name,args 置空:
{
"commands": [
{
"request_id": "req-pat-status-002",
"command_name": "profile_pat_suffix_probe",
"args": {}
}
]
}
批量设置资料的完整 poll 响应:以下 JSON 由服务端作为 POST /agent/poll 的响应体返回。
{
"commands": [
{
"request_id": "req-profile-update-001",
"command_name": "profile_update",
"args": {
"sex": 2,
"region_code": "CN_Guangdong_Shenzhen",
"signature": "今天也要开心"
}
}
]
}
对应的资料提交结果:插件向服务端 POST /agent/command_result 上报,示例中本地读回匹配目标值。
{
"device_id": "webridge_device_xxxx",
"session_id": "sess_xxx",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1791936000000,
"request_id": "req-profile-update-001",
"command_name": "profile_update",
"ok": true,
"duration_ms": 120,
"result": {
"accepted": true,
"status": "submitted",
"local_verified": true,
"server_confirmed": false,
"requested": {"sex":2,"country":"CN","province":"Guangdong","city":"Shenzhen","signature":"今天也要开心"},
"before": {"sex":0,"country":"","province":"","city":"","signature":"","current_user":"wxid_self"},
"current": {"sex":2,"country":"CN","province":"Guangdong","city":"Shenzhen","signature":"今天也要开心","current_user":"wxid_self"},
"changed_fields": ["city", "country", "province", "sex", "signature"],
"readback_error": "",
"message": "资料已提交微信同步队列;本地读回不代表服务器确认"
},
"error": ""
}
资料操作失败时,命令信封顶层为 ok:false,具体错误在顶层 error,result 继续保留可取得的受理状态和读回值。探针预校验不通过时,探针命令本身仍可能 ok:true,应检查 result.validation.valid 和 result.validation.error。部分原生判断会以 0/1 输出,客户端应按布尔语义读取。
12.8 状态查询与后台保活
account_status
{
"is_logged_in": true,
"account_id": "wxid_self",
"user_name": "wxid_self",
"display_name": "测试账号",
"remark": "",
"alias": "",
"nick_name": "测试账号",
"avatar_url": "",
"sex": 0,
"province": "",
"city": "",
"signature": "",
"mobile": "",
"country": "",
"email": "",
"has_profile_data": true,
"wechat_version": "8.0.80"
}
device_status
命令无参数(args:{}),外层 ok/error 按命令结果信封判断;成功时 result 示例:
{
"ts_ms": 1780018858960,
"version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"device": {
"ios_version": "16.1.1",
"device_model": "iPhone14,3"
},
"process_memory": {
"phys_footprint_mb": 537.1,
"resident_mb": 753.5,
"internal_mb": 296.2
},
"system_memory": {
"available_estimate_mb": 1411.6,
"used_estimate_percent": 74.9,
"physical_total_mb": 5618.6
},
"battery": {
"percent": 89,
"state": "unplugged"
},
"power": {
"low_power_mode": false
},
"thermal": {
"state": "nominal",
"raw": 0
},
"network": {
"connected": true,
"network_type": "wifi",
"cellular_generation": ""
},
"application": {
"state": "background",
"foreground": false,
"active": false,
"background": true
}
}
| 分组 | 字段 | 说明 |
|---|---|---|
| 顶层 | ts_ms |
快照时间(毫秒) |
| 顶层 | version / build_marker |
插件版本与构建标记 |
device |
ios_version |
设备 iOS 版本,如 16.1.1。变长格式(16.1 就是两段)。读取失败时为空串 |
device |
device_model |
机型标识符,取自 uname 的 machine,如 iPhone14,3。插件不做商名映射,需要商名请自行映射;模拟器上可能是 arm64 / x86_64 |
process_memory |
12 个字段 | 6 个字节值(resident_bytes、virtual_bytes、phys_footprint_bytes、internal_bytes、compressed_bytes、reusable_bytes)+ 对应的 6 个 *_mb 值。取不到时输出 0,字段恒在 |
system_memory |
ok |
是否成功采集到系统内存 |
system_memory |
24 个测量字段 | page_size、physical_total_*、free_*、active_*、inactive_*、wired_*、compressed_*、speculative_*、purgeable_*、external_*、available_estimate_*(字节与 MB 双份)、used_estimate_ratio(0–1)、used_estimate_percent(0–100)、note |
system_memory |
⚠️ 采集失败时 | 只有 ok: false 和 error,上述 24 个字段全部不出现——你的解析代码必须容忍这种退化的响应 |
battery |
level / percent |
level 为 0–1,percent 为 0–100。读不到时值为 null(不是 0) |
battery |
state |
unplugged / charging / full / unknown |
power |
low_power_mode |
是否低电量模式 |
thermal |
state / raw |
nominal / fair / serious / critical / unknown,及原始值 |
network |
connected |
网络是否可达 |
network |
network_type |
wifi / cellular / none / unknown |
network |
cellular_generation |
5g / 4g / 3g / 2g / unknown;非蜂窝网络时为空串 "" |
application |
state |
active / inactive / background / unknown |
application |
foreground / active / background |
由 state 推导的三个布尔量 |
⚠️ 内存字段请用字节版或 MB 版,不要自己换算:
process_memory和system_memory都同时提供字节与 MB 两份,MB 版是字节 / 1024 / 1024。 ⚠️system_memory里的available_estimate是估算值(空闲 + 非活跃 + 推测 + 可回收),插件自己在note字段里也写了"仅作趋势参考"。
字节与 MB 字段同时提供,MB 为字节除以 1024²。system_memory.available_estimate_* 仅作趋势参考,不是系统可用内存的精确承诺。
bridge_status
插件的完整诊断快照,含数百个动态字段。用途是排障,不要依赖其中任何字段做业务判断。里面包含 version、build_marker 以及内部运行诊断(含长轮询自己的运行状态:agent_registered、agent_last_poll_success、agent_consecutive_failures、最近一次命令的 request_id 与结果等)。
保活诊断与测试:通过已有命令获取共享状态,无参数。将以下命令放入服务端 /agent/poll 的响应:
{
"commands": [
{
"request_id": "req-keepalive-status-001",
"command_name": "bridge_status",
"args": {}
}
]
}
在插件随后 POST 到 /agent/command_result 的 result 中读取下表字段,均带 background_keepalive_ 前缀;表中为省略前缀后的名称。例如策略的完整路径为 result.background_keepalive_strategy。每次查询使用新的 request_id;这些字段不会因开启保活就自动周期上报。
| 后缀 | 判断方法 |
|---|---|
enabled、audio_enabled、location_enabled |
保活总开关及保存的子配置;关闭总开关后子配置保留,可能仍为 true |
strategy、active |
后台有效通道为 audio / location / audio+location;degraded 为有检查定时器但无有效通道;foreground 为前台定位;stopped 为停止。active 仅在后台且至少一路有效时为 true |
audio_playing、audio_wait_reason、last_error |
音频实际播放、等待原因及原有保活错误 |
location_updates_requested、location_active |
已请求定位与本次启动收到有效定位;只发起请求不算成功,前台也可能定位有效 |
location_background_mode、location_authorization |
宿主定位声明及授权;启动需声明存在,权限为 when_in_use 或已有的 always |
location_wait_reason、location_last_error |
定位等待原因及独立错误;重点检查 waiting_foreground、waiting_permission、waiting_fix、permission_denied、missing_background_mode |
last_check_at、last_state_update_at、app_state |
后台检查与状态更新的 Unix 秒,以及当时前后台枚举(0 活跃、1 非活跃、2 后台) |
location_last_callback_at、location_last_update_age_seconds |
最近定位回调的 Unix 秒及有效数据年龄;未取得有效数据时年龄为 null,静止时不保证频繁回调 |
available_memory_bytes、footprint_bytes |
进程剩余内存预算及物理占用;剩余预算不等于系统空闲内存 |
timer_active、stop_reason |
检查定时器是否存在;停止原因仅为历史最近停止音频和检查的原因,不能单凭它判断当前定位状态 |
设置页“后台保活”默认关闭,“音频保活”默认开,“持续定位保活”默认关。开启微桥及保活总开关,在微信前台开启定位并等待有效回调后转后台;仅请求“使用 App 期间”权限,无需修改 IPA。
按以下顺序验证,并在每组测试中实际执行一条文本命令和检查一次消息事件上报:
- 单定位:关音频、开定位;后台预期
strategy=location、active=true、location_active=true、audio_playing=false。 - 单音频:关定位、开音频;后台预期
strategy=audio、active=true、audio_playing=true、location_updates_requested=false。 - 双开:两路都开且定位已有效;后台预期
strategy=audio+location。通话时音频可能让出,定位有效时策略可暂变为location,挂断后观察恢复。 - 关闭总开关:子开关保留选择并置灰;预期
enabled/active/timer_active/audio_playing/location_updates_requested/location_active均为false、strategy=stopped。再次开启后按保留配置运行。
该命令读取共享快照,不会强制刷新保活状态或启动通道,结合 last_state_update_at 判断时效。相隔 30 秒、2 分钟、10 分钟采样,比较后台 last_check_at、回调时间和业务结果;查询成功或一次 location_active=true 都不能证明此前持续运行。两路同属微信进程,系统结束微信时都会停止。
需要即时刷新保活及内存时,可从能访问手机的电脑调用 GET http://手机IP:19088/debug/keepalive_status,该接口的字段位于 keepalive,去掉上述前缀;没有 keepalive_status 长轮询命令。完整设备内存使用已有 device_status 命令。当前源码的构建标记为 webridge_build_20261106_settings_update_8080;保活与专用诊断已由用户在微信 8.0.80 验证通过;长期锁屏、通话及低内存下的运行仍受系统调度影响。详细采样及失败排查见 巨魔后台保活测试说明(资料未收录)。
12.9 本地 HTTP 专用入口与本模式限制
| HTTP 专用入口 | 当前命令通道的处理方式 |
|---|---|
旧 /download_message_image、/debug/download_message_image |
不存在同名命令;改用 download_message_media,参数传 message_id,从 result.resolved_local_path 读手机路径。支持图片、SILK 语音、视频、文件和表情 |
POST /events |
不下发 events 命令;使用本模式的事件接收链路,具体封装及字段均在本篇事件章节 |
GET /、GET /health |
不存在 health 命令;用本模式连接/注册状态及 account_status、bridge_status 判断运行状态 |
GET /debug/keepalive_status |
不存在 keepalive_status 命令;用 bridge_status 查询保活共享快照、device_status 查询设备内存。本模式的完整读取规则见保活诊断;本模式不能强制刷新 HTTP 专用快照 |
GET /moments/media/file/{operation_id} |
没有文件字节读取命令;让 moments_media_download 携带 upload,手机主动上传后端,见本篇媒体准备。聊天媒体用 upload_message_media 上传 |
/debug/transfer_latest |
不存在 transfer_latest 命令;诊断可使用本篇 transfer_probe,业务转账 ID 从本模式事件保存 |
/debug/mock_incoming、/debug/contact_mgr_probe、/debug/contact_friend_probe、/debug/group_member_nickname_probe、/debug/web_entry_probe、/debug/web_dom_probe、/debug/image_runtime_probe、/debug/quote_runtime_probe、/debug/message_probe、/debug/forward_runtime_probe、/debug/inline_mention_send |
没有同名命令。这些是可选本地诊断入口,不是本模式正常接入的前置步骤;需要本地专用诊断时才额外调用手机 HTTP |
不要把 HTTP 路径直接放入 command_name。未支持的名字返回 unsupported command_name。下载成功仅准备手机文件;后端要取得字节,应继续调用 upload_message_media,无需后端能直连手机。
13. 好友备注、星标和黑名单
通过 command_name 使用下表同名命令,参数原样放入 args;WebSocket 和长轮询共享分发器。
仅支持已登录账号的普通微信好友,id 必须是其真实 user_name / wxid。不支持自己、群、公众号、系统号、OpenIM 或已注销好友;已拉黑的好友仍可取消黑名单。沿用鉴权和发送开关,拒绝未知字段。
| 接口/命令 | 参数 | 设置 | 清空/取消 |
|---|---|---|---|
set_friend_remark |
id, remark |
{"id":"wxid_example","remark":"张三"} |
{"id":"wxid_example","remark":""} |
set_friend_star |
id, starred |
{"id":"wxid_example","starred":true} |
{"id":"wxid_example","starred":false} |
set_friend_blacklist |
id, blacklisted |
{"id":"wxid_example","blacklisted":true} |
{"id":"wxid_example","blacklisted":false} |
remark 必须显式传入字符串,使用微信 formatter 规范化,再按微信 charLength <= 32 校验,超限拒绝;这不是通用 Unicode 字符数限制。starred / blacklisted 必须显式传入 JSON 布尔值,0/1、"false"、null、缺省均拒绝。相同目标值返回 unchanged,不再次提交原生修改。
status |
ok |
说明 |
|---|---|---|
applied_locally |
true | 原生方法返回成功,重新查询的本地值与目标一致 |
unchanged |
true | 原值已等于目标;already_set=true,accepted=false、sync_requested=false |
failed |
false | 参数、账号、好友类型、写开关或 ABI 校验失败,未进入修改调用 |
unknown |
false | 进入调用后返回失败、发生异常、账号变化或回读不一致,可能已有本地变化 |
accepted 仅代表原生 BOOL 返回成功;sync_requested 仅说明已进入原生同步调用路径,不保证入队成功。local_verified 是本地联系人回读;server_confirmed 固定 false,当前实现不等待服务器 ACK。ok=true 不保证另一台设备已同步,unknown 不宜自动重提。
响应中的 remark / starred / blacklisted 是请求目标值(备注为规范化值),不是失败后的实际值;非法或缺失字段可能使用空串/false 占位,必须结合 ok / status。联系人摘要在 getter 可读时附带 is_starred / is_blacklisted,未知则省略。原有 remark / display_name 继续可读,清空备注后显示名回退昵称。
好友设置已由用户在微信 8.0.80 验证通过;Unicode 长度限制及断网/跨设备同步仍按本节约定和微信实际状态处理。
下发的命令对象示例(外层任务结构沿用本协议):
{"request_id":"friend_star_001","command_name":"set_friend_star","args":{"id":"wxid_example","starred":true}}
响应沿用命令结果信封:ok / error 位于外层,result 包含 id、目标字段及其余状态字段。失败时同样保留 result,不能只按外层 ok 丢弃 unknown 的状态信息。
14. 创建群聊、直接加人和发送入群邀请
命令为 create_group、add_group_member、invite_group_member,均已加入能力清单;邀请保留 group_invite_member 别名。沿用本篇连接/注册鉴权和命令信封,以下完整列出业务约定。
三项操作分别表示创建普通微信群、尝试直接加入群成员、发送需要对方接受的入群邀请。插件不按“40 人”硬切换,也不把添加失败自动转成发送邀请;是否可直接加入取决于微信服务端和群设置。所有结果均须查看业务状态,原生方法返回成功不等于成员已经入群。2026-10-29 用户通过 Apifox 完成 HTTP 真机联调:两位好友建群、直接添加已在群成员、移出后重新直接添加、邀请已在群成员、移出后发送邀请,五项结果均符合预期。邀请成功仅确认发送,不代表对方已接受。用户现已确认建群、添加与邀请成员功能在微信 8.0.80 验证通过。是否可直接加人仍由微信与群设置决定,审批、部分成功、超时及账号切换按本节状态处理。request_submitted 为布尔值。
使用范围与完整参数
必须已登录并开启插件写入/发送开关。待操作对象必须是当前账号的普通微信好友,传真实内部 ID;不支持昵称、完整微信号 alias、企业微信用户或群 ID 充当成员。添加和邀请只支持普通 @chatroom 群,并须能从当前群成员快照确认本人在群;无须由插件预设“只有群主能加人”,实际权限和人数限制由微信返回。
| 参数 | 类型 | 创建 create_group |
添加 add_group_member |
邀请 invite_group_member |
|---|---|---|---|---|
request_id |
string | 必填 | 必填 | 强烈建议传;具体位置与兼容默认值见本节通道说明 |
member_id |
string | 与 member_ids 二选一,但单个其他好友无法达到首版建群最低人数 |
与 member_ids 二选一 |
单人邀请;与旧字段合并规则见下文 |
member_ids |
string[] | 非空数组;去重并排除本人后至少 2 位其他好友 | 非空数组;与 member_id 二选一 |
非空数组,支持批量 |
room_id |
string | 不接受 | 必填 | 必填,兼容群 ID 别名见下文 |
reason |
string | 不接受 | 选填,默认空串,表示入群申请说明 | 不接受 |
request_id 去除首尾空白后须为 1–128 个字符;成员和群 ID 也去除首尾空白,内部不能含空白。成员去重后按 ID 排序。创建时自动排除本人;至少两位其他好友是本接口首版支持范围,不是对微信服务端所有建群方式最低人数的断言。创建不接受 topic/群名,群创建后如需命名,使用已有 set_group_name,其结果独立判断。
添加/邀请先排除本机快照中已在群的成员,只向微信提交剩余普通好友;已在群者放入 already_in_room_member_ids。全部已在群时返回 already_in_room,不提交新请求;仅本机快照确认,server_confirmed:false。若任一待提交成员不是普通好友,整次请求在提交前失败,不偷偷跳过该人。
邀请保留单值别名 user_name/wxid、数组别名 user_names/wxids;可以和 member_id/member_ids 同时提供,合并、校验、去重排序。群 ID 可用 chatroom_id/group_id/to_user_name,同时传多个群字段时规范化后须一致。创建和添加不支持这些旧别名,也不允许同时传多个成员字段。未列出的参数直接拒绝,不静默忽略;null 不能代替字符串或数组。
返回字段与成功判断
下表列出进入群操作服务后的完整业务字段;ok/error 所在位置见本节通道说明。服务内初始失败可能尚未解析到成员或群 ID,因此对应字段为空。message、error、error_code 和 replayed 为按情况出现的字段。命令信封、插件开关、队列容量等前置拒绝尚未进入群操作服务,result 可能为空或只有部分字段;例如 args.request_id 冲突只返回 status 和 error_code,此时不要假定成员或提交状态字段存在。
| 字段 | 类型/默认值 | 含义 |
|---|---|---|
ok |
boolean,false | 创建时表示已确认建成群;添加时表示目标均已加入或已在群;邀请时表示本批邀请已发送,或目标本来全已在群 |
status |
string,failed |
当前业务状态,见下表 |
mode |
string | create/add/invite |
request_id |
string,空串 | 本次规范化后的业务请求 ID,须保存用于查询和重试去重 |
room_id |
string,空串 | 添加/邀请的目标群;创建成功后为微信实际返回的新群 ID |
member_ids、member_count |
string[]/integer,[]/0 |
去重排序后的目标成员和数量;创建时已排除本人 |
submitted_member_ids |
string[],[] |
筛选出的本次原生目标成员;是否实际提交还须看 request_submitted |
already_in_room_member_ids |
string[],[] |
提交前本机快照已确认在群的目标成员 |
accepted |
boolean,false | 微信原生方法的布尔返回值,不代表发出、成功或成员已入群 |
request_submitted |
boolean,false | 已取得本次原生事件 ID;不代表微信服务端接受 |
response_received |
boolean,false | 已收到与本请求匹配的原生回包;内容仍可能不完整 |
server_confirmed |
boolean,false | 本动作已由微信服务端确认成功:创建得到有效新群ID、添加确认所有目标已入群、邀请批次发送成功;拒绝、需邀请、待审批或全部原已在群时为false |
request_cgi |
integer/null,null | 本次原生请求类型:创建119、添加120、邀请610;尚未提交时可能为 null |
event_id |
integer/null,null | 本次原生事件编号,用于诊断;不是事件拉取编号或单独查询入口 |
wechat_ret |
integer/null,null | 微信业务返回码,不能把缺失值按0处理 |
wechat_error_message |
string,空串 | 微信原始错误说明 |
message_type、transport_error_type、transport_error_code |
integer/null,null | 原生消息状态与传输错误字段 |
created |
boolean,false | 是否确认本次创建了群;其他两种操作为false |
all_members_added |
boolean,false | 是否确认所有目标均已加入或已在群;发出邀请不会因此变成true |
member_results |
object[],[] |
创建/添加的逐成员结果;每项有 member_id:string、status:string、native_status:integer/null |
invite_required_member_ids |
string[],[] |
完整回包确认可进一步发送邀请的成员;由调用方显式发起新邀请操作 |
replayed |
boolean,可省略 | 为true时表示读取了同ID的已登记操作,没有重新提交原生请求 |
message |
string,可省略 | 成功后的补充说明 |
error_code、error |
string,可省略 | 失败、需后续处理或结果未知时的错误分类与中文说明 |
status |
ok |
如何处理 |
|---|---|---|
created |
true | 群已创建,读取新 room_id;仍须看 all_members_added 与逐成员结果,不能因部分成员失败再次建群 |
added |
true | 所有目标都已加入或本来就在群 |
invitation_sent |
true | 仅确认本批邀请发送,对方接受后才会入群;不提供伪造的逐人成功结果 |
already_in_room |
true | 全部目标在提交前已存在于本机群快照,没有新请求 |
requires_invitation |
false | 直接加人未完成;只对 invite_required_member_ids 按业务需要显式调用邀请,并用新的业务ID |
requires_verification |
false | 成员需要好友验证,先处理好友关系 |
approval_pending |
false | 已进入群主/管理员确认流程,等待确认;尚未加入 |
partial |
false | 添加批次有多种结果,包括需验证等已知结果与unknown混合;逐人查看 member_results,含unknown时仍保留同群占用,不自动重复整批 |
processing |
false | 同ID操作仍在等待回包,稍后以相同参数、同ID读取 |
failed |
false | 参数、环境或业务失败;结合是否提交、错误码和微信原始提示处理 |
unknown |
false | 无法确认是否生效;以同ID读取迟到结果,必要时核实真实群状态,不自动重发 |
逐成员 status 可能为 added、already_in_room、requires_invitation、requires_verification、approval_pending、failed、unknown。原生 native_status=0 不能独立判断加入成功,必须与总返回码共同解释:例如添加回包 wechat_ret=-2012 表示需要邀请的分支,-2028 可表示等待群主/管理员确认。wechat_ret=-2012 下 native_status=3 表示拒绝加入,逐成员为failed;状态10含义尚不能确认,保留unknown。创建成功但成员信息不完整时仍返回 created,相关成员为unknown、all_members_added:false。正常邀请回包只确认批次发送,member_results:[];全部目标已在群而未发送时可返回逐人的 already_in_room。
错误、等待、去重与重试
error_code |
含义与处理 |
|---|---|
INVALID_PARAMETERS、INVALID_REQUEST_ID、INVALID_ROOM_ID、INVALID_MEMBER_IDS、INVALID_REASON、TOO_FEW_MEMBERS |
参数不符合本节契约,未启动业务请求 |
REQUEST_ID_CONFLICT |
同ID已经对应其他规范化参数,或命令args与信封ID冲突;不得用改参数方式查询原操作 |
ACCOUNT_UNAVAILABLE、WRITE_DISABLED、ROOM_STATE_UNAVAILABLE、FRIEND_REQUIRED |
登录/开关/群快照/好友条件不足,未发起新请求 |
UNSUPPORTED_ROOM |
无法确认目标是普通微信群,或属于不支持的企业互通/升级群,未发起新请求 |
OPERATION_BUSY |
同群,或当前账号的建群操作,存在等待中或未解决的未知结果;先处理原操作 |
REQUEST_RECORD_LIMIT |
当前登录上下文已登记512项操作,新操作被拒绝;已有ID仍可查询,不淘汰旧记录后冒险重发 |
NATIVE_API_UNAVAILABLE、INVALID_CALL_THREAD |
原生接口不兼容或调用线程错误;保留结果用于排查 |
REQUEST_NOT_CONFIRMED、NATIVE_REQUEST_FAILED |
原生调用或提交确认异常;检查 status、accepted、request_submitted,可能未知,不自动重试 |
WECHAT_REQUEST_REJECTED |
微信明确拒绝本次业务,查看 wechat_ret 与原始说明 |
MEMBERS_NOT_ALL_ADDED、MEMBER_RESPONSE_INCOMPLETE |
添加未确认所有目标入群,逐人处理;成员信息不完整不能当作全部成功 |
RESPONSE_MISMATCH、RESPONSE_INCOMPLETE、RESPONSE_INVALID、ROOM_ID_MISSING |
回包错配/缺失/异常,或建群成功码缺少群ID,结果未知 |
OPERATION_TIMEOUT、ACCOUNT_CHANGED |
等待超时或登录上下文改变,核实原业务结果 |
服务等待真实回包约30秒,外层等待保护约31秒;客户端建议至少40秒,iOS挂起、通信排队或断线可能更久,不能把连接超时直接当业务失败。该能力不新增完成事件或单独查询接口;迟到回包会更新当前进程内记录,使用原接口/命令、原ID和相同规范化参数读取即可。
三种接入方式共用当前账号登录上下文内的操作记录。登记后相同ID、相同操作/群/去重排序成员/reason只读取记录,不再次提交;在途返回processing,结束后返回当前结果并带 replayed:true。同ID不同参数返回冲突。参数或环境校验阶段尚未登记的失败不等于已建立去重记录。新业务必须用新ID;同ID只能查询/重放原业务,不用于强制重新执行。
最多保留512项已登记操作,达到上限拒绝新操作,不按最近条目淘汰。添加操作只要仍有待添加成员结果为 unknown,即使回包字段齐全或顶层为 partial,也继续占用同群操作槽位,并等待迟到回包;已知结果与未知结果混合时逐人处理,不能据此重发整批。唯一可继续的同群操作是显式邀请:新邀请的全部目标必须属于该未完成添加记录的 invite_required_member_ids 已确认名单,并使用新的业务ID;不在名单内的邀请和其他添加仍返回OPERATION_BUSY,原添加记录继续等待。只有成员结果完整且均已明确,才解除该添加占用。邀请本身结果未知也保留同群占用,上述例外不会绕过其他未完成的邀请。建群尚未取得有效新群ID时的未知结果会占用当前账号建群槽位;已取得有效新群ID则返回 created 并解除建群占用,成员部分未知也不能重复创建原业务群。记录仅在当前微信进程和登录上下文内有效,进程重启、重新登录或服务上下文变化(含群服务重新初始化)后不保证去重;不要通过重启或换ID绕过未知结果。后端应保存业务ID及收到的结果,跨重启先核实群状态,再由业务决定下一步。
本模式封装与调用示例
request_id 必须在命令信封提供;args.request_id 可以省略,若提供必须与信封ID一致。参数置于args。外层 ok/error 为业务结果,其他字段置于 result,其中不再重复 ok/error;失败码读取 result.error_code。
这些命令及邀请别名绕过通用“最近100条”命令缓存,由群操作服务执行账号校验、参数冲突检查及上述512项记录规则;本节不适用“失败后换新ID自动重试”的通用建议。
在 /agent/poll 响应的commands数组交付命令;插件顺序处理批次,当前命令完成并上报结果后才推进下一项。poll请求的等待时间不等于此业务完成期限。下例为直接添加:
{
"commands": [
{
"request_id": "req-add-001",
"command_name": "add_group_member",
"args": {
"room_id": "123456789@chatroom",
"member_id": "wxid_member_a",
"reason": ""
}
}
]
}
创建和邀请使用同一信封,替换command_name和args如下:
{
"request_id": "req-create-001",
"command_name": "create_group",
"args": {
"member_ids": [
"wxid_member_a",
"wxid_member_b"
]
}
}
{
"request_id": "req-invite-001",
"command_name": "invite_group_member",
"args": {
"room_id": "123456789@chatroom",
"member_ids": [
"wxid_member_a"
]
}
}
建群成功示例(字段格式示例,不是真机记录):
以下省略公共设备/时间字段,由插件POST到 /agent/command_result:
{
"request_id": "req-create-001",
"command_name": "create_group",
"ok": true,
"error": "",
"result": {
"status": "created",
"mode": "create",
"request_id": "req-create-001",
"room_id": "123456789@chatroom",
"member_ids": [
"wxid_member_a",
"wxid_member_b"
],
"member_count": 2,
"submitted_member_ids": [
"wxid_member_a",
"wxid_member_b"
],
"already_in_room_member_ids": [],
"accepted": true,
"request_submitted": true,
"response_received": true,
"server_confirmed": true,
"request_cgi": 119,
"event_id": 42,
"wechat_ret": 0,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0,
"created": true,
"all_members_added": true,
"member_results": [
{
"member_id": "wxid_member_a",
"status": "added",
"native_status": 0
},
{
"member_id": "wxid_member_b",
"status": "added",
"native_status": 0
}
],
"invite_required_member_ids": []
}
}
直接添加需要改走邀请示例(字段格式示例,不是真机记录):
以下省略公共设备/时间字段,由插件POST到 /agent/command_result:
{
"request_id": "req-add-001",
"command_name": "add_group_member",
"ok": false,
"error": "成员未全部确认加入,请查看 member_results;需要邀请时显式调用 invite_group_member",
"result": {
"status": "requires_invitation",
"mode": "add",
"request_id": "req-add-001",
"room_id": "123456789@chatroom",
"member_ids": [
"wxid_member_a"
],
"member_count": 1,
"submitted_member_ids": [
"wxid_member_a"
],
"already_in_room_member_ids": [],
"accepted": true,
"request_submitted": true,
"response_received": true,
"server_confirmed": false,
"request_cgi": 120,
"event_id": 42,
"wechat_ret": -2012,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0,
"created": false,
"all_members_added": false,
"member_results": [
{
"member_id": "wxid_member_a",
"status": "requires_invitation",
"native_status": 0
}
],
"invite_required_member_ids": [
"wxid_member_a"
],
"error_code": "MEMBERS_NOT_ALL_ADDED"
}
}
邀请批次已发送示例(字段格式示例,不是真机记录):
以下省略公共设备/时间字段,由插件POST到 /agent/command_result:
{
"request_id": "req-invite-001",
"command_name": "invite_group_member",
"ok": true,
"error": "",
"result": {
"status": "invitation_sent",
"mode": "invite",
"request_id": "req-invite-001",
"room_id": "123456789@chatroom",
"member_ids": [
"wxid_member_a"
],
"member_count": 1,
"submitted_member_ids": [
"wxid_member_a"
],
"already_in_room_member_ids": [],
"accepted": true,
"request_submitted": true,
"response_received": true,
"server_confirmed": true,
"request_cgi": 610,
"event_id": 42,
"wechat_ret": 0,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0,
"created": false,
"all_members_added": false,
"member_results": [],
"invite_required_member_ids": []
}
}
已提交但超时未知示例(字段格式示例,不是真机记录):
以下省略公共设备/时间字段,由插件POST到 /agent/command_result:
{
"request_id": "req-add-001",
"command_name": "add_group_member",
"ok": false,
"error": "等待微信回包超时,可能已生效;请用相同 request_id 查询,勿换 ID 重发",
"result": {
"status": "unknown",
"mode": "add",
"request_id": "req-add-001",
"room_id": "123456789@chatroom",
"member_ids": [
"wxid_member_a"
],
"member_count": 1,
"submitted_member_ids": [
"wxid_member_a"
],
"already_in_room_member_ids": [],
"accepted": true,
"request_submitted": true,
"response_received": false,
"server_confirmed": false,
"request_cgi": 120,
"event_id": 42,
"wechat_ret": null,
"wechat_error_message": "",
"message_type": null,
"transport_error_type": null,
"transport_error_code": null,
"created": false,
"all_members_added": false,
"member_results": [],
"invite_required_member_ids": [],
"error_code": "OPERATION_TIMEOUT"
}
}
15. 入群邀请:查询、静默接受与探针
用户已确认微信 8.0.80 的邀请查询与接受入群功能验证通过。收到普通私聊邀请链接卡片后,插件从原消息取得 URL、发送方和消息上下文,经微信原生 A8Key 鉴权调用官方邀请详情/接受协议。全程不创建网页、不点击、不执行网页脚本。HTTP 的邀请查询和探针已有验证记录;用户提供的接受回包为 ok=true/status=joined、membership_verified=true/in_chatroom=true,确认该样本已入群。查询时 roomInfo.roomName 可缺省,目标群 ID 可在接受成功后才取得。
| 命令 | 行为 |
|---|---|
get_group_invitation |
只查详情和限制,不接受 |
accept_group_invitation |
内部重新查询,满足条件才提交接受,无需先调用查询 |
group_invitation_probe |
只读探针,stage=context/auth/info,默认 info |
业务请求统一为:
{"message_id":"123456789012345678"}
message_id 取收到卡片事件的 body.message_id,必须是正整数十进制字符串(最多 20 位且不超过 uint64,无前导零)。不接受 JSON 数字,以免大整数丢精度。不需要 invite_url、chat_id、room_id、账号凭据或 ticket。账号选择沿用既有设备/账号通道;本地复核消息属于当前登录账号。回调里的 chat_id 是来源会话,私聊的 room_id 为空,不是邀请目标群。群名等来自服务器详情;目标群 ID 在查询时可为空,接受成功后从提交回包取得。
仅处理当前账号收到的普通私聊 49/5 卡片及 support.weixin.qq.com/cgi-bin/mmsupport-bin/addchatroombyinvite 邀请。 鉴权后的地址允许微信内置的 7 个 support 域名(support/szsupport/mlsupport/hksupport.weixin.qq.com、support/sgsupport/ussupport.wechat.com),仍严格检查 HTTPS、原路径、原 ticket、端口和凭据;详情/接受请求及 Origin 使用本次鉴权域名,不跟随重定向。原消息必须仍在近期缓存(当前最多 200 条),否则返回 MESSAGE_CONTEXT_NOT_FOUND,需重新取得一条新邀请。切换账号或登录上下文时停止操作。微信被系统挂起或进程结束时不能保证后台执行;“静默”指进程有运行时间时无界面地执行协议。
三项命令均受本模式插件启用和授权校验;接受操作在真正提交前再次检查有效发送状态。查询和探针不另行执行发送判断。
结果判定
status |
ok |
含义 |
|---|---|---|
ready |
true | 查询成功,本次条件允许接受;不承诺后续提交必成功 |
restricted |
true | 查询成功但不允许接受,读取 restriction_code / warning |
already_joined |
true | 本次服务器详情确认已经在群,不提交接受 |
accepted |
true | 微信明确接受成功并取得有效群 ID,已有群 ID 时核对一致;后续详情尚未确认成员状态 |
joined |
true | 微信接受成功,且本次后续服务器详情确认在同一群 |
unknown |
false | 已开始提交,但断网、坏响应或账号变化导致无法确认结果;先查状态 |
failed |
false | 前置条件失败,或微信明确拒绝 |
context_ready / authenticated |
true | 仅供对应阶段探针,分别表示上下文检查通过 / 取得合法鉴权结果 |
ticket_used=true 单独不能证明当前还在群。openurl、额外交互或不支持的 A8Key action 会明确受限/失败。不会转到页面继续操作。查询阶段允许服务器暂不返回群 ID,仍可返回群名、人数和是否可接受;接受成功必须取得有效群 ID。后续成员核实按同一 ticket 的本次服务器详情判断,若返回群 ID 则必须匹配。旧本地群联系人或成员缓存不作为本次在群证据。
查询响应示例(依据第28包查询结果,消息 ID 与群名已替换;查询时群 ID 缺省):
{"ok":true,"status":"ready","message_id":"123456789012345678","room_id":"","room_name":"示例群","member_count":198,"can_accept":true,"in_chatroom":false,"ticket_used":false,"restriction_code":"","warning":"为维护微信平台绿色网络环境,请勿在群内传播违法违规内容。","request_submitted":false,"membership_verified":false,"wechat_ret":0,"a8key_action":6,"wechat_err_code":0,"wechat_error_type":0,"http_status":200,"stage":"info","error_code":"","error":"","elapsed_ms":1417}
接受响应主要看 status、request_submitted、membership_verified。request_submitted=true 只表示本次或此前同票据的接受请求已开始,不能证明服务器已接收;accepted 不等于已确认在群。接受操作的 can_accept 固定为 false,避免把已处理结果再次当作可提交邀请。
字段来源与快照语义
接受返回整合了接受前详情、接受 POST 和接受后核实三次请求:
room_name、member_count、ticket_used、warning保留接受前详情快照;核实阶段不刷新这些字段。因此成功回包中ticket_used=false、member_count=198仍可与status=joined同时出现,不能据此判断票据仍可用或入群后人数仍为 198。room_id在接受成功时从 POST 回包取得;membership_verified与in_chatroom=true表示后续同一票据的服务器详情已确认在群。verification_*只记录这次核实请求,不覆盖接受 POST 的状态码。warning来自微信详情的data.antispamwarning,为空时取data.securitytips,再隐藏其中的凭据和链接。当前返回没有记录具体采用了哪一个源字段。这条“为维护微信平台绿色网络环境,请勿在群内传播违法违规内容。”是微信返回的普通提醒;仅有该文案不表示违规、受限或失败,限制看status/restriction_code。
后端判断已确认在群,使用 ok=true、status 为 joined 或 already_joined,并且 membership_verified=true。request_submitted=true 仅说明开始过提交;accepted 表示微信已确认接受但成员核实尚未成功,应查询详情确认,不能自动重提。接受操作结束时 can_accept 固定为 false,表示该操作结果不再作为可接受邀请,不能把它解释成本次接受被拒绝。
实测 wechat_ret=0 表示 A8Key 鉴权成功,a8key_action=6 是原生 URL 处理分支编号。接受回包的 http_status=201 是微信接受 POST 的成功 HTTP 状态;再结合 wechat_err_code=0/wechat_error_type=0 和有效群 ID 判断接受成功。verification_http_status=200 与核实双零业务码来自随后的详情 GET,成员是否核实仍看 membership_verified。本模式仍按命令结果外层 ok/error 判断业务结果,不能把 result.http_status 当作服务端接收结果信封的 HTTP 状态。
| 字段 | 类型与约定 |
|---|---|
ok, status, error_code, error |
业务成败、状态、机器错误码、中文说明;成功错误字段为空串 |
message_id |
原消息 ID 字符串 |
room_id, room_name |
群名来自详情;群 ID 在查询时可能为空,接受成功后从提交回包取得;未知为空串 |
member_count |
非负整数或 null;接受结果保留接受前详情人数,不是入群后实时人数 |
can_accept |
查询是否允许接受;不能作为跨请求授权凭证;接受操作结束时固定 false |
in_chatroom |
布尔或 null;详情中的在群状态;接受后核实成功更新为 true |
ticket_used |
布尔或 null;接受结果保留接受前详情的票据状态;单独 true 也不能证明当前在群 |
restriction_code |
限制分类,未受限为空串;取值见下方限制码 |
warning |
微信详情 antispamwarning 优先、securitytips 兜底,脱敏后返回;接受结果保留接受前快照;普通提醒不等于受限 |
request_submitted, membership_verified |
是否开始过接受提交、本次是否由服务器确认在群 |
wechat_ret |
原生 A8Key baseResponse.ret,0 表示鉴权无业务错误,未到达时 null |
a8key_action |
原生 actionCode 分支号,当前允许 0/2/6 继续校验目标地址及密钥;实测为 6,未到达时 null |
wechat_err_code, wechat_error_type |
最近详情/接受 POST 的顶层 errCode、data.errorType,双零表示无业务错误;未取得为 null;不被核实请求覆盖 |
http_status |
微信详情/接受 POST 的 HTTP 状态,接受成功实测 201;未知为 null,未取得响应时可为 0;不是手机本地 API 的 HTTP 状态 |
verification_err_code, verification_error_type, verification_http_status |
可选;提交成功后成员核实请求的结果,不覆盖接受结果 |
stage |
context/auth/info/submit/verify,最后到达的阶段 |
elapsed_ms |
可选;整个操作从创建到结束的总耗时,毫秒,含鉴权、详情、提交和核实中已执行的阶段 |
version |
手机 HTTP 出口附加的 WeBridge 插件完整版本,例如 8.0.80-2,包含 deb 构建号;不是原生微信版本;本功能 WS/轮询命令结果不附加此字段 |
replayed |
可选;true 表示此前提交过,本次只查不再 POST |
diagnostics |
仅探针在创建操作后返回;早期参数/缓存失败可能没有 |
错误码包括 INVALID_ARGUMENT、INVALID_CALL_THREAD、NOT_LOGGED_IN、ACCOUNT_CHANGED、MESSAGE_CONTEXT_NOT_FOUND、MESSAGE_CONTEXT_MISMATCH、NATIVE_UNAVAILABLE、NATIVE_EXCEPTION、A8KEY_FAILED、A8KEY_TIMEOUT、RESPONSE_INVALID、INVITATION_TIMEOUT、INVITATION_EXPIRED、INVITATION_INVALID、WECHAT_REJECTED、SEND_DISABLED、BUSY、INVITATION_LIMIT、RESULT_UNKNOWN。限制码为 INVITATION_USED、INVITATION_RESTRICTED、UNSUPPORTED_INVITATION_FLOW,接受受限时也作为错误码。
去重、超时与重试
同账号登录上下文、同票据、同操作及探针阶段的并发请求合并等待;创建操作上限 16。接受提交前登记内存记录,同一票据以后调用接受会重新查询、不重复 POST;明确拒绝可移除提交记录。提交记录当前最多 64,满时拒绝新提交,避免淘汰未知记录导致误重试。账号/会话变化或进程重启后记录不保留,不保证跨重启幂等;后端也应按账号和原消息记录已提交/未知结果,重启后先查询,不自动重发接受。
鉴权等待约 18 秒,单次 HTTP 最多 12 秒,总操作约 55 秒。调用方等待建议 75 秒。本模式在工作线程等待,按 request_id 匹配最终命令结果;同一批命令仍在本条结果上报成功后才继续。不要因 30 秒内没有业务结果就自动重投。
只读探针与验证范围
使用 group_invitation_probe 命令,以下参数放在 args:
{"message_id":"123456789012345678","stage":"context"}
context:检查原消息、当前账号、原生类和 ABI,不按固定微信版本号拦截,不调用 A8Key 或业务 HTTP。auth:在前述基础上调用原生鉴权并核对 fullUrl、ticket、exportkey,不请求邀请详情。info(默认):完成鉴权并 GET 详情,不 POST 接受。三阶段request_submitted均为 false。
诊断含 native_ready、native_signatures、wechat_version、message_context_found、message_type、app_msg_type、a8key_scene、a8key_opcode、callback_received;收到回调后可有 event_id、cgi、a8key_message、transport_error_type/code、exportkey_present、full_url_allowed、full_url_same_host/path/ticket、full_url_https、full_url_has_fragment、full_url_host(仅 hostname)、full_url_host_allowed、full_url_port(未显式设置时 null)、full_url_has_credentials、a8key_header_names;HTTP 后可有 business_http_host(应与鉴权 hostname 相同)、http_response_mime、last_response_bytes、network_error_code、http_json_object、data_object、room_info_object 及固定业务字段名称列表 invitation_fields。业务解析后还有 response_field_types(固定字段的 JSON 类型,含 missing/null)、response_string_lengths(群信息字符串长度)和 response_validation(信封、业务层、布尔字段、roomInfo 和群 ID 校验结果),不返回这些字段的原始字符串值。full_url_same_host 仅表示与原入口主域名相同;官方地区域名可使其为 false,应结合 full_url_host_allowed/full_url_allowed 判断。字段按到达阶段提供。探针不返回原始链接、ticket、exportkey、Cookie 或鉴权 header 值,也不提供任意 URL、原生方法执行能力。
若具备直连手机的条件,可另外运行项目自带的 HTTP 只读验证脚本;它仅用于排障,不代替长轮询端到端验证:
python tests/group_invitation_smoke.py --base-url http://<手机IP>:19088 --message-id <刚收到的消息ID>
排障按 context → auth → info 检查;确认 status=ready 后按业务需要调用接受接口。脱敏探针 JSON 可定位原消息、ABI、A8Key 或 HTTP 问题,避免盲目重建。若 auth 成功但 info 失败,检查 http_status、双层业务码、网络错误与 header 名称。用户已确认邀请查询与接受入群在微信 8.0.80 验证通过。实现不再按固定版本号拦截,仍检查实际原生类、方法和参数类型。过期/受限邀请、网络异常、账号切换及跨重启幂等按本节状态和重试边界处理。Apifox GUI 导入未测试。
命令下发示例(放在 poll 响应 commands 数组):
{"request_id":"invite-001","command_name":"accept_group_invitation","args":{"message_id":"123456789012345678"}}
命令回包沿用既有信封:业务 ok/error 在顶层,其余字段位于 result,本功能不附加 HTTP 出口的 version。上方查询示例展示业务字段;下面是接受命令结果:
接受并核实入群成功的命令结果格式(将用户提供的 HTTP 真机回包按既有命令信封转换,身份字段已替换):
{
"request_id": "invite-001",
"command_name": "accept_group_invitation",
"ok": true,
"result": {
"status": "joined",
"message_id": "123456789012345678",
"room_id": "123456789@chatroom",
"room_name": "示例群",
"member_count": 198,
"can_accept": false,
"in_chatroom": true,
"ticket_used": false,
"restriction_code": "",
"warning": "为维护微信平台绿色网络环境,请勿在群内传播违法违规内容。",
"request_submitted": true,
"membership_verified": true,
"wechat_ret": 0,
"a8key_action": 6,
"wechat_err_code": 0,
"wechat_error_type": 0,
"http_status": 201,
"stage": "verify",
"error_code": "",
"elapsed_ms": 3876,
"verification_http_status": 200,
"verification_err_code": 0,
"verification_error_type": 0
},
"error": ""
}
长轮询请求继续携带 session_id/device_id/ts_ms 等公共字段,不添加 WebSocket 的 type 字段;按 request_id 匹配结果。
16. 群管理员、群免打扰与个人二维码
三项支持 HTTP、长轮询和 WebSocket,使用相同参数和业务状态。示例 ID 和二维码 URL 均为虚构。
| 用途 | 命令 | 必填参数 |
|---|---|---|
| 添加/移除管理员、转让群主 | group_admin_operate |
room_id、oper_type、member_ids |
| 设置当前账号的群免打扰 | set_group_msg_silence |
room_id、silence |
| 获取当前登录账号二维码 | get_self_qrcode |
无,args:{} |
需要正常鉴权和登录,拒绝未知字段。群管理与免打扰受发送写开关约束;个人二维码不受发送开关约束。群 ID 必须是普通微信群真实 ...@chatroom,不接受群名或 OpenIM 群。
群管理员操作
{"room_id":"123456789@chatroom","oper_type":1,"member_ids":["wxid_member_a"]}
oper_type |
操作 | CGI | member_ids |
|---|---|---|---|
| 1 | 添加管理员 | 889 | 非空数组,可批量 |
| 2 | 移除管理员 | 259 | 非空数组,可批量 |
| 3 | 转让群主 | 990 | 去重后恰好一人 |
oper_type 必须是 JSON 整数,拒绝布尔值、字符串。成员必须是群内其他普通微信成员的真实 user_name/wxid,不接受昵称或本人;数组去重并保留顺序。当前账号必须为群主,无法从本地群资料确认权限或成员时先刷新群资料。同一群同时只处理一个权限变更,全局最多 32 项待处理。
status |
ok |
含义 |
|---|---|---|
succeeded |
true | 本次匹配的微信响应明确成功,wechat_ret=0、server_confirmed=true |
failed |
false | 前置校验失败,或微信明确拒绝;查看 error_code/error |
unknown |
false | 已调用但未取得完整确认、超时或账号变化,操作可能已经生效 |
accepted 仅为原生方法的 BOOL 返回;request_submitted 仅表示取得本次原生事件 ID,均不能单独判断成功。每次调用等待约 30 秒并一次返回结果,调用方超时建议至少 40 秒,无需业务状态轮询。超时、断线或 unknown 时先核实群状态,不要自动重新提交;转让成功后原群主不再具备群主权限。
返回保留 room_id、oper_type、去重后的 member_ids,以及以下诊断字段:
| 字段 | 含义 |
|---|---|
accepted、request_submitted、server_confirmed |
分别表示原生接受、取得事件 ID、服务端明确确认 |
request_cgi、event_id |
本次 CGI 与事件 ID,未取得时为 null;不是业务轮询凭证 |
wechat_ret、wechat_error_message |
微信原始业务错误码及说明,错误码未知为 null |
message_type |
原生消息状态,未取得为 null |
transport_error_type、transport_error_code |
原生传输错误类型与错误码,未取得为 null |
常见 error_code:OWNER_REQUIRED、ROOM_STATE_UNAVAILABLE、OPERATION_BUSY、WRITE_DISABLED、REQUEST_NOT_CONFIRMED、WECHAT_REQUEST_REJECTED、OPERATION_TIMEOUT、ACCOUNT_CHANGED。不要把 null 错误码按 0 处理。
群聊消息免打扰
{"room_id":"123456789@chatroom","silence":true}
silence:true 开启免打扰,false 恢复通知。必须显式传入 JSON 布尔值,拒绝 0/1、字符串、null 和缺省。普通群成员即可操作,只影响当前账号的消息提醒设置。
status |
ok |
含义 |
|---|---|---|
applied_locally |
true | 原生接受修改,联系人数据库回读与目标一致,local_verified=true |
unchanged |
true | 数据库中原值已一致,already_set=true、accepted=false、sync_requested=false、local_verified=true |
failed |
false | 参数、账号、群状态或写开关等前置检查失败 |
unknown |
false | 已进入修改路径,但原生返回失败、回读不一致或账号变化,结果未知 |
响应保留 room_id、目标值 silence、accepted、sync_requested、already_set、local_verified、server_confirmed、status;非法或缺失的 silence 可为 null。accepted 是原生 BOOL,sync_requested 只表示进入原生同步路径;server_confirmed 固定为 false,本地数据库确认不等于服务端同步 ACK。失败或未知时附 error_code/error,未知结果先核实设置,不自动重提。
获取自己的二维码
请求体必须为 {};自动使用当前登录账号,不接受 appId、wxid、style、refresh 或其他字段。本次请求二维码 URL,必要时生成 PNG;不读取旧磁盘图片,也不撤销旧二维码。同账号并发查询合并,等待约 30 秒,调用方超时建议至少 40 秒。
| 字段 | 类型 | 含义 |
|---|---|---|
status |
string | ok、restricted、failed 或 timeout,结合外层 ok 判断成功 |
wxid |
string | 当前账号 ID,前置失败时可为空 |
qr_code_url |
string | 二维码内容 URL,可为空;不是图片下载地址 |
image_base64 |
string/null | PNG Base64,不含 data URI 前缀;无图为 null |
image_mime_type |
string/null | 有图片时为 image/png,否则 null |
has_qrcode、has_image |
boolean | 本次是否有二维码 URL/可解码图片;有 URL 无图片也可成功 |
qrcode_status |
integer/null | 微信原始二维码状态,未知为 null,不硬编码具体限制含义 |
restricted |
boolean/null | 已知状态是否非零,未知为 null |
footer_wording |
string | 微信原始提示,可为空 |
request_submitted |
boolean | 是否取得本次原生事件 ID |
request_cgi、event_id |
integer/null | 本次 CGI(168)与事件 ID,未取得为 null |
wechat_ret、wechat_error_message |
integer/null、string | 微信原始业务结果与说明 |
message_type、transport_error_type、transport_error_code |
integer/null | 原生消息/传输状态,未取得为 null |
受限或失败时保留原始状态、提示及可用图片,不把受限结果按成功处理。失败时结合 error_code/error,不能只检查图片是否存在。
命令信封、等待与重试
参数放在 args,个人二维码使用 args:{}。回包外层 ok/error 表示业务结果,status、error_code 和其余业务字段在 result 中;读取错误码使用 result.error_code,result 不再包含 ok/error。
group_admin_operate、get_self_qrcode 复用后台等待路径,一次返回终态,等待不占用通信队列;每次进入共享服务重新校验当前账号,不使用通用 request_id 结果缓存。request_id 用于关联结果,不能保证操作去重。免打扰沿用普通命令的结果缓存规则。每项新操作使用新的 request_id,结果丢失、超时或未知时先核实当前状态,不能靠重发群权限命令查询结果。
长轮询仍按批次顺序执行,取得当前命令结果并完成提交后才执行下一项;后台等待仅释放通信队列,不表示并行执行整批。等待期间允许停止或重启长轮询。
在 /agent/poll 响应中下发:
{
"commands": [
{
"request_id": "req-admin-001",
"command_name": "group_admin_operate",
"args": {
"room_id": "123456789@chatroom",
"oper_type": 1,
"member_ids": [
"wxid_member_a"
]
}
}
]
}
插件通过 /agent/command_result 提交结果。以下省略传输公共字段,仅展示本条业务结果:
{
"request_id": "req-admin-001",
"command_name": "group_admin_operate",
"ok": true,
"error": "",
"result": {
"room_id": "123456789@chatroom",
"oper_type": 1,
"member_ids": [
"wxid_member_a"
],
"status": "succeeded",
"accepted": true,
"request_submitted": true,
"server_confirmed": true,
"request_cgi": 889,
"event_id": 42,
"wechat_ret": 0,
"wechat_error_message": "",
"message_type": 0,
"transport_error_type": 0,
"transport_error_code": 0
}
}
验证范围
用户已确认群免打扰正常;第 51 包的管理员添加、移除和群主转让均收到 ok=true、status=succeeded、wechat_ret=0、server_confirmed=true 的真机回包。用户现已确认个人二维码、好友备注及相关业务在微信 8.0.80 验证通过。这些群管理接口不包含进群申请审核。
17. 媒体下载与上传
适用插件:8.0.80。既有版本的图片、SILK 语音、视频、普通文件、表情的 HTTP 下载与上传,以及微信 CDN 上传已通过联调;视频和动画表情已验证缓存复用。三种通道共用同一媒体业务服务;本节分别给出当前通道的完整调用约定。
| command_name | 用途 | 默认等待 |
|---|---|---|
download_message_media |
准备消息媒体,返回手机本地副本 | 60 秒 |
upload_message_media |
自动准备媒体,再上传我们的服务器 | 60 秒 |
upload_file_to_wechat_cdn |
本地文件或外链文件上传微信 CDN | 120 秒 |
message_media_probe |
查看媒体或已有 CDN 任务状态 | 读取本地状态 |
后端在 POST /agent/poll 响应的 commands 数组中下发,命令对象无需 type。执行完成后,插件向 POST /agent/command_result 上报一次结果;后端接收后返回 HTTP 2xx。命令仍逐条执行,不在 poll 请求中等待媒体任务完成。文件字节由手机另行通过 HTTP 上传到 upload_url,不放进命令 JSON。
参数放在 args,外层 request_id 为本次真实命令 ID;微信 CDN 上传使用这个 ID,不在 args 重复填写。业务 ok/error 移到结果外层,其余字段位于 result,业务机器码位于 result.error_code,中文说明位于 result.message。开关或协议校验失败时可能仅有外层 error、result:{}。每次执行重新校验当前账号,不使用通用命令结果缓存;CDN 任务复用由媒体服务处理。后端等待结果建议至少 200 秒,业务接口 timeout_seconds 范围 1~180 秒。
支持 image 图片、voice 语音、video 视频、file 普通文件(49/6)、emoji 普通表情。微信 WXGF/WXAM 表情转换为 GIF,保留动画;原本为 GIF、PNG、JPEG、WebP 的表情保留原格式。转换失败返回错误,不输出 .bin;游戏表情不支持导出。语音只输出微信原始 SILK,不转 MP3;Opus 返回 unsupported_voice_codec。
原事件为 49/74(或 49/131)时,文件完成后微信可能保留同一本地记录、把数据库内层类型更新为 6/130,并更换服务器消息 ID。插件只有在当前账号、会话、localID、发送方/接收方、消息类、创建时间及外层类型均匹配,旧/新服务器 ID 非零且不同、创建时间有效,并且完成文件的 m_overwriteNewMsgId 明确等于原服务器 ID 时,才接受这种覆盖恢复。内层类型和覆盖编号优先读取 app 扩展,不能由 wrap 上的 0 遮住。
仍使用原事件 body.message_id 调用即可;业务恢复成功后会把原事件 ID 的定位更新为完成记录,并保存最终服务器 ID 别名。两个 ID 均可继续定位该消息;没有登记过的任意历史消息不能因此恢复。此处 6/130 是覆盖定位的接受条件,实际媒体下载/上传白名单仍只有普通文件 49/6:仍为 74/131 的记录尚未进入可下载类型,130 也未在当前媒体白名单中开放。media_type:"file" 只校验类型,不能强制转换;是否能下载以恢复后的实际消息及附件状态为准。
当前数据库记录仍为 49/74 或 49/131 时,下载/上传返回 error_code:"unsupported_media_type",中文说明为“该文件处于对方或我们,上传尚未完成(上传中)阶段,或完成消息尚未同步,请稍后使用同一 message_id 重试”。等待本机或对方上传完成、且完成记录同步到本机后,继续用同一个原事件 ID 重试即可;仅增大 timeout_seconds 不会让当前仍为 74/131 的请求等待类型变化。若长期没有完成记录,先用 message_media_probe 查看数据库类型、附件字段存在性及覆盖编号,不能据该提示认定本机或对方一定尚未完成,也不要把 media_type:"file" 当作强制下载开关。其他不支持类型仍返回原有通用提示;当前 130 文件的定位可恢复,但未开放媒体下载。
旧 /download_message_image(含 /debug/download_message_image 别名)和 /upload_message_image 已废弃,仅保留兼容。新接入分别使用 /download_message_media 和 /upload_message_media;图片仍传 message_id,类型自动识别,返回字段按统一媒体接口读取。长轮询 / WebSocket 的旧 upload_message_image 命令同样保留兼容,改用 upload_message_media。
上传接口要求有效发送状态;下载和探针不额外检查发送状态,仍受本模式插件总开关与授权校验。授权沿用当前通道约定。以下消息 ID、文件路径、资源 ID 和域名均为示意,需替换成实际值。
1. 下载媒体
在 POST /agent/poll 的响应中下发:
{
"commands": [
{
"request_id": "media_download_001",
"command_name": "download_message_media",
"args": {
"message_id": "1234567890123456789"
}
}
]
}
| 参数 | 必填 | 说明 |
|---|---|---|
message_id |
是 | 当前账号已登记消息事件的 body.message_id,必须为字符串,避免大整数精度丢失;原 74 文件事件可按本节覆盖规则恢复完成记录,无需自行改为新 ID |
media_type |
否 | 指定时校验是否与原消息匹配;可省略 |
timeout_seconds |
否 | 本次总等待时间,默认 60,范围 1~180 秒,数字 |
只支持当前账号已登记且定位可恢复的消息,不增加任意历史消息查询。下载成功结果示例(数据、路径均为示意):
{
"device_id": "webridge_device_example",
"session_id": "sess_example",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1792713600000,
"request_id": "media_download_001",
"command_name": "download_message_media",
"ok": true,
"duration_ms": 42,
"result": {
"message": "已使用缓存文件",
"message_id": "1234567890123456789",
"media_type": "voice",
"downloaded": true,
"from_cache": true,
"resolved_local_path": "/var/mobile/Containers/Data/Application/EXAMPLE/tmp/webridge_media/ACCOUNT_HASH/1234567890123456789/voice.silk",
"resolved_file_name": "voice.silk",
"file_size": 18576,
"mime_type": "application/octet-stream",
"md5": "0123456789abcdef0123456789abcdef",
"codec": "silk",
"voice_length_ms": 3260,
"duration_ms": 42
},
"error": ""
}
resolved_local_path 是手机沙盒路径,服务器无法直接 GET。需要服务器文件时调用上传接口。voice_length_ms 为语音时长,取不到为 0;duration_ms 为接口耗时。语音保持原字节,不播放、不主动设置已听状态。
缓存命中(插件副本或请求开始时微信已具备完整文件)返回 from_cache:true、message:"已使用缓存文件";新下载返回 false、"下载完成"。图片沿用现有原生下载和解码策略,可能回退微信提供的标准清晰度,不承诺原图。
视频同时检查下载队列、声明大小和副本可读性;原生状态缺失时,必须核对完整文件 MD5 才允许导出,不能仅凭大小一致放行。表情转换后的 file_size、md5 和 mime_type 对应实际导出文件;GIF 的 MIME 为 image/gif。旧 emoji.bin 缓存会自动重新准备。
本次等待超时返回 media_timeout;后台资源准备最多继续到 180 秒。重复相同消息请求会合并正在进行的准备任务;可稍后重试同一个 message_id。
2. 上传我们的服务器
在 POST /agent/poll 的响应中下发:
{
"commands": [
{
"request_id": "media_upload_001",
"command_name": "upload_message_media",
"args": {
"message_id": "1234567890123456789",
"timeout_seconds": 120
}
}
]
}
沿用下载命令的 args 参数;timeout_seconds 默认 60,覆盖本次等待准备和 HTTP 上传。另有可选 args.upload_url。command_request_id 使用命令外层真实 request_id。
默认 upload_media 就是设置里的业务服务器 base URL 加 /agent/upload_media,保留 base URL 的路径前缀。未配置业务服务器时须传入完整 HTTP/HTTPS upload_url。
请求体为 multipart/form-data,文件字段为 file。文字字段:
| 字段 | 内容 |
|---|---|
message_id / resolved_source_message_id |
来源消息 ID |
command_request_id |
本次真实 HTTP / 远程请求 ID |
file_name / file_size / mime_type / md5 |
实际上传副本的文件名、大小、MIME、MD5 |
media_type |
image / voice / video / file / emoji |
account_id / device_id / session_id |
沿用现有账号、设备和会话字段 |
sender_id / chat_id / room_id / is_group |
来源用户和会话,is_group 为文字 true / false |
codec / voice_length_ms |
仅语音,分别为 silk 和时长毫秒数 |
服务器必须返回 HTTP 2xx 和 JSON {"ok":true,"asset_id":"实际资源ID"},其中 asset_id 为非空字符串。可以额外返回 url、local_path,分别映射为 server_url、server_local_path。仅有 HTTP 200 状态、HTML 页面、空响应或缺少 asset_id 都判为失败。视频、文件通过磁盘 multipart 文件上传,不把整个请求体装入内存。
成功返回下载元数据,另加:
{
"device_id": "webridge_device_example",
"session_id": "sess_example",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1792713600000,
"request_id": "media_upload_001",
"command_name": "upload_message_media",
"ok": true,
"duration_ms": 42,
"result": {
"message": "上传成功",
"message_id": "1234567890123456789",
"media_type": "voice",
"downloaded": true,
"from_cache": true,
"uploaded": true,
"asset_id": "asset_voice_example",
"upload_http_status": 200,
"server_url": "https://example.com/media/asset_voice_example"
},
"error": ""
}
from_cache 仅说明源媒体来自本地;每次调用仍向服务器发起上传。上传失败可复用缓存重试;服务器是否按 command_request_id 去重由服务器实现。旧 /upload_message_image 也复用新的磁盘 multipart helper,成功同样要求 ok:true 和非空 asset_id;旧图片接口其他字段保持原约定。
3. 上传微信 CDN
在 POST /agent/poll 的响应中下发:
{
"commands": [
{
"request_id": "cdn_example_001",
"command_name": "upload_file_to_wechat_cdn",
"args": {
"file_path": "/var/mobile/Containers/Data/Application/EXAMPLE/tmp/webridge_media/ACCOUNT_HASH/1234567890123456789/voice.silk",
"timeout_seconds": 120
}
}
]
}
也可以把 args.file_path 换成 args.file_url:"https://example.com/demo.pdf",二者只传一个;外层 request_id 仍必填。
| 参数 | 必填 | 说明 |
|---|---|---|
外层 request_id |
是 | 1~128 字符字符串;不要在 args 重复填写 |
file_path / file_url |
二选一 | 手机绝对路径 / 可下载的 HTTP(S) 文件地址 |
timeout_seconds |
否 | 默认 120,范围 1~180 秒 |
当前统一通过“给文件传输助手发送普通文件”完成上传,因此允许产生一条 filehelper 文件消息。下载得到的语音、图片等字节也可提交,但返回的都是普通文件附件票据,不能直接当作图片、语音、视频或表情专用发送票据,也不是公开下载 URL。
只有本次确切消息完成发送,且账号、localID、文件大小、MD5、服务端消息 ID、file_id、AES key 均满足校验时返回成功:
{
"device_id": "webridge_device_example",
"session_id": "sess_example",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1792713600000,
"request_id": "cdn_example_001",
"command_name": "upload_file_to_wechat_cdn",
"ok": true,
"duration_ms": 42,
"result": {
"message": "微信 CDN 上传成功",
"request_id": "cdn_example_001",
"uploaded": true,
"pending": false,
"stage": "uploaded",
"cdn_type": "file",
"receiver": "filehelper",
"file_name": "voice.silk",
"file_size": 18576,
"md5": "0123456789abcdef0123456789abcdef",
"local_id": 123,
"server_id": "1234567890123456790",
"cdn": {
"file_id": "微信实际返回值",
"aes_key": "微信实际返回值",
"attach_id": "有则返回"
},
"staging_deleted": false,
"cleanup_status": "pending_ticket_validation"
},
"error": ""
}
当前保留临时消息,staging_deleted:false。不启用按条清理,以保留后续发送所需的票据;当前实现不会因超时删除发送中的消息。CDN 密钥只出现在成功业务响应中,探针不返回密钥或完整 XML。
同账号相同 request_id 绑定原任务,重复参数复用等待或结果,不再次发送;本地文件还存在时会核对内容变化,参数变化返回 request_id_conflict。同 URL 重试指原任务的下载结果,不重新抓取 URL。幂等记录保存在当前微信进程内,重启后失效;结果未知时不要重启后盲目重提。
等待超时返回 外层 ok:false,error:"media_timeout",result.pending:true;保留同一个 request_id 和文件参数继续调用,或用探针查询。后台最多观察 15 分钟;其后业务接口仍会按原消息重查迟到结果。最多保留 128 个 CDN 请求 ID,达到上限返回 cdn_task_limit,不会淘汰旧任务而隐式重传。
外链文件单次上限 512MB,开始前需预留相应缓存配额;本地文件受全局 1GB 配额限制。源文件、媒体缓存和上传体并存时,实际可用单文件大小会更低。空间不足明确返回错误,不扩大配额。
4. 调试探针
查询某条消息,不触发下载、播放或发送:
在 POST /agent/poll 的响应中下发:
{
"commands": [
{
"request_id": "media_probe_001",
"command_name": "message_media_probe",
"args": {
"message_id": "1234567890123456789"
}
}
]
}
消息探针 v2 在媒体白名单检查前执行,因此可检查 49/74、49/130、49/131 等当前不能下载的类型。ok:true 只表示取得可诊断的消息对象,不代表媒体可下载、文件完整或定位关系已更新。它不会调用 ChangeForDisplay、触发下载/播放/发送,不回写消息索引或覆盖关系;缓存检查不创建目录、不触发清理,也不更新缓存使用时间。
cached_message 是缓存对象快照,database_message 是按已登记 localID 读出的、其余身份字段已核对的数据库候选快照;两者可能引用同一个原生对象,不能视作不可变的历史前后对照。顶层消息状态优先取数据库候选,没有候选才取缓存,以 probe_source 区分;即使候选因服务器 ID 变化被业务恢复拒绝,也会优先用于诊断。两者均不可用时返回 message_not_found;诊断期间账号变化返回 account_changed,业务机器码在 result.error_code/外层 error,原因在 result.message。
以下字段位于本次命令结果的 result;字段类型中的布尔值在部分原生回包中可能序列化为 0/1,读取时应兼容。null 表示未取得/不适用,与已读取到数值 0 不同。
| 顶层字段 | 类型 | 含义 |
|---|---|---|
probe_version / message_id |
整数 / 字符串 | 当前消息探针版本为 2;消息 ID 仍是本次传入的原事件 ID,不会被替换为最终服务器 ID |
probe_source |
字符串 | database 或 cache,决定顶层消息快照来源 |
cached_message / database_message |
对象或 null | 各自的消息快照;没有相应对象为 null,快照字段见下表 |
lookup |
对象 | 定位诊断,包含 database_status、refresh_accepted,取得登记记录时另有两个 registered_*_id 字符串 |
lookup.registered_local_id / lookup.registered_server_id |
十进制字符串,可缺省 | 探针开始时已登记的 localID/服务器 ID;保留无符号 64 位值,禁止转为 JavaScript Number |
lookup.refresh_accepted |
布尔 | 仅表示当前候选通过业务定位规则,包括服务器 ID 相同或满足明确文件覆盖条件;仍为 74 时也可能为 true,不代表可下载,也不表示探针已保存更新 |
cache_ready |
布尔 | 以当前账号、传入消息 ID 和诊断媒体类型,只读检查插件副本是否可用;false 不等于微信没有原生文件 |
message_manager_class / signatures |
字符串 / 对象 | 原生消息管理器类及下载入口签名诊断,保留 responds、type_encoding、返回类型和参数信息;不能仅凭方法存在认定下载成功 |
emoji |
对象,可缺省 | 仅普通表情保留原有资源诊断;不代表已导出或上传 |
message |
字符串 | 中文诊断说明;成功通常为“已读取媒体状态,未触发下载或发送” |
lookup.database_status 的完整取值:
| 值 | 含义 |
|---|---|
ok |
数据库候选通过定位规则,refresh_accepted:true;可接受的明确文件覆盖也属于此状态,但仍不保证类型受支持或文件完整 |
lookup_not_found |
当前账号没有可用的已登记定位信息;若缓存对象还在,仍可返回缓存诊断 |
native_unavailable |
原生消息管理器或数据库重读入口不可用 |
server_id_changed |
其余身份匹配,但服务器 ID 不同且没有可接受的赋号/文件覆盖证据;refresh_accepted:false,候选仅供诊断 |
message_not_found_or_identity_mismatch |
没找到数据库对象,或 localID、会话、双方、消息类、时间、外层类型等身份不匹配;不返回该候选 |
account_changed |
诊断期间当前账号变化;本次业务结果失败,不沿用原账号对象 |
以下为顶层、cached_message、database_message 共用的消息快照字段;顶层另带上述探针与定位字段:
| 快照字段 | 类型 | 含义 |
|---|---|---|
message_class / extension_class |
字符串 | 消息对象/扩展对象类;扩展不存在时为空字符串 |
raw_message_type |
整数或 null | 原生外层类型,例如 49 |
app_msg_type / wrap_app_msg_type / extension_app_msg_type |
整数或 null | app_msg_type 是实际按扩展优先规则读取的 app 内层类型,非外层 49 时为 null;后两项分别直接读取 wrap 与扩展。wrap 为 0、扩展为 6 时不能把 wrap 的 0 当成最终类型 |
media_type / type_supported |
字符串 / 布尔 | 当前业务识别类型及是否在下载白名单;不支持时 media_type:""、type_supported:false |
diagnostic_media_type |
字符串 | 仅用于读取状态;外层 49 且内层 6/74/130/131 时为 file,不扩大业务支持范围 |
m_uiMesLocalID / m_n64MesSvrID / m_overwriteNewMsgId |
十进制字符串或 null | 本地 ID、当前服务器 ID、覆盖的原服务器 ID;取得数值 0 时返回 "0",缺失为 null |
bAppMsgCompleteFlag / m_bAppAttachExistInSvr |
数字或 null | 原生完成标记/附件已存在于服务器的标记,常见 0/1;保留现值,不单独据此判定本地文件完整 |
m_uiDownloadStatus / m_uiUploadStatus |
整数或 null | 原生字段现值;未取得为 null,0 不是缺失,也不能把上传状态当成下载完成 |
IsFileMsg / IsFileUploadingMsg |
布尔或 null | 微信原生分类结果;方法不可用或签名不符为 null。分类名不代替附件完成/可下载证据 |
has_m_nsAppAttachID / has_m_nsMsgAttachUrl / has_m_nsAesKey / has_m_authkey |
布尔 | 相应原生附件标识、地址、密钥/鉴权字段是否非空,只返回存在性,不输出原值 |
native_path |
字符串 | 微信原生文件路径;无法取得时为空字符串,不是服务器可直接 GET 的 URL |
native_file_exists / native_file_readable |
布尔 | 原生路径是否存在/是否可读;不表示已完整下载 |
native_size / expected_size |
非负整数 | 原生本地大小/原生声明大小,单位字节;没有可用大小时为 0 |
size_matches |
布尔 | 声明大小大于 0 且与本地大小相同;仅比较大小,未做完整文件校验,不能据此认定下载完成 |
download_status |
整数 | 兼容的原生下载状态,缺失时回退 0;需要区分缺失时读取上面的 m_uiDownloadStatus |
native_complete / native_downloading |
布尔 | 原生完成/队列进行中观察值;文件完成状态为 2,普通视频为 4、原始视频为 11,语音读取原生结束标记。false 不代替完整性结论;队列入口可用性另看 signatures/视频的 video_queue_known |
video_queue_known / has_expected_md5 / raw_video |
布尔 | 视频下载队列查询是否可用、是否有 32 位声明 MD5、是否原始视频;不返回声明 MD5 原文 |
voice_cancelled / voice_length_ms / voice_format |
非负整数 | 原生语音取消标记、毫秒时长及格式值;未取得时为 0,只用于语音诊断 |
xml |
对象,可缺省 | 仅外层 49 的 XML 安全摘要,字段如下;不返回完整 XML |
xml.parsed 为布尔;无法识别或解析时仅返回 {"parsed":false}。解析成功时,xml.type、xml.totallen、xml.overwrite_newmsgid 均为十进制字符串或 null,分别对应 app 类型、XML 声明字节数和覆盖的原服务器 ID;缺失或不是纯数字为 null,与字符串 "0" 不同。xml.has_attachid、xml.has_cdnattachurl、xml.has_aeskey、xml.has_authkey 为布尔,表示 XML 对应附件字段非空,只提供存在性。
探针不执行完整文件 MD5、视频解码或媒体导出校验,也不通过会修正数据库的状态接口恢复下载状态。因此 native_complete:false 不一定表示业务下载必定失败,size_matches:true 也不表示业务下载必定成功;最终以下载/上传接口返回的校验结果为准。
插件向 POST /agent/command_result 上报的成功结果示意(节选,编号与路径均为虚构,省略类名、签名及部分通用快照字段):
{
"device_id": "webridge_device_example",
"session_id": "sess_example",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1793836800000,
"request_id": "media_probe_001",
"command_name": "message_media_probe",
"ok": true,
"result": {
"probe_version": 2,
"message": "已读取媒体状态,未触发下载或发送",
"message_id": "1234567890123456789",
"probe_source": "database",
"lookup": {
"database_status": "ok",
"refresh_accepted": 1,
"registered_local_id": "4096",
"registered_server_id": "1234567890123456789"
},
"cache_ready": false,
"raw_message_type": 49,
"app_msg_type": 6,
"media_type": "file",
"type_supported": 1,
"diagnostic_media_type": "file",
"m_uiMesLocalID": "4096",
"m_n64MesSvrID": "1234567890123456790",
"m_overwriteNewMsgId": "1234567890123456789",
"native_size": 0,
"expected_size": 8126,
"native_complete": false,
"cached_message": {
"raw_message_type": 49,
"app_msg_type": 74,
"wrap_app_msg_type": 0,
"extension_app_msg_type": 74,
"media_type": "",
"type_supported": 0,
"diagnostic_media_type": "file",
"m_uiMesLocalID": "4096",
"m_n64MesSvrID": "1234567890123456789",
"m_overwriteNewMsgId": "0",
"bAppMsgCompleteFlag": 0,
"m_bAppAttachExistInSvr": 0,
"m_uiDownloadStatus": 0,
"m_uiUploadStatus": null,
"has_m_nsAppAttachID": false,
"has_m_nsMsgAttachUrl": false,
"has_m_nsAesKey": false,
"has_m_authkey": false,
"native_path": "/var/mobile/Containers/Data/Application/EXAMPLE/Documents/appdata/4096.csv",
"native_size": 0,
"expected_size": 8126,
"native_file_exists": false,
"native_file_readable": false,
"size_matches": false,
"download_status": 0,
"native_complete": false,
"native_downloading": false,
"xml": {
"parsed": true,
"type": "74",
"totallen": "8126",
"overwrite_newmsgid": "0",
"has_attachid": false,
"has_cdnattachurl": false,
"has_aeskey": false,
"has_authkey": false
}
},
"database_message": {
"raw_message_type": 49,
"app_msg_type": 6,
"wrap_app_msg_type": 0,
"extension_app_msg_type": 6,
"media_type": "file",
"type_supported": 1,
"diagnostic_media_type": "file",
"m_uiMesLocalID": "4096",
"m_n64MesSvrID": "1234567890123456790",
"m_overwriteNewMsgId": "1234567890123456789",
"bAppMsgCompleteFlag": 1,
"m_bAppAttachExistInSvr": 1,
"m_uiDownloadStatus": 0,
"m_uiUploadStatus": null,
"has_m_nsAppAttachID": true,
"has_m_nsMsgAttachUrl": true,
"has_m_nsAesKey": true,
"has_m_authkey": false,
"native_path": "/var/mobile/Containers/Data/Application/EXAMPLE/Documents/appdata/4096.csv",
"native_size": 0,
"expected_size": 8126,
"native_file_exists": false,
"native_file_readable": false,
"size_matches": false,
"download_status": 0,
"native_complete": false,
"native_downloading": false,
"xml": {
"parsed": true,
"type": "6",
"totallen": "8126",
"overwrite_newmsgid": "1234567890123456789",
"has_attachid": true,
"has_cdnattachurl": true,
"has_aeskey": true,
"has_authkey": false
}
}
},
"error": ""
}
示例中数据库文件已完成并明确覆盖原 ID,因此定位 ok 且 refresh_accepted:1;本地仍没有 8126 字节文件,需调用 download_message_media 才会准备资源。探针本身不会保存这次覆盖恢复。若数据库仍为 74,即使 refresh_accepted:1,也只表示其身份可接受,不能当作已具备可下载附件。
查看已有 CDN 任务,不创建新任务,也不返回 AES / XML:
在 POST /agent/poll 的响应中下发:
{
"commands": [
{
"request_id": "cdn_probe_001",
"command_name": "message_media_probe",
"args": {
"request_id": "cdn_example_001"
}
}
]
}
结果外层 ok:true 表示读取成功;result 内的 operation_ok、stage、pending、task_finished、task_error 表示上传状态。has_file_id / has_aes_key 仅表示字段是否存在。业务成功结果及票据仍从 upload_file_to_wechat_cdn 命令的 result 获取。
探针的 message_id 和 request_id 二选一。查询 CDN 时,参数里的 request_id 是被查询的原上传任务 ID。外层 request_id 则是本次探针调用 ID,二者不同。
5. 缓存文件规则
- 顺序:插件完整副本 → 微信完整本地文件 → 原生下载。同账号、消息和媒体类型的并发准备合并;同时最多准备 32 项。
- 插件副本位于
tmp/webridge_media/<账号摘要>/<消息ID>/;微信原文件不移动、不删除。写.part,校验大小、必要 MD5 和 SILK 头后原子改名;只返回正式副本。 - 记录文件名、大小、实际 MD5、类型和最近使用时间,不记录 AES 或 XML。命中前检查文件是否存在、可读、非空、大小一致,语音另核对 SILK 头;旧
emoji.bin不再直接命中,会从微信资源重新导出。 - 最近使用后保留 1 小时,所有账号共用 1GB 上限,包含本次新增副本、CDN 来源暂存和 multipart 临时文件。空间不足先淘汰最久未使用的空闲项,在用项不删,仍不足返回
cache_space_insufficient。已有其他发送目录继续使用其原有规则。 - 启动及新任务开始时清理;上传成功也不马上删除。系统可提前回收 tmp,文件丢失后会重新准备。
6. 后端接收服务复用
聊天媒体与朋友圈媒体可以使用同一个后端应用、同一个端口和同一套存储。 当前上传格式不同,推荐在现有业务后端提供两个接收入口,共用保存文件和生成访问地址的逻辑:
| 用途 | 推荐接收路由 | 手机发出的请求 | 成功条件 |
|---|---|---|---|
| 聊天媒体回传 | POST /agent/upload_media |
multipart/form-data,文件字段 file,其余字段见本节上传约定 |
HTTP 2xx,JSON ok:true 且非空 asset_id |
| 朋友圈媒体回传 | PUT /uploads/{resource_id} |
原始文件正文,Content-Type 为实际图片/视频 MIME;插件也支持原始正文 POST | 完整接收并保存后返回 HTTP 2xx;插件还核对发送字节数 |
朋友圈使用本篇 朋友圈媒体下载 中的 upload 对象:url 指向上述接收地址,resource_id 由后端分配,method 默认 PUT,headers 可携带鉴权。插件不解析朋友圈上传响应正文,只读取状态码;响应不要超过 64KiB。朋友圈首次受理不等于文件上传完成,仍需等 moments_result 或查询任务,确认 status:"succeeded"、media_ready:true、delivery:"upload"、upload_completed:true。上传结果未知时先按 resource_id 核对后端,插件不会自动重传。
仅处理 multipart 的 /agent/upload_media 不能直接接收朋友圈原始正文,把朋友圈 method 改成 POST 也不会改变正文格式。后端增加原始正文入口即可,插件不需要为了共用后端而改动。两个入口可以共用资源表、文件目录和 GET /files/...;若沿用朋友圈网页示例,需支持按 resource_id 读取,视频读取需支持 Range。
upload_file_to_wechat_cdn 是手机通过微信原生链路上传微信 CDN,不需要自有上传接收服务。其 file_url 可以使用上述后端已有文件地址;返回的普通文件附件票据不能代替朋友圈发布专用上传结果。
仓库中的 demo/消息媒体上传接收端/media_upload_server.py 与 demo/朋友圈媒体上传接收端/server.py 目前仍是两个独立示例,尚未合并;正式接入推荐复用同一业务后端,按本节支持两个入口。
7. 调用检查与常见错误
长轮询结果读取 /agent/command_result 请求正文的 result。不要把结果信封中的业务字段当作文件字节。
- 用新收到事件中的
body.message_id调下载,再调一次检查from_cache:true;核对文件名、MIME、大小和 MD5。语音应为 SILK,WXGF/WXAM 动画表情应导出 GIF。 - 调上传我们的服务器,确认文件实际落盘、
asset_id非空;from_cache:true只表示手机复用源文件,每次请求仍会上传。 - 下载或上传等待超时可重试同一
message_id;微信 CDN 超时则必须保留原request_id和文件参数,避免重复发送。CDN 临时消息目前保留,删除后的票据消费不在已确认范围内。 - 下载失败时同时提供业务响应和媒体探针响应;CDN 结果未知时用原上传 ID 查询,探针不含可消费票据。
常见错误:invalid_message_id、media_type_mismatch、message_not_found、not_logged_in、account_changed、unsupported_media_type、unsupported_voice_codec、media_timeout、cache_space_insufficient、media_checksum_mismatch、server_upload_failed、upload_url_required、request_id_conflict、cdn_task_limit、cdn_result_unknown。
等待超时结果示例:
{
"device_id": "webridge_device_example",
"session_id": "sess_example",
"account_id": "wxid_self",
"runtime_version": "8.0.80-2",
"build_marker": "webridge_build_20261106_settings_update_8080",
"ts_ms": 1792713600000,
"request_id": "media_download_001",
"command_name": "download_message_media",
"ok": false,
"duration_ms": 10001,
"result": {
"error_code": "media_timeout",
"message": "本次等待超时,原下载继续;可用同一 message_id 重试",
"message_id": "1234567890123456789",
"media_type": "video",
"duration_ms": 10001
},
"error": "media_timeout"
}
业务失败优先读取 result.error_code / 外层 error,中文原因读取 result.message;协议或开关失败按外层 error 处理。
本次验证范围: 本次文件覆盖恢复已由用户在 HTTP 真机确认:使用原 49/74 事件 ID,首次下载 8126 字节 CSV 成功,from_cache:false;同一个 ZIP 未完成时数据库仍为 74 且无 AES/附件/CDN 字段,完成后数据库变为 6 并以覆盖编号指向原 ID,随后首次下载 12,155,230 字节 ZIP 成功,from_cache:false。这些历史结果记录了共享媒体服务的实际文件链路;用户现已确认相关媒体功能在微信 8.0.80 验证通过。媒体白名单、索引保存及文件完整性条件仍适用。
18. 公众号:搜索、列表、关注/取关与历史文章
公众号五项支持 HTTP、长轮询和 WebSocket,订阅号与服务号统一管理。公众号相关业务已由用户在微信 8.0.80 验证通过。目标必须使用真实公众号 username,不接受昵称或 alias,也不依赖 gh_ 前缀判断类型。下方示例均为虚构。
| 命令 | 必需参数 | 可选参数 |
|---|---|---|
official_account_search |
keyword |
— |
official_accounts |
— | type、keyword、limit、offset |
official_account_follow |
id |
— |
official_account_unfollow |
id |
— |
official_account_articles |
id |
limit、cursor |
三种通道使用相同参数及业务语义。参数采用严格类型校验,未知字段报 INVALID_ARGUMENT;整数不接受数字字符串或布尔值。需正常授权和登录;搜索、列表、文章查询不受发送总开关限制,关注/取关需要开启该开关,关闭时报 SEND_DISABLED。
已关注列表
{"type":"all","keyword":"","limit":100,"offset":0}
所有参数可省略。type 支持 all(默认)、subscription、service,区分大小写;keyword 是按账号、展示名、昵称、备注、alias 做不区分大小写的本地包含匹配。limit 默认 100、范围 1–1000;offset 默认 0、范围 0–4294967295。
返回 status:"ok"、type、keyword、items、total、limit、offset、has_more。total 是筛选后的数量;按展示名和账号排序,偏移超出范围时返回空页。每项沿用联系人摘要中的 account_id、display_name、nick_name、remark、alias、avatar_url 等字段,并增加 account_type(subscription/service)及 is_followed:true。这是本机已同步的名单,关注/取关后的关系可能延迟更新。
未登录返回 ACCOUNT_UNAVAILABLE,联系人尚未就绪返回 CONTACTS_NOT_READY,不能把失败当作空名单。它不是全网公众号搜索。
公众号关键词搜索
{"keyword":"示例"}
调用 official_account_search(HTTP 为 POST /official_account_search),仅接受 keyword 字符串,去除首尾空白后长度为 1–256 个 UTF-16 代码单元。输入公众号名称、微信号或关键词,联网返回微信原生首批候选,支持发现未关注公众号。
成功返回 status:"ok"、keyword、items、returned_count 和 request_submitted;取得原生返回码时包含 native_ret。候选复用联系人摘要中的 account_id、display_name、nick_name、alias、avatar_url 等字段,增加 account_type(本地已知时为 subscription/service,未取得为 null)与 is_followed(本地当前关注关系,可能延迟同步)。contact_found 仅表示本地联系人是否存在;未缓存的联网候选仍会返回。保留微信顺序并按真实 username 去重。无匹配时为 ok:true、items:[]、returned_count:0;超时、断网和微信业务错误不能当作无结果。
选中候选后,将其 account_id 作为 official_account_follow 的 id,例如 {"id":"official_account_example"}。搜索不会自动关注,也不受发送开关限制。它仅返回首批候选,不支持 limit、offset、cursor、page,不返回 total 或 has_more;匹配范围、排序及条数由微信决定。
约 30 秒业务超时,客户端等待建议至少 40 秒。
响应包含 search_diagnostics:当前 request_cgi 为 719,has_content 表示是否取得非空 JSON,json_byte_count 为其 UTF-8 字节数,json_ret 为 JSON 业务状态,section_count 为结果分组数。raw_item_count、parsed_item_count、contact_item_count、missing_username_count、duplicate_count 用于定位候选解析;missing_contact_count、unsupported_type_count 为旧诊断保留字段,当前为 0。native_business_type 为请求的公众号类型 1,native_continue_flag、native_offset 仅用于诊断,不开放分页。原生错误、JSON 业务错误、缺失内容或格式不兼容均返回失败,不能当成无匹配;只有有效成功的空列表才返回 ok:true、items:[]。诊断从开始原生请求时即可出现,不包含原始 JSON 或账号凭据。
关注与取消关注
两项均只传 {"id":"official_account_example"}。关注会先核对目标身份,再走公众号关注流程;返回单次结果,无需查询任务。
| 操作 | status |
ok |
含义 |
|---|---|---|---|
| 关注 | already_followed |
true | 本地已关注,request_submitted=false |
| 关注 | followed |
true | 原生成功回调,且本地已观察到关注 |
| 关注 | pending_sync |
true | 原生成功回调,本地关系尚未同步;is_followed=false 不代表关注失败 |
| 关注 | unknown |
false | 已调用原生关注入口,尚无确定结果;先查列表,不自动重发 |
| 关注 | timeout |
false | 前置资料查询等阶段超时,尚未调用关注入口 |
| 取关 | already_unfollowed |
true | 本地已无关注关系,request_submitted=false,不重复清理 |
| 取关 | submitted |
true | 本地流程及同步提交已执行,request_submitted=true;未确认服务器最终结果 |
| 取关 | unknown |
false | 原生调用中发生异常,可能已有副作用,request_submitted=null |
| 两项 | failed |
false | 参数、前置校验或微信业务失败,查看错误码及中文错误 |
响应包含目标 account_id,并按取得的信息返回 is_followed、request_submitted。is_followed 只表示本地观察值,未知可为 null 或省略;校验失败时也可省略目标字段。关注无回包时 native_invoked=true、request_submitted=null,不能视为确认提交;收到本次回包才可标为 true。profile_request_submitted=true 只表示前置资料查询已提交。
取关可能清理对应本地会话、聊天消息和其他关联信息。 进入原生取关调用后返回 chat_history_may_be_deleted=true;submitted 后本地 is_followed 仍可能暂为 true。结果未知时先查询关注关系,避免重复清理。
同一公众号仍在关注时返回 OPERATION_IN_PROGRESS;搜索、关注及文章请求合计最多同时保留 16 个,超出返回 TOO_MANY_REQUESTS。搜索、关注与文章查询整次约 30 秒超时、约 32 秒等待保护,客户端等待建议至少 40 秒。
历史文章按篇数批量续取
{"id":"official_account_example","limit":100}
limit 默认 100,范围 1–1000,按多图文展开后的文章篇数计数。插件自动连续读取微信原生小页并合并,最多返回指定篇数;不支持 page、page_size、offset,不提供历史总文章数,也不提供完整正文。
成功返回以下字段:
| 字段 | 类型 | 含义 |
|---|---|---|
status |
string | ok |
account_id |
string | 目标公众号 username |
limit |
integer | 本次最多返回篇数 |
returned_count |
integer | 等于 items.length,不超过 limit |
items |
array | 本批展开并合并后的文章 |
has_more |
boolean | 仍有缓存余量,或微信原生历史尚未结束 |
next_cursor |
string/null | 有后续时原样传回;全部结束时为 null |
request_submitted |
boolean | 本次是否提交过微信原生资料请求;仅使用缓存余量时为 false |
每篇文章包含 account_id、app_msg_id(十进制字符串)、item_index(原生篇目位置)、title、digest、cover_url、url、author、publish_time(Unix 秒,未知为 null)、is_paid(boolean/null)。account_id + app_msg_id + item_index 共同定位文章;付费标记不能证明当前账号拥有阅读权限。
需要更早历史时,传入上一批的游标:
{"id":"official_account_example","limit":100,"cursor":"上一批返回的next_cursor"}
原生一页超过本批额度的文章会随游标保留,下一批先返回余量,不跳篇。微信已经结束但缓存未取尽时 has_more 仍为 true;空原生页会继续翻,不等于历史结束。成功不足 limit 时表示已到微信允许展示的末尾。整批失败返回 ok=false,不把部分结果伪装成完整成功;在游标有效期内可从原输入游标重试读取,或降低 limit。
cursor 可省略或传空字符串,从最新文章开始;不能传 null。它是进程内不透明 token,绑定当前账号、登录会话和目标公众号,单个 token 从生成起有效 10 分钟;全局最多保留 128 个,超出时淘汰最旧者。切换账号/会话、重启插件都会使旧游标失效,返回 INVALID_CURSOR 后应从最新文章重新读取。
错误、账号变化与发送文本
错误沿用中文 error 和机器可读 error_code。常见错误包括 INVALID_ARGUMENT、INVALID_ACCOUNT_ID、INVALID_ACCOUNT_TYPE、ACCOUNT_UNAVAILABLE、CONTACTS_NOT_READY、NATIVE_API_UNAVAILABLE、REQUEST_NOT_STARTED、REQUEST_TIMEOUT、WECHAT_REQUEST_FAILED、RESPONSE_INVALID。文章另有 INVALID_CURSOR、ARTICLES_RESTRICTED、CURSOR_NOT_ADVANCING;同一请求期间账号或登录会话变化返回 ACCOUNT_CHANGED。失败时不保证有 items、has_more、next_cursor 等成功字段。
向公众号发送文本仍使用已有 send_message,将同一个 username 放入 receiver,例如 {"receiver":"official_account_example","content":"你好"}。沿用原发送结果与公众号私信权限限制,成功不代表对方已收到或已读。
命令回包、等待与去重
请求字段放在 args。回包沿用 command_result:外层 ok/error 表示结果,业务的 status、error_code、request_submitted、items、next_cursor 等保留在 result,读取错误码用 result.error_code。外层 account_id 是当前登录账号,result.account_id 才是目标公众号;前置校验失败可省略后者。
五项命令在后台执行并一次返回终态,不阻塞通信队列;仍需等待约 30 秒业务超时,客户端建议至少 40 秒。每次都会进入共享服务重新校验当前账号,公众号五项不走通用的最近 100 条 request_id 结果缓存。request_id 用于关联回包,不能充当操作去重保证。不要因结果丢失而盲目重发关注/取关,先查询本地关系。
在 /agent/poll 响应中下发:
{"commands":[{"request_id":"req-official-articles-001","command_name":"official_account_articles","args":{"id":"official_account_example","limit":100}}]}
插件执行后向 /agent/command_result 提交结果,再继续下一条命令或下一轮 poll。等待期间可停止或重启轮询;同批命令仍逐条串行执行。
19. 朋友圈:调用、结果与后续操作
你的后端实现本协议前文的5个/agent/*入口;插件主动访问后端。以下下发JSON是后端对/agent/poll的响应,不是向手机POST。无需让后端访问手机HTTP。示例ID、上传域名和Token均需替换,所有数据均为虚构。
最短读取流程:
- 注册/握手后确认capabilities包含所需
moments_*命令。 - 下发
moments_list,保存command_result.result.operation_id;首次ok=true不表示页面已返回。 - 等
moments_result完成事件,或下发moments_operation查询原编号。 - 从完成结果
items[]继续详情、历史、互动或媒体上传。
{
"commands": [
{
"request_id": "demo-list-001",
"command_name": "moments_list",
"args": {}
}
]
}
查询同一操作:
{
"commands": [
{
"request_id": "demo-operation-query-001",
"command_name": "moments_operation",
"args": {
"operation_id": "11111111-1111-4111-8111-111111111111"
}
}
]
}
每次查询必须使用新的外层request_id(例如query-002、query-003),args.operation_id保持原值。重复request_id会命中命令结果缓存,可能一直看到第一次pending。查询命令的外层request_id对应本次查询,result.request_id仍属于原任务。
返回结果怎么判断
| status/情况 | 意义 | 接下来怎么做 |
|---|---|---|
| preparing | 准备资源或构造任务 | 等完成事件或查询原operation_id |
| pending | 微信请求或媒体上传进行中 | 查询原操作,不能再调用入口来“催进度” |
| succeeded | 本次业务完成 | 按下表读取对应业务结果 |
| failed | 本次失败 | 看error与诊断;保留operation_id;上传可能需要核对后端 |
| unknown | 未取得可信最终确认 | 尤其write_accepted=true时不重发写入;继续查询原操作或核实实际状态 |
| 同步返回且没有operation_id | 本地权限读取或空参数状态探针 | 直接使用当前结果,不再查询不存在的操作 |
| 业务 | 成功判断 |
|---|---|
| 页面/详情/联网写操作 | status=succeeded,通常server_confirmed=true;unchanged表示联网确认无需写入 |
| friend/recent权限查询 | 同步ok=true、source=local_settings;server_confirmed=false正常 |
| 视频准备探针 | status=succeeded且media_ready=true;没有发帖,server_confirmed=false正常 |
| 本地媒体准备 | status=succeeded且media_ready=true,delivery=local,读取media_url |
| 手机主动上传 | status=succeeded且media_ready=true且upload_completed=true,delivery=upload,按resource_id读取后端 |
accepted只表示微信查询/任务受理。互动和单帖权限先查详情,所以accepted=true可能只是详情查询已发出。write_accepted才表示提交了微信写操作;它不用于表达上传文件。server_confirmed只描述微信结果,不描述上传目标;媒体即使上传成功仍为false。
request_id用于关联;最长128个UTF-16单位,本模式由命令外层写入,不在 args 重复传入。它不是跨重启的幂等保证。操作记录最多48项,完成记录在后续请求进入时可能淘汰,通常最多保留10分钟;重启即丢失,不是永久任务数据库。账号切换后不能查询旧账号操作,旧账号事件也不会包成新账号事件。
一般查询/资源准备120秒超时且未提交微信写入时失败;已提交写入却未确认时可能unknown,晚回包仍可用更高revision更新原操作。相同范围处理中或unknown锁未释放时会拒绝新任务。网络断开也不能证明微信写入或后端上传没有发生。
首次POST /agent/command_result只代表受理(以下省略通用设备和时间字段):
{
"request_id": "demo-list-001",
"command_name": "moments_list",
"ok": true,
"error": "",
"result": {
"status": "pending",
"accepted": true,
"write_accepted": false,
"server_confirmed": false,
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "list"
}
}
ok/error在命令结果外层,result内会剥掉这两个字段;其他业务字段留在result。最终完成通过POST /agent/event到达,完整业务结果在body内,body含自己的ok/error。媒体上传最长可能约300秒,首次命令结果的duration_ms不包含整个异步操作耗时。
{
"event_name": "moments_result",
"event_id": "msg:moments:11111111-1111-4111-8111-111111111111:1",
"account_id": "wxid_self",
"body": {
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "list",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"items": [],
"next_cursor": "12345678901234567890",
"has_more": null,
"snapshot_complete": true,
"snapshot_source": "network",
"missing_ids": [],
"raw_count": 1,
"truncated": false
}
}
命令结果和完成事件可能先后交错;按account_id、body.operation_id关联,用revision合并较新状态,按event_id去重,不按到达顺序猜测。完成事件不能通过回一个HTTP200或接收WS帧来替代业务成功判断。
朋友圈数据怎么继续使用
| 取到的字段 | 含义 | 下一步 |
|---|---|---|
| operation_id | 本次异步操作UUID | 用operation查询,不能传给detail |
| items[].sns_id/item.sns_id | 服务器动态ID | detail、点赞、评论、删除、单帖权限 |
| next_cursor | 下一页边界 | 同一时间线/同一用户下一次请求的cursor |
| media[].mid | 本帖媒体标识 | media/download的media_id;不是URL或数组下标 |
| comments[].comment_id/comment64_id | 本帖评论标识 | 回复时传reply_comment_id,删除时传comment_id |
| resource_id | 你的后端分配的上传资源标识 | 查询后端存储或生成后端读取地址 |
| media_url | 仅本地模式的手机临时文件地址 | 拼接手机HTTP基址GET,远程上传模式不依赖它 |
提交的动态ID、评论ID和cursor必须是非零UInt64十进制字符串,不接受JSON数字或溢出。输出sns_id和游标为字符串;评论ID沿用微信原生类型,可能为数字或字符串,后端需要无损解析大整数再转字符串。不要让64位ID经过JavaScript Number;网页demo已有无损保护。media_id保持mid原值,不要求它是十进制数。所有时间戳字段按字段名区分秒(create_time)与毫秒(*_ms)。
动态对象字段包括sns_id、wxid、create_time、content、type、is_mine、liked、like_count、comment_count、private、visibility_type、media、likes、comments。type和visibility_type保留微信原始值,不要把它们直接当发布接口的type/visibility枚举。普通图片动态type通常1,普通视频通常15。
媒体包含mid、type、subType、totalSize、videoDuration、width、height、preview_urls及原生可用的dataUrl/hdUrl/uhdUrl/lowBandUrl/attachUrl/attachThumbUrl。某字段不存在表示原生未提供。CDN地址不保证电脑直接可用;HTTP200也可能是占位图。列表不自动下载整页资源。
评论包含comment_id、comment64_id、wxid、nickname、content、create_time、is_mine及可用的reply_comment_id/reply_comment64_id/reply_wxid;likes沿用类似摘要结构。每条动态媒体最多32项、赞与评论各200项,media_truncated/interactions_truncated为true表示已截断;接口没有继续分页获取全部评论的入口。
历史和快照的正确判断
direction默认head。显式head不能带cursor;传cursor且省略direction时按tail;tail必须带上一页next_cursor,不能传0,不能自行减1,也不接受自定义page_size。页大小由微信控制。
has_more=false才表示原始历史网络空页已确认到头;has_more=null表示暂不确定,有next_cursor可以继续。items不足40条、筛选后空页都不等于到头。has_more不明确但next_cursor=null时,停止本轮继续翻页,不要编造游标;可以保留结果后稍后重新从最新页核实。
snapshot_complete表示本页是否补齐,不是全量历史完成。snapshot_source=network/network_merged_cache区分网络完整对象与网络/缓存合并。missing_ids列出无法补齐的动态,可查detail。unchanged=true也要检查完整性。单页最多200条,truncated表示原始页超出保护上限。
好友时间线是微信当前账号按权限筛选后的信息流,不保证是所有好友相册并集或无限历史。用户相册的 visible_period 使用明确原生字段或三天/半年提示;提示为空仍为 unknown。空页、缓存计数或 ret_tips 均不足以证明被屏蔽;可用 check_visibility 获取首屏诊断。后端自行保存、去重、比较历史;插件不自动定时刷新。
远程后端:手机主动上传媒体
推荐远程接入使用这一流程:后端先分配resource_id和上传地址 → 下发media/download并附upload → 手机调用微信下载、解密或复用缓存 → 手机主动HTTP上传 → 接收moments_result → 后端按resource_id取自己保存的文件。全程无需后端访问手机HTTP,也不会把文件Base64塞进长轮询/WS。
upload字段:
| 字段 | 必填/默认 | 使用说明 |
|---|---|---|
| url | 必填 | 手机可达的HTTP(S)上传完整地址;可含签名查询参数,不允许URL账号密码、片段或重定向 |
| resource_id | 必填 | 后端自分配的非空标识,最多128个UTF-16单位;用于关联和核实重复上传 |
| method | PUT | 可选PUT或POST;请求正文是原始文件二进制,不是JSON、Base64或multipart |
| headers | 空对象 | 可传Authorization或签名所需头;最多16项、单值4096字符、合计16KiB,不接收控制字符或重复大小写头名 |
插件自动设置Content-Type=image/jpeg或video/mp4、Content-Length。不能覆盖这两个头、Host、Transfer-Encoding、Connection、Cookie、Proxy-Authorization。使用签名上传时,后端需按对应文件类型签名;resource_id不会自动变成HTTP头,后端自行在上传路径或自己的签名头中关联。
后端接收完整文件并成功存储后才返回2xx;推荐201或204。插件不解析上传响应正文,因此响应正文控制在64KiB内;文件访问URL由你的后端通过resource_id自行生成。返回2xx且上传字节完整后才upload_completed=true;30x不跟随,4xx/5xx失败,不进行插件层自动重传。
准备阶段最多120秒;开始上传后另有180秒总上限(请求等待上限90秒),一次上传模式操作最多约300秒。上传过程按需读文件,不整体加载视频。上传中断、超时或成功状态但缺完整传输确认时upload_outcome_unknown=true:后端可能已经存储,先按resource_id核实,再决定是否重传;接收端应对同一resource_id幂等保存。重传由后端显式发起,插件不提供断点续传。
上传URL与鉴权头只用于当前任务,不进入结果、事件或完成后的操作记录。切号、微信退出或进程重启可能中止任务;这不是跨进程持久后台上传。上传模式成功后没有手机media_url,也没有后端文件的expires_at_ms;保留期限由你的后端决定。微信原生缓存可复用;插件生成的JPEG在上传终态清理,不删除微信视频缓存。
已有零依赖示例位于demo/朋友圈媒体上传接收端/server.py:PUT /uploads/{小写UUID}接收文件,要求Authorization: Bearer <MOMENTS_UPLOAD_TOKEN>;GET /files/{同UUID}读取临时文件,支持Range。后端用UUID同时作为resource_id和路径即可。示例保留10分钟、最多16个资源、重启清空;GET链接含不可猜测UUID,知道链接即可读取,生产环境应按自己的账号鉴权和保留策略实现。
网页demo/朋友圈网页工作台可展开“手机主动上传媒体”,填写已部署接收端地址和Token;随后每次点击媒体下载会生成独立UUID并上传,网页从接收端读取。手机不能访问电脑的127.0.0.1,需要使用手机可达的接收端地址。网页仍用HTTP测试业务;长轮询/WS业务后端直接使用本节命令参数即可。
发布、权限与排错边界
发布visibility=public(公开)、private(仅自己)、include(仅指定用户)、exclude(不给指定用户看)。include/exclude须带非空wxids,最多200项;public/private不能带非空名单。新发布和修改已有本人动态均支持这四种范围;不临时修改微信UI全局发帖设置。
| 资源/场景 | 当前保护限制 |
|---|---|
| 发布正文 | 微信动态text_limit及原生加权计数校验;接口输入另限32768个UTF-16单位,不等于固定可发32768字 |
| 发布图片 | 1~9张;单张输入20MiB,处理后JPEG合计20MiB,最长边2048 |
| 发布视频 | 输入100MiB;时长读当前微信配置video_duration_limit_seconds;检查有效视频轨道、时长、可播放性;thumb省略自动取封面 |
| 下载已发布媒体 | 单文件120MiB,最多2个并发准备;图片转最长边2048 JPEG,不保证原图字节;视频保留MP4,不转码 |
| 视频准备探针 | 每账号最多1项,不占发布锁;视频100MiB,显式封面20MiB,不创建发布任务 |
| 评论 | 非空,接口最多4096个UTF-16单位;微信服务器仍可能拒绝 |
这些MiB是插件保护上限,不是微信通用服务器限额。视频时长、正文配置以当前账号探针为准,不能固定写成30秒或2000字。媒体下载成功也不保证所有电脑浏览器支持该视频编码。
好友权限friend查询为本地hide_from/dont_see;recent查询为本人period。修改recent支持three_days/one_month/half_year/all;查看别人的可见期限使用user。post查询是联网详情,返回item、visible_users、excluded_users;visibility_type是原生字段,修改参数仍使用字符串visibility。只允许修改本人动态。
权限写入的server_confirmed=true而local_verified=false表示服务器已成功、本地读回异常,local_update_error给出原因,不应因此再写一次。dont_see完整服务器确认缺失会保留unknown。不要用“本地开关已变化”代替服务器确认。
视频发布前下载支持无文件后缀URL,结合文件头/响应文件名处理,并异步等待AVFoundation元数据。video_diagnostics.stage为download/metadata/thumbnail/ready;保留file_bytes、extension、duration_seconds、video_track_count、playable、thumb_ready等已取得信息。下载及可选thumb_download含http_status、received_bytes、expected_bytes(-1未知)、elapsed_ms、phase及底层error_domain/error_code;系统提供时还有DNS/连接/响应/传输metrics。准备失败不发布。下载/元数据/封面问题可先使用probe检查,不能把probe成功当发布成功。
评论错误保留raw_ret、server_error及comment_diagnostics;引用不存在、本人评论或无权限对象会明确拒绝。点赞、评论、删除和修改已有单帖都先联网详情;一个帖子的处理中任务可能阻止同帖新请求,按原operation_id等结果。
朋友圈读取、发布互动、单帖权限和本地媒体预览已有 HTTP 真机验收;实现标识为 moments_20261016_upload_v1。用户现已确认朋友圈相关功能(含主动上传)在微信 8.0.80 验证通过。
接口/命令完整用法
下列每项包含请求字段、可直接使用的场景示例、结果含义与下一步。重复字段含义以本章前面的表格为准。下面展示完整业务对象:通过命令结果返回时,ok/error 位于外层,其余字段放入 result;通过 moments_result 事件返回时,完整对象位于 body。所有示例响应均为虚构,省略部分可选字段。
好友最新时间线与历史:moments_list
每次请求一页当前账号能看到的好友时间线。默认最新页;提供cursor且省略direction时按历史页处理。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
direction |
string | 可选 | head 最新页;tail 历史页。省略时,有 cursor 按 tail,否则按 head;显式 head 不能带 cursor。 |
cursor |
string | direction=tail 时必填 |
上一页返回的 next_cursor,原样传入,不自行减 1。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
最新页
{
"commands": [
{
"request_id": "demo-list-001",
"command_name": "moments_list",
"args": {}
}
]
}
历史下一页
{
"commands": [
{
"request_id": "demo-list-002",
"command_name": "moments_list",
"args": {
"direction": "tail",
"cursor": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "list",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"items": [
{
"sns_id": "12345678901234567890",
"wxid": "wxid_friend",
"create_time": 1775000000,
"content": "示例动态",
"type": 1,
"is_mine": false,
"liked": false,
"like_count": 0,
"comment_count": 1,
"private": false,
"visibility_type": 1,
"media": [
{
"mid": "example-media-id",
"type": 2,
"width": 1080,
"height": 1440,
"preview_urls": []
}
],
"likes": [],
"comments": [
{
"comment_id": 67,
"comment64_id": "670000000000000001",
"wxid": "wxid_friend",
"content": "示例评论",
"is_mine": false
}
],
"media_truncated": false,
"interactions_truncated": false
}
],
"next_cursor": "12345678901234567890",
"has_more": null,
"snapshot_complete": true,
"snapshot_source": "network",
"missing_ids": [],
"raw_count": 1,
"truncated": false
}
下一步: 取items[].sns_id可查详情或互动;取next_cursor请求下一页;取media[].mid准备媒体。
指定用户相册与可见期限:moments_user
传 wxid 获取本人或好友相册。visible_period 优先读取原生期限字段;字段不足时仅从明确的三天/半年提示补充,空提示仍为 unknown。visibility_status 是本次响应的可见性判断,不能据此确认被屏蔽、拉黑或删除。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
wxid |
string | 必填 | 用户 wxid,不能是 @chatroom 群 ID。 |
direction |
string | 可选 | head 最新页;tail 历史页。省略时,有 cursor 按 tail,否则按 head;显式 head 不能带 cursor。 |
cursor |
string | direction=tail 时必填 |
上一页返回的 next_cursor,原样传入,不自行减 1。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
check_visibility |
boolean | 可选 | 默认 false;仅指定用户相册的 head 请求可传。true 时清空本次请求的首屏 MD5,尝试获取完整首屏用于诊断,不保证服务器一定返回完整数据;不支持 timeline 或 tail。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
可见性检查(HTTP 请求体或命令 args)
{"wxid":"wxid_xxx","check_visibility":true}
普通请求继续使用微信的 MD5 增量策略。诊断仍复用同一个相册入口;刷新接口在 scope=user、direction=head 时也支持 check_visibility。按原 operation_id 等待终态,items=[] 本身不能证明空相册或被屏蔽。
用户最新页
{
"commands": [
{
"request_id": "demo-user-001",
"command_name": "moments_user",
"args": {
"wxid": "wxid_xxx"
}
}
]
}
用户历史页
{
"commands": [
{
"request_id": "demo-user-002",
"command_name": "moments_user",
"args": {
"wxid": "wxid_xxx",
"cursor": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "user",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"items": [
{
"sns_id": "12345678901234567890",
"wxid": "wxid_friend",
"create_time": 1775000000,
"content": "示例动态",
"type": 1,
"is_mine": false,
"liked": false,
"like_count": 0,
"comment_count": 1,
"private": false,
"visibility_type": 1,
"media": [
{
"mid": "example-media-id",
"type": 2,
"width": 1080,
"height": 1440,
"preview_urls": []
}
],
"likes": [],
"comments": [
{
"comment_id": 67,
"comment64_id": "670000000000000001",
"wxid": "wxid_friend",
"content": "示例评论",
"is_mine": false
}
],
"media_truncated": false,
"interactions_truncated": false
}
],
"next_cursor": "12345678901234567890",
"has_more": null,
"snapshot_complete": true,
"snapshot_source": "network",
"missing_ids": [],
"raw_count": 1,
"truncated": false,
"visible_period": "all",
"visible_period_source": "sns_user_info",
"visibility_status": "visible",
"check_visibility": false,
"request_md5_empty": true,
"top_raw_count": 0,
"same_md5_count": 0,
"raw_ret": 0,
"privacy_flags_raw": 4096,
"privacy_recent_raw": 4294967295,
"ret_tips": "",
"limited_id": "0",
"continue_id": "0"
}
可见性诊断字段
| 字段 | 类型 | 含义 |
|---|---|---|
visibility_status |
string | visible:本次响应含有效 ID 且非 noChange 的普通或置顶对象;empty_or_restricted:新首屏确认无内容,但无法区分从未发布、无权查看或非好友;unknown:证据不足。 |
visible_period_source |
string | sns_user_info 原生期限字段;ret_tips 明确提示补充;unknown 未确定。 |
check_visibility |
boolean | 本次是否请求清空首屏 MD5。 |
request_md5_empty |
boolean / null | 实际请求 MD5 是否为空;不可读取时为 null,不能仅凭 check_visibility 判断请求内容。 |
raw_count |
integer / null | 普通 objectList 的原始条数;不可读取时为 null,可能与 items 长度不同。 |
top_raw_count |
integer / null | 置顶 topListObjectList 的原始条数;置顶对象目前仅用于诊断,不新增到 items。 |
same_md5_count |
integer / null | objectCountForSameMd5 原始计数;大于零仅说明同 MD5 计数,不能单独证明当前可见。 |
privacy_recent_raw / privacy_flags_raw |
integer / null | 原生期限小时数和标志位;不可读取时为 null。 |
continue_id / limited_id |
string / null | 原生 UInt64 继续/限制边界;不可读取时为 null,翻页仍用 next_cursor。 |
只有原生返回码为 0 或 207、head 请求实际 MD5 为空、普通和置顶条数及同 MD5 计数均明确为零、continue_id 明确为 "0" 时,才报告 empty_or_restricted。同 MD5 未变化、仅有缓存计数、字段缺失或首屏未完成时不能据空数组下结论。
操作状态保持兼容:非零 raw_ret 仍返回 status=failed、ok=false、server_confirmed=false,同时保留取得的诊断字段;visibility_status 独立描述响应证据。没有原生响应的网络失败中,visibility_status 保持 unknown,不输出确定的 blocked 标志。受理结果及失败回调可能只有未知值,必须等待原 operation_id 的终态或对应 moments_result。
下一步: 继续历史时保留同一个wxid并使用本页next_cursor;历史页visible_period=unknown不能覆盖此前已确认的期限。
联网获取动态详情:moments_detail
根据列表、发布结果或已知sns_id联网查询,返回item。媒体、评论及权限信息仍受当前账号服务器可见范围限制。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
动态详情
{
"commands": [
{
"request_id": "demo-detail-001",
"command_name": "moments_detail",
"args": {
"sns_id": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "detail",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"item": {
"sns_id": "12345678901234567890",
"wxid": "wxid_friend",
"create_time": 1775000000,
"content": "示例动态",
"type": 1,
"is_mine": false,
"liked": false,
"like_count": 0,
"comment_count": 1,
"private": false,
"visibility_type": 1,
"media": [
{
"mid": "example-media-id",
"type": 2,
"width": 1080,
"height": 1440,
"preview_urls": []
}
],
"likes": [],
"comments": [
{
"comment_id": 67,
"comment64_id": "670000000000000001",
"wxid": "wxid_friend",
"content": "示例评论",
"is_mine": false
}
],
"media_truncated": false,
"interactions_truncated": false
}
}
下一步: 点赞、评论使用item.sns_id;回复取本帖comments中的comment_id或comment64_id;媒体下载取本帖media中的mid。
服务端触发刷新并接收结果:moments_refresh
scope省略时为timeline;scope=user时必须传wxid。刷新获取本次页面快照并产生moments_result,不启动定时任务,也不自动计算全局增删差异。支持与list/user相同的历史参数。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
scope |
string | 可选 | timeline 当前账号好友时间线,user 指定用户相册。;枚举:timeline, user;默认:timeline |
wxid |
string | scope=user 时必填 |
目标用户的 wxid,不能是群 ID;scope=timeline 无需传入。 |
direction |
string | 可选 | head 最新页;tail 历史页。省略时,有 cursor 按 tail,否则按 head;显式 head 不能带 cursor。 |
cursor |
string | direction=tail 时必填 |
上一页返回的 next_cursor,原样传入,不自行减 1。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
check_visibility |
boolean | 可选 | 默认 false;仅指定用户相册的 head 请求可传。true 时清空本次请求的首屏 MD5,尝试获取完整首屏用于诊断,不保证服务器一定返回完整数据;不支持 timeline 或 tail。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
刷新好友最新页
{
"commands": [
{
"request_id": "demo-refresh-001",
"command_name": "moments_refresh",
"args": {
"scope": "timeline"
}
}
]
}
刷新用户最新页
{
"commands": [
{
"request_id": "demo-refresh-002",
"command_name": "moments_refresh",
"args": {
"scope": "user",
"wxid": "wxid_xxx"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "refresh",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"scope": "timeline",
"direction": "head",
"items": [
{
"sns_id": "12345678901234567890",
"wxid": "wxid_friend",
"create_time": 1775000000,
"content": "示例动态",
"type": 1,
"is_mine": false,
"liked": false,
"like_count": 0,
"comment_count": 1,
"private": false,
"visibility_type": 1,
"media": [
{
"mid": "example-media-id",
"type": 2,
"width": 1080,
"height": 1440,
"preview_urls": []
}
],
"likes": [],
"comments": [
{
"comment_id": 67,
"comment64_id": "670000000000000001",
"wxid": "wxid_friend",
"content": "示例评论",
"is_mine": false
}
],
"media_truncated": false,
"interactions_truncated": false
}
],
"next_cursor": "12345678901234567890",
"has_more": null,
"snapshot_complete": true,
"snapshot_source": "network",
"missing_ids": [],
"raw_count": 1,
"truncated": false
}
下一步: 按operation_id关联本次页面;后端自行按sns_id去重、保存历史和比较变化。持续监听事件不会主动刷新。
点赞:moments_like
先联网查详情,再提交点赞并等待原生确认。已经点赞返回unchanged=true。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
点赞
{
"commands": [
{
"request_id": "demo-like-001",
"command_name": "moments_like",
"args": {
"sns_id": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "like",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890"
}
下一步: 最终成功后可重新查详情显示最新赞和评论;不要因第一次pending再提交一次点赞。
取消本人点赞:moments_unlike
只取消当前账号的点赞;原本未点赞返回unchanged=true。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
取消点赞
{
"commands": [
{
"request_id": "demo-unlike-001",
"command_name": "moments_unlike",
"args": {
"sns_id": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "unlike",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890",
"unchanged": true
}
下一步: 最终成功后可查详情更新显示。unchanged=true表示无需再次写入。
评论与回复他人评论:moments_comment
普通评论只传sns_id/content;回复增加本帖可见评论的reply_comment_id,可匹配comment_id或comment64_id。当前微信原生实测不能回复本人评论,提交前明确拒绝,不降级为普通评论。content非空,接口最多4096个UTF-16单位,这不是已验证的微信服务器字数上限。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
content |
string | 必填 | 非空评论,插件输入上限4096个UTF-16单位;不是已验证微信服务器字数上限。 |
reply_comment_id |
string | 回复他人评论时必填 | 本帖可见评论的 comment_id 或 comment64_id,无损转成字符串;普通评论省略。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
评论
{
"commands": [
{
"request_id": "demo-comment-001",
"command_name": "moments_comment",
"args": {
"sns_id": "12345678901234567890",
"content": "测试评论"
}
}
]
}
回复评论
{
"commands": [
{
"request_id": "demo-comment-002",
"command_name": "moments_comment",
"args": {
"sns_id": "12345678901234567890",
"content": "测试回复",
"reply_comment_id": "12345"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "comment",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890",
"comment_id": "",
"raw_ret": 0
}
下一步: 最终成功后重新查detail取得评论列表和可用于后续删除/回复的评论ID;即使成功结果comment_id为空,也不要重发。
删除评论:moments_delete_comment
允许删除本人评论,或本人动态下的可见评论;不能删除他人动态中他人的评论。comment_id可使用本帖comment_id或comment64_id。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
comment_id |
string | 必填 | 本帖待删除评论的 comment_id 或 comment64_id,无损转成字符串。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
删除本人评论
{
"commands": [
{
"request_id": "demo-delete_comment-001",
"command_name": "moments_delete_comment",
"args": {
"sns_id": "12345678901234567890",
"comment_id": "12345"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "delete_comment",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890"
}
下一步: 最终成功后查detail核对列表。若原评论已不在可见列表,会明确失败,不推断为已经删除成功。
发布文字、图文或视频:moments_publish
type=text/images/video,默认text;visibility默认public。媒体来源为手机可访问的HTTP(S)地址或手机绝对路径,不能传电脑C盘路径。下载、解码、封面完成后才提交微信发布。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
type |
string | 可选,默认 text |
text 纯文字、images 图文、video 视频。 |
content |
string | type=text 时必填且非空 |
图文/视频可省略。按微信当前动态配置和原生加权计数校验,接口最多 32768 个 UTF-16 单位。 |
visibility |
string | 可选 | public 公开、private 仅自己、include 指定人可见、exclude 不给指定人看。;枚举:public, private, include, exclude;默认:public |
wxids |
array | include/exclude时必填 | include/exclude 必须非空,其他可见范围须为空或省略;重复 wxid 去重。 |
images |
array | type=images 时必填 |
1~9 个手机本地绝对路径或 HTTP(S) 图片 URL。 |
video |
string | type=video 时必填 |
手机视频绝对路径或 HTTP(S) URL。 |
thumb |
string | 可选 | 可选封面手机绝对路径或HTTP(S) URL;省略则从视频生成。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
文字(仅自己)
{
"commands": [
{
"request_id": "demo-publish-001",
"command_name": "moments_publish",
"args": {
"type": "text",
"content": "朋友圈测试",
"visibility": "private"
}
}
]
}
图文(仅自己)
{
"commands": [
{
"request_id": "demo-publish-002",
"command_name": "moments_publish",
"args": {
"type": "images",
"content": "图文测试",
"images": [
"https://example.com/image.jpg"
],
"visibility": "private"
}
}
]
}
视频(仅自己)
{
"commands": [
{
"request_id": "demo-publish-003",
"command_name": "moments_publish",
"args": {
"type": "video",
"content": "视频测试",
"video": "https://example.com/video.mp4",
"visibility": "private"
}
}
]
}
指定人可见
{
"commands": [
{
"request_id": "demo-publish-004",
"command_name": "moments_publish",
"args": {
"type": "text",
"content": "指定可见测试",
"visibility": "include",
"wxids": [
"wxid_xxx"
]
}
}
]
}
不给指定人看
{
"commands": [
{
"request_id": "demo-publish-005",
"command_name": "moments_publish",
"args": {
"type": "text",
"content": "示例",
"visibility": "exclude",
"wxids": [
"wxid_friend"
]
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "publish",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890",
"local_sns_id": "local-example"
}
下一步: 等待最终服务器确认后使用sns_id查详情、修改权限或删除。local_sns_id只是本地编号,不能代替服务器sns_id。
删除本人动态:moments_delete
联网确认作者为当前账号后删除,仅支持本人已发布动态。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
删除本人动态
{
"commands": [
{
"request_id": "demo-delete-001",
"command_name": "moments_delete",
"args": {
"sns_id": "12345678901234567890"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "delete",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890"
}
下一步: 等待服务器确认;结果未知时保留原操作核实,不能只因界面消失就重新删除。
查询好友、期限、单帖权限:moments_permissions_query
type=friend/recent同步读取本地设置,不产生operation_id和完成事件;server_confirmed=false正常。friend中dont_see=null表示原生分组尚未加载,不能当false。type=post会联网查询并返回操作编号。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
type |
string | 必填,无默认值 | friend 好友权限、recent 本人可见期限、post 单条动态可见范围。 |
wxid |
string | type=friend 时必填 |
目标用户的 wxid,不能是群 ID;type=recent/post 无需传入。 |
sns_id |
string | type=post 时必填 |
目标动态的 sns_id;type=friend/recent 无需传入。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
好友权限
{
"commands": [
{
"request_id": "demo-permissions-query-001",
"command_name": "moments_permissions_query",
"args": {
"type": "friend",
"wxid": "wxid_xxx"
}
}
]
}
本人可见期限
{
"commands": [
{
"request_id": "demo-permissions-query-002",
"command_name": "moments_permissions_query",
"args": {
"type": "recent"
}
}
]
}
单帖可见范围
{
"commands": [
{
"request_id": "demo-permissions-query-003",
"command_name": "moments_permissions_query",
"args": {
"type": "post",
"sns_id": "12345678901234567890"
}
}
]
}
结果字段示例(同步返回):
{
"ok": true,
"type": "friend",
"wxid": "wxid_friend",
"source": "local_settings",
"server_confirmed": false,
"hide_from": false,
"dont_see": null
}
recent同步返回{ok:true,type:"recent",source:"local_settings",server_confirmed:false,hours:72,period:"three_days"};post须等待终态,返回item及visible_users/excluded_users。以上三种场景不能统一假设都有operation_id。
下一步: 修改好友权限用type=friend;改本人对外期限用type=recent;改已发布本人动态用type=post。查看对方期限则使用moments_user。
修改好友、期限、单帖权限:moments_permissions_update
friend的hide_from(不让他看我)与dont_see(我不看他)为独立开关;enabled必须是真正JSON布尔值,false解除。recent修改本人可见期限;post只修改本人动态的范围。需开启发送/写入功能。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
type |
string | 必填,无默认值 | friend 好友权限、recent 本人可见期限、post 单条动态可见范围。 |
wxid |
string | type=friend 时必填 |
目标用户的 wxid,不能是群 ID;type=recent/post 无需传入。 |
direction |
string | type=friend 时必填,无默认值 |
hide_from 不让他看我;dont_see 我不看他。 |
enabled |
boolean | 修改好友权限时必填 | 严格 JSON 布尔值;true 开启,false 解除。 |
period |
string | 修改本人期限时必填 | 修改本人期限时必填;枚举:three_days, one_month, half_year, all |
sns_id |
string | type=post 时必填 |
本人动态的 sns_id;type=friend/recent 无需传入。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
visibility |
string | 可选 | public 公开、private 仅自己、include 指定人可见、exclude 不给指定人看。;枚举:public, private, include, exclude;默认:public |
wxids |
array | include/exclude时必填 | include/exclude 必须非空,其他可见范围须为空或省略;重复 wxid 去重。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
不让他看我
{
"commands": [
{
"request_id": "demo-permissions-update-001",
"command_name": "moments_permissions_update",
"args": {
"type": "friend",
"wxid": "wxid_xxx",
"direction": "hide_from",
"enabled": true
}
}
]
}
我不看他
{
"commands": [
{
"request_id": "demo-permissions-update-002",
"command_name": "moments_permissions_update",
"args": {
"type": "friend",
"wxid": "wxid_xxx",
"direction": "dont_see",
"enabled": true
}
}
]
}
解除我不看他
{
"commands": [
{
"request_id": "demo-permissions-update-003",
"command_name": "moments_permissions_update",
"args": {
"type": "friend",
"wxid": "wxid_xxx",
"direction": "dont_see",
"enabled": false
}
}
]
}
本人仅三天可见
{
"commands": [
{
"request_id": "demo-permissions-update-004",
"command_name": "moments_permissions_update",
"args": {
"type": "recent",
"period": "three_days"
}
}
]
}
本人全部可见
{
"commands": [
{
"request_id": "demo-permissions-update-005",
"command_name": "moments_permissions_update",
"args": {
"type": "recent",
"period": "all"
}
}
]
}
本人单帖仅自己
{
"commands": [
{
"request_id": "demo-permissions-update-006",
"command_name": "moments_permissions_update",
"args": {
"type": "post",
"sns_id": "12345678901234567890",
"visibility": "private"
}
}
]
}
本人单帖不给指定人看
{
"commands": [
{
"request_id": "demo-permissions-update-007",
"command_name": "moments_permissions_update",
"args": {
"type": "post",
"sns_id": "12345678901234567890",
"visibility": "exclude",
"wxids": [
"wxid_xxx"
]
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "permissions/update",
"accepted": true,
"write_accepted": true,
"server_confirmed": true,
"revision": 1,
"error": "",
"type": "friend",
"direction": "dont_see",
"enabled": false,
"local_verified": true
}
下一步: 等待服务器确认后再查询对应权限。服务器已确认但local_verified=false时先核对本地同步,不要重复修改服务器。dont_see缺完整确认时可能unknown,查询原操作。
查询原异步操作:moments_operation
传原operation_id读取当前账号内存操作记录,不创建新业务任务。返回的action/request_id仍属于原任务。查询结果通过 command_result 返回,按外层 ok/error 和 result.status 判断;不存在、跨账号或已淘汰时返回失败。服务端接收该结果时返回的 HTTP 2xx 仅确认收到信封。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
operation_id |
string | 必填 | 上一步返回的操作UUID,查询时始终保持同一值。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
操作结果
{
"commands": [
{
"request_id": "demo-operation-001",
"command_name": "moments_operation",
"args": {
"operation_id": "替换为实际operation_id"
}
}
]
}
结果字段示例(等待终态后):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "list",
"accepted": true,
"write_accepted": false,
"server_confirmed": true,
"revision": 1,
"error": "",
"items": [
{
"sns_id": "12345678901234567890",
"wxid": "wxid_friend",
"create_time": 1775000000,
"content": "示例动态",
"type": 1,
"is_mine": false,
"liked": false,
"like_count": 0,
"comment_count": 1,
"private": false,
"visibility_type": 1,
"media": [
{
"mid": "example-media-id",
"type": 2,
"width": 1080,
"height": 1440,
"preview_urls": []
}
],
"likes": [],
"comments": [
{
"comment_id": 67,
"comment64_id": "670000000000000001",
"wxid": "wxid_friend",
"content": "示例评论",
"is_mine": false
}
],
"media_truncated": false,
"interactions_truncated": false
}
],
"next_cursor": "12345678901234567890",
"has_more": null,
"snapshot_complete": true,
"snapshot_source": "network",
"missing_ids": [],
"raw_count": 1,
"truncated": false
}
下一步: preparing/pending继续查询同一编号;succeeded读取业务结果;failed看error;unknown尤其write_accepted=true时禁止自动重新写入。
运行能力与视频准备诊断:moments_probe
{}同步读取实现标识、hooks、配置和operations,不刷新、不下载、不发布。传video及可选thumb则异步检查完整下载、视频元数据与封面,仍不发布,成功后清理本次临时资源。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
video |
string | 可选 | 传入后异步检查视频,不发帖;省略则同步返回能力和状态快照。 |
thumb |
string | 可选 | 选填封面;仅与video一起使用,省略自动生成。 |
运行时能力与任务状态
{
"commands": [
{
"request_id": "demo-probe-001",
"command_name": "moments_probe",
"args": {}
}
]
}
视频下载与解码诊断(不发布)
{
"commands": [
{
"request_id": "demo-probe-002",
"command_name": "moments_probe",
"args": {
"video": "https://example.com/video.mp4"
}
}
]
}
结果字段示例(同步返回):
{
"ok": true,
"implementation_revision": "moments_20261016_upload_v1",
"media_upload_supported": true,
"text_limit": 2000,
"video_duration_limit_seconds": 300,
"operations": []
}
传video时为异步操作,最终status=succeeded, probe_only=true, media_ready=true, accepted=false, write_accepted=false, server_confirmed=false。探针中的text_limit和video_duration_limit_seconds只是示例数值,实际以当前配置为准。
下一步: 视频准备探针通过后,如需发布,显式调用publish;探针没有创建可供media/file读取的朋友圈资源。
准备媒体并可主动上传:moments_media_download
传sns_id和本帖media[].mid。省略upload为本地读取模式;提供upload则手机在下载、解密/复用缓存后主动上传到指定后端。只支持普通图片和视频朋友圈,不支持视频号、链接附件。
| 参数 | 类型 | 要求 | 含义 |
|---|---|---|---|
sns_id |
string | 必填 | 目标动态的 sns_id,取自列表、详情或发布结果。非零 UInt64 十进制字符串,最大 18446744073709551615;不能提交 JSON 数字。 |
media_id |
string | 必填 | 本动态media[]中mid原值,不能传URL或数组下标。 |
外层 request_id |
string | 必填 | 命令结果关联标识,最多128个UTF-16单位;不放进 args,不是持久幂等键。 |
upload |
object | 可选 | 手机出站上传目标;只有上传完成整个操作才成功。省略此对象沿用本地读取。 |
远程后端:手机主动上传
{
"commands": [
{
"request_id": "demo-media-download-001",
"command_name": "moments_media_download",
"args": {
"sns_id": "12345678901234567890",
"media_id": "example-media-id",
"upload": {
"url": "https://upload.example.com/uploads/22222222-2222-4222-8222-222222222222",
"resource_id": "22222222-2222-4222-8222-222222222222",
"method": "PUT",
"headers": {
"Authorization": "Bearer REPLACE_WITH_SHORT_LIVED_TOKEN"
}
}
}
}
]
}
本地模式:手机临时文件
{
"commands": [
{
"request_id": "demo-media-download-002",
"command_name": "moments_media_download",
"args": {
"sns_id": "12345678901234567890",
"media_id": "example-media-id"
}
}
]
}
上传模式最终结果(server_confirmed=false正常):
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "media/download",
"accepted": true,
"write_accepted": false,
"server_confirmed": false,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890",
"media_id": "example-media-id",
"media_ready": true,
"delivery": "upload",
"upload_completed": true,
"upload_http_status": 201,
"upload_outcome_unknown": false,
"resource_id": "22222222-2222-4222-8222-222222222222",
"content_type": "image/jpeg",
"file_bytes": 297446,
"cached": true,
"source": "wechat_native"
}
省略upload时的本地模式结果:
{
"ok": true,
"status": "succeeded",
"operation_id": "11111111-1111-4111-8111-111111111111",
"request_id": "demo-request-001",
"action": "media/download",
"accepted": true,
"write_accepted": false,
"server_confirmed": false,
"revision": 1,
"error": "",
"sns_id": "12345678901234567890",
"media_id": "example-media-id",
"media_ready": true,
"delivery": "local",
"content_type": "image/jpeg",
"file_bytes": 297446,
"cached": true,
"source": "wechat_native",
"media_url": "/moments/media/file/11111111-1111-4111-8111-111111111111",
"expires_at_ms": 1775000600000
}
下一步: 远程后端推荐upload模式:等upload_completed=true后,按后端自分配的resource_id读取后端文件;无需访问手机HTTP。省略upload时才使用返回的media_url。
本地模式读取文件:GET /moments/media/file/{operation_id}
这是本地 HTTP 专用入口,没有同名长轮询命令。只有省略upload且本地媒体准备成功后,具备直连手机条件时才可额外调用;远程后端优先使用主动上传。media_url为相对路径,拼接手机HTTP基址;operation_id为这次媒体准备的UUID,不是sns_id。链接最多10分钟,切号、重启、操作表淘汰、微信清缓存可提前失效。
curl --request GET 'http://192.0.2.10:19088/moments/media/file/11111111-1111-4111-8111-111111111111' --output preview.jpg
这是二进制响应,按Content-Type选择文件格式。无Range返回200整文件;Range单段返回206,支持bytes=0-65535、bytes=65536-、bytes=-65536;无效/越界/多段返回416与Content-Range: bytes */文件总长。非法UUID为400;未授权403;未就绪/过期/账号切换/缓存失效404。读取不触发下载,不支持任意手机文件路径。
常用业务按这个顺序调用
| 目标 | 调用顺序 |
|---|---|
| 获取最新好友动态 | list → 等本次完成 → items |
| 采集当前可见历史 | list最新页 → 保存并去重 → next_cursor请求下一页 → 逐页等待;不能一个pending未完成就抢同范围下一页 |
| 获取指定用户历史 | user带wxid → 等结果 → 同wxid加next_cursor翻页 |
| 回复评论 | detail → comments选他人评论 → comment带reply_comment_id → 等完成 → detail更新显示 |
| 发布后修改范围 | publish → 等服务器sns_id → permissions/update(type=post) → 等完成 → permissions/query(type=post) |
| 删除刚发布动态 | publish → 等服务器sns_id → delete → 等完成 |
| 远程读取图片/视频 | list/detail取sns_id和mid → 后端准备上传目标 → media/download带upload → 等upload_completed → 从后端按resource_id读取 |
| 诊断发布视频 | probe带video → 等media_ready → 需要发布时再显式publish,不会自动发帖 |
20. 收藏:分页读取、发送与删除
用户已确认收藏功能在微信 8.0.80 验证通过;当前响应中的插件完整版本以设备实际返回为准。历史 0.8.79-13 曾用于早期收藏验证,不作为当前接入的版本条件。
收藏使用本篇命令通道,无需后端访问手机 HTTP。业务结果一次等待后回传;下面的结果示例省略设备、会话、账号、版本及时间等公共字段。
收藏三项命令可执行但尚未出现在当前 capabilities;接入时按本篇收藏参数与结果处理,不因清单缺省而拦截。其他声明缺口见本篇能力清单。
| 功能 | 命令名 |
|---|---|
| 分页读取收藏 | favorites_list |
| 发送指定收藏 | favorites_send |
| 删除指定收藏 | favorites_delete |
收藏参数严格校验,不适用本文其他接口的宽松类型转换、字符串自动裁剪或目标别名规则。 favorites_list 的 args 只允许 limit、refresh、cursor;发送/删除的业务 request_id 统一取命令外层,不要把它放进 args。
favorites_list:最新在前的游标分页
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
limit |
integer | 否 | 默认 50,范围 1–100;不接受数字字符串、布尔值或非整数 |
refresh |
boolean | 否 | 默认 false,读取本机已同步内容;true 先等待微信同步及正文补齐,再返回第一页。只允许用于首请求,cursor 须省略或为空字符串 |
cursor |
string | 否 | 上一页返回的 next_cursor,最多 128 个字符;首请求省略或传空字符串 |
顺序为最新收藏在前、最早收藏在后,当前实现按本机递增的 local_id 从大到小扫描。下一页继续向更早的 ID 读取,不要按 fav_id 的字符串大小或 create_time 自行重排后再推算游标。
分页由插件控制,底层读取微信本地收藏库;微信原生同步分批与对外分页是两层机制。首次建议 refresh:true,后续页只带 limit、cursor,直到 has_more:false。
- 单页最多返回
limit条;扫描和正文预算可能使一页不足limit,甚至items:[]但has_more:true。是否继续以has_more为准,继续时原样使用本页next_cursor。 has_more:true表示还有本地 ID 范围未扫描,不保证下一页一定有条目。结束时next_cursor为空字符串。- 游标绑定当前账号和登录周期,最长保留 30 分钟;内存最多保存 256 个游标,满额时旧游标也可能提前失效。账号切换、微信重启或游标失效后重新从第一页获取。
- 第一页固定本轮扫描的最大本地 ID,翻页期间新收藏不插入本轮结果;要看新增项,重新请求第一页。期间删除、编辑或同步仍可能改变已有内容,这不是不可变快照。
- 不支持
page、offset、排序参数、总数查询或一次返回全部。ID 有空洞,不能用返回条数推算下一页。
在 /agent/poll 响应中下发:
{
"commands": [
{
"request_id": "fav-list-demo-001",
"command_name": "favorites_list",
"args": {
"limit": 50,
"refresh": true
}
}
]
}
下一页使用新的命令请求 ID,args 为以下内容(复制上页 next_cursor,不再刷新):
{
"limit": 50,
"cursor": "11111111-1111-4111-8111-111111111111"
}
插件上报到 /agent/command_result:
{
"request_id": "fav-list-demo-001",
"command_name": "favorites_list",
"ok": true,
"result": {
"account_id": "wxid_self_example",
"sync_state": "completed",
"items": [
{
"fav_id": "123456",
"local_id": "801",
"type": 1,
"title": "",
"description": "收藏的示例文本",
"url": "",
"create_time": 1792112400,
"update_time": 1792112400,
"tags": [],
"content_state": "ready",
"forward_state": "ready",
"block_reason": "",
"data_list": [],
"content_truncated": false
}
],
"has_more": true,
"next_cursor": "11111111-1111-4111-8111-111111111111"
},
"error": ""
}
业务 ok 和 error 位于命令结果外层,result 中不重复这两个字段;不要读取 result.ok 判断成功。
收藏列表结果字段
| 字段 | 类型 | 含义 |
|---|---|---|
account_id |
string | 当前登录账号的微信 ID |
sync_state |
string | 本次 refresh:true 成功后为 completed;未请求刷新为 not_requested,不代表账号从未同步 |
items |
array | 本页收藏,按本地 ID 递减排列 |
has_more / next_cursor |
boolean / string | 是否还有扫描范围及继续读取的游标 |
items[] 的字段:
| 字段 | 类型 | 含义 |
|---|---|---|
fav_id |
string | 微信收藏 ID;发送、删除使用此值。可能为 "0" 的未就绪条目不能用于这两个操作 |
local_id |
string | 本机数据库 ID,仅用于本地排序,不可替代 fav_id |
type |
integer | 微信原生收藏类型;3 为语音,当前不支持转发语音收藏 |
title / description / url |
string | 当前可读取的标题、正文或说明、链接;文本收藏正文在 description,缺失时为空字符串 |
create_time / update_time |
number | 微信原生 srcCreateTime / updateTime,Unix 秒时间;不是翻页位置 |
tags |
string[] | 可读取的文本标签 |
content_state |
string | ready 已解析;metadata_pending 未同步或未解析完成;invalid 正文解析失败 |
forward_state |
string | ready 当前可进入转发;needs_download 转发前需要原生补齐附件;forbidden 禁止转发;unknown 尚不能确定 |
block_reason |
string | 不可转发的原因,可能为空 |
content_truncated |
boolean | 正文、标签或嵌套内容触及保护上限,不能把已返回内容视为完整正文 |
data_list |
array | 复合收藏的嵌套内容摘要,结构见下表 |
data_list[] 字段为 data_id(string)、data_type(integer)、title、description、format(string)、size(number,字节)、duration(number,微信原生值,单位依类型而定)、resource_state(local / remote / missing / unknown)和可递归的 data_list。资源状态仅描述微信本机可用情况,不提供可下载地址;data_id 不能用于发送或删除收藏。
列表不主动下载媒体。返回内容有每页正文预算、单字段长度、标签数量和嵌套节点保护;content_truncated 与分页的 has_more 含义不同。当前收藏能力只包含列表、发送和删除,没有独立的收藏详情、附件下载或任务查询接口。
favorites_send:发送到好友或群
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
request_id |
string | 命令外层必填;不放进 args |
调用方生成的业务请求标识,1–128 字符,不含首尾空白或控制字符 |
fav_id |
string | 是 | 列表返回的收藏 ID,ASCII 十进制字符串,范围 1–4294967295,不允许前导零 |
receiver |
string | 是 | 完整好友微信 ID,或含 @chatroom 后缀的完整群 ID;1–128 字符,不含首尾空白或控制字符 |
目标只使用 receiver,不支持另传 wxid、roomid、room_id 或接收人别名。好友须为当前账号可用的普通好友;群须仍有当前账号在群内。也支持当前账号自身及 filehelper。
正文须已解析且微信允许转发,语音收藏不支持。若原生转发要求附件,插件会让微信补齐所需资源并等待,可能增加耗时;这不提供独立的附件下载接口。列表的 forward_state 是读取当时的检查结果,发送时会再次检查。
{
"commands": [
{
"request_id": "fav-send-demo-001",
"command_name": "favorites_send",
"args": {
"fav_id": "123456",
"receiver": "wxid_friend_example"
}
}
]
}
群目标仅将 args.receiver 换成完整群 ID,例如 12345678901@chatroom。
命令回包示例:
{
"request_id": "fav-send-demo-001",
"command_name": "favorites_send",
"ok": true,
"result": {
"status": "accepted",
"server_confirmed": false,
"request_id": "fav-send-demo-001",
"fav_id": "123456",
"receiver": "wxid_friend_example"
},
"error": ""
}
发送结果未知时:
{
"request_id": "fav-send-demo-001",
"command_name": "favorites_send",
"ok": false,
"result": {
"status": "unknown",
"server_confirmed": false,
"request_id": "fav-send-demo-001",
"error_code": "SEND_RESULT_UNKNOWN"
},
"error": "收藏操作超时,勿自动重发"
}
favorites_delete:等待服务器确认删除
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
request_id |
string | 命令外层必填;不放进 args |
业务请求标识,格式与发送相同 |
fav_id |
string | 是 | 列表返回的收藏 ID,范围和格式与发送相同;不可传 local_id 或 data_id |
删除的是当前登录账号的微信收藏,会进入微信原生同步删除流程。仅在微信服务器对该收藏返回明确成功后才返回 deleted。删除不要求收藏正文已解析、可转发或附件已下载。
ok:true、status:"deleted"、server_confirmed:true 三者同时成立才表示服务器确认删除。收藏已从列表消失、原生方法接收请求或本地待删除,都不能单独证明云端已删除。
{
"commands": [
{
"request_id": "fav-delete-demo-001",
"command_name": "favorites_delete",
"args": {
"fav_id": "123456"
}
}
]
}
服务器确认删除的命令回包:
{
"request_id": "fav-delete-demo-001",
"command_name": "favorites_delete",
"ok": true,
"result": {
"status": "deleted",
"server_confirmed": true,
"request_id": "fav-delete-demo-001",
"fav_id": "123456"
},
"error": ""
}
删除结果未知的命令回包:
{
"request_id": "fav-delete-demo-001",
"command_name": "favorites_delete",
"ok": false,
"result": {
"status": "unknown",
"server_confirmed": false,
"request_id": "fav-delete-demo-001",
"fav_id": "123456",
"error_code": "DELETE_RESULT_UNKNOWN"
},
"error": "等待收藏删除确认超时,微信队列可能继续处理,请勿自动重新提交"
}
命令外层 ok 表示本次业务判断;服务端接收结果时的 HTTP 2xx(或 WS 传输成功)仅表示收到回包,不代表收藏已发送或已删除。
等待、幂等与错误处理
收藏接口采用单次调用等待结果,不返回需要另行轮询的 operation_id,也不产生专门的收藏完成事件。列表无刷新时直接读取本地;刷新约 20 秒、删除约 20 秒、发送准备及提交约 40 秒业务期限,另有约 45 秒后台等待保护。客户端业务结果等待建议至少 60 秒;代理和后端也需允许该时长。HTTP 的请求接收超时和命令通道的网络超时不是这段业务等待时长。
发送成功返回的 accepted 表示收藏已进入微信本地原生转发链路,没有微信服务器送达确认,所以 server_confirmed:false 是预期值。不要据此判为失败或自动重发;接口也不保证接收方已收到或已读。发送和删除的 unknown 都表示结果不能确认,HTTP 超时、断线或命令结果未收到也不能据此认定操作未执行。
- 对已登记的同一发送或删除操作,相同账号和登录周期内,相同
request_id与相同参数会复用原结果;仍在处理中返回REQUEST_IN_PROGRESS,不重复提交。相同 ID 改参数返回REQUEST_ID_CONFLICT。参数校验等登记前失败不占用幂等记录。 - 发送和删除各最多保留 512 条内存幂等记录;满额拒绝新请求,不通过淘汰旧记录再次执行。微信重启、账号或登录周期变化后记录失效,后端仍需持久保存业务请求及结果。
- 相同 ID 返回已有结果并不是重新查询微信最终状态。已经返回
unknown时,不要通过更换 ID 自动重发或重删,先人工核实微信中的实际结果。 - 当前同一账号只允许一个等待中的发送、一个等待中的删除;同一收藏的发送准备与删除互斥。收藏删除后列表会跳过本地待删除项,但列表缺项不能代替服务器确认。
三种接入方式使用相同业务 error_code。列表失败可不含 status;发送、删除通常返回 failed 或 unknown。优先按错误码分支,不要精确匹配中文错误文案。
error_code |
含义与处理 |
|---|---|
INVALID_ARGUMENT |
参数名、类型或范围不合法;收藏采用严格校验,拒绝未支持的字段 |
ACCOUNT_CHANGED |
未登录、账号或登录周期已变化;重新确认账号,重取列表和游标 |
FEATURE_DISABLED |
插件关闭,或发送/转发开关关闭 |
NATIVE_API_UNAVAILABLE |
当前微信原生方法、回调或签名不可用,检查插件与微信版本 |
FAVORITES_SYNC_BUSY / FAVORITES_SYNC_FAILED / FAVORITES_SYNC_TIMEOUT |
刷新正在进行、失败或超时;失败可能带 sync_diagnostics,没有返回本次“刷新完成”的列表 |
FAVORITE_NOT_FOUND |
本机无法找到该收藏或已删除;删除时不能据此认定云端删除成功 |
FAVORITE_METADATA_PENDING |
发送所需正文未就绪或解析失败,先刷新收藏列表 |
FAVORITE_NOT_FORWARDABLE |
微信禁止转发,如语音收藏,原因见 error |
FAVORITE_DOWNLOAD_FAILED / FAVORITE_DOWNLOAD_TIMEOUT |
发送准备时所需资源补齐失败或超时 |
NATIVE_CONVERSION_FAILED / SEND_FAILED |
原生消息转换失败或转发取消;是否已经进入提交仍看 status |
TARGET_NOT_FOUND / TARGET_UNAVAILABLE |
目标不存在、不可用、不是普通好友或当前账号不在群内 |
FAVORITES_SEND_BUSY / FAVORITES_DELETE_BUSY / FAVORITES_OPERATION_BUSY |
有在途操作或同一收藏操作冲突,先等已有操作结果 |
REQUEST_IN_PROGRESS / REQUEST_ID_CONFLICT / IDEMPOTENCY_CAPACITY |
同一请求仍在处理、ID 与参数冲突或本登录周期幂等记录已满 |
FAVORITE_DELETE_PENDING |
收藏已在微信删除队列中,返回 unknown,不会再次提交 |
SEND_RESULT_UNKNOWN / DELETE_RESULT_UNKNOWN |
已提交或等待超时后结果未知;不要自动重新提交 |
删除未知响应可能额外包含 native_error_code,表示等待期间观察到的微信原始错误码;微信原生队列可能继续重试,不能只用它推导最终成功或失败。
收藏绕过命令通道通用的“最近 100 条”结果缓存,由收藏服务按当前账号、登录周期及完整参数校验。列表不提供 request_id 结果缓存;要获取某页,应使用其游标重新读取。
长轮询通过后台线程等待收藏结果,同一批命令仍按原流程在结果上报成功后继续;不要一次下发许多依赖前一页游标的命令,应取得本页结果后再下发下一页。
21. 事件字段
21.1 事件类型总表
event_name |
说明 |
|---|---|
moments_result |
本次接口/命令对应的朋友圈页面或写入结果;含 operation_id/request_id/status/revision,见 本篇朋友圈完整用法 |
message |
普通聊天消息(文本、图片、语音、视频、表情、文件、链接、拍一拍、系统消息等) |
red_packet_received |
收到红包 |
transfer_received |
收到转账 |
transfer_refund_result |
转账退还结果:成功、失败或未知,按 native_status 判断 |
group_member_join |
群成员加入 |
group_member_leave |
群成员离开 |
group_member_nickname_change |
群成员修改或清除群内昵称 |
friend_request |
收到好友申请 |
21.2 message 事件
{
"event_name": "message",
"event_id": "msg:7307587870543245070",
"body": {
"message_id": "7307587870543245070",
"msg_type": "text",
"raw_message_type": 1,
"chat_id": "50136399923@chatroom",
"room_id": "50136399923@chatroom",
"sender_id": "wxid_member",
"is_group": true,
"is_at": false,
"at_user_list": [],
"at_all": false,
"create_time": 1774772029,
"content": "你好"
}
}
恒有字段(一定会出现)
| 字段 | 类型 | 说明 |
|---|---|---|
message_id |
string | 消息 ID。取不到服务端消息 ID 时会退化为毫秒时间戳字符串 |
msg_type |
string | 插件映射的消息类型,见下表;可能为 unknown |
raw_message_type |
int | 微信原生消息类型数字,见下表 |
content |
string | 消息内容。对文本是文字,对图片/文件等是原始 XML 串,也可能是空串 "" |
chat_id |
string | 会话 ID(群聊为群 ID,私聊为对方或自己 wxid)。可能为空串 |
room_id |
string | 群 ID;私聊时为空串 ""(字段仍在) |
sender_id |
string | 发送者 wxid |
is_group |
bool | 是否群聊 |
is_at |
bool | 是否 @ 了当前账号本人;私聊恒为 false |
at_user_list |
string[] | 被 @ 的 wxid 列表;无 @ 或私聊时为空数组 [] |
at_all |
bool | 是否 @ 所有人 |
create_time |
int | 微信消息时间,单位:秒(注意与 ts_ms 的毫秒不同) |
消息类型对照
msg_type |
raw_message_type |
说明 |
|---|---|---|
text |
1 | 文本 |
image |
3 | 图片 |
voice |
34 | 语音 |
video |
43 | 视频 |
contact_card |
42 | 名片 |
emoji |
47 | 表情 |
app |
49 | App 消息(链接、文件、小程序、合并转发、引用等都在这一类) |
pat |
0 | 拍一拍 |
system |
10000 | 系统消息(撤回、群名修改等) |
unknown |
其他 | 未能识别的类型 |
⚠️ 注意一个真实差异:代码里的
msg_type实际取值包含contact_card和app,没有file和link。文件和链接是app类型下的子分类,靠msg_subtype区分。请不要按msg_type == "file"来判断文件消息。
条件字段(只在满足条件时出现,不满足则整个字段不存在)
| 出现条件 | 字段 | 类型 | 说明 |
|---|---|---|---|
content 能被识别为 XML |
content_xml |
string | 归一化后的完整 XML |
| 同上 | content_prefix |
string | XML 之前的非空文本前缀 |
msg_type == image |
image_md5、image_aes_key、image_thumb_url、image_mid_url、image_origin_md5 |
string | 图片解密与多档清晰度地址——下载图片需要 image_aes_key |
| 同上 | image_length |
int | 字节 |
msg_type == voice |
voice_url、voice_aes_key、voice_md5、voice_from_user、voice_client_msg_id |
string | 语音下载信息 |
| 同上 | voice_length_ms |
int | 毫秒 |
| 同上 | voice_data_length |
int | 字节 |
msg_type == emoji |
emoji_md5、emoji_aes_key、emoji_cdn_url、emoji_encrypt_url |
string | |
| 同上 | emoji_length、emoji_width、emoji_height |
int | 字节 / 像素 |
msg_type == contact_card |
card_username、card_nickname、card_alias、card_province、card_city、card_sign、card_big_head_img_url、card_small_head_img_url |
string | |
| 同上 | card_sex、card_scene |
int | |
msg_type == app |
app_msg_type |
int | App 消息的内层类型:5 链接、6 文件、8 上传、19 合并转发、33 小程序、57 引用,其他为未知 |
| 同上 | msg_subtype |
string | 对应的可读名:link / file / upload / merged_records / mini_program / quote / app_unknown |
| 同上 | app_msg_title、app_msg_desc、app_msg_url、app_source_username、app_source_display_name、app_thumb_url、app_thumb_md5、app_aes_key |
string | |
msg_subtype == quote |
quote_msg_id、quote_from_user、quote_chat_user、quote_display_name、quote_content |
string | 引用消息的原文信息 |
| 同上 | quote_msg_type、quote_create_time |
int | 引用消息的类型与时间 |
| 拍一拍专属 | pat_from_user、pat_patted_user、pat_template |
string | 见本篇拍一拍事件 |
⚠️ 判断文件消息的正确方式:
msg_type == "app"且msg_subtype == "file"。
被主动剥掉的字段(不会出现在 body 里)
from_user、to_user、sender_id_type、real_chat_user、source、hook_selector、hook_type、self_user、以及内部的 msg_id(已被改名为 message_id)。
这意味着服务端拿不到"这条消息是发给谁的"直接的收发方向字段,只能靠 chat_id / room_id / sender_id / is_group 自己推断方向。这是设计如此,不是 bug。
群内 @ 字段的精确语义
| 字段 | 说明 |
|---|---|
is_at |
本条消息是否 @ 了当前登录账号本人 |
at_user_list |
本条消息里所有被 @ 的 wxid。取自微信消息自带的 @ 名单,插件只做去空白和去重,不做昵称解析 |
at_all |
是否 @ 了所有人;等价于 at_user_list 中含协议标记 notify@all |
三条必须知道的约定:
- @所有人 时微信写入的是协议标记
notify@all而不是 wxid,插件按原样透出并同时给出at_all: true,不会展开成全部群成员(消息时刻的成员列表无法还原,展开会失真) is_at与at_user_list相互独立:is_at以微信自身判断为准,at_user_list是消息里的原始名单。@所有人 或消息未携带相关数据时,可能出现is_at: true而at_user_list为空- 群消息中
chat_id与room_id相同且is_group为true;私聊中room_id为空串、is_group为false
21.3 拍一拍
msg_type 为 pat,走系统消息通道,raw_message_type 为 0:
{
"event_name": "message",
"event_id": "msg:7307587870543245070",
"body": {
"message_id": "7307587870543245070",
"msg_type": "pat",
"raw_message_type": 0,
"chat_id": "wxid_friend",
"room_id": "",
"sender_id": "wxid_friend",
"is_group": false,
"create_time": 1774772029,
"content": "你拍了拍自己",
"pat_from_user": "wxid_friend",
"pat_patted_user": "wxid_self",
"pat_template": "你拍了拍自己"
}
}
| 字段 | 说明 |
|---|---|
pat_from_user |
拍的人的 wxid |
pat_patted_user |
被拍的人的 wxid |
pat_template |
原始模板文本(如"你拍了拍xxx") |
注意两个字段的区别:
content是把${wxid}占位符替换成显示名之后的文案(给人看的);pat_template是未解析的原始模板。要展示请用content。
21.4 红包事件
{
"event_name": "red_packet_received",
"event_id": "red_packet:rp_msg_7307587870543245070",
"body": {
"red_packet_id": "rp_msg_7307587870543245070",
"message_id": "7307587870543245070",
"chat_id": "50136399923@chatroom",
"room_id": "50136399923@chatroom",
"sender_id": "wxid_sender",
"from_user": "wxid_sender",
"to_user": "wxid_self",
"create_time": 1774772029,
"raw_message_type": 49,
"send_id": "1000039501202603311234567890",
"channel_id": "1",
"msg_type": "1",
"native_url": "…",
"sign": "",
"scene_id": 1002,
"session_user_name": "…",
"nick_name": "…",
"head_img": "",
"is_group": true,
"is_openim": false,
"is_group_sender": false,
"in_way": "0",
"timing_identifier": "",
"status": "received",
"created_at_ms": 1774772029000,
"updated_at_ms": 1774772029000
}
}
| 字段 | 说明 |
|---|---|
red_packet_id |
领取红包时唯一需要回传的参数(receive_red_packet / red_packet_detail)。去重请用它 |
send_id、channel_id、native_url |
红包领取链路必需。这三者任取不到时,整个事件不会生成 |
timing_identifier |
事件发出时恒为空串 ""(领取流程中才回填) |
status |
事件发出时恒为 "received"。后续领取状态只更新本地缓存,不会重新上报成事件 |
msg_type |
⚠️ 语义冲突:这里不是微信消息类型名,而是红包链路的 msgtype 查询参数(空则 "1")。别和 message 事件的 msg_type 混用 |
created_at_ms / updated_at_ms |
毫秒(注意 create_time 是秒) |
nick_name / head_img |
发红包的人的昵称与头像 |
session_user_name |
群内自己发时为 to_user,否则为 from_user |
⚠️ 红包事件 body 里没有
content/content_xml,也没有app_msg_*系列字段——它是独立构造的,不复用消息字段。
21.5 转账事件
{
"event_name": "transfer_received",
"event_id": "transfer:1000050001202604140530193566598",
"body": {
"transfer_id": "1000050001202604140530193566598",
"message_id": "8871556727500760296",
"chat_id": "wxid_demo_account",
"room_id": "",
"sender_id": "wxid_demo_account",
"from_user": "wxid_demo_account",
"to_user": "wxid_demo_friend",
"real_chat_user": "wxid_demo_account",
"current_user": "wxid_demo_friend",
"receiver_username": "wxid_demo_friend",
"payer_username": "",
"control_transaction_id": "53010002669213202604143932510122",
"amount_text": "¥0.02",
"amount_value": "0.02",
"amount_fen": 2,
"memo": "",
"remark": "",
"nickname": "…",
"app_msg_type": 2000,
"app_msg_title": "微信转账",
"app_msg_desc": "",
"app_msg_url": "",
"msg_subtype": "…",
"content": "",
"content_xml": "<msg>...</msg>",
"native_url": "",
"create_time": 1774772029,
"raw_message_type": 49,
"status": "received",
"created_at_ms": 1774772029000,
"updated_at_ms": 1774772029000,
"pay_info_class": "…",
"pay_info_probe": {}
}
}
金额字段说明(三个都有,别搞混):
| 字段 | 类型 | 说明 |
|---|---|---|
amount_text |
string | 带符号的展示文本,如 "¥0.02" |
amount_value |
string | 字符串形式的元值,如 "0.02" |
amount_fen |
int | 整数分,如 2 → 推荐用它做金额计算,避免浮点误差 |
身份字段说明(六个高度重叠,按需取用):
| 字段 | 含义 |
|---|---|
sender_id |
消息发送者 |
from_user |
转账付款方 |
to_user |
转账收款方 |
receiver_username |
收款方用户名 |
payer_username |
付款方用户名(可能为空串) |
current_user |
历史接收方上下文字段,不是独立登录凭证;不要据此覆盖原生付款方/收款方参数 |
建议至少持久化:transfer_id、message_id、control_transaction_id、amount_text、amount_fen、sender_id、chat_id。
推荐用 transfer_id 去重。
注意:
| 事项 | 说明 |
|---|---|
status |
首次解析通常为 "received",这里只表示收到了转账消息,不代表资金已收取;同笔上下文重读时可保留已有状态。资金状态请用 get_transfer_status 的 native_status 判断;退还结果另有独立事件 |
create_time |
秒;取不到时为 0(不是当前时间) |
app_msg_type / msg_subtype |
可能是 JSON null,解析时要容忍 |
room_id |
有,但没有 is_group 字段(与 message 事件不同) |
content_xml |
原始 XML,字段很长,不需要的话别存 |
| 防误报 | 如果这条消息被识别成红包证据,插件不会生成转账事件 |
转账退还结果事件 transfer_refund_result
本事件沿本模式标准事件外壳传输。长轮询上报到 /agent/event。下面展示本节相关字段,公共时刻、设备、账号与队列字段按本模式事件约定携带:
{
"event_name": "transfer_refund_result",
"event_id": "transfer_refund:refund_result_example",
"body": {
"ok": true,
"transfer_id": "transfer_example",
"message_id": "message_example",
"accepted": true,
"confirmed": true,
"status": "refunded",
"native_status": 2002,
"ret_code": 0,
"error_code": "",
"error": "",
"refund_result_id": "refund_result_example",
"source": "webridge_live"
}
}
body 保留退还业务结果的全部基础字段:ok、transfer_id、message_id、accepted、confirmed、status、native_status、ret_code、error_code、error,另有非空 string refund_result_id(本次结果事件的独立标识)及 string source(来源诊断字段,不用于业务判断)。微信错误回包还可能带 wx_error_type(integer 或 null)。字段类型、所有状态与错误码见本文 转账退还与查询。event_id 为 transfer_refund:<refund_result_id>,消费时按完整 event_id 去重,不按 transfer_id 去重,以免把查询或后续结果吞掉。
事件名不代表成功:只有 native_status=2002 且 status="refunded" 才确认已退还。已进入退还提交且操作结束时才可能产生事件,可能是成功、失败或 refund_unknown;仅返回 refund_pending 时还未结束,后续回包或主动查询查到终态可能完成原动作并产生事件。在 refund_unknown/failed 等已结束动作之后再查询,只保证返回状态和回收占用,不保证补发第二个退款结果事件。 未提交就被拒绝、预检发现已经退还均不发此事件;账号会话变化后的旧结果不投递给新账号。因此客户端必须保留主动查询路径,不能只等事件。
21.6 群成员变更事件
{
"event_name": "group_member_join",
"event_id": "msg:group_member_join:50136399923@chatroom:wxid_member:1775000001123",
"body": {
"room_id": "50136399923@chatroom",
"member_id": "wxid_member",
"member_nick_name": "张三",
"member_remark": "",
"member_alias": "",
"member_display_name": "张三",
"room_nick_name": "项目群",
"room_remark": "",
"message_id": "group_member_join:50136399923@chatroom:wxid_member:1775000001123",
"snapshot_before_count": 23,
"snapshot_after_count": 24,
"create_time": 1775000001,
"is_group": true
}
}
group_member_leave 使用相同的群/成员字段,表示成员离开;snapshot_before_count、snapshot_after_count 分别为变更前后成员数。
昵称变更事件额外带:
{
"event_name": "group_member_nickname_change",
"body": {
"old_room_nick_name": "lumen",
"new_room_nick_name": "lumen123",
"snapshot_before_count": 23,
"snapshot_after_count": 23
}
}
注意:
| 事项 | 说明 |
|---|---|
群成员事件没有 msg_type / raw_message_type / sender_id / chat_id |
它们被主动剥掉了,请用 room_id + member_id |
create_time |
秒 |
snapshot_before_count / snapshot_after_count |
加入/离开事件里是群成员数;昵称变更事件里是昵称快照的条目数。⚠️ 同一字段名两种语义,别跨事件类型复用判断逻辑 |
首次建群快照不会产生 group_member_join |
插件第一次看到某个群时不报加入 |
首次建立昵称快照不会产生 group_member_nickname_change |
只有后续修改/清除才上报 |
| 退群事件有短暂确认延迟 | 避免同步抖动误报 |
| 昵称变更的确认机制 | 会按 150ms / 500ms / 1500ms 三次延迟复查确认,所以事件到达会略有延迟,且 member_display_name 等字段是复查后的值 |
21.7 好友申请事件
event_name 为 friend_request。
这个事件的 body 结构有明显的不确定性:event_id 会按以下优先级生成,说明至少存在三种数据形态:
- 有申请 ID →
friend_request:<申请ID> - 否则有消息 ID →
msg:<消息ID> - 都取不到 → 当前长轮询信封可能省略
event_id,由接收端组合事件名、捕获时间和业务编号兜底
恒有字段:
| 字段 | 类型 | 说明 |
|---|---|---|
message_id |
string | 消息 ID(可能是兜底的时间戳字符串) |
msg_type |
string | 固定 "friend_request" |
content |
string | 可能为空串 |
room_id |
string | 强制为空串 "" |
is_group |
bool | 强制为 false |
source_xml |
string | 归一化后的完整 XML |
create_time / is_at / at_user_list / at_all |
— | 继承自消息基础字段 |
条件字段(取不到则不出现):
| 字段 | 说明 |
|---|---|
request_id |
好友申请业务 ID |
encrypt_username |
加密用户名,同意申请时必须 |
ticket |
凭证,同意申请时必须 |
scene |
场景值 |
from_user / sender_id / chat_id |
仅当解析出申请方用户名时才写入这三个键 |
nick_name、alias、sex、country、province、city、signature、big_head_img_url、small_head_img_url |
申请方资料,按可取到的填 |
⚠️ 同意好友申请前请先校验
encrypt_username和ticket是否都存在,缺失时不要盲调accept_friend_request。识别条件:只有满足特定特征的消息才会成为
friend_request事件,否则会退回普通message事件。所以不要假设每个好友申请都一定会以friend_request出现。
21.8 事件的可靠性语义
| 事项 | 说明 |
|---|---|
| 成功判定 | HTTP 2xx |
| 失败重试 | 见第 23 节,延迟 1/2/5/10 秒 |
| 去重 | 插件侧有20 秒的内部去重窗口,但跨重试/跨重启仍可能重复上报。你必须按 event_id 去重 |
| 丢失可能 | 见第 24 节的容量/TTL 淘汰。插件不会告诉你丢了多少条 |
| 进程重启 | 未发完的事件会从磁盘恢复继续补发 |
22. 运行节奏与状态机
22.1 完整流程
插件启动 / 服务端地址变更
│
▼
地址为空? ── 是 ──► 每 5 秒重试一次,不做任何 register
│ 否
▼
POST /agent/register (超时 15 秒)
│ 失败 → 退避重试(阻断在此,不会继续)
│ 成功 → 失败计数清零
▼
POST /agent/state (超时 15 秒)
│ 无论成功失败 → 都继续,且都不重试
▼
POST /agent/poll (超时 max(15秒, 间隔+10秒))
│
├── 失败 → 退避重试
│
├── 无命令 → **立即**发起下一轮 poll(0 秒延迟)
│
└── 有命令 → 逐条执行:
执行第 1 条 → POST /agent/command_result
│ 失败 → 退避重试,**不推进到下一条**
│ 成功 → 执行第 2 条 …
全部完成 → **立即**发起下一轮 poll
22.2 三个必须理解的行为
① 轮询节奏由"你的服务端"决定,不由「轮询间隔」决定
插件每轮结束立即发下一轮,中间不等待。设置页里的「轮询间隔」只用于计算超时:
poll 超时 = max(15 秒, 轮询间隔 + 10 秒)
| 轮询间隔 | poll 超时 |
|---|---|
| 1 秒 | 15 秒 |
| 5 秒 | 15 秒 |
| 10 秒 | 20 秒 |
| 20 秒(默认) | 30 秒 |
| 60 秒 | 70 秒 |
| 300 秒(上限) | 310 秒 |
⚠️ 设置里的间隔不表示两次 poll 之间的休眠。服务端应在无命令时等待、有命令时立即返回,兼顾请求量与投递延迟。
② 命令执行是"上报成功才继续"的串行模式
- 一次 poll 返回多条命令 → 逐条执行
- 执行完一条 → 必须等
/agent/command_result返回 2xx → 才执行下一条 - 结果上报一直失败 → 插件卡在这条命令上,后续命令都不执行,不断退避重试
影响:如果你有 10 条命令要下发,而你的 command_result 接口很慢(比如每条要 5 秒),那么整批命令的完成时间会被这个接口拖住。请把它做成快速返回(可靠保存后返回 2xx,后续业务异步处理)。
③ state 只发一次
state 在每次"register 成功后"发一次。它不是一个周期性的状态同步接口。要持续获取状态,请在 poll 响应里下发 device_status 或 bridge_status 命令。
22.3 地址变更
| 情况 | 插件行为 |
|---|---|
| 你把地址改成另一个非空地址 | 下一轮 poll 开始前检测到变化 → 重置状态(清空待执行命令与待上报结果)→ 同一轮内立即重新 register |
| 你把地址清空 | 判定为"未配置",停止 register/poll,每 5 秒重试一次 |
| 地址规范化后没变 | 什么都不做 |
⚠️ 重置会丢弃待执行命令与待上报结果。如果你在插件正忙时改地址,那批命令的结果就没了。改地址请在空闲期做。
23. 错误的处理与重试
23.1 两套独立的重试机制
这是最容易搞混的地方:命令链路和事件链路的重试策略完全不同、互不影响。
| 命令链路(register / poll / command_result) | 事件链路(event) | |
|---|---|---|
| 退避序列 | 2s → 5s → 10s → 20s(第 4 次起封顶 20 秒) | 1s → 2s → 5s → 10s(第 4 次起封顶 10 秒) |
| 失败计数 | 全局单计数器,跨 register / poll / command_result 累加 | 每条事件各自计数 |
| 何时归零 | 任一请求成功即清零(含 state 成功) | 该条事件上报成功 |
| 重试上限 | 无上限,一直重试 | 无上限,一直重试 |
| 阻断效应 | register 失败阻断后续;command_result 失败阻断后续命令 |
队首事件失败阻塞整个事件队列 |
⚠️ 命令链路的失败计数是全局共享的。也就是说:
poll连续失败 2 次之后,如果下一次command_result也失败,计数会算作第 3 次(等 10 秒)——它们共用同一个计数器。
23.2 事件队列的"队首阻塞"
事件上报是严格先进先出 + 单条在飞:
队首事件发送失败 → 整个队列暂停(后面的等待)
→ 按 1/2/5/10 秒退避重试**同一条**
→ 成功后才发下一条
后果:一条"有毒"的事件(例如它导致你的服务端 500)会卡住它后面所有事件。
建议:
- 你的 /agent/event 要尽量快速返回 200,业务处理异步化
- 如果某类事件你的服务端处理不了,也要返回 200(记日志后丢弃),而不是返回 5xx——否则会阻塞整条队列
- state 只在注册后上报一次;持续监控时定期下发 bridge_status,读取 pending_event_queue_count 和 oldest_pending_event_age_ms
23.3 未配置地址时的行为
地址为空时,插件不做 register/poll,每 5 秒重试一次检查配置。这个重试不计入失败计数。
23.4 服务端实现建议
| 建议 | 原因 |
|---|---|
| 永远返回 200,除非你确实希望插件重试 | 非 2xx 会触发退避,可能拖慢整个链路 |
/agent/command_result 必须快 |
它阻塞后续命令执行 |
/agent/event 必须快且稳健 |
它阻塞整个事件队列 |
| 不要靠响应体传业务状态 | 插件只看状态码(poll 的 commands 除外) |
poll 必须实现 hold |
否则退化成高频空转 |
24. 离线队列与补发
事件上报失败时会暂存到手机上,稍后补发。
| 项目 | 值 |
|---|---|
| 存储位置 | 插件沙盒缓存目录下 webridge/lp_outbox.json |
| 最大条数 | 默认 500,设置页可调,硬上限 5000 |
| 超出容量 | 从队头(最旧的)开始丢弃 |
| 最大暂存时间(TTL) | 默认 120 秒(界面显示"2 分钟");设为空或 0 表示不过期 |
| TTL 判定基准 | 事件的 enqueued_at_ms(入队时间),不是捕获时间 |
| TTL 检查时机 | 入队前一次、每次取队首发车前再一次 |
| 落盘时机 | 入队后立即全量写盘;移除/TTL 清理走节流(累计 20 条或 500ms 兜底) |
| 补发顺序 | 严格先进先出 |
| 进程重启 | 从磁盘恢复队列,并把每条的重试时间清零(立即尝试重发) |
关键含义
- 只有事件走这个队列。 命令结果不落盘——如果
/agent/command_result一直失败,插件只是内存里一直重试同一条(不落盘),进程重启后这条结果就丢了。 - TTL 默认只有 2 分钟。 服务端停机超过 2 分钟,这期间的事件会丢且不会被通知。需要长停机不丢数据,请把 TTL 调大或置空,并提高条数上限。
- 暂存开关关闭时,上报失败的事件直接丢弃、不重试。
- 接收回调开关关闭时,插件会清空整个事件队列(含已落盘),并停止上报。
- 插件不会告诉你丢了多少条。 可通过定期下发
bridge_status,读取诊断快照中的pending_event_queue_count(积压量)和oldest_pending_event_age_ms(最老积压多久)。
⚠️ 强烈建议:在服务端做基于
event_id的去重,并监控/agent/event的实际接收量与微信侧应有量的差异。容量淘汰、TTL 淘汰、开关关闭,这三种丢数据都没有任何协议层通知。
切换模式时的队列边界
本模式使用 lp_outbox.json,与 WebSocket 的暂存队列独立;共用暂存开关、条数和时间配置。切回长轮询时恢复自己的事件队列;长轮询命令结果仅在内存等待上报,不因启用事件暂存而获得落盘恢复能力。
25. 幂等与去重
25.1 命令侧(你 → 插件)
以下“最近 100 条”规则描述通用命令缓存。创建群聊、直接加人和发送群邀请(含 group_invite_member)使用独立的群操作去重规则,不适用本节换新ID重试建议。好友支付姓名、群二维码、个人二维码、群管理员操作、统一媒体四项、入群邀请三项、收藏三项、公众号五项及位置反查探针每次进入共享服务重新校验账号,不使用此缓存。收藏发送/删除按收藏专用幂等规则处理;公众号五项不走此缓存,关注/取关结果未知时先查关系,见公众号。
插件对最近 100 条 request_id 缓存了执行结果(内存缓存,进程重启后失效,没有时间过期,只有按条数淘汰)。
收到重复 request_id 时的行为:
| 情况 | 行为 |
|---|---|
| 命中缓存 | 直接回放上次的结果信封,不重新执行(包括 ok、duration_ms、ts_ms 都是首次执行时的值) |
| 缓存已满 | 最旧的被淘汰,再收到它就会当作新命令重新执行 |
| 上次是失败结果 | 也照样缓存。所以"用同一个 request_id 重试失败的命令"永远不会真的重试 |
⚠️ 这是最容易踩的坑:你下发了一条
send_message失败(ok:false),然后用同一个request_id再发一次想让它重试——插件会直接回放那个失败结果,不会重新执行。 确认上次未产生副作用且本命令允许重试后,才使用新的request_id;未知写入先核实状态,业务专用去重规则优先。
对你的含义:
- 仅在当前进程仍命中该通用缓存时,相同
request_id才回放原结果;不能据此保证跨淘汰、跨重启或所有命令都不会重复执行 - 但不要把"换新 request_id 重试"交给插件去重——重试语义需要你在业务层定义
- 缓存上限 100 条且无 TTL,所以旧
request_id会失效,届时重发会真的再执行一次
25.2 事件侧(插件 → 你)
| 事项 | 说明 |
|---|---|
| 插件侧去重 | 有 20 秒的内部去重窗口,但只防"同一事件被钩子触发两次" |
| 插件是否保证不重复 | 不保证。上报失败重试、进程重启补发都可能产生重复 |
| 你的责任 | 按 event_id 去重 |
event_id 缺失时 |
用 (event_name, captured_at_ms, body.message_id) 之类的组合键兜底,或接受重复 |
25.3 补发导致的"时间乱序"
重试和补发保留原 enqueued_at_ms(入队时刻)与 captured_at_ms(捕获时刻),本次 upload_started_at_ms 更新为真正开始上报的时刻。
所以你会收到"过去的"事件,且它们可能夹在当前事件中间。
建议:用 captured_at_ms 做业务时间排序,用 event_id 做去重,不要用收到时刻做业务时间。
26. 常见问题排查
插件日志显示「未配置长轮询HTTP接口」
设置页的「服务端地址」为空。填上即可(只填基础地址,不要带 /agent/poll)。
插件一直 register 失败
按可能性排序:
- 路径不对:确认你的服务端真的实现了
/agent/register(如果填了路径前缀/api,那应该是/api/agent/register) - 返回了非 2xx:检查是否返回 302、4xx 或 5xx;201 和 204 属于成功的 2xx,不会因此失败
- 网络不通:手机能否访问到你的服务器
- HTTPS 证书问题:开发阶段建议先用 HTTP
验证方法:
curl -X POST http://你的服务端/agent/register \
-H "Content-Type: application/json" \
-d '{}'
⚠️ 不要用浏览器直接打开这个地址——它是 POST 接口,浏览器打开必然失败,会让你误判为"服务端有问题"。
register 成功,但插件不 poll
- 注册成功后先发送一次
state,最多等待约 15 秒;无论成功或失败都会继续 poll,持续不 poll 不能简单归因于 state 的业务错误。 - 检查服务端是否已收到 poll 但仍在等待,以及插件最近的 poll/命令结果上报是否进入重试。
- 检查通信模式、服务端地址与插件有效状态是否发生变化。
服务端下发了命令,但插件不执行/收不到结果
按可能性排序:
commands不是数组:响应必须是{"commands": [...]}。返回{"command": {...}}(不带 s)或直接返回数组都会被当作"没有命令"command_name拼错:会返回ok:false+unsupported command_name: xxx,看有没有这条request_id为空:长轮询下会返回一个request_id为空串的失败结果("request_id is required")——通过这个空 request_id 你能识别出是自己忘了填/agent/command_result报错:插件会卡住,后续命令不执行- 命令不在
capabilities里:当前声明清单仍存在业务命令与兼容别名缺口,详见本篇能力清单。按清单过滤可能误拦已实现命令;结合本篇命令表、别名表与实际插件构建处理,不要仅凭版本号认定清单完整。
收不到 command_result
/agent/command_result路径写错或返回非 2xx → 插件会一直退避重试同一条,卡住- 命令正在执行中:含下载/转码的命令(语音、视频、表情)可能耗时数秒到几十秒
- 命令名不支持 → 会有结果,但
ok:false request_id/command_name为空 → 长轮询仍会回一个空 request_id 的失败结果
事件上报失败/积压
/agent/event返回了非 2xx → 看服务端日志- 队首有毒事件阻塞了整条队列 → 检查你的服务端是不是对某类事件返回了 5xx
- 接收回调开关被关了 → 插件会清空整个队列
- 积压超过 2 分钟 TTL → 已经丢了,检查定期
bridge_status中的队列年龄和接收量;年龄字段不等于丢失计数
事件重复
预期行为。按 event_id 去重。
服务端重启后收不到历史事件
正常。默认只保留 2 分钟。要更长请调大「最大暂存时间」。
插件"好像死了",什么都不发
最可能是你的某个接口返回了非 2xx,插件在退避重试。 检查:
/agent/command_result是否一直失败(会卡住命令执行)/agent/poll是否一直失败(会卡住整个循环)
长轮询延迟很高
先确认服务端有命令时是否立即结束正在等待的 poll,再检查当前批次是否仍在执行业务、/agent/command_result 是否重试以及网络延迟。未做 hold 主要导致空请求过多,不能仅据此解释命令延迟。
在 /agent/poll 中实现“有命令立即返回,没有命令则等待”;无命令等待时长须小于 max(15, 轮询间隔+10),例如默认超时 30 秒时等待约 20~25 秒。
请求量太大/太频繁
同样是没实现 hold 导致的空转。另外检查是不是有多个插件实例连到同一个服务端。
怎么确认插件和服务器连通
- 打开插件设置页 → 最近日志
- 查找 register / poll 相关记录
- 或调用手机上插件的 HTTP 接口
GET http://手机IP:19088/debug/bridge_status,看内部的 Agent 通道状态(agent_registered、agent_last_poll_success、agent_consecutive_failures等)
附录 A. 接入检查清单
- 「轮询间隔」不控制拉取频率,只在 poll 超时公式里。不实现 hold 就会高频空转。
poll超时有 15 秒下限,不是"间隔 + 10"。- 地址不要带
/agent/poll,会被自动剥掉。 register失败会阻断,state失败不会——后者会静默。/agent/command_result必须快速返回 200,它阻塞后续所有命令。/agent/event也要快速返回 200,一条有毒事件会阻塞整个队列。- 用同一个
request_id重试失败命令不会真的重试,只会回放失败结果。 last_result_request_id不代表"服务端已收到"。friends_all只返回普通好友;total、friend_count与返回条数一致,其他分类计数不能加进好友数组。
附录 B. 兼容别名与已移除功能
兼容命令别名
以下均由当前分发器实际支持,与右侧主命令共用参数和结果;新接入使用主命令。别名未必出现在 capabilities,不能据清单缺省推断分发器不支持。
| 兼容命令 | 主命令 |
|---|---|
group_invite_member |
invite_group_member |
group_invite_member_probe |
invite_group_member_probe |
update_nickname |
profile_nickname |
update_nickname_probe |
profile_nickname_probe |
set_group_self_nickname_probe |
rename_group_probe |
set_group_nickname_probe |
rename_group_probe |
已移除的已读能力
mark_session_read 与 mark_session_read_probe 已移除。
已废弃且已移除。 原单个、批量、全部已读,以及 mark_session_read_probe 探针均不再执行,当前无替代命令。
通过前置鉴权和插件状态校验、进入命令分发后,返回既有 command_result 信封,保留原 request_id 和 command_name,业务字段为:
{
"ok": false,
"result": {},
"error": "unsupported command_name: mark_session_read"
}
探针的 error 为 unsupported command_name: mark_session_read_probe。这是命令已移除的失败结果,无异步完成事件;请停止下发,重试或更换 request_id 不会恢复该能力。旧版本的 mode、marked、failed 等成功字段不再适用。
发送后自动已读及其设置项也已移除;手动已读不再列入命令索引或 capabilities,当前没有替代命令。
其他历史入口
主动发红包及其探针已移除。旧 upload_message_image 仍保留兼容,推荐新接入使用 upload_message_media;当前可用的别名以本附录和命令索引为准。