@zhin.js/adapter-wechat-mp 0.1.11 → 0.1.13

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 (3) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +65 -433
  3. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @zhin.js/adapter-wechat-mp
2
2
 
3
+ ## 0.1.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b27e633]
8
+ - @zhin.js/http@1.0.18
9
+ - zhin.js@1.0.27
10
+
11
+ ## 0.1.12
12
+
13
+ ### Patch Changes
14
+
15
+ - 106d357: fix: ai
16
+ - Updated dependencies [106d357]
17
+ - @zhin.js/http@1.0.17
18
+ - zhin.js@1.0.26
19
+
3
20
  ## 0.1.11
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -1,17 +1,14 @@
1
- # WeChat Official Account 适配器
1
+ # @zhin.js/adapter-wechat-mp
2
2
 
3
- 基于微信公众号开发者API的 Zhin 机器人适配器,支持接收和发送微信公众号消息。
3
+ Zhin.js 微信公众号适配器,支持微信公众号的消息收发。
4
4
 
5
5
  ## 功能特性
6
6
 
7
- - 🔌 **完整协议支持**: 支持微信公众号开发者模式
8
- - 📨 **消息处理**: 支持文本、图片、语音、视频、地理位置等消息类型
9
- - 🎯 **事件处理**: 支持关注、取关、菜单点击等事件
10
- - 🔐 **安全验证**: 完整的签名验证和加密支持
11
- - 🔄 **Token管理**: 自动获取和刷新access_token
12
- - 💬 **双向通信**: 支持被动回复和主动推送
13
- - 🎛️ **多媒体支持**: 支持图片、语音、视频等多媒体消息
14
- - 🌐 **集成化**: 集成 zhin-next HTTP 服务,无需独立服务器
7
+ - Webhook 事件接收(HTTP 回调)
8
+ - 签名验证
9
+ - Access Token 自动刷新
10
+ - XML 消息解析
11
+ - 可选消息加密(AES)
15
12
 
16
13
  ## 安装
17
14
 
@@ -19,460 +16,95 @@
19
16
  pnpm add @zhin.js/adapter-wechat-mp
20
17
  ```
21
18
 
22
- ## 依赖库
19
+ ## 依赖
23
20
 
24
- 本适配器使用以下库:
25
- - `xml2js` - XML解析和生成
26
- - `axios` - HTTP请求
27
- - `crypto` - 签名验证 (Node.js 内置)
28
- - `@zhin.js/http` - HTTP 服务和路由 (peer dependency)
29
-
30
- ## 前置准备
31
-
32
- ### 1. 申请微信公众号
33
-
34
- 1. 前往 [微信公众平台](https://mp.weixin.qq.com/) 注册账号
35
- 2. 选择订阅号或服务号(服务号功能更丰富)
36
- 3. 完成认证(可选,但认证后功能更多)
37
-
38
- ### 2. 获取开发者信息
39
-
40
- 1. 登录微信公众平台
41
- 2. 进入「开发」->「基本配置」
42
- 3. 获取以下信息:
43
- - **AppID** (应用ID)
44
- - **AppSecret** (应用密钥)
45
- - 设置 **Token** (自定义,用于验证)
46
- - 设置 **EncodingAESKey** (可选,用于加密)
47
-
48
- ### 3. 配置服务器
49
-
50
- 1. 在「基本配置」中设置服务器地址:
51
- ```
52
- URL: http://your-domain.com/wechat
53
- Token: 你设置的token
54
- ```
55
- 2. 选择消息加解密方式(明文模式或安全模式)
21
+ - `@zhin.js/http` — HTTP 服务(提供 Webhook 路由)
56
22
 
57
23
  ## 配置
58
24
 
59
- ### 基础配置
60
-
61
- ```typescript
62
- import { WeChatMPConfig } from '@zhin.js/adapter-wechat-mp'
63
-
64
- const wechatConfig: WeChatMPConfig = {
65
- context: 'wechat-mp',
66
- name: 'my-wechat-bot',
67
- appId: 'your-app-id',
68
- appSecret: 'your-app-secret',
69
- token: 'your-token',
70
- path: '/wechat'
71
- }
72
- ```
73
-
74
- ### 完整配置
75
-
76
- ```typescript
77
- const wechatConfig: WeChatMPConfig = {
78
- context: 'wechat-mp',
79
- name: 'advanced-wechat-bot',
80
- appId: 'wx1234567890abcdef',
81
- appSecret: 'your-app-secret-key',
82
- token: 'your-verification-token',
83
- encodingAESKey: 'your-encoding-aes-key', // 加密模式需要
84
- encrypt: false, // 是否启用加密模式
85
- path: '/wechat/webhook' // Webhook路径(在 HTTP 服务上)
86
- }
25
+ ```yaml
26
+ # zhin.config.yml
27
+ bots:
28
+ - context: wechat-mp
29
+ name: my-wechat-bot
30
+ appId: ${WECHAT_APP_ID}
31
+ appSecret: ${WECHAT_APP_SECRET}
32
+ token: ${WECHAT_TOKEN}
33
+ path: /wechat/webhook
34
+ # 可选配置
35
+ # encodingAESKey: your-aes-key
36
+ # encrypt: false
37
+
38
+ plugins:
39
+ - adapter-wechat-mp
40
+ - http
87
41
  ```
88
42
 
89
- > **注意**: 这个适配器依赖于 `@zhin.js/http` 插件。需要在配置中同时启用 HTTP 插件。
90
-
91
- ## 使用示例
92
-
93
- ### 基础使用
43
+ ### TypeScript 配置
94
44
 
95
45
  ```typescript
96
- import { createApp } from 'zhin.js'
97
- import WeChatMPAdapter from '@zhin.js/adapter-wechat-mp'
46
+ import { defineConfig } from 'zhin.js'
98
47
 
99
- const app = createApp({
100
- // 必须启用 HTTP 插件
101
- plugins: ['@zhin.js/http'],
102
- adapters: {
103
- 'wechat-mp': {
48
+ export default defineConfig({
49
+ bots: [
50
+ {
104
51
  context: 'wechat-mp',
105
52
  name: 'my-wechat-bot',
106
- appId: 'your-app-id',
107
- appSecret: 'your-app-secret',
108
- token: 'your-token',
109
- path: '/wechat'
53
+ appId: process.env.WECHAT_APP_ID!,
54
+ appSecret: process.env.WECHAT_APP_SECRET!,
55
+ token: process.env.WECHAT_TOKEN!,
56
+ path: '/wechat/webhook',
110
57
  }
111
- }
112
- })
113
-
114
- // 处理文本消息
115
- app.on('message.receive', (message) => {
116
- if (message.$adapter === 'wechat-mp') {
117
- console.log('收到微信消息:', message.$content)
118
-
119
- // 自动回复
120
- message.$reply('感谢您的消息!')
121
- }
58
+ ],
59
+ plugins: ['adapter-wechat-mp', 'http']
122
60
  })
123
-
124
- // 处理关注事件
125
- app.on('message.receive', (message) => {
126
- if (message.$adapter === 'wechat-mp' &&
127
- message.$content.some(seg => seg.type === 'event' && seg.data.event === 'subscribe')) {
128
- message.$reply('欢迎关注我们的公众号!')
129
- }
130
- })
131
-
132
- app.start()
133
61
  ```
134
62
 
135
- ### 发送不同类型的消息
136
-
137
- ```typescript
138
- // 发送文本消息
139
- await app.sendMessage({
140
- context: 'wechat-mp',
141
- bot: 'my-wechat-bot',
142
- id: 'user-openid',
143
- type: 'private',
144
- content: '这是一条文本消息'
145
- })
146
-
147
- // 发送图片消息
148
- await app.sendMessage({
149
- context: 'wechat-mp',
150
- bot: 'my-wechat-bot',
151
- id: 'user-openid',
152
- type: 'private',
153
- content: [
154
- { type: 'image', data: { mediaId: 'uploaded-media-id' } }
155
- ]
156
- })
157
-
158
- // 发送语音消息
159
- await app.sendMessage({
160
- context: 'wechat-mp',
161
- bot: 'my-wechat-bot',
162
- id: 'user-openid',
163
- type: 'private',
164
- content: [
165
- { type: 'voice', data: { mediaId: 'voice-media-id' } }
166
- ]
167
- })
168
- ```
169
-
170
- ### 处理不同消息类型
171
-
172
- ```typescript
173
- app.on('message.receive', (message) => {
174
- if (message.$adapter !== 'wechat-mp') return;
175
-
176
- for (const segment of message.$content) {
177
- switch (segment.type) {
178
- case 'text':
179
- console.log('文本消息:', segment.data.text);
180
- break;
181
-
182
- case 'image':
183
- console.log('图片消息:', segment.data.url, segment.data.mediaId);
184
- break;
185
-
186
- case 'voice':
187
- console.log('语音消息:', segment.data.mediaId, segment.data.recognition);
188
- break;
189
-
190
- case 'video':
191
- console.log('视频消息:', segment.data.mediaId);
192
- break;
193
-
194
- case 'location':
195
- console.log('位置消息:', segment.data.latitude, segment.data.longitude);
196
- break;
197
-
198
- case 'link':
199
- console.log('链接消息:', segment.data.title, segment.data.url);
200
- break;
201
-
202
- case 'event':
203
- console.log('事件:', segment.data.event, segment.data.eventKey);
204
- break;
205
- }
206
- }
207
- })
208
- ```
209
-
210
- ### 使用公众号API
211
-
212
- ```typescript
213
- import { WeChatMPBot } from '@zhin.js/adapter-wechat-mp'
214
-
215
- // 获取bot实例
216
- const bot = app.getContext('wechat-mp')?.['my-wechat-bot'] as WeChatMPBot;
217
-
218
- // 获取用户信息
219
- const userInfo = await bot.getUserInfo('user-openid');
220
- console.log('用户信息:', userInfo);
221
-
222
- // 上传多媒体文件
223
- const mediaId = await bot.uploadMedia('image', imageBuffer);
224
- console.log('媒体ID:', mediaId);
225
- ```
226
-
227
- ## 支持的消息类型
228
-
229
- ### 接收消息
230
-
231
- | 微信消息类型 | MessageSegment 类型 | 说明 |
232
- |------------|-------------------|------|
233
- | text | `text` | 文本消息 |
234
- | image | `image` | 图片消息 |
235
- | voice | `voice` | 语音消息 |
236
- | video | `video` | 视频消息 |
237
- | shortvideo | `video` | 小视频消息 |
238
- | location | `location` | 地理位置消息 |
239
- | link | `link` | 链接消息 |
240
- | event | `event` | 事件消息 |
241
-
242
- ### 发送消息
243
-
244
- | MessageSegment 类型 | 微信API | 说明 |
245
- |-------------------|---------|------|
246
- | `text` | 客服消息 | 文本消息 |
247
- | `image` | 客服消息 | 图片消息(需要mediaId) |
248
- | `voice` | 客服消息 | 语音消息(需要mediaId) |
249
- | `video` | 客服消息 | 视频消息(需要mediaId) |
250
-
251
- ### 事件类型
252
-
253
- | 事件类型 | 说明 |
254
- |---------|------|
255
- | subscribe | 关注事件 |
256
- | unsubscribe | 取关事件 |
257
- | CLICK | 菜单点击事件 |
258
- | VIEW | 菜单链接事件 |
259
- | LOCATION | 地理位置事件 |
260
-
261
- ## 频道类型
262
-
263
- | 类型 | 说明 | channel_id 格式 |
264
- |------|------|----------------|
265
- | `private` | 用户私聊 | 用户OpenID |
266
-
267
- 注意:微信公众号只支持 `private` 类型,因为所有消息都是用户与公众号的私聊。
268
-
269
- ## 开发模式设置
270
-
271
- ### 1. 本地开发
272
-
273
- 使用内网穿透工具(如ngrok):
274
-
275
- ```bash
276
- # 安装ngrok
277
- npm install -g ngrok
278
-
279
- # 启动内网穿透
280
- ngrok http 3000
281
-
282
- # 将生成的https地址设置为微信服务器URL
283
- # 例如: https://abc123.ngrok.io/wechat
284
- ```
63
+ ## 使用示例
285
64
 
286
- ### 2. 服务器部署
65
+ ### 注册命令
287
66
 
288
67
  ```typescript
289
- // 生产环境配置
290
- const wechatConfig = {
291
- context: 'wechat-mp',
292
- name: 'production-bot',
293
- appId: process.env.WECHAT_APP_ID,
294
- appSecret: process.env.WECHAT_APP_SECRET,
295
- token: process.env.WECHAT_TOKEN,
296
- port: process.env.PORT || 80,
297
- path: '/wechat'
298
- }
299
- ```
68
+ import { usePlugin, MessageCommand } from 'zhin.js'
300
69
 
301
- ### 3. 使用Nginx反向代理
70
+ const { addCommand } = usePlugin()
302
71
 
303
- ```nginx
304
- server {
305
- listen 80;
306
- server_name your-domain.com;
307
-
308
- location /wechat {
309
- proxy_pass http://localhost:3000/wechat;
310
- proxy_set_header Host $host;
311
- proxy_set_header X-Real-IP $remote_addr;
312
- }
313
- }
72
+ addCommand(
73
+ new MessageCommand('hello')
74
+ .desc('微信问候')
75
+ .action((message) => `你好,${message.$sender.name}!`)
76
+ )
314
77
  ```
315
78
 
316
- ## 高级功能
317
-
318
- ### 1. 自定义菜单
79
+ ### 消息中间件
319
80
 
320
81
  ```typescript
321
- // 创建菜单需要通过微信API
322
- const menuData = {
323
- "button": [
324
- {
325
- "type": "click",
326
- "name": "功能1",
327
- "key": "MENU_KEY_1"
328
- },
329
- {
330
- "type": "view",
331
- "name": "官网",
332
- "url": "https://your-website.com"
333
- }
334
- ]
335
- }
336
- ```
82
+ import { usePlugin } from 'zhin.js'
337
83
 
338
- ### 2. 模板消息
84
+ const { addMiddleware } = usePlugin()
339
85
 
340
- ```typescript
341
- // 发送模板消息
342
- const templateMessage = {
343
- touser: 'user-openid',
344
- template_id: 'template-id',
345
- data: {
346
- first: { value: '标题' },
347
- keyword1: { value: '内容1' },
348
- keyword2: { value: '内容2' },
349
- remark: { value: '备注' }
86
+ addMiddleware(async (message, next) => {
87
+ if (message.$adapter === 'wechat-mp') {
88
+ console.log('收到微信消息:', message.$content)
350
89
  }
351
- }
352
- ```
353
-
354
- ### 3. 素材管理
355
-
356
- ```typescript
357
- // 上传永久素材
358
- const permanentMedia = await bot.uploadPermanentMedia('image', buffer);
359
-
360
- // 获取素材列表
361
- const mediaList = await bot.getMediaList('image', 0, 20);
362
- ```
363
-
364
- ## 错误处理
365
-
366
- ### 常见错误码
367
-
368
- | 错误码 | 说明 | 解决方案 |
369
- |--------|------|----------|
370
- | 40001 | AppSecret错误 | 检查AppSecret配置 |
371
- | 40002 | 不合法的凭证类型 | 检查access_token |
372
- | 40003 | 不合法的OpenID | 检查用户OpenID |
373
- | 40004 | 不合法的媒体文件类型 | 检查上传文件格式 |
374
- | 40013 | 不合法的AppID | 检查AppID配置 |
375
- | 42001 | access_token超时 | 会自动刷新token |
376
-
377
- ### 调试建议
378
-
379
- 1. **检查签名验证**:
380
- ```javascript
381
- // 验证微信服务器配置时的日志
382
- console.log('Signature verification:', { signature, timestamp, nonce });
383
- ```
384
-
385
- 2. **查看XML消息**:
386
- ```javascript
387
- // 打印接收到的原始XML
388
- console.log('Received XML:', xmlBody);
389
- ```
390
-
391
- 3. **监控Token状态**:
392
- ```javascript
393
- // 定期检查token有效性
394
- console.log('Access Token:', this.accessToken);
395
- console.log('Expires at:', new Date(this.tokenExpireTime));
396
- ```
397
-
398
- ## 安全注意事项
399
-
400
- 1. **保护敏感信息**: 不要在代码中硬编码AppSecret
401
- 2. **使用HTTPS**: 生产环境建议使用HTTPS
402
- 3. **签名验证**: 始终验证微信请求的签名
403
- 4. **加密通信**: 敏感场景可启用消息加密
404
- 5. **频率限制**: 注意微信API的调用频率限制
405
-
406
- ## API限制
407
-
408
- 1. **消息发送**: 每天主动推送消息有限制
409
- 2. **API调用**: 大部分API每分钟调用次数有限制
410
- 3. **媒体上传**: 临时素材3天后失效
411
- 4. **用户信息**: 只能获取关注用户的信息
412
-
413
- ## 故障排除
414
-
415
- ### 1. 服务器验证失败
416
- - 检查Token配置是否正确
417
- - 确认URL可以正常访问
418
- - 检查签名算法实现
419
-
420
- ### 2. 消息接收异常
421
- - 检查HTTP服务器是否正常启动
422
- - 验证防火墙和端口配置
423
- - 查看微信开发者工具的错误日志
424
-
425
- ### 3. 消息发送失败
426
- - 检查access_token是否有效
427
- - 确认用户已关注公众号
428
- - 检查消息格式是否正确
429
-
430
- ### 4. 多媒体消息问题
431
- - 确认文件格式和大小符合要求
432
- - 检查mediaId是否有效
433
- - 验证文件上传是否成功
434
-
435
- ## 示例项目
436
-
437
- 完整的使用示例可以在 `example/` 目录中找到。
438
-
439
- ## API 参考
440
-
441
- ### WeChatMPBot 类
442
-
443
- #### 配置选项
444
-
445
- ```typescript
446
- interface WeChatMPConfig {
447
- context: 'wechat-mp'
448
- name: string
449
- appId: string
450
- appSecret: string
451
- token: string
452
- encodingAESKey?: string
453
- port?: number
454
- path?: string
455
- encrypt?: boolean
456
- }
90
+ await next()
91
+ })
457
92
  ```
458
93
 
459
- #### 主要方法
94
+ ## 微信公众号配置
460
95
 
461
- - `$connect()`: 启动HTTP服务器和Token管理
462
- - `$disconnect()`: 关闭服务器连接
463
- - `$formatMessage(wechatMsg)`: 格式化微信消息
464
- - `$sendMessage(options)`: 发送消息
465
- - `getUserInfo(openid)`: 获取用户信息
466
- - `uploadMedia(type, buffer)`: 上传多媒体文件
96
+ 1. 登录 [微信公众平台](https://mp.weixin.qq.com/)
97
+ 2. 在「开发 → 基本配置」中获取 `AppID` 和 `AppSecret`
98
+ 3. 配置服务器地址(URL):`http://your-server:8086/api/wechat/webhook`
99
+ 4. 设置 Token(与配置文件中的 `token` 一致)
100
+ 5. 如需消息加解密,设置 EncodingAESKey
467
101
 
468
- #### 事件
102
+ ## 注意事项
469
103
 
470
- - `message.receive`: 接收到微信消息时触发
104
+ - 微信公众号要求服务器必须在 5 秒内响应
105
+ - 服务器地址必须是公网可访问的 HTTP/HTTPS URL
106
+ - 建议使用已备案的域名
471
107
 
472
- ## 更新日志
108
+ ## 许可证
473
109
 
474
- ### v0.1.0
475
- - 支持基础消息收发
476
- - 实现签名验证和Token管理
477
- - 支持多种消息类型和事件
478
- - 提供完整的API封装
110
+ MIT License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-wechat-mp",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Zhin.js adapter for WeChat Official Account (微信公众号)",
6
6
  "main": "lib/index.js",
@@ -12,14 +12,14 @@
12
12
  "form-data": "^4.0.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "@zhin.js/http": "1.0.16",
16
- "zhin.js": "1.0.25"
15
+ "@zhin.js/http": "1.0.18",
16
+ "zhin.js": "1.0.27"
17
17
  },
18
18
  "devDependencies": {
19
19
  "typescript": "^5.3.3",
20
20
  "@types/node": "^20.10.6",
21
21
  "@types/koa": "^2.15.0",
22
- "zhin.js": "1.0.25"
22
+ "zhin.js": "1.0.27"
23
23
  },
24
24
  "keywords": [
25
25
  "zhin",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "scripts": {
57
57
  "build": "pnpm build:node",
58
- "clean": "rm -rf lib",
58
+ "clean": "rimraf lib",
59
59
  "build:node": "tsc"
60
60
  }
61
61
  }