@zhin.js/adapter-wechat-mp 1.0.1 → 1.1.2

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 (58) hide show
  1. package/CHANGELOG.md +551 -0
  2. package/README.md +41 -88
  3. package/adapters/wechat-mp/index.js +32 -0
  4. package/adapters/wechat-mp/index.ts +38 -0
  5. package/commands/wechat-mp/endpoint/add/[id]/index.js +3 -0
  6. package/commands/wechat-mp/endpoint/add/[id]/index.ts +3 -0
  7. package/commands/wechat-mp/endpoint/definition.js +20 -0
  8. package/commands/wechat-mp/endpoint/definition.ts +20 -0
  9. package/commands/wechat-mp/endpoint/list/index.js +3 -0
  10. package/commands/wechat-mp/endpoint/list/index.ts +3 -0
  11. package/commands/wechat-mp/endpoint/remove/[id]/index.js +3 -0
  12. package/commands/wechat-mp/endpoint/remove/[id]/index.ts +3 -0
  13. package/lib/client.d.ts +32 -0
  14. package/lib/client.js +70 -0
  15. package/lib/endpoint.d.ts +31 -72
  16. package/lib/endpoint.js +226 -747
  17. package/lib/index.d.ts +6 -15
  18. package/lib/index.js +6 -25
  19. package/lib/media-upload.d.ts +22 -0
  20. package/lib/media-upload.js +64 -0
  21. package/lib/passive-reply.d.ts +0 -1
  22. package/lib/passive-reply.js +0 -1
  23. package/lib/protocol.d.ts +126 -0
  24. package/lib/protocol.js +350 -0
  25. package/lib/side-event-dispatch.d.ts +4 -0
  26. package/lib/side-event-dispatch.js +38 -0
  27. package/lib/webhook.d.ts +20 -0
  28. package/lib/webhook.js +152 -0
  29. package/lib/wechat-mp-runtime-state.d.ts +1 -0
  30. package/lib/wechat-mp-runtime-state.js +6 -0
  31. package/package.json +57 -12
  32. package/plugin.js +14 -0
  33. package/schema.json +144 -0
  34. package/src/client.ts +121 -0
  35. package/src/endpoint.ts +276 -902
  36. package/src/index.ts +56 -35
  37. package/src/media-upload.ts +82 -0
  38. package/src/protocol.ts +508 -0
  39. package/src/side-event-dispatch.ts +45 -0
  40. package/src/webhook.ts +237 -0
  41. package/src/wechat-mp-runtime-state.ts +7 -0
  42. package/lib/adapter.d.ts +0 -14
  43. package/lib/adapter.d.ts.map +0 -1
  44. package/lib/adapter.js +0 -17
  45. package/lib/adapter.js.map +0 -1
  46. package/lib/endpoint.d.ts.map +0 -1
  47. package/lib/endpoint.js.map +0 -1
  48. package/lib/index.d.ts.map +0 -1
  49. package/lib/index.js.map +0 -1
  50. package/lib/passive-reply.d.ts.map +0 -1
  51. package/lib/passive-reply.js.map +0 -1
  52. package/lib/types.d.ts +0 -58
  53. package/lib/types.d.ts.map +0 -1
  54. package/lib/types.js +0 -2
  55. package/lib/types.js.map +0 -1
  56. package/skills/wechat-mp/SKILL.md +0 -33
  57. package/src/adapter.ts +0 -22
  58. package/src/types.ts +0 -60
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @zhin.js/adapter-wechat-mp
2
2
 
3
- Zhin.js 微信公众号适配器,支持微信公众号的消息收发。
3
+ Zhin.js 微信公众号适配器(Plugin Runtime),通过 Runtime Host HTTP Webhook 收发消息。
4
4
 
5
5
  ## 功能特性
6
6
 
7
- - Webhook 事件接收(HTTP 回调)
8
- - 签名验证
7
+ - Webhook 事件接收(`httpHostToken` GET 验签 + POST 消息)
8
+ - 签名验证与可选 AES 加解密
9
9
  - Access Token 自动刷新
10
10
  - XML 消息解析
11
- - 可选消息加密(AES)
11
+ - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
12
 
13
13
  ## 安装
14
14
 
@@ -16,105 +16,54 @@ Zhin.js 微信公众号适配器,支持微信公众号的消息收发。
16
16
  pnpm add @zhin.js/adapter-wechat-mp
17
17
  ```
18
18
 
19
+ ## Plugin Runtime
20
+
21
+ - `@zhin.js/adapter` — 约定式 `adapters/wechat-mp/index.ts`(`defineAdapter`)
22
+ - `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
23
+ - `@zhin.js/host-http` — `httpHostToken` 注册 Webhook 路由(**非** legacy host-router/Koa)
24
+ - `zhin.js` — `plugin.ts`(`definePlugin`)
25
+ - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
+
27
+ 入站:`gateway.receive({ conversation: { endpoint, kind: 'private', id: openid }, message: { conversation, id }, content: text, sender, metadata })`
28
+ 出站:`send({ conversation, payload })` → 被动回复 XML(默认)或客服消息 API(`replyMode: customer_service`)
29
+
19
30
  ## 前置条件
20
31
 
21
32
  | 要求 | 说明 |
22
33
  |------|------|
23
34
  | **公众号** | 已注册微信公众号,并在 [微信公众平台](https://mp.weixin.qq.com/) 获取 `AppID`、`AppSecret` |
24
35
  | **服务器配置** | 配置 Token(与 `token` 字段一致);服务器 URL 须公网可访问 |
25
- | **host-router** | **必需** — 适配器在 `path` 上注册 GET/POST Webhook 路由 |
36
+ | **host-http** | **必需** — Runtime Host 提供 HTTP;适配器在 `path` 上注册 GET/POST |
26
37
  | **响应时限** | 微信要求 **5 秒内**响应;超时会导致接入失败 |
27
- | **回复模式** | 默认 `replyMode: passive`(订阅号被动回复,在 webhook 响应里返回 XML);服务号可设 `customer_service` 走客服 API |
28
- | **消息加密** | 可选;`encrypt: true` + `encodingAESKey`;`encryptMode: compatible`(默认,被动回复明文)或 `secure`(被动回复也加密) |
38
+ | **回复模式** | 默认 `replyMode: passive`(订阅号被动回复);服务号可设 `customer_service` |
39
+ | **消息加密** | 可选;`encrypt: true` + `encodingAESKey`;`encryptMode: compatible`(默认)或 `secure` |
29
40
 
30
- 必填字段见 `WeChatMPConfig`:`context`、`name`、`appId`、`appSecret`、`token`、`path`。
41
+ 必填字段(`endpoints[i]`):`id`、`appId`、`appSecret`、`token`。运行时由 AdapterIndex
42
+ 把实例级默认值与每个 endpoint 合并;协议实现只接收一份展开后的 endpoint 配置。
31
43
 
32
44
  ## 最小配置
33
45
 
34
46
  ```yaml
47
+ # zhin.config.yml(Plugin Runtime)
35
48
  plugins:
36
- - "@zhin.js/adapter-wechat-mp"
37
- - "@zhin.js/host-router"
38
-
39
- endpoints:
40
- - context: wechat-mp
41
- name: my-wechat-bot
42
- appId: "${WECHAT_APP_ID}"
43
- appSecret: "${WECHAT_APP_SECRET}"
44
- token: "${WECHAT_TOKEN}"
45
- path: /wechat/webhook
46
- ```
47
-
48
- ## 依赖
49
-
50
- - `@zhin.js/host-router` — HTTP 服务(提供 Webhook 路由)
51
-
52
- ## 配置
53
-
54
- ### TypeScript 配置
55
-
56
- 完整选项示例(含可选加密字段):
57
-
58
- ```yaml
59
- endpoints:
60
- - context: wechat-mp
61
- name: my-wechat-bot
62
- appId: ${WECHAT_APP_ID}
63
- appSecret: ${WECHAT_APP_SECRET}
64
- token: ${WECHAT_TOKEN}
49
+ wechat-mp:
65
50
  path: /wechat/webhook
66
- # encodingAESKey: your-aes-key
67
- # encrypt: false
51
+ endpoints:
52
+ - id: my-wechat-bot
53
+ appId: "${WECHAT_APP_ID}"
54
+ appSecret: "${WECHAT_APP_SECRET}"
55
+ token: "${WECHAT_TOKEN}"
68
56
  ```
69
57
 
70
- ```typescript
71
- import { defineConfig } from 'zhin.js'
72
-
73
- export default defineConfig({
74
- endpoints: [
75
- {
76
- context: 'wechat-mp',
77
- name: 'my-wechat-bot',
78
- appId: process.env.WECHAT_APP_ID!,
79
- appSecret: process.env.WECHAT_APP_SECRET!,
80
- token: process.env.WECHAT_TOKEN!,
81
- path: '/wechat/webhook',
82
- }
83
- ],
84
- plugins: ['@zhin.js/adapter-wechat-mp', '@zhin.js/host-router']
85
- })
86
- ```
87
-
88
- ## 使用示例
89
-
90
- ### 注册命令
91
-
92
- ```typescript
93
- import { usePlugin, MessageCommand } from 'zhin.js'
94
-
95
- const { addCommand } = usePlugin()
96
-
97
- addCommand(
98
- new MessageCommand('hello')
99
- .desc('微信问候')
100
- .action((message) => `你好,${message.$sender.name}!`)
101
- )
102
- ```
103
-
104
- ### 消息中间件
105
-
106
- ```typescript
107
- import { usePlugin } from 'zhin.js'
58
+ 根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-wechat-mp`(`instanceKey: wechat-mp`)。
59
+ Runtime Host(`http`)须已 listen,Webhook 才可达。
108
60
 
109
- const { addMiddleware } = usePlugin()
61
+ ### 可选字段
110
62
 
111
- addMiddleware(async (message, next) => {
112
- if (message.$adapter === 'wechat-mp') {
113
- console.log('收到微信消息:', message.$content)
114
- }
115
- await next()
116
- })
117
- ```
63
+ - `path`:Webhook 路径,默认 `/wechat/webhook`
64
+ - `replyMode`:`passive`(默认)| `customer_service`
65
+ - `passiveReplyTimeoutMs`:被动回复等待上限,默认 `4500`
66
+ - `encrypt` / `encodingAESKey` / `encryptMode`
118
67
 
119
68
  ## 微信公众号配置
120
69
 
@@ -128,12 +77,16 @@ addMiddleware(async (message, next) => {
128
77
 
129
78
  | 现象 | 排查 |
130
79
  |------|------|
131
- | 服务器配置验证失败 | `token` 与公众平台一致;URL 为 `https://<host>/wechat/webhook`;Host 已启动且公网可达 |
132
- | 收不到用户消息 | 公众号类型是否支持消息接口;用户是否已关注;`path` 与公众平台 URL 一致 |
133
- | 回复超时 / 无回复 | 默认被动回复须在 **~4.5s** 内完成;AI 过慢会返回 `success` 无正文,可改 `replyMode: customer_service`(需客服接口权限) |
80
+ | 服务器配置验证失败 | `token` 与公众平台一致;URL 为 `https://<host>/wechat/webhook`;Runtime Host 已 listen 且公网可达 |
81
+ | 收不到用户消息 | 公众号类型是否支持消息接口;用户是否已关注;`path` 与公众平台 URL 一致;endpoint 已 `open()` |
82
+ | 回复超时 / 无回复 | 默认被动回复须在 **~4.5s** 内完成;可改 `replyMode: customer_service`(需客服接口权限) |
134
83
  | `48001 api unauthorized` | 未认证订阅号无客服 API;保持默认 `replyMode: passive` |
135
84
  | 加密模式报错 | `encodingAESKey`、`encrypt` 与公众平台「安全模式」设置一致 |
136
85
 
86
+ ## AI 工具
87
+
88
+ 技能说明见 `skills/wechat-mp/SKILL.md`。
89
+
137
90
  ## 文档链接
138
91
 
139
92
  - [微信公众号适配器文档](https://zhin.js.org/adapters/wechat-mp)
@@ -0,0 +1,32 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * Convention entry: discover `adapters/wechat-mp/index.ts` → defineAdapter.
4
+ */
5
+ import { defineAdapter } from 'zhin.js/adapter';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { WeChatMpEndpoint } from "../../lib/endpoint.js";
8
+ import { resolveWeChatMpConfig, } from "../../lib/protocol.js";
9
+ import { wechatMpRuntimeStateToken } from "../../lib/wechat-mp-runtime-state.js";
10
+ export { WeChatMpEndpoint } from "../../lib/endpoint.js";
11
+ export default defineAdapter({
12
+ capabilities: ['inbound', 'outbound'],
13
+ // 客服消息媒体统一经 /cgi-bin/media/upload 物化为 media_id(url 下载后上传,
14
+ // kind=file 的 MediaRef 视为既有 media_id 直传);公众号无卡片交互面,交互段降级纯文本。
15
+ segments: {
16
+ outboundMedia: ['upload'],
17
+ interactive: 'text',
18
+ },
19
+ create(context) {
20
+ const config = resolveWeChatMpConfig(context.config);
21
+ // 注册到插件运行时状态(wechat-mp endpoint list 的"运行中"数据源)
22
+ context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
23
+ id: config.id,
24
+ mode: 'webhook',
25
+ });
26
+ return new WeChatMpEndpoint({
27
+ id: context.id,
28
+ http: context.use(httpHostToken),
29
+ config,
30
+ });
31
+ },
32
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Convention entry: discover `adapters/wechat-mp/index.ts` → defineAdapter.
3
+ */
4
+ import { defineAdapter } from 'zhin.js/adapter';
5
+ import { httpHostToken } from '@zhin.js/host-http';
6
+ import { WeChatMpEndpoint } from '../../src/endpoint.js';
7
+ import {
8
+ resolveWeChatMpConfig,
9
+ type WeChatMpEndpointConfig,
10
+ } from '../../src/protocol.js';
11
+ import { wechatMpRuntimeStateToken } from '../../src/wechat-mp-runtime-state.js';
12
+
13
+ export { WeChatMpEndpoint } from '../../src/endpoint.js';
14
+ export type { WeChatMpEndpointOptions } from '../../src/endpoint.js';
15
+ export type { WeChatMpFetch } from '../../src/client.js';
16
+
17
+ export default defineAdapter<WeChatMpEndpointConfig>({
18
+ capabilities: ['inbound', 'outbound'],
19
+ // 客服消息媒体统一经 /cgi-bin/media/upload 物化为 media_id(url 下载后上传,
20
+ // kind=file 的 MediaRef 视为既有 media_id 直传);公众号无卡片交互面,交互段降级纯文本。
21
+ segments: {
22
+ outboundMedia: ['upload'],
23
+ interactive: 'text',
24
+ },
25
+ create(context) {
26
+ const config = resolveWeChatMpConfig(context.config);
27
+ // 注册到插件运行时状态(wechat-mp endpoint list 的"运行中"数据源)
28
+ context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
29
+ id: config.id,
30
+ mode: 'webhook',
31
+ });
32
+ return new WeChatMpEndpoint({
33
+ id: context.id,
34
+ http: context.use(httpHostToken),
35
+ config,
36
+ });
37
+ },
38
+ });
@@ -0,0 +1,3 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { wechatMpEndpointCommands } from '../../definition.js';
3
+ export default wechatMpEndpointCommands.add;
@@ -0,0 +1,3 @@
1
+ import { wechatMpEndpointCommands } from '../../definition.js';
2
+
3
+ export default wechatMpEndpointCommands.add;
@@ -0,0 +1,20 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * `wechat-mp endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
4
+ * commands/wechat-mp/endpoint/ 下的 list / add / remove 直接默认导出这三项。
5
+ */
6
+ import { createEndpointCommands } from 'zhin.js/adapter';
7
+ import { defineCommand } from 'zhin.js/command';
8
+ import { wechatMpRuntimeStateToken } from "../../../lib/wechat-mp-runtime-state.js";
9
+ export const wechatMpEndpointCommands = createEndpointCommands({
10
+ adapterKey: 'wechat-mp',
11
+ adapterDisplayName: 'WeChat MP',
12
+ fields: [
13
+ { key: 'appId', required: true, env: true, description: '公众号 AppID' },
14
+ { key: 'appSecret', required: true, env: true, description: '公众号 AppSecret' },
15
+ { key: 'token', required: true, env: true, description: '服务器配置 token' },
16
+ { key: 'encodingAESKey', env: true, description: '消息加解密 key' },
17
+ ],
18
+ running: (use) => use(wechatMpRuntimeStateToken).endpoints.values(),
19
+ describeEntry: (entry) => `appId: ${String(entry.appId)}`,
20
+ }, defineCommand);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `wechat-mp endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
+ * commands/wechat-mp/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
+ */
5
+ import { createEndpointCommands } from 'zhin.js/adapter';
6
+ import { defineCommand } from 'zhin.js/command';
7
+ import { wechatMpRuntimeStateToken } from '../../../src/wechat-mp-runtime-state.js';
8
+
9
+ export const wechatMpEndpointCommands = createEndpointCommands({
10
+ adapterKey: 'wechat-mp',
11
+ adapterDisplayName: 'WeChat MP',
12
+ fields: [
13
+ { key: 'appId', required: true, env: true, description: '公众号 AppID' },
14
+ { key: 'appSecret', required: true, env: true, description: '公众号 AppSecret' },
15
+ { key: 'token', required: true, env: true, description: '服务器配置 token' },
16
+ { key: 'encodingAESKey', env: true, description: '消息加解密 key' },
17
+ ],
18
+ running: (use) => use(wechatMpRuntimeStateToken).endpoints.values(),
19
+ describeEntry: (entry) => `appId: ${String(entry.appId)}`,
20
+ }, defineCommand);
@@ -0,0 +1,3 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { wechatMpEndpointCommands } from '../definition.js';
3
+ export default wechatMpEndpointCommands.list;
@@ -0,0 +1,3 @@
1
+ import { wechatMpEndpointCommands } from '../definition.js';
2
+
3
+ export default wechatMpEndpointCommands.list;
@@ -0,0 +1,3 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { wechatMpEndpointCommands } from '../../definition.js';
3
+ export default wechatMpEndpointCommands.remove;
@@ -0,0 +1,3 @@
1
+ import { wechatMpEndpointCommands } from '../../definition.js';
2
+
3
+ export default wechatMpEndpointCommands.remove;
@@ -0,0 +1,32 @@
1
+ import type { ResolvedWeChatMpConfig, WeChatAPIResponse } from './protocol.js';
2
+ export type WeChatMpFetch = (url: string, init?: {
3
+ readonly method?: string;
4
+ readonly body?: unknown;
5
+ readonly headers?: Record<string, string>;
6
+ }) => Promise<{
7
+ readonly data: unknown;
8
+ }>;
9
+ /** Direct WeChat Official Account API client exposed to plugins. */
10
+ export declare class WeChatMpClient {
11
+ #private;
12
+ readonly config: ResolvedWeChatMpConfig;
13
+ readonly fetch: WeChatMpFetch;
14
+ constructor(config: ResolvedWeChatMpConfig, fetch: WeChatMpFetch);
15
+ get accessToken(): string | null;
16
+ get tokenExpired(): boolean;
17
+ refreshAccessToken(): Promise<string>;
18
+ request<T = unknown>(path: string, init?: Parameters<WeChatMpFetch>[1]): Promise<T>;
19
+ sendCustomerService(messageData: unknown): Promise<WeChatAPIResponse>;
20
+ uploadMedia(type: 'image' | 'voice' | 'video', body: unknown): Promise<string>;
21
+ getFollowerIds(): Promise<readonly string[]>;
22
+ }
23
+ export type WeChatMpClientEventMap = Record<string, unknown>;
24
+ declare module '@zhin.js/feature-kit' {
25
+ interface AdapterClientRegistry {
26
+ readonly 'wechat-mp': {
27
+ readonly client: WeChatMpClient;
28
+ readonly events: WeChatMpClientEventMap;
29
+ };
30
+ }
31
+ }
32
+ export declare const wechatMpClient: import("@zhin.js/adapter").EndpointClientToken<WeChatMpClient, WeChatMpClientEventMap>;
package/lib/client.js ADDED
@@ -0,0 +1,70 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ const TOKEN_INVALID_ERRCODES = new Set([40001, 40014, 42001]);
3
+ /** Direct WeChat Official Account API client exposed to plugins. */
4
+ export class WeChatMpClient {
5
+ config;
6
+ fetch;
7
+ #accessToken = null;
8
+ #tokenExpireTime = 0;
9
+ constructor(config, fetch) {
10
+ this.config = config;
11
+ this.fetch = fetch;
12
+ }
13
+ get accessToken() {
14
+ return this.#accessToken;
15
+ }
16
+ get tokenExpired() {
17
+ return !this.#accessToken || Date.now() >= this.#tokenExpireTime;
18
+ }
19
+ async refreshAccessToken() {
20
+ const { appId, appSecret } = this.config;
21
+ const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
22
+ const response = await this.fetch(url);
23
+ const data = response.data;
24
+ if (!data.access_token) {
25
+ throw new Error(data.errmsg
26
+ ? `Failed to get access token: ${data.errcode} ${data.errmsg}`
27
+ : 'Failed to get access token');
28
+ }
29
+ this.#accessToken = data.access_token;
30
+ this.#tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000;
31
+ return data.access_token;
32
+ }
33
+ async request(path, init) {
34
+ if (this.tokenExpired)
35
+ await this.refreshAccessToken();
36
+ const separator = path.includes('?') ? '&' : '?';
37
+ const response = await this.fetch(`https://api.weixin.qq.com${path}${separator}access_token=${this.#accessToken}`, init);
38
+ return response.data;
39
+ }
40
+ async sendCustomerService(messageData) {
41
+ let result = await this.request('/cgi-bin/message/custom/send', { method: 'POST', body: messageData });
42
+ if (result.errcode && TOKEN_INVALID_ERRCODES.has(Number(result.errcode))) {
43
+ await this.refreshAccessToken();
44
+ result = await this.request('/cgi-bin/message/custom/send', { method: 'POST', body: messageData });
45
+ }
46
+ return result;
47
+ }
48
+ async uploadMedia(type, body) {
49
+ const data = await this.request(`/cgi-bin/media/upload?type=${type}`, { method: 'POST', body });
50
+ if (data.media_id)
51
+ return data.media_id;
52
+ throw new Error(`WeChat media upload failed: ${data.errcode ?? 'unknown'} ${data.errmsg ?? ''}`.trim());
53
+ }
54
+ async getFollowerIds() {
55
+ const followers = [];
56
+ let nextOpenid = '';
57
+ do {
58
+ const data = await this.request(`/cgi-bin/user/get?next_openid=${encodeURIComponent(nextOpenid)}`);
59
+ if (data.errcode && data.errcode !== 0) {
60
+ throw new Error(`WeChat API error: ${data.errcode} - ${data.errmsg}`);
61
+ }
62
+ followers.push(...(data.data?.openid ?? []).filter(Boolean));
63
+ if (Number(data.count ?? 0) < 10_000)
64
+ break;
65
+ nextOpenid = data.next_openid ?? '';
66
+ } while (nextOpenid);
67
+ return Object.freeze(followers);
68
+ }
69
+ }
70
+ export const wechatMpClient = defineEndpointClient('wechat-mp');
package/lib/endpoint.d.ts CHANGED
@@ -1,73 +1,32 @@
1
- import { EventEmitter } from "events";
2
- import { Endpoint, Message, MessageSegment, SendOptions } from 'zhin.js';
3
- import { type Router } from "@zhin.js/host-router/router";
4
- import type { WeChatMPConfig, WeChatMessage } from "./types.js";
5
- import type { WeChatMPAdapter } from "./adapter.js";
6
- export declare class WeChatMPEndpoint extends EventEmitter implements Endpoint<WeChatMPConfig, WeChatMessage> {
7
- adapter: WeChatMPAdapter;
8
- $config: WeChatMPConfig;
9
- $connected: boolean;
10
- router: Router;
11
- private accessToken;
12
- private tokenExpireTime;
13
- get logger(): import("zhin.js").Logger;
14
- get $id(): string;
15
- constructor(adapter: WeChatMPAdapter, router: Router, config: WeChatMPConfig);
16
- private setupRoutes;
17
- $connect(): Promise<void>;
18
- $disconnect(): Promise<void>;
19
- private handleVerification;
20
- private handleMessage;
21
- private computeSignatureHash;
22
- private verifySignature;
23
- private parseXMLMessage;
24
- $formatMessage(wechatMsg: WeChatMessage): Message<WeChatMessage>;
25
- static parseMessageContent(wechatMsg: WeChatMessage): MessageSegment[];
26
- $sendMessage(options: SendOptions): Promise<string>;
27
- $recallMessage(id: string): Promise<void>;
28
- private sendCustomerServiceMessage;
29
- private formatSendContent;
30
- private getReplyMode;
31
- private getEncryptMode;
32
- private usesPassiveReply;
33
- private extractSendText;
34
- private collectPassiveReplyXml;
35
- private handlePassiveReply;
36
- private buildTextReply;
37
- private refreshAccessToken;
38
- private tokenRefreshTimer?;
39
- private startTokenRefreshTimer;
40
- getUserInfo(openid: string): Promise<any>;
41
- /**
42
- * 上传多媒体文件到微信服务器
43
- * @param type 媒体类型:image(图片)、voice(语音)、video(视频)、thumb(缩略图)
44
- * @param buffer 文件 Buffer
45
- * @param filename 文件名(可选,用于确定文件类型)
46
- * @returns 微信服务器返回的 media_id
47
- */
48
- uploadMedia(type: 'image' | 'voice' | 'video' | 'thumb', buffer: Buffer, filename?: string): Promise<string>;
49
- /**
50
- * 获取文件扩展名
51
- */
52
- private getFileExtension;
53
- /**
54
- * 获取 Content-Type
55
- */
56
- private getContentType;
57
- private getAESKey;
58
- /** 微信安全模式加密 echostr 为较长 Base64;明文/兼容模式多为短字符串 */
59
- private isEncryptedEchostr;
60
- /**
61
- * 解密安全模式 URL 验证中的 echostr
62
- */
63
- private decryptEchostr;
64
- /**
65
- * 解密微信推送的加密消息
66
- */
67
- private decryptMessage;
68
- /**
69
- * 加密被动回复消息
70
- */
71
- private encryptMessage;
1
+ import { Endpoint } from 'zhin.js/adapter';
2
+ import type { EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
3
+ import type { HttpHost } from '@zhin.js/host-http';
4
+ import type { CapabilityId } from 'zhin.js';
5
+ import { type ResolvedWeChatMpConfig, type WeChatMessage } from './protocol.js';
6
+ import { WeChatMpClient, type WeChatMpFetch } from './client.js';
7
+ export interface WeChatMpEndpointOptions {
8
+ readonly id: CapabilityId;
9
+ readonly http: HttpHost;
10
+ readonly config: ResolvedWeChatMpConfig;
11
+ readonly fetch?: WeChatMpFetch;
12
+ }
13
+ export declare class WeChatMpEndpoint extends Endpoint<WeChatMpClient> {
14
+ #private;
15
+ readonly client: WeChatMpClient;
16
+ readonly management: EndpointManagement;
17
+ constructor(options: WeChatMpEndpointOptions);
18
+ /** Used by webhook handler. */
19
+ get isOpen(): boolean;
20
+ get config(): ResolvedWeChatMpConfig;
21
+ get id(): CapabilityId;
22
+ /** 微信 5s 重推去重:见过该 MsgId 时返回首次回复 XML(含空串=success)。 */
23
+ getCachedReply(msgId: string): string | undefined;
24
+ cacheReply(msgId: string, replyXML: string): void;
25
+ start(): Promise<void>;
26
+ open(): void;
27
+ close(): void;
28
+ stop(): Promise<void>;
29
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
30
+ /** Test / internal: admit a parsed message when open (non-webhook path). */
31
+ admit(msg: WeChatMessage): void | Promise<unknown>;
72
32
  }
73
- //# sourceMappingURL=endpoint.d.ts.map