@tencent-weixin/openclaw-weixin 1.0.0

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 (41) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/CHANGELOG.zh_CN.md +3 -0
  3. package/LICENSE +21 -0
  4. package/README.md +271 -0
  5. package/README.zh_CN.md +269 -0
  6. package/index.ts +27 -0
  7. package/openclaw.plugin.json +9 -0
  8. package/package.json +55 -0
  9. package/src/api/api.ts +240 -0
  10. package/src/api/config-cache.ts +79 -0
  11. package/src/api/session-guard.ts +58 -0
  12. package/src/api/types.ts +222 -0
  13. package/src/auth/accounts.ts +321 -0
  14. package/src/auth/login-qr.ts +331 -0
  15. package/src/auth/pairing.ts +120 -0
  16. package/src/cdn/aes-ecb.ts +21 -0
  17. package/src/cdn/cdn-upload.ts +77 -0
  18. package/src/cdn/cdn-url.ts +17 -0
  19. package/src/cdn/pic-decrypt.ts +85 -0
  20. package/src/cdn/upload.ts +155 -0
  21. package/src/channel.ts +380 -0
  22. package/src/config/config-schema.ts +22 -0
  23. package/src/log-upload.ts +126 -0
  24. package/src/media/media-download.ts +141 -0
  25. package/src/media/mime.ts +76 -0
  26. package/src/media/silk-transcode.ts +74 -0
  27. package/src/messaging/debug-mode.ts +69 -0
  28. package/src/messaging/error-notice.ts +31 -0
  29. package/src/messaging/inbound.ts +171 -0
  30. package/src/messaging/process-message.ts +381 -0
  31. package/src/messaging/send-media.ts +72 -0
  32. package/src/messaging/send.ts +267 -0
  33. package/src/messaging/slash-commands.ts +110 -0
  34. package/src/monitor/monitor.ts +221 -0
  35. package/src/runtime.ts +70 -0
  36. package/src/storage/state-dir.ts +11 -0
  37. package/src/storage/sync-buf.ts +81 -0
  38. package/src/util/logger.ts +143 -0
  39. package/src/util/random.ts +17 -0
  40. package/src/util/redact.ts +46 -0
  41. package/src/vendor.d.ts +25 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ [简体中文](CHANGELOG.zh_CN.md)
4
+
5
+ This project follows the [Keep a Changelog](https://keepachangelog.com/) format.
@@ -0,0 +1,3 @@
1
+ # 变更日志
2
+
3
+ 本项目遵循 [Keep a Changelog](https://keepachangelog.com/) 格式。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tencent Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # WeChat
2
+
3
+ [简体中文](README.zh_CN.md)
4
+
5
+ OpenClaw's WeChat channel plugin, supporting login authorization via QR code scanning.
6
+
7
+ ## Prerequisites
8
+
9
+ [OpenClaw](https://docs.openclaw.ai/install) must be installed (the `openclaw` CLI needs to be available).
10
+
11
+ ## Quick Install
12
+
13
+ ```bash
14
+ npx -y @tencent-weixin/openclaw-weixin-cli install
15
+ ```
16
+
17
+ ## Manual Installation
18
+
19
+ If the quick install doesn't work, follow these steps manually:
20
+
21
+ ### 1. Install the plugin
22
+
23
+ ```bash
24
+ openclaw plugins install "@tencent-weixin/openclaw-weixin"
25
+ ```
26
+
27
+ ### 2. Enable the plugin
28
+
29
+ ```bash
30
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
31
+ ```
32
+
33
+ ### 3. QR code login
34
+
35
+ ```bash
36
+ openclaw channels login --channel openclaw-weixin
37
+ ```
38
+
39
+ A QR code will appear in the terminal. Scan it with your phone and confirm the authorization. Once confirmed, the login credentials will be saved locally automatically — no further action is needed.
40
+
41
+ ### 4. Restart the gateway
42
+
43
+ ```bash
44
+ openclaw gateway restart
45
+ ```
46
+
47
+ ## Adding More WeChat Accounts
48
+
49
+ ```bash
50
+ openclaw channels login --channel openclaw-weixin
51
+ ```
52
+
53
+ Each QR code login creates a new account entry, supporting multiple WeChat accounts online simultaneously.
54
+
55
+ ## Multi-Account Context Isolation
56
+
57
+ By default, all channels share the same AI conversation context. To isolate conversation context for each WeChat account:
58
+
59
+ ```bash
60
+ openclaw config set agents.mode per-channel-per-peer
61
+ ```
62
+
63
+ This gives each "WeChat account + message sender" combination its own independent AI memory, preventing context cross-talk between accounts.
64
+
65
+ ## Backend API Protocol
66
+
67
+ This plugin communicates with the backend gateway via HTTP JSON API. Developers integrating with their own backend need to implement the following interfaces.
68
+
69
+ All endpoints use `POST` with JSON request and response bodies. Common request headers:
70
+
71
+ | Header | Description |
72
+ |--------|-------------|
73
+ | `Content-Type` | `application/json` |
74
+ | `AuthorizationType` | Fixed value `ilink_bot_token` |
75
+ | `Authorization` | `Bearer <token>` (obtained after login) |
76
+ | `X-WECHAT-UIN` | Base64-encoded random uint32 |
77
+
78
+ ### Endpoint List
79
+
80
+ | Endpoint | Path | Description |
81
+ |----------|------|-------------|
82
+ | getUpdates | `getupdates` | Long-poll for new messages |
83
+ | sendMessage | `sendmessage` | Send a message (text/image/video/file) |
84
+ | getUploadUrl | `getuploadurl` | Get CDN upload pre-signed URL |
85
+ | getConfig | `getconfig` | Get account config (typing ticket, etc.) |
86
+ | sendTyping | `sendtyping` | Send/cancel typing status indicator |
87
+
88
+ ### getUpdates
89
+
90
+ Long-polling endpoint. The server responds when new messages arrive or on timeout.
91
+
92
+ **Request body:**
93
+
94
+ ```json
95
+ {
96
+ "get_updates_buf": ""
97
+ }
98
+ ```
99
+
100
+ | Field | Type | Description |
101
+ |-------|------|-------------|
102
+ | `get_updates_buf` | `string` | Sync cursor from the previous response; empty string for the first request |
103
+
104
+ **Response body:**
105
+
106
+ ```json
107
+ {
108
+ "ret": 0,
109
+ "msgs": [...],
110
+ "get_updates_buf": "<new cursor>",
111
+ "longpolling_timeout_ms": 35000
112
+ }
113
+ ```
114
+
115
+ | Field | Type | Description |
116
+ |-------|------|-------------|
117
+ | `ret` | `number` | Return code, `0` = success |
118
+ | `errcode` | `number?` | Error code (e.g., `-14` = session timeout) |
119
+ | `errmsg` | `string?` | Error description |
120
+ | `msgs` | `WeixinMessage[]` | Message list (structure below) |
121
+ | `get_updates_buf` | `string` | New sync cursor to pass in the next request |
122
+ | `longpolling_timeout_ms` | `number?` | Server-suggested long-poll timeout for the next request (ms) |
123
+
124
+ ### sendMessage
125
+
126
+ Send a message to a user.
127
+
128
+ **Request body:**
129
+
130
+ ```json
131
+ {
132
+ "msg": {
133
+ "to_user_id": "<target user ID>",
134
+ "context_token": "<conversation context token>",
135
+ "item_list": [
136
+ {
137
+ "type": 1,
138
+ "text_item": { "text": "Hello" }
139
+ }
140
+ ]
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### getUploadUrl
146
+
147
+ Get CDN upload pre-signed parameters. Call this endpoint before uploading a file to obtain `upload_param` and `thumb_upload_param`.
148
+
149
+ **Request body:**
150
+
151
+ ```json
152
+ {
153
+ "filekey": "<file identifier>",
154
+ "media_type": 1,
155
+ "to_user_id": "<target user ID>",
156
+ "rawsize": 12345,
157
+ "rawfilemd5": "<plaintext MD5>",
158
+ "filesize": 12352,
159
+ "thumb_rawsize": 1024,
160
+ "thumb_rawfilemd5": "<thumbnail plaintext MD5>",
161
+ "thumb_filesize": 1040
162
+ }
163
+ ```
164
+
165
+ | Field | Type | Description |
166
+ |-------|------|-------------|
167
+ | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE |
168
+ | `rawsize` | `number` | Original file plaintext size |
169
+ | `rawfilemd5` | `string` | Original file plaintext MD5 |
170
+ | `filesize` | `number` | Ciphertext size after AES-128-ECB encryption |
171
+ | `thumb_rawsize` | `number?` | Thumbnail plaintext size (required for IMAGE/VIDEO) |
172
+ | `thumb_rawfilemd5` | `string?` | Thumbnail plaintext MD5 (required for IMAGE/VIDEO) |
173
+ | `thumb_filesize` | `number?` | Thumbnail ciphertext size (required for IMAGE/VIDEO) |
174
+
175
+ **Response body:**
176
+
177
+ ```json
178
+ {
179
+ "upload_param": "<original image upload encrypted parameters>",
180
+ "thumb_upload_param": "<thumbnail upload encrypted parameters>"
181
+ }
182
+ ```
183
+
184
+ ### getConfig
185
+
186
+ Get account configuration, including the typing ticket.
187
+
188
+ **Request body:**
189
+
190
+ ```json
191
+ {
192
+ "ilink_user_id": "<user ID>",
193
+ "context_token": "<optional, conversation context token>"
194
+ }
195
+ ```
196
+
197
+ **Response body:**
198
+
199
+ ```json
200
+ {
201
+ "ret": 0,
202
+ "typing_ticket": "<base64-encoded typing ticket>"
203
+ }
204
+ ```
205
+
206
+ ### sendTyping
207
+
208
+ Send or cancel the typing status indicator.
209
+
210
+ **Request body:**
211
+
212
+ ```json
213
+ {
214
+ "ilink_user_id": "<user ID>",
215
+ "typing_ticket": "<obtained from getConfig>",
216
+ "status": 1
217
+ }
218
+ ```
219
+
220
+ | Field | Type | Description |
221
+ |-------|------|-------------|
222
+ | `status` | `number` | `1` = typing, `2` = cancel typing |
223
+
224
+ ### Message Structure
225
+
226
+ #### WeixinMessage
227
+
228
+ | Field | Type | Description |
229
+ |-------|------|-------------|
230
+ | `seq` | `number?` | Message sequence number |
231
+ | `message_id` | `number?` | Unique message ID |
232
+ | `from_user_id` | `string?` | Sender ID |
233
+ | `to_user_id` | `string?` | Receiver ID |
234
+ | `create_time_ms` | `number?` | Creation timestamp (ms) |
235
+ | `session_id` | `string?` | Session ID |
236
+ | `message_type` | `number?` | `1` = USER, `2` = BOT |
237
+ | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH |
238
+ | `item_list` | `MessageItem[]?` | Message content list |
239
+ | `context_token` | `string?` | Conversation context token, must be passed back when replying |
240
+
241
+ #### MessageItem
242
+
243
+ | Field | Type | Description |
244
+ |-------|------|-------------|
245
+ | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO |
246
+ | `text_item` | `{ text: string }?` | Text content |
247
+ | `image_item` | `ImageItem?` | Image (with CDN reference and AES key) |
248
+ | `voice_item` | `VoiceItem?` | Voice (SILK encoded) |
249
+ | `file_item` | `FileItem?` | File attachment |
250
+ | `video_item` | `VideoItem?` | Video |
251
+ | `ref_msg` | `RefMessage?` | Referenced message |
252
+
253
+ #### CDN Media Reference (CDNMedia)
254
+
255
+ All media types (image/voice/file/video) are transferred via CDN using AES-128-ECB encryption:
256
+
257
+ | Field | Type | Description |
258
+ |-------|------|-------------|
259
+ | `encrypt_query_param` | `string?` | Encrypted parameters for CDN download/upload |
260
+ | `aes_key` | `string?` | Base64-encoded AES-128 key |
261
+
262
+ ### CDN Upload Flow
263
+
264
+ 1. Calculate the file's plaintext size, MD5, and ciphertext size after AES-128-ECB encryption
265
+ 2. If a thumbnail is needed (image/video), calculate the thumbnail's plaintext and ciphertext parameters as well
266
+ 3. Call `getUploadUrl` to get `upload_param` (and `thumb_upload_param`)
267
+ 4. Encrypt the file content with AES-128-ECB and PUT upload to the CDN URL
268
+ 5. Encrypt and upload the thumbnail in the same way
269
+ 6. Use the returned `encrypt_query_param` to construct a `CDNMedia` reference, include it in the `MessageItem`, and send
270
+
271
+ > 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).
@@ -0,0 +1,269 @@
1
+ # 微信
2
+
3
+ OpenClaw 的微信渠道插件,支持通过扫码完成登录授权。
4
+
5
+ ## 前提条件
6
+
7
+ 已安装 [OpenClaw](https://docs.openclaw.ai/install)(需要 `openclaw` CLI 可用)。
8
+
9
+ ## 一键安装
10
+
11
+ ```bash
12
+ npx -y @tencent-weixin/openclaw-weixin-cli install
13
+ ```
14
+
15
+ ## 手动安装
16
+
17
+ 如果一键安装不适用,可以按以下步骤手动操作:
18
+
19
+ ### 1. 安装插件
20
+
21
+ ```bash
22
+ openclaw plugins install "@tencent-weixin/openclaw-weixin"
23
+ ```
24
+
25
+ ### 2. 启用插件
26
+
27
+ ```bash
28
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
29
+ ```
30
+
31
+ ### 3. 扫码登录
32
+
33
+ ```bash
34
+ openclaw channels login --channel openclaw-weixin
35
+ ```
36
+
37
+ 终端会显示一个二维码,用手机扫码并在手机上确认授权。确认后,登录凭证会自动保存到本地,无需额外操作。
38
+
39
+ ### 4. 重启 gateway
40
+
41
+ ```bash
42
+ openclaw gateway restart
43
+ ```
44
+
45
+ ## 添加更多微信账号
46
+
47
+ ```bash
48
+ openclaw channels login --channel openclaw-weixin
49
+ ```
50
+
51
+ 每次扫码登录都会创建一个新的账号条目,支持多个微信号同时在线。
52
+
53
+ ## 多账号上下文隔离
54
+
55
+ 默认情况下,所有渠道的 AI 会话共享同一个上下文。如果希望每个微信账号的对话上下文相互隔离:
56
+
57
+ ```bash
58
+ openclaw config set agents.mode per-channel-per-peer
59
+ ```
60
+
61
+ 这样每个「微信账号 + 发消息用户」组合都会拥有独立的 AI 记忆,账号之间不会串台。
62
+
63
+ ## 后端 API 协议
64
+
65
+ 本插件通过 HTTP JSON API 与后端网关通信。二次开发者若需对接自有后端,需实现以下接口。
66
+
67
+ 所有接口均为 `POST`,请求和响应均为 JSON。通用请求头:
68
+
69
+ | Header | 说明 |
70
+ |--------|------|
71
+ | `Content-Type` | `application/json` |
72
+ | `AuthorizationType` | 固定值 `ilink_bot_token` |
73
+ | `Authorization` | `Bearer <token>`(登录后获取) |
74
+ | `X-WECHAT-UIN` | 随机 uint32 的 base64 编码 |
75
+
76
+ ### 接口列表
77
+
78
+ | 接口 | 路径 | 说明 |
79
+ |------|------|------|
80
+ | getUpdates | `getupdates` | 长轮询获取新消息 |
81
+ | sendMessage | `sendmessage` | 发送消息(文本/图片/视频/文件) |
82
+ | getUploadUrl | `getuploadurl` | 获取 CDN 上传预签名 URL |
83
+ | getConfig | `getconfig` | 获取账号配置(typing ticket 等) |
84
+ | sendTyping | `sendtyping` | 发送/取消输入状态指示 |
85
+
86
+ ### getUpdates
87
+
88
+ 长轮询接口。服务端在有新消息或超时后返回。
89
+
90
+ **请求体:**
91
+
92
+ ```json
93
+ {
94
+ "get_updates_buf": ""
95
+ }
96
+ ```
97
+
98
+ | 字段 | 类型 | 说明 |
99
+ |------|------|------|
100
+ | `get_updates_buf` | `string` | 上次响应返回的同步游标,首次请求传空字符串 |
101
+
102
+ **响应体:**
103
+
104
+ ```json
105
+ {
106
+ "ret": 0,
107
+ "msgs": [...],
108
+ "get_updates_buf": "<新游标>",
109
+ "longpolling_timeout_ms": 35000
110
+ }
111
+ ```
112
+
113
+ | 字段 | 类型 | 说明 |
114
+ |------|------|------|
115
+ | `ret` | `number` | 返回码,`0` = 成功 |
116
+ | `errcode` | `number?` | 错误码(如 `-14` = 会话超时) |
117
+ | `errmsg` | `string?` | 错误描述 |
118
+ | `msgs` | `WeixinMessage[]` | 消息列表(结构见下方) |
119
+ | `get_updates_buf` | `string` | 新的同步游标,下次请求时回传 |
120
+ | `longpolling_timeout_ms` | `number?` | 服务端建议的下次长轮询超时(ms) |
121
+
122
+ ### sendMessage
123
+
124
+ 发送一条消息给用户。
125
+
126
+ **请求体:**
127
+
128
+ ```json
129
+ {
130
+ "msg": {
131
+ "to_user_id": "<目标用户 ID>",
132
+ "context_token": "<会话上下文令牌>",
133
+ "item_list": [
134
+ {
135
+ "type": 1,
136
+ "text_item": { "text": "你好" }
137
+ }
138
+ ]
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### getUploadUrl
144
+
145
+ 获取 CDN 上传预签名参数。上传文件前需先调用此接口获取 `upload_param` 和 `thumb_upload_param`。
146
+
147
+ **请求体:**
148
+
149
+ ```json
150
+ {
151
+ "filekey": "<文件标识>",
152
+ "media_type": 1,
153
+ "to_user_id": "<目标用户 ID>",
154
+ "rawsize": 12345,
155
+ "rawfilemd5": "<明文 MD5>",
156
+ "filesize": 12352,
157
+ "thumb_rawsize": 1024,
158
+ "thumb_rawfilemd5": "<缩略图明文 MD5>",
159
+ "thumb_filesize": 1040
160
+ }
161
+ ```
162
+
163
+ | 字段 | 类型 | 说明 |
164
+ |------|------|------|
165
+ | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE |
166
+ | `rawsize` | `number` | 原文件明文大小 |
167
+ | `rawfilemd5` | `string` | 原文件明文 MD5 |
168
+ | `filesize` | `number` | AES-128-ECB 加密后的密文大小 |
169
+ | `thumb_rawsize` | `number?` | 缩略图明文大小(IMAGE/VIDEO 时必填) |
170
+ | `thumb_rawfilemd5` | `string?` | 缩略图明文 MD5(IMAGE/VIDEO 时必填) |
171
+ | `thumb_filesize` | `number?` | 缩略图密文大小(IMAGE/VIDEO 时必填) |
172
+
173
+ **响应体:**
174
+
175
+ ```json
176
+ {
177
+ "upload_param": "<原图上传加密参数>",
178
+ "thumb_upload_param": "<缩略图上传加密参数>"
179
+ }
180
+ ```
181
+
182
+ ### getConfig
183
+
184
+ 获取账号配置,包括 typing ticket。
185
+
186
+ **请求体:**
187
+
188
+ ```json
189
+ {
190
+ "ilink_user_id": "<用户 ID>",
191
+ "context_token": "<可选,会话上下文令牌>"
192
+ }
193
+ ```
194
+
195
+ **响应体:**
196
+
197
+ ```json
198
+ {
199
+ "ret": 0,
200
+ "typing_ticket": "<base64 编码的 typing ticket>"
201
+ }
202
+ ```
203
+
204
+ ### sendTyping
205
+
206
+ 发送或取消输入状态指示。
207
+
208
+ **请求体:**
209
+
210
+ ```json
211
+ {
212
+ "ilink_user_id": "<用户 ID>",
213
+ "typing_ticket": "<从 getConfig 获取>",
214
+ "status": 1
215
+ }
216
+ ```
217
+
218
+ | 字段 | 类型 | 说明 |
219
+ |------|------|------|
220
+ | `status` | `number` | `1` = 正在输入,`2` = 取消输入 |
221
+
222
+ ### 消息结构
223
+
224
+ #### WeixinMessage
225
+
226
+ | 字段 | 类型 | 说明 |
227
+ |------|------|------|
228
+ | `seq` | `number?` | 消息序列号 |
229
+ | `message_id` | `number?` | 消息唯一 ID |
230
+ | `from_user_id` | `string?` | 发送者 ID |
231
+ | `to_user_id` | `string?` | 接收者 ID |
232
+ | `create_time_ms` | `number?` | 创建时间戳(ms) |
233
+ | `session_id` | `string?` | 会话 ID |
234
+ | `message_type` | `number?` | `1` = USER, `2` = BOT |
235
+ | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH |
236
+ | `item_list` | `MessageItem[]?` | 消息内容列表 |
237
+ | `context_token` | `string?` | 会话上下文令牌,回复时需回传 |
238
+
239
+ #### MessageItem
240
+
241
+ | 字段 | 类型 | 说明 |
242
+ |------|------|------|
243
+ | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO |
244
+ | `text_item` | `{ text: string }?` | 文本内容 |
245
+ | `image_item` | `ImageItem?` | 图片(含 CDN 引用和 AES 密钥) |
246
+ | `voice_item` | `VoiceItem?` | 语音(SILK 编码) |
247
+ | `file_item` | `FileItem?` | 文件附件 |
248
+ | `video_item` | `VideoItem?` | 视频 |
249
+ | `ref_msg` | `RefMessage?` | 引用消息 |
250
+
251
+ #### CDN 媒体引用 (CDNMedia)
252
+
253
+ 所有媒体类型(图片/语音/文件/视频)通过 CDN 传输,使用 AES-128-ECB 加密:
254
+
255
+ | 字段 | 类型 | 说明 |
256
+ |------|------|------|
257
+ | `encrypt_query_param` | `string?` | CDN 下载/上传的加密参数 |
258
+ | `aes_key` | `string?` | base64 编码的 AES-128 密钥 |
259
+
260
+ ### CDN 上传流程
261
+
262
+ 1. 计算文件明文大小、MD5,以及 AES-128-ECB 加密后的密文大小
263
+ 2. 如需缩略图(图片/视频),同样计算缩略图的明文和密文参数
264
+ 3. 调用 `getUploadUrl` 获取 `upload_param`(和 `thumb_upload_param`)
265
+ 4. 使用 AES-128-ECB 加密文件内容,PUT 上传到 CDN URL
266
+ 5. 缩略图同理加密并上传
267
+ 6. 使用返回的 `encrypt_query_param` 构造 `CDNMedia` 引用,放入 `MessageItem` 发送
268
+
269
+ > 完整的类型定义见 [`src/api/types.ts`](src/api/types.ts),API 调用实现见 [`src/api/api.ts`](src/api/api.ts)。
package/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { buildChannelConfigSchema } from "openclaw/plugin-sdk";
3
+
4
+ import { weixinPlugin } from "./src/channel.js";
5
+ import { WeixinConfigSchema } from "./src/config/config-schema.js";
6
+ import { registerWeixinCli } from "./src/log-upload.js";
7
+ import { setWeixinRuntime } from "./src/runtime.js";
8
+
9
+ const plugin = {
10
+ id: "openclaw-weixin",
11
+ name: "Weixin",
12
+ description: "Weixin channel (getUpdates long-poll + sendMessage)",
13
+ configSchema: buildChannelConfigSchema(WeixinConfigSchema),
14
+ register(api: OpenClawPluginApi) {
15
+ if (!api?.runtime) {
16
+ throw new Error("[weixin] api.runtime is not available in register()");
17
+ }
18
+ setWeixinRuntime(api.runtime);
19
+
20
+ api.registerChannel({ plugin: weixinPlugin });
21
+ api.registerCli(({ program, config }) => registerWeixinCli({ program, config }), {
22
+ commands: ["openclaw-weixin"],
23
+ });
24
+ },
25
+ };
26
+
27
+ export default plugin;
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "openclaw-weixin",
3
+ "channels": ["openclaw-weixin"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@tencent-weixin/openclaw-weixin",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw Weixin channel",
5
+ "license": "MIT",
6
+ "author": "Tencent",
7
+ "type": "module",
8
+ "files": [
9
+ "src/",
10
+ "!src/**/*.test.ts",
11
+ "!src/**/node_modules/",
12
+ "index.ts",
13
+ "openclaw.plugin.json",
14
+ "README.md",
15
+ "README.zh_CN.md",
16
+ "CHANGELOG.md",
17
+ "CHANGELOG.zh_CN.md"
18
+ ],
19
+ "scripts": {
20
+ "test": "vitest run --coverage",
21
+ "typecheck": "tsc --noEmit",
22
+ "build": "tsc",
23
+ "prepublishOnly": "npm run typecheck && npm run build"
24
+ },
25
+ "engines": {
26
+ "node": ">=22"
27
+ },
28
+ "dependencies": {
29
+ "qrcode-terminal": "0.12.0",
30
+ "zod": "4.3.6"
31
+ },
32
+ "devDependencies": {
33
+ "@vitest/coverage-v8": "^3.1.0",
34
+ "typescript": "^5.8.0",
35
+ "vitest": "^3.1.0"
36
+ },
37
+ "openclaw": {
38
+ "extensions": [
39
+ "./index.ts"
40
+ ],
41
+ "channel": {
42
+ "id": "openclaw-weixin",
43
+ "label": "Weixin",
44
+ "selectionLabel": "Weixin",
45
+ "docsPath": "/channels/openclaw-weixin",
46
+ "docsLabel": "openclaw-weixin",
47
+ "blurb": "Weixin channel",
48
+ "order": 75
49
+ },
50
+ "install": {
51
+ "npmSpec": "@tencent-weixin/openclaw-weixin",
52
+ "defaultChoice": "npm"
53
+ }
54
+ }
55
+ }