@zhin.js/adapter-wechat-mp 3.0.2 → 3.0.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @zhin.js/adapter-wechat-mp
2
2
 
3
+ ## 3.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - cc5c94d: 约定式插件运行时迁移(breaking):插件与适配器由 `usePlugin()` / `extends Adapter` 迁移为 `definePlugin` / `defineAdapter` + `plugin.ts` + 约定目录(`adapters/`、`commands/`、`components/`、`tools/` 等)。
8
+
9
+ - 新增约定式运行时包:`@zhin.js/plugin-runtime`、`@zhin.js/adapter`、`@zhin.js/runtime`、`@zhin.js/host-http`(首版 1.0.0 走 init-publish,不在本 changeset 内 bump)。
10
+ - 全部 20 个平台适配器改为约定式 `defineAdapter`,旧 `usePlugin` / `extends Adapter` / `segment-mapper` 生产入口已删除;onebot11 反向 WSS、onebot12 webhook/wss、milky sse/webhook/wss、satori webhook、kook webhook、qq webhook/middleware 等 slice 1 推迟的连接模式已补齐。
11
+ - 游戏 / 工具 / 服务插件同步迁移到约定目录结构。
12
+ - CLI 增加 plugin-runtime host installer(http/database/outbound/schedule/console 等)。
13
+
14
+ 后续加固(同批):
15
+
16
+ - CLI:`zhin runtime start --daemon`(pidfile/崩溃拉起/风暴保护),orphan watchdog 防僵尸进程;legacy `zhin dev` / `zhin start` 已移除(含 `zhin restart`),`zhin stop` 兼容新 daemon。
17
+ - 安全:builtin 工具统一走 `security/policy-facade.ts` 的 `runToolPolicies`(声明式策略表,deny 优先);审计日志 close flush + 背压队列;`splitCompoundCommand` 引号感知、`extractCommandName` 去引号堵绕过。
18
+ - 日志:Logger 双堆栈修复、本地时区、`getLogger` 挂树(`setLevel` 递归生效)、第三方库(log4js/discord)桥接、启动人读总结。
19
+ - 结构:`plugins/games/shared` 迁为 `packages/game-kit`(`@zhin.js/game-kit`);死目录 `plugins/adapters/common` 删除。
20
+ - 脚手架:`create-zhin-app` / `zhin new` / scaffold-wizard 生成物改为 Plugin Runtime 形态(minimal-bot 同构,新配置格式)。
21
+ - Console:endpoint.list 真实名称与 phase、schema:get-all 按 instanceKey 映射、db:\* 接 DatabaseHost。
22
+
23
+ 注:按仓库发布惯例(见 1bb345dd2),本次 breaking 迁移统一使用 patch,避免 zhin.js 5.0 级联。
24
+
25
+ - Updated dependencies [16ec4e8]
26
+ - Updated dependencies [cc5c94d]
27
+ - Updated dependencies [447f3e2]
28
+ - @zhin.js/core@1.3.5
29
+ - @zhin.js/host-http@1.0.1
30
+ - zhin.js@4.1.3
31
+ - @zhin.js/logger@1.0.75
32
+ - @zhin.js/plugin-runtime@1.0.1
33
+ - @zhin.js/adapter@1.0.1
34
+
3
35
  ## 3.0.2
4
36
 
5
37
  ### Patch Changes
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,28 +16,36 @@ 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.ts`(`defineAdapter`)
22
+ - `@zhin.js/core` — `messageGatewayToken` 入站/出站
23
+ - `@zhin.js/host-http` — `httpHostToken` 注册 Webhook 路由(**非** legacy host-router/Koa)
24
+ - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
+ - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
+
27
+ 入站:`gateway.receive({ adapter, target: openid, content: text, sender, metadata })`
28
+ 出站:`send({ target, 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
+ 必填字段:`appId`、`appSecret`、`token`。
31
42
 
32
43
  ## 最小配置
33
44
 
34
45
  ```yaml
46
+ # zhin.config.yml(Plugin Runtime)
35
47
  plugins:
36
- - "@zhin.js/adapter-wechat-mp"
37
- - "@zhin.js/host-router"
38
-
39
- endpoints:
40
- - context: wechat-mp
48
+ wechat-mp:
41
49
  name: my-wechat-bot
42
50
  appId: "${WECHAT_APP_ID}"
43
51
  appSecret: "${WECHAT_APP_SECRET}"
@@ -45,76 +53,15 @@ endpoints:
45
53
  path: /wechat/webhook
46
54
  ```
47
55
 
48
- ## 依赖
49
-
50
- - `@zhin.js/host-router` — HTTP 服务(提供 Webhook 路由)
51
-
52
- ## 配置
53
-
54
- ### TypeScript 配置
56
+ 根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-wechat-mp`(`instanceKey: wechat-mp`)。
57
+ Runtime Host(`http`)须已 listen,Webhook 才可达。
55
58
 
56
- 完整选项示例(含可选加密字段):
59
+ ### 可选字段
57
60
 
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}
65
- path: /wechat/webhook
66
- # encodingAESKey: your-aes-key
67
- # encrypt: false
68
- ```
69
-
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'
108
-
109
- const { root } = usePlugin()
110
-
111
- root.addMiddleware(async (message, next) => {
112
- if (message.$adapter === 'wechat-mp') {
113
- console.log('收到微信消息:', message.$content)
114
- }
115
- await next()
116
- })
117
- ```
61
+ - `path`:Webhook 路径,默认 `/wechat/webhook`
62
+ - `replyMode`:`passive`(默认)| `customer_service`
63
+ - `passiveReplyTimeoutMs`:被动回复等待上限,默认 `4500`
64
+ - `encrypt` / `encodingAESKey` / `encryptMode`
118
65
 
119
66
  ## 微信公众号配置
120
67
 
@@ -128,16 +75,15 @@ root.addMiddleware(async (message, next) => {
128
75
 
129
76
  | 现象 | 排查 |
130
77
  |------|------|
131
- | 服务器配置验证失败 | `token` 与公众平台一致;URL 为 `https://<host>/wechat/webhook`;Host 已启动且公网可达 |
132
- | 收不到用户消息 | 公众号类型是否支持消息接口;用户是否已关注;`path` 与公众平台 URL 一致 |
133
- | 回复超时 / 无回复 | 默认被动回复须在 **~4.5s** 内完成;AI 过慢会返回 `success` 无正文,可改 `replyMode: customer_service`(需客服接口权限) |
78
+ | 服务器配置验证失败 | `token` 与公众平台一致;URL 为 `https://<host>/wechat/webhook`;Runtime Host listen 且公网可达 |
79
+ | 收不到用户消息 | 公众号类型是否支持消息接口;用户是否已关注;`path` 与公众平台 URL 一致;endpoint 已 `open()` |
80
+ | 回复超时 / 无回复 | 默认被动回复须在 **~4.5s** 内完成;可改 `replyMode: customer_service`(需客服接口权限) |
134
81
  | `48001 api unauthorized` | 未认证订阅号无客服 API;保持默认 `replyMode: passive` |
135
82
  | 加密模式报错 | `encodingAESKey`、`encrypt` 与公众平台「安全模式」设置一致 |
136
83
 
137
84
  ## AI 工具
138
85
 
139
- 技能说明见 `agent/skills/wechat-mp.md`。群管与平台工具按公众号能力在 `src/index.ts` 注册。
140
-
86
+ 技能说明见 `agent/skills/wechat-mp.md`。
141
87
 
142
88
  ## 文档链接
143
89
 
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Convention entry: discover `adapters/wechat-mp.ts` → defineAdapter.
3
+ */
4
+ import { defineAdapter } from '@zhin.js/adapter';
5
+ import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { WeChatMpEndpoint } from '../src/endpoint.js';
8
+ import {
9
+ resolveWeChatMpConfig,
10
+ type WeChatMpAdapterConfig,
11
+ } from '../src/protocol.js';
12
+
13
+ export { WeChatMpEndpoint } from '../src/endpoint.js';
14
+ export type { WeChatMpEndpointOptions, WeChatMpFetch } from '../src/endpoint.js';
15
+
16
+ export default defineAdapter<WeChatMpAdapterConfig>({
17
+ capabilities: ['inbound', 'outbound'],
18
+ create(context) {
19
+ return new WeChatMpEndpoint({
20
+ id: context.id,
21
+ gateway: context.use(messageGatewayToken),
22
+ http: context.use(httpHostToken),
23
+ config: resolveWeChatMpConfig(context.config),
24
+ });
25
+ },
26
+ });
package/lib/endpoint.d.ts CHANGED
@@ -1,73 +1,38 @@
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 type { EndpointInstance } from '@zhin.js/adapter';
2
+ import type { MessageGateway } from '@zhin.js/core/runtime';
3
+ import type { HttpHost } from '@zhin.js/host-http';
4
+ import type { CapabilityId } from '@zhin.js/plugin-runtime';
5
+ import { type ResolvedWeChatMpConfig, type WeChatMessage } from './protocol.js';
6
+ export type WeChatMpFetch = (url: string, init?: {
7
+ readonly method?: string;
8
+ readonly body?: unknown;
9
+ readonly headers?: Record<string, string>;
10
+ }) => Promise<{
11
+ readonly data: unknown;
12
+ }>;
13
+ export interface WeChatMpEndpointOptions {
14
+ readonly id: CapabilityId;
15
+ readonly gateway: MessageGateway;
16
+ readonly http: HttpHost;
17
+ readonly config: ResolvedWeChatMpConfig;
18
+ readonly fetch?: WeChatMpFetch;
19
+ }
20
+ export declare class WeChatMpEndpoint implements EndpointInstance {
21
+ #private;
22
+ constructor(options: WeChatMpEndpointOptions);
23
+ /** Used by webhook handler. */
24
+ get isOpen(): boolean;
25
+ get config(): ResolvedWeChatMpConfig;
26
+ get id(): CapabilityId;
27
+ get gateway(): MessageGateway;
28
+ start(): Promise<void>;
29
+ open(): void;
30
+ close(): void;
31
+ stop(): Promise<void>;
32
+ send({ target, payload }: {
33
+ readonly target: string;
34
+ readonly payload: unknown;
35
+ }): Promise<string>;
36
+ /** Test / internal: admit a parsed message when open (non-webhook path). */
37
+ admit(msg: WeChatMessage): void;
72
38
  }
73
- //# sourceMappingURL=endpoint.d.ts.map