@netcat-ai/openclaw-weixin 2.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/CHANGELOG.zh_CN.md +156 -0
  3. package/LICENSE +27 -0
  4. package/README.md +355 -0
  5. package/README.zh_CN.md +351 -0
  6. package/dist/index.js +16 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/src/api/api.js +484 -0
  9. package/dist/src/api/api.js.map +1 -0
  10. package/dist/src/api/config-cache.js +64 -0
  11. package/dist/src/api/config-cache.js.map +1 -0
  12. package/dist/src/api/session-guard.js +49 -0
  13. package/dist/src/api/session-guard.js.map +1 -0
  14. package/dist/src/api/types.js +37 -0
  15. package/dist/src/api/types.js.map +1 -0
  16. package/dist/src/auth/accounts.js +328 -0
  17. package/dist/src/auth/accounts.js.map +1 -0
  18. package/dist/src/auth/login-qr.js +331 -0
  19. package/dist/src/auth/login-qr.js.map +1 -0
  20. package/dist/src/auth/pairing.js +104 -0
  21. package/dist/src/auth/pairing.js.map +1 -0
  22. package/dist/src/cdn/aes-ecb.js +19 -0
  23. package/dist/src/cdn/aes-ecb.js.map +1 -0
  24. package/dist/src/cdn/cdn-upload.js +74 -0
  25. package/dist/src/cdn/cdn-upload.js.map +1 -0
  26. package/dist/src/cdn/cdn-url.js +14 -0
  27. package/dist/src/cdn/cdn-url.js.map +1 -0
  28. package/dist/src/cdn/pic-decrypt.js +89 -0
  29. package/dist/src/cdn/pic-decrypt.js.map +1 -0
  30. package/dist/src/cdn/upload.js +115 -0
  31. package/dist/src/cdn/upload.js.map +1 -0
  32. package/dist/src/channel.js +478 -0
  33. package/dist/src/channel.js.map +1 -0
  34. package/dist/src/compat.js +67 -0
  35. package/dist/src/compat.js.map +1 -0
  36. package/dist/src/config/config-schema.js +20 -0
  37. package/dist/src/config/config-schema.js.map +1 -0
  38. package/dist/src/config/reply-progress.js +5 -0
  39. package/dist/src/config/reply-progress.js.map +1 -0
  40. package/dist/src/media/media-download.js +95 -0
  41. package/dist/src/media/media-download.js.map +1 -0
  42. package/dist/src/media/mime.js +73 -0
  43. package/dist/src/media/mime.js.map +1 -0
  44. package/dist/src/media/silk-transcode.js +64 -0
  45. package/dist/src/media/silk-transcode.js.map +1 -0
  46. package/dist/src/messaging/debug-mode.js +63 -0
  47. package/dist/src/messaging/debug-mode.js.map +1 -0
  48. package/dist/src/messaging/error-notice.js +25 -0
  49. package/dist/src/messaging/error-notice.js.map +1 -0
  50. package/dist/src/messaging/inbound.js +201 -0
  51. package/dist/src/messaging/inbound.js.map +1 -0
  52. package/dist/src/messaging/markdown-filter.js +368 -0
  53. package/dist/src/messaging/markdown-filter.js.map +1 -0
  54. package/dist/src/messaging/outbound-hooks.js +58 -0
  55. package/dist/src/messaging/outbound-hooks.js.map +1 -0
  56. package/dist/src/messaging/process-message.js +406 -0
  57. package/dist/src/messaging/process-message.js.map +1 -0
  58. package/dist/src/messaging/reply-progress-sender.js +93 -0
  59. package/dist/src/messaging/reply-progress-sender.js.map +1 -0
  60. package/dist/src/messaging/send-media.js +54 -0
  61. package/dist/src/messaging/send-media.js.map +1 -0
  62. package/dist/src/messaging/send.js +220 -0
  63. package/dist/src/messaging/send.js.map +1 -0
  64. package/dist/src/messaging/slash-commands.js +70 -0
  65. package/dist/src/messaging/slash-commands.js.map +1 -0
  66. package/dist/src/monitor/monitor.js +146 -0
  67. package/dist/src/monitor/monitor.js.map +1 -0
  68. package/dist/src/storage/state-dir.js +9 -0
  69. package/dist/src/storage/state-dir.js.map +1 -0
  70. package/dist/src/storage/sync-buf.js +64 -0
  71. package/dist/src/storage/sync-buf.js.map +1 -0
  72. package/dist/src/util/logger.js +120 -0
  73. package/dist/src/util/logger.js.map +1 -0
  74. package/dist/src/util/random.js +16 -0
  75. package/dist/src/util/random.js.map +1 -0
  76. package/dist/src/util/redact.js +54 -0
  77. package/dist/src/util/redact.js.map +1 -0
  78. package/index.ts +19 -0
  79. package/openclaw.plugin.json +22 -0
  80. package/package.json +75 -0
  81. package/src/api/api.ts +586 -0
  82. package/src/api/config-cache.ts +79 -0
  83. package/src/api/session-guard.ts +58 -0
  84. package/src/api/types.ts +278 -0
  85. package/src/auth/accounts.ts +394 -0
  86. package/src/auth/login-qr.ts +458 -0
  87. package/src/auth/pairing.ts +120 -0
  88. package/src/cdn/aes-ecb.ts +21 -0
  89. package/src/cdn/cdn-upload.ts +93 -0
  90. package/src/cdn/cdn-url.ts +20 -0
  91. package/src/cdn/pic-decrypt.ts +101 -0
  92. package/src/cdn/upload.ts +168 -0
  93. package/src/channel.ts +548 -0
  94. package/src/compat.ts +77 -0
  95. package/src/config/config-schema.ts +23 -0
  96. package/src/config/reply-progress.ts +10 -0
  97. package/src/media/media-download.ts +149 -0
  98. package/src/media/mime.ts +76 -0
  99. package/src/media/silk-transcode.ts +74 -0
  100. package/src/messaging/debug-mode.ts +69 -0
  101. package/src/messaging/error-notice.ts +32 -0
  102. package/src/messaging/inbound.ts +262 -0
  103. package/src/messaging/markdown-filter.ts +361 -0
  104. package/src/messaging/outbound-hooks.ts +88 -0
  105. package/src/messaging/process-message.ts +525 -0
  106. package/src/messaging/reply-progress-sender.ts +122 -0
  107. package/src/messaging/send-media.ts +72 -0
  108. package/src/messaging/send.ts +294 -0
  109. package/src/messaging/slash-commands.ts +110 -0
  110. package/src/monitor/monitor.ts +224 -0
  111. package/src/storage/state-dir.ts +11 -0
  112. package/src/storage/sync-buf.ts +81 -0
  113. package/src/util/logger.ts +145 -0
  114. package/src/util/random.ts +17 -0
  115. package/src/util/redact.ts +54 -0
  116. package/src/vendor.d.ts +25 -0
package/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # WeChat
2
+
3
+ [简体中文](./README.zh_CN.md)
4
+
5
+ OpenClaw's WeChat channel plugin, supporting login authorization via QR code scanning.
6
+
7
+ ## Compatibility
8
+
9
+ | Plugin Version | OpenClaw Version | npm dist-tag | Status |
10
+ |---------------|------------------------|--------------|-------------|
11
+ | 2.0.x | >=2026.3.22 | `latest` | Active |
12
+ | 1.0.x | >=2026.1.0 <2026.3.22 | `legacy` | Maintenance |
13
+
14
+ > The plugin checks the host version at startup and will refuse to load if the
15
+ > running OpenClaw version is outside the supported range.
16
+
17
+ ## Prerequisites
18
+
19
+ [OpenClaw](https://docs.openclaw.ai/install) must be installed (the `openclaw` CLI needs to be available).
20
+
21
+ Check your version: `openclaw --version`
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ openclaw plugins install @netcat-ai/openclaw-weixin
27
+ ```
28
+
29
+ Enable the plugin:
30
+
31
+ ```bash
32
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
33
+ ```
34
+
35
+ For a custom iLink backend, set the service URL before the login that should use it:
36
+
37
+ ```bash
38
+ openclaw config set channels.openclaw-weixin.baseUrl http://127.0.0.1:38080
39
+ ```
40
+
41
+ Then start QR code login:
42
+
43
+ ```bash
44
+ openclaw channels login --channel openclaw-weixin
45
+ ```
46
+
47
+ A QR code will appear in the terminal. Scan it with your phone and confirm the authorization. Once confirmed, the login credentials and service URL are saved with that account.
48
+
49
+ Changing the channel-level `baseUrl` later only affects the next new account login. Existing accounts keep their saved backend, so one `openclaw-weixin` channel can use the official backend and multiple Webox instances at the same time.
50
+
51
+ Restart the gateway:
52
+
53
+ ```bash
54
+ openclaw gateway restart
55
+ ```
56
+
57
+ ## Adding More WeChat Accounts
58
+
59
+ Log in through the official backend:
60
+
61
+ ```bash
62
+ openclaw config set channels.openclaw-weixin.baseUrl https://ilinkai.weixin.qq.com
63
+ openclaw channels login --channel openclaw-weixin
64
+ ```
65
+
66
+ Log in through another Webox instance:
67
+
68
+ ```bash
69
+ openclaw config set channels.openclaw-weixin.baseUrl http://127.0.0.1:38081
70
+ openclaw channels login --channel openclaw-weixin
71
+ ```
72
+
73
+ Each QR code login creates a new account entry, supporting multiple WeChat accounts online simultaneously.
74
+
75
+ ## Multi-Account Context Isolation
76
+
77
+ By default, DMs can share one session bucket. For **multiple logged-in WeChat accounts**, isolate by account + channel + sender:
78
+
79
+ ```bash
80
+ openclaw config set session.dmScope per-account-channel-peer
81
+ ```
82
+
83
+ ## Custom BotAgent (optional)
84
+
85
+ Every outbound request to the WeChat backend carries a self-declared `bot_agent`
86
+ identifier — analogous to an HTTP `User-Agent` — used for log attribution and
87
+ monitoring aggregation. The default is `OpenClaw`. Declaring your own app name
88
+ makes it much easier to trace your traffic in backend logs.
89
+
90
+ Add one line to `openclaw.json`:
91
+
92
+ ```json
93
+ {
94
+ "channels": {
95
+ "openclaw-weixin": {
96
+ "botAgent": "MyBot/1.2.0"
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Format** (UA-style):
103
+
104
+ - One or more `Name/Version` tokens, space-separated
105
+ - Each token may optionally be followed by ` (comment)`
106
+ - ASCII only; total length ≤ 256 bytes
107
+ - Invalid tokens are silently dropped during sanitization; falls back to
108
+ `OpenClaw` if nothing valid remains
109
+
110
+ Examples that pass through unchanged:
111
+
112
+ - `MyBot/1.2.0`
113
+ - `MyBot/1.2.0 (region=cn;env=prod)`
114
+ - `MyBot/1.2.0 LangChain/0.3.5`
115
+ - `MyBot/1.2.0-rc.1+build.5`
116
+
117
+ **Note**: `bot_agent` is for observability only — it is not used for
118
+ authentication or routing. All registered agents on this plugin instance
119
+ currently share the same `botAgent` declaration; per-agent overrides may be
120
+ added in a future version if needed.
121
+
122
+ ## Backend API Protocol
123
+
124
+ This plugin communicates with the backend gateway via HTTP JSON API. Developers integrating with their own backend need to implement the following interfaces.
125
+
126
+ All endpoints use `POST` with JSON request and response bodies. Common request headers:
127
+
128
+ | Header | Description |
129
+ |--------|-------------|
130
+ | `Content-Type` | `application/json` |
131
+ | `AuthorizationType` | Fixed value `ilink_bot_token` |
132
+ | `Authorization` | `Bearer <token>` (obtained after login) |
133
+ | `X-WECHAT-UIN` | Base64-encoded random uint32 |
134
+
135
+ ### Endpoint List
136
+
137
+ | Endpoint | Path | Description |
138
+ |----------|------|-------------|
139
+ | getUpdates | `getupdates` | Long-poll for new messages |
140
+ | sendMessage | `sendmessage` | Send a message (text/image/video/file) |
141
+ | getUploadUrl | `getuploadurl` | Get CDN upload pre-signed URL |
142
+ | getConfig | `getconfig` | Get account config (typing ticket, etc.) |
143
+ | sendTyping | `sendtyping` | Send/cancel typing status indicator |
144
+
145
+ ### getUpdates
146
+
147
+ Long-polling endpoint. The server responds when new messages arrive or on timeout.
148
+
149
+ **Request body:**
150
+
151
+ ```json
152
+ {
153
+ "get_updates_buf": ""
154
+ }
155
+ ```
156
+
157
+ | Field | Type | Description |
158
+ |-------|------|-------------|
159
+ | `get_updates_buf` | `string` | Sync cursor from the previous response; empty string for the first request |
160
+
161
+ **Response body:**
162
+
163
+ ```json
164
+ {
165
+ "ret": 0,
166
+ "msgs": [...],
167
+ "get_updates_buf": "<new cursor>",
168
+ "longpolling_timeout_ms": 35000
169
+ }
170
+ ```
171
+
172
+ | Field | Type | Description |
173
+ |-------|------|-------------|
174
+ | `ret` | `number` | Return code, `0` = success |
175
+ | `errcode` | `number?` | Error code (e.g., `-14` = session timeout) |
176
+ | `errmsg` | `string?` | Error description |
177
+ | `msgs` | `WeixinMessage[]` | Message list (structure below) |
178
+ | `get_updates_buf` | `string` | New sync cursor to pass in the next request |
179
+ | `longpolling_timeout_ms` | `number?` | Server-suggested long-poll timeout for the next request (ms) |
180
+
181
+ ### sendMessage
182
+
183
+ Send a message to a user.
184
+
185
+ **Request body:**
186
+
187
+ ```json
188
+ {
189
+ "msg": {
190
+ "to_user_id": "<target user ID>",
191
+ "context_token": "<conversation context token>",
192
+ "item_list": [
193
+ {
194
+ "type": 1,
195
+ "text_item": { "text": "Hello" }
196
+ }
197
+ ]
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### getUploadUrl
203
+
204
+ Get CDN upload pre-signed parameters. Call this endpoint before uploading a file to obtain `upload_param` and `thumb_upload_param`.
205
+
206
+ **Request body:**
207
+
208
+ ```json
209
+ {
210
+ "filekey": "<file identifier>",
211
+ "media_type": 1,
212
+ "to_user_id": "<target user ID>",
213
+ "rawsize": 12345,
214
+ "rawfilemd5": "<plaintext MD5>",
215
+ "filesize": 12352,
216
+ "thumb_rawsize": 1024,
217
+ "thumb_rawfilemd5": "<thumbnail plaintext MD5>",
218
+ "thumb_filesize": 1040
219
+ }
220
+ ```
221
+
222
+ | Field | Type | Description |
223
+ |-------|------|-------------|
224
+ | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE |
225
+ | `rawsize` | `number` | Original file plaintext size |
226
+ | `rawfilemd5` | `string` | Original file plaintext MD5 |
227
+ | `filesize` | `number` | Ciphertext size after AES-128-ECB encryption |
228
+ | `thumb_rawsize` | `number?` | Thumbnail plaintext size (required for IMAGE/VIDEO) |
229
+ | `thumb_rawfilemd5` | `string?` | Thumbnail plaintext MD5 (required for IMAGE/VIDEO) |
230
+ | `thumb_filesize` | `number?` | Thumbnail ciphertext size (required for IMAGE/VIDEO) |
231
+
232
+ **Response body:**
233
+
234
+ ```json
235
+ {
236
+ "upload_param": "<original image upload encrypted parameters>",
237
+ "thumb_upload_param": "<thumbnail upload encrypted parameters>"
238
+ }
239
+ ```
240
+
241
+ ### getConfig
242
+
243
+ Get account configuration, including the typing ticket.
244
+
245
+ **Request body:**
246
+
247
+ ```json
248
+ {
249
+ "ilink_user_id": "<user ID>",
250
+ "context_token": "<optional, conversation context token>"
251
+ }
252
+ ```
253
+
254
+ **Response body:**
255
+
256
+ ```json
257
+ {
258
+ "ret": 0,
259
+ "typing_ticket": "<base64-encoded typing ticket>"
260
+ }
261
+ ```
262
+
263
+ ### sendTyping
264
+
265
+ Send or cancel the typing status indicator.
266
+
267
+ **Request body:**
268
+
269
+ ```json
270
+ {
271
+ "ilink_user_id": "<user ID>",
272
+ "typing_ticket": "<obtained from getConfig>",
273
+ "status": 1
274
+ }
275
+ ```
276
+
277
+ | Field | Type | Description |
278
+ |-------|------|-------------|
279
+ | `status` | `number` | `1` = typing, `2` = cancel typing |
280
+
281
+ ### Message Structure
282
+
283
+ #### WeixinMessage
284
+
285
+ | Field | Type | Description |
286
+ |-------|------|-------------|
287
+ | `seq` | `number?` | Message sequence number |
288
+ | `message_id` | `number?` | Unique message ID |
289
+ | `from_user_id` | `string?` | Sender ID |
290
+ | `to_user_id` | `string?` | Receiver ID |
291
+ | `create_time_ms` | `number?` | Creation timestamp (ms) |
292
+ | `session_id` | `string?` | Session ID |
293
+ | `message_type` | `number?` | `1` = USER, `2` = BOT |
294
+ | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH |
295
+ | `item_list` | `MessageItem[]?` | Message content list |
296
+ | `context_token` | `string?` | Conversation context token, must be passed back when replying |
297
+
298
+ #### MessageItem
299
+
300
+ | Field | Type | Description |
301
+ |-------|------|-------------|
302
+ | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO |
303
+ | `text_item` | `{ text: string }?` | Text content |
304
+ | `image_item` | `ImageItem?` | Image (with CDN reference and AES key) |
305
+ | `voice_item` | `VoiceItem?` | Voice (SILK encoded) |
306
+ | `file_item` | `FileItem?` | File attachment |
307
+ | `video_item` | `VideoItem?` | Video |
308
+ | `ref_msg` | `RefMessage?` | Referenced message |
309
+
310
+ #### CDN Media Reference (CDNMedia)
311
+
312
+ All media types (image/voice/file/video) are transferred via CDN using AES-128-ECB encryption:
313
+
314
+ | Field | Type | Description |
315
+ |-------|------|-------------|
316
+ | `encrypt_query_param` | `string?` | Encrypted parameters for CDN download/upload |
317
+ | `aes_key` | `string?` | Base64-encoded AES-128 key |
318
+
319
+ ### CDN Upload Flow
320
+
321
+ 1. Calculate the file's plaintext size, MD5, and ciphertext size after AES-128-ECB encryption
322
+ 2. If a thumbnail is needed (image/video), calculate the thumbnail's plaintext and ciphertext parameters as well
323
+ 3. Call `getUploadUrl` to get `upload_param` (and `thumb_upload_param`)
324
+ 4. Encrypt the file content with AES-128-ECB and PUT upload to the CDN URL
325
+ 5. Encrypt and upload the thumbnail in the same way
326
+ 6. Use the returned `encrypt_query_param` to construct a `CDNMedia` reference, include it in the `MessageItem`, and send
327
+
328
+ > For complete type definitions, see [`src/api/types.ts`](src/api/types.ts). For API call implementations, see [`src/api/api.ts`](src/api/api.ts).
329
+
330
+ ## Uninstall
331
+
332
+ ```bash
333
+ openclaw plugins uninstall @netcat-ai/openclaw-weixin
334
+ ```
335
+
336
+ ## Troubleshooting
337
+
338
+ ### "requires OpenClaw >=2026.3.22" error
339
+
340
+ Your OpenClaw version is too old for this plugin version. Check with:
341
+
342
+ ```bash
343
+ openclaw --version
344
+ ```
345
+
346
+ Upgrade OpenClaw before installing this plugin.
347
+
348
+ ### Channel shows "OK" but doesn't connect
349
+
350
+ Ensure `plugins.entries.openclaw-weixin.enabled` is `true` in `~/.openclaw/openclaw.json`:
351
+
352
+ ```bash
353
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
354
+ openclaw gateway restart
355
+ ```
@@ -0,0 +1,351 @@
1
+ # 微信
2
+
3
+ [English](./README.md)
4
+
5
+ OpenClaw 的微信渠道插件,支持通过扫码完成登录授权。
6
+
7
+ ## 兼容性
8
+
9
+ | 插件版本 | OpenClaw 版本 | npm dist-tag | 状态 |
10
+ |---------|--------------------------|--------------|--------|
11
+ | 2.0.x | >=2026.3.22 | `latest` | 活跃 |
12
+ | 1.0.x | >=2026.1.0 <2026.3.22 | `legacy` | 维护中 |
13
+
14
+ > 插件在启动时会检查宿主版本,如果运行的 OpenClaw 版本超出支持范围,插件将拒绝加载。
15
+
16
+ ## 前提条件
17
+
18
+ 已安装 [OpenClaw](https://docs.openclaw.ai/install)(需要 `openclaw` CLI 可用)。
19
+
20
+ 查看版本:`openclaw --version`
21
+
22
+ ## 安装
23
+
24
+ ```bash
25
+ openclaw plugins install @netcat-ai/openclaw-weixin
26
+ ```
27
+
28
+ 启用插件:
29
+
30
+ ```bash
31
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
32
+ ```
33
+
34
+ 使用自定义 iLink 后端时,在扫码登录前设置本次登录使用的服务地址:
35
+
36
+ ```bash
37
+ openclaw config set channels.openclaw-weixin.baseUrl http://127.0.0.1:38080
38
+ ```
39
+
40
+ 开始扫码登录:
41
+
42
+ ```bash
43
+ openclaw channels login --channel openclaw-weixin
44
+ ```
45
+
46
+ 终端会显示一个二维码,用手机扫码并在手机上确认授权。确认后,登录凭证和本次使用的服务地址会自动保存到该账号。
47
+
48
+ 之后修改渠道级 `baseUrl` 只影响下一次新账号登录,不会改变已有账号使用的后端。因此,同一个 `openclaw-weixin` 渠道可以同时连接官方后端和多个 Webox。
49
+
50
+ 重启 gateway:
51
+
52
+ ```bash
53
+ openclaw gateway restart
54
+ ```
55
+
56
+ ## 添加更多微信账号
57
+
58
+ 登录官方后端:
59
+
60
+ ```bash
61
+ openclaw config set channels.openclaw-weixin.baseUrl https://ilinkai.weixin.qq.com
62
+ openclaw channels login --channel openclaw-weixin
63
+ ```
64
+
65
+ 登录另一个 Webox:
66
+
67
+ ```bash
68
+ openclaw config set channels.openclaw-weixin.baseUrl http://127.0.0.1:38081
69
+ openclaw channels login --channel openclaw-weixin
70
+ ```
71
+
72
+ 每次扫码登录都会创建一个新的账号条目,支持多个微信号同时在线。
73
+
74
+ ## 多账号上下文隔离
75
+
76
+ 默认情况下,私聊可能共用同一会话桶。**多个微信号同时登录**时,建议按「账号 + 渠道 + 对端」隔离:
77
+
78
+ ```bash
79
+ openclaw config set session.dmScope per-account-channel-peer
80
+ ```
81
+
82
+ ## 自定义 BotAgent(可选)
83
+
84
+ 每条出站请求会带一个自我声明的 `bot_agent` 字段——类似 HTTP `User-Agent`——用于
85
+ 后台日志归因和监控聚合。**默认值为 `OpenClaw`**。声明自己的应用名能让你的流量
86
+ 在后台日志中更容易识别。
87
+
88
+ 在 `openclaw.json` 中加一行即可:
89
+
90
+ ```json
91
+ {
92
+ "channels": {
93
+ "openclaw-weixin": {
94
+ "botAgent": "MyBot/1.2.0"
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ **格式规范**(UA 风格):
101
+
102
+ - 一个或多个 `Name/Version` token,空格分隔
103
+ - 每个 token 可选地跟一个 ` (comment)`
104
+ - 仅允许 ASCII 字符;总长 ≤ 256 字节
105
+ - 不合规的 token 在清洗时静默丢弃;如果最终为空,回退到 `OpenClaw`
106
+
107
+ 可直接使用的示例:
108
+
109
+ - `MyBot/1.2.0`
110
+ - `MyBot/1.2.0 (region=cn;env=prod)`
111
+ - `MyBot/1.2.0 LangChain/0.3.5`
112
+ - `MyBot/1.2.0-rc.1+build.5`
113
+
114
+ **注意**:`bot_agent` 仅用于观测,**不参与鉴权或路由**。当前本插件实例下所有
115
+ 已注册的 agent 共享同一个 `botAgent` 声明;如有需要按 agent 单独标识的场景,
116
+ 可在后续版本扩展配置。
117
+
118
+ ## 后端 API 协议
119
+
120
+ 本插件通过 HTTP JSON API 与后端网关通信。二次开发者若需对接自有后端,需实现以下接口。
121
+
122
+ 所有接口均为 `POST`,请求和响应均为 JSON。通用请求头:
123
+
124
+ | Header | 说明 |
125
+ |--------|------|
126
+ | `Content-Type` | `application/json` |
127
+ | `AuthorizationType` | 固定值 `ilink_bot_token` |
128
+ | `Authorization` | `Bearer <token>`(登录后获取) |
129
+ | `X-WECHAT-UIN` | 随机 uint32 的 base64 编码 |
130
+
131
+ ### 接口列表
132
+
133
+ | 接口 | 路径 | 说明 |
134
+ |------|------|------|
135
+ | getUpdates | `getupdates` | 长轮询获取新消息 |
136
+ | sendMessage | `sendmessage` | 发送消息(文本/图片/视频/文件) |
137
+ | getUploadUrl | `getuploadurl` | 获取 CDN 上传预签名 URL |
138
+ | getConfig | `getconfig` | 获取账号配置(typing ticket 等) |
139
+ | sendTyping | `sendtyping` | 发送/取消输入状态指示 |
140
+
141
+ ### getUpdates
142
+
143
+ 长轮询接口。服务端在有新消息或超时后返回。
144
+
145
+ **请求体:**
146
+
147
+ ```json
148
+ {
149
+ "get_updates_buf": ""
150
+ }
151
+ ```
152
+
153
+ | 字段 | 类型 | 说明 |
154
+ |------|------|------|
155
+ | `get_updates_buf` | `string` | 上次响应返回的同步游标,首次请求传空字符串 |
156
+
157
+ **响应体:**
158
+
159
+ ```json
160
+ {
161
+ "ret": 0,
162
+ "msgs": [...],
163
+ "get_updates_buf": "<新游标>",
164
+ "longpolling_timeout_ms": 35000
165
+ }
166
+ ```
167
+
168
+ | 字段 | 类型 | 说明 |
169
+ |------|------|------|
170
+ | `ret` | `number` | 返回码,`0` = 成功 |
171
+ | `errcode` | `number?` | 错误码(如 `-14` = 会话超时) |
172
+ | `errmsg` | `string?` | 错误描述 |
173
+ | `msgs` | `WeixinMessage[]` | 消息列表(结构见下方) |
174
+ | `get_updates_buf` | `string` | 新的同步游标,下次请求时回传 |
175
+ | `longpolling_timeout_ms` | `number?` | 服务端建议的下次长轮询超时(ms) |
176
+
177
+ ### sendMessage
178
+
179
+ 发送一条消息给用户。
180
+
181
+ **请求体:**
182
+
183
+ ```json
184
+ {
185
+ "msg": {
186
+ "to_user_id": "<目标用户 ID>",
187
+ "context_token": "<会话上下文令牌>",
188
+ "item_list": [
189
+ {
190
+ "type": 1,
191
+ "text_item": { "text": "你好" }
192
+ }
193
+ ]
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### getUploadUrl
199
+
200
+ 获取 CDN 上传预签名参数。上传文件前需先调用此接口获取 `upload_param` 和 `thumb_upload_param`。
201
+
202
+ **请求体:**
203
+
204
+ ```json
205
+ {
206
+ "filekey": "<文件标识>",
207
+ "media_type": 1,
208
+ "to_user_id": "<目标用户 ID>",
209
+ "rawsize": 12345,
210
+ "rawfilemd5": "<明文 MD5>",
211
+ "filesize": 12352,
212
+ "thumb_rawsize": 1024,
213
+ "thumb_rawfilemd5": "<缩略图明文 MD5>",
214
+ "thumb_filesize": 1040
215
+ }
216
+ ```
217
+
218
+ | 字段 | 类型 | 说明 |
219
+ |------|------|------|
220
+ | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE |
221
+ | `rawsize` | `number` | 原文件明文大小 |
222
+ | `rawfilemd5` | `string` | 原文件明文 MD5 |
223
+ | `filesize` | `number` | AES-128-ECB 加密后的密文大小 |
224
+ | `thumb_rawsize` | `number?` | 缩略图明文大小(IMAGE/VIDEO 时必填) |
225
+ | `thumb_rawfilemd5` | `string?` | 缩略图明文 MD5(IMAGE/VIDEO 时必填) |
226
+ | `thumb_filesize` | `number?` | 缩略图密文大小(IMAGE/VIDEO 时必填) |
227
+
228
+ **响应体:**
229
+
230
+ ```json
231
+ {
232
+ "upload_param": "<原图上传加密参数>",
233
+ "thumb_upload_param": "<缩略图上传加密参数>"
234
+ }
235
+ ```
236
+
237
+ ### getConfig
238
+
239
+ 获取账号配置,包括 typing ticket。
240
+
241
+ **请求体:**
242
+
243
+ ```json
244
+ {
245
+ "ilink_user_id": "<用户 ID>",
246
+ "context_token": "<可选,会话上下文令牌>"
247
+ }
248
+ ```
249
+
250
+ **响应体:**
251
+
252
+ ```json
253
+ {
254
+ "ret": 0,
255
+ "typing_ticket": "<base64 编码的 typing ticket>"
256
+ }
257
+ ```
258
+
259
+ ### sendTyping
260
+
261
+ 发送或取消输入状态指示。
262
+
263
+ **请求体:**
264
+
265
+ ```json
266
+ {
267
+ "ilink_user_id": "<用户 ID>",
268
+ "typing_ticket": "<从 getConfig 获取>",
269
+ "status": 1
270
+ }
271
+ ```
272
+
273
+ | 字段 | 类型 | 说明 |
274
+ |------|------|------|
275
+ | `status` | `number` | `1` = 正在输入,`2` = 取消输入 |
276
+
277
+ ### 消息结构
278
+
279
+ #### WeixinMessage
280
+
281
+ | 字段 | 类型 | 说明 |
282
+ |------|------|------|
283
+ | `seq` | `number?` | 消息序列号 |
284
+ | `message_id` | `number?` | 消息唯一 ID |
285
+ | `from_user_id` | `string?` | 发送者 ID |
286
+ | `to_user_id` | `string?` | 接收者 ID |
287
+ | `create_time_ms` | `number?` | 创建时间戳(ms) |
288
+ | `session_id` | `string?` | 会话 ID |
289
+ | `message_type` | `number?` | `1` = USER, `2` = BOT |
290
+ | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH |
291
+ | `item_list` | `MessageItem[]?` | 消息内容列表 |
292
+ | `context_token` | `string?` | 会话上下文令牌,回复时需回传 |
293
+
294
+ #### MessageItem
295
+
296
+ | 字段 | 类型 | 说明 |
297
+ |------|------|------|
298
+ | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO |
299
+ | `text_item` | `{ text: string }?` | 文本内容 |
300
+ | `image_item` | `ImageItem?` | 图片(含 CDN 引用和 AES 密钥) |
301
+ | `voice_item` | `VoiceItem?` | 语音(SILK 编码) |
302
+ | `file_item` | `FileItem?` | 文件附件 |
303
+ | `video_item` | `VideoItem?` | 视频 |
304
+ | `ref_msg` | `RefMessage?` | 引用消息 |
305
+
306
+ #### CDN 媒体引用 (CDNMedia)
307
+
308
+ 所有媒体类型(图片/语音/文件/视频)通过 CDN 传输,使用 AES-128-ECB 加密:
309
+
310
+ | 字段 | 类型 | 说明 |
311
+ |------|------|------|
312
+ | `encrypt_query_param` | `string?` | CDN 下载/上传的加密参数 |
313
+ | `aes_key` | `string?` | base64 编码的 AES-128 密钥 |
314
+
315
+ ### CDN 上传流程
316
+
317
+ 1. 计算文件明文大小、MD5,以及 AES-128-ECB 加密后的密文大小
318
+ 2. 如需缩略图(图片/视频),同样计算缩略图的明文和密文参数
319
+ 3. 调用 `getUploadUrl` 获取 `upload_param`(和 `thumb_upload_param`)
320
+ 4. 使用 AES-128-ECB 加密文件内容,PUT 上传到 CDN URL
321
+ 5. 缩略图同理加密并上传
322
+ 6. 使用返回的 `encrypt_query_param` 构造 `CDNMedia` 引用,放入 `MessageItem` 发送
323
+
324
+ > 完整的类型定义见 [`src/api/types.ts`](src/api/types.ts),API 调用实现见 [`src/api/api.ts`](src/api/api.ts)。
325
+
326
+ ## 卸载
327
+
328
+ ```bash
329
+ openclaw plugins uninstall @netcat-ai/openclaw-weixin
330
+ ```
331
+
332
+ ## 故障排查
333
+
334
+ ### "requires OpenClaw >=2026.3.22" 报错
335
+
336
+ 你的 OpenClaw 版本太旧,不兼容当前插件版本。检查版本:
337
+
338
+ ```bash
339
+ openclaw --version
340
+ ```
341
+
342
+ 请先升级 OpenClaw,再安装此插件。
343
+
344
+ ### Channel 显示 "OK" 但未连接
345
+
346
+ 确保 `~/.openclaw/openclaw.json` 中 `plugins.entries.openclaw-weixin.enabled` 为 `true`:
347
+
348
+ ```bash
349
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
350
+ openclaw gateway restart
351
+ ```