@zhin.js/adapter-wechat-mp 5.0.1 → 5.0.4
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 +55 -0
- package/README.md +2 -2
- package/adapters/wechat-mp.js +2 -2
- package/adapters/wechat-mp.ts +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +30 -29
- package/lib/protocol.d.ts +8 -2
- package/lib/protocol.js +14 -3
- package/lib/webhook.js +6 -6
- package/package.json +13 -12
- package/schema.json +41 -5
- package/src/endpoint.ts +32 -30
- package/src/protocol.ts +21 -5
- package/src/webhook.ts +6 -5
- /package/commands/endpoint/add/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @zhin.js/adapter-wechat-mp
|
|
2
2
|
|
|
3
|
+
## 5.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [0de46a8]
|
|
8
|
+
- Updated dependencies [c106ecc]
|
|
9
|
+
- Updated dependencies [ca92e03]
|
|
10
|
+
- Updated dependencies [b0f37ae]
|
|
11
|
+
- Updated dependencies [ba08a2f]
|
|
12
|
+
- Updated dependencies [daffd4c]
|
|
13
|
+
- Updated dependencies [36c7400]
|
|
14
|
+
- Updated dependencies [162fa34]
|
|
15
|
+
- Updated dependencies [e40b048]
|
|
16
|
+
- Updated dependencies [f1708c3]
|
|
17
|
+
- Updated dependencies [e53444f]
|
|
18
|
+
- Updated dependencies [92b0dd7]
|
|
19
|
+
- Updated dependencies [a7df753]
|
|
20
|
+
- @zhin.js/host-http@1.0.8
|
|
21
|
+
- @zhin.js/im-contract@1.0.3
|
|
22
|
+
- @zhin.js/adapter@1.1.7
|
|
23
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
24
|
+
- @zhin.js/command@1.0.9
|
|
25
|
+
- @zhin.js/core@1.5.4
|
|
26
|
+
- zhin.js@6.0.4
|
|
27
|
+
|
|
28
|
+
## 5.0.3
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- f8c7a54: fix: im
|
|
33
|
+
- Updated dependencies [f8c7a54]
|
|
34
|
+
- @zhin.js/logger@1.0.76
|
|
35
|
+
- @zhin.js/host-http@1.0.7
|
|
36
|
+
- @zhin.js/adapter@1.1.6
|
|
37
|
+
- @zhin.js/command@1.0.8
|
|
38
|
+
- @zhin.js/core@1.5.3
|
|
39
|
+
- @zhin.js/im-contract@1.0.2
|
|
40
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
41
|
+
- zhin.js@6.0.3
|
|
42
|
+
|
|
43
|
+
## 5.0.2
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- Updated dependencies [afc0e66]
|
|
48
|
+
- Updated dependencies [2e41ad5]
|
|
49
|
+
- Updated dependencies [9f57124]
|
|
50
|
+
- @zhin.js/core@1.5.2
|
|
51
|
+
- @zhin.js/adapter@1.1.5
|
|
52
|
+
- @zhin.js/im-contract@1.0.1
|
|
53
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
54
|
+
- @zhin.js/command@1.0.7
|
|
55
|
+
- @zhin.js/host-http@1.0.6
|
|
56
|
+
- zhin.js@6.0.2
|
|
57
|
+
|
|
3
58
|
## 5.0.1
|
|
4
59
|
|
|
5
60
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-wechat-mp
|
|
|
24
24
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
|
|
27
|
-
入站:`gateway.receive({
|
|
28
|
-
出站:`send({
|
|
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
29
|
|
|
30
30
|
## 前置条件
|
|
31
31
|
|
package/adapters/wechat-mp.js
CHANGED
|
@@ -20,8 +20,8 @@ export default defineAdapter({
|
|
|
20
20
|
create(context) {
|
|
21
21
|
const config = resolveWeChatMpConfig(context.config);
|
|
22
22
|
// 注册到插件运行时状态(wechat-mp.endpoint list 的"运行中"数据源)
|
|
23
|
-
context.use(wechatMpRuntimeStateToken).endpoints.set(config.
|
|
24
|
-
|
|
23
|
+
context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
|
|
24
|
+
id: config.id,
|
|
25
25
|
mode: 'webhook',
|
|
26
26
|
});
|
|
27
27
|
return new WeChatMpEndpoint({
|
package/adapters/wechat-mp.ts
CHANGED
|
@@ -25,8 +25,8 @@ export default defineAdapter<WeChatMpAdapterConfig>({
|
|
|
25
25
|
create(context) {
|
|
26
26
|
const config = resolveWeChatMpConfig(context.config);
|
|
27
27
|
// 注册到插件运行时状态(wechat-mp.endpoint list 的"运行中"数据源)
|
|
28
|
-
context.use(wechatMpRuntimeStateToken).endpoints.set(config.
|
|
29
|
-
|
|
28
|
+
context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
|
|
29
|
+
id: config.id,
|
|
30
30
|
mode: 'webhook',
|
|
31
31
|
});
|
|
32
32
|
return new WeChatMpEndpoint({
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointFriend, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointFriend, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -33,10 +33,7 @@ export declare class WeChatMpEndpoint implements EndpointInstance {
|
|
|
33
33
|
open(): void;
|
|
34
34
|
close(): void;
|
|
35
35
|
stop(): Promise<void>;
|
|
36
|
-
send({
|
|
37
|
-
readonly target: string;
|
|
38
|
-
readonly payload: unknown;
|
|
39
|
-
}): Promise<string>;
|
|
36
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
40
37
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
41
38
|
admit(msg: WeChatMessage): void;
|
|
42
39
|
/**
|
package/lib/endpoint.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* WeChatMpEndpoint — lifecycle, outbound, admit, access token refresh.
|
|
3
3
|
*/
|
|
4
4
|
import axios from 'axios';
|
|
5
|
-
import { formatCompact,
|
|
6
|
-
import { extractOutboundText, formatCustomerServiceBody, formatInboundContent, formatInboundId, } from './protocol.js';
|
|
5
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
|
+
import { extractOutboundText, formatCustomerServiceBody, formatInboundContent, formatInboundId, wechatMpInboundConversation, } from './protocol.js';
|
|
7
7
|
import { buildMediaUploadForm, readOutboundMedia, resolveMediaBinary, } from './media-upload.js';
|
|
8
8
|
import { getPassiveReplyCapture, recordPassiveReplyText, } from './passive-reply.js';
|
|
9
9
|
import { registerWeChatMpWebhookRoutes } from './webhook.js';
|
|
10
|
-
const logger = getLogger('wechat-mp');
|
|
11
10
|
/** token 失效类错误码:40001/40014 invalid access_token、42001 access_token expired。 */
|
|
12
11
|
const TOKEN_INVALID_ERRCODES = new Set([40001, 40014, 42001]);
|
|
13
12
|
/**
|
|
@@ -29,6 +28,7 @@ function defaultFetch(url, init) {
|
|
|
29
28
|
}).then((response) => ({ data: response.data }));
|
|
30
29
|
}
|
|
31
30
|
export class WeChatMpEndpoint {
|
|
31
|
+
#logger;
|
|
32
32
|
#options;
|
|
33
33
|
#fetch;
|
|
34
34
|
#routeReleases = [];
|
|
@@ -42,6 +42,7 @@ export class WeChatMpEndpoint {
|
|
|
42
42
|
#started = false;
|
|
43
43
|
management = createWeChatMpEndpointManagement(this);
|
|
44
44
|
constructor(options) {
|
|
45
|
+
this.#logger = getAdapterLogger('wechat-mp', options.config.id);
|
|
45
46
|
this.#options = options;
|
|
46
47
|
this.#fetch = options.fetch ?? defaultFetch;
|
|
47
48
|
}
|
|
@@ -81,15 +82,15 @@ export class WeChatMpEndpoint {
|
|
|
81
82
|
await this.#refreshAccessToken();
|
|
82
83
|
this.#routeReleases.push(...registerWeChatMpWebhookRoutes(this.#options.http, this));
|
|
83
84
|
this.#startTokenRefreshTimer();
|
|
84
|
-
logger.debug(formatCompact({
|
|
85
|
-
endpoint: this.#options.config.
|
|
85
|
+
this.#logger.debug(formatCompact({
|
|
86
|
+
endpoint: this.#options.config.id,
|
|
86
87
|
op: 'webhook',
|
|
87
88
|
path: this.#options.config.path,
|
|
88
89
|
}));
|
|
89
90
|
}
|
|
90
91
|
catch (error) {
|
|
91
92
|
await this.stop();
|
|
92
|
-
logger.error('Failed to connect WeChat MP bot:', error);
|
|
93
|
+
this.#logger.error('Failed to connect WeChat MP bot:', error);
|
|
93
94
|
throw error;
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -108,22 +109,22 @@ export class WeChatMpEndpoint {
|
|
|
108
109
|
for (const release of this.#routeReleases.splice(0))
|
|
109
110
|
release();
|
|
110
111
|
this.#started = false;
|
|
111
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
112
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
112
113
|
}
|
|
113
|
-
async send({
|
|
114
|
+
async send({ conversation, payload }) {
|
|
114
115
|
if (getPassiveReplyCapture()) {
|
|
115
116
|
const text = extractOutboundText(payload);
|
|
116
117
|
recordPassiveReplyText(text);
|
|
117
118
|
return `passive_${Date.now()}`;
|
|
118
119
|
}
|
|
119
120
|
if (this.#options.config.replyMode === 'customer_service') {
|
|
120
|
-
return this.#sendCustomerService(
|
|
121
|
+
return this.#sendCustomerService(conversation.id, payload);
|
|
121
122
|
}
|
|
122
|
-
logger.warn(formatCompact({
|
|
123
|
+
this.#logger.warn(formatCompact({
|
|
123
124
|
op: 'send',
|
|
124
125
|
skip: 'passive_outside_webhook',
|
|
125
|
-
endpoint: this.#options.config.
|
|
126
|
-
target
|
|
126
|
+
endpoint: this.#options.config.id,
|
|
127
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
127
128
|
}));
|
|
128
129
|
return `passive_skipped_${Date.now()}`;
|
|
129
130
|
}
|
|
@@ -131,22 +132,22 @@ export class WeChatMpEndpoint {
|
|
|
131
132
|
admit(msg) {
|
|
132
133
|
if (!this.#open)
|
|
133
134
|
return;
|
|
135
|
+
const conversation = wechatMpInboundConversation(String(this.#options.id), msg);
|
|
134
136
|
void this.#options.gateway.receive({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
conversation,
|
|
138
|
+
message: { conversation, id: formatInboundId(msg) },
|
|
137
139
|
content: formatInboundContent(msg),
|
|
138
|
-
sender: msg.FromUserName,
|
|
139
|
-
|
|
140
|
+
sender: { id: msg.FromUserName },
|
|
141
|
+
endpointId: this.#options.config.id,
|
|
140
142
|
metadata: Object.freeze({
|
|
141
143
|
msgType: msg.MsgType,
|
|
142
144
|
event: msg.Event,
|
|
143
|
-
endpoint: this.#options.config.name,
|
|
144
145
|
toUserName: msg.ToUserName,
|
|
145
146
|
}),
|
|
146
147
|
}).catch((err) => {
|
|
147
|
-
logger.warn(formatCompact({
|
|
148
|
+
this.#logger.warn(formatCompact({
|
|
148
149
|
op: 'wechat_mp_gateway_receive_failed',
|
|
149
|
-
target:
|
|
150
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
150
151
|
error: err instanceof Error ? err.message : String(err),
|
|
151
152
|
}));
|
|
152
153
|
});
|
|
@@ -161,7 +162,7 @@ export class WeChatMpEndpoint {
|
|
|
161
162
|
let result = await this.#postCustomerService(messageData);
|
|
162
163
|
if (result.errcode && TOKEN_INVALID_ERRCODES.has(Number(result.errcode))) {
|
|
163
164
|
// 对端提前作废 token(多端共用等):刷新后重试一次。
|
|
164
|
-
logger.warn(formatCompact({
|
|
165
|
+
this.#logger.warn(formatCompact({
|
|
165
166
|
op: 'wechat_mp_token_invalid_retry',
|
|
166
167
|
errcode: result.errcode,
|
|
167
168
|
}));
|
|
@@ -171,7 +172,7 @@ export class WeChatMpEndpoint {
|
|
|
171
172
|
if (result.errcode && result.errcode !== 0) {
|
|
172
173
|
throw new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`);
|
|
173
174
|
}
|
|
174
|
-
logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
|
|
175
|
+
this.#logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
|
|
175
176
|
return result.msgid?.toString() || `cs_${Date.now()}`;
|
|
176
177
|
}
|
|
177
178
|
/**
|
|
@@ -202,9 +203,9 @@ export class WeChatMpEndpoint {
|
|
|
202
203
|
return item;
|
|
203
204
|
if (typeof data.media_id === 'string' && data.media_id)
|
|
204
205
|
return item;
|
|
205
|
-
logger.warn(formatCompact({
|
|
206
|
+
this.#logger.warn(formatCompact({
|
|
206
207
|
op: 'wechat_mp_outbound_media_dropped',
|
|
207
|
-
endpoint: this.#options.config.
|
|
208
|
+
endpoint: this.#options.config.id,
|
|
208
209
|
type: seg.type,
|
|
209
210
|
reason: 'missing_media_ref',
|
|
210
211
|
}));
|
|
@@ -215,9 +216,9 @@ export class WeChatMpEndpoint {
|
|
|
215
216
|
return { type: seg.type, data: { mediaId: media.value } };
|
|
216
217
|
}
|
|
217
218
|
if (!uploadType) {
|
|
218
|
-
logger.warn(formatCompact({
|
|
219
|
+
this.#logger.warn(formatCompact({
|
|
219
220
|
op: 'wechat_mp_outbound_media_dropped',
|
|
220
|
-
endpoint: this.#options.config.
|
|
221
|
+
endpoint: this.#options.config.id,
|
|
221
222
|
type: seg.type,
|
|
222
223
|
reason: 'unsupported_segment_type',
|
|
223
224
|
}));
|
|
@@ -228,9 +229,9 @@ export class WeChatMpEndpoint {
|
|
|
228
229
|
return { type: seg.type, data: { mediaId } };
|
|
229
230
|
}
|
|
230
231
|
catch (error) {
|
|
231
|
-
logger.warn(formatCompact({
|
|
232
|
+
this.#logger.warn(formatCompact({
|
|
232
233
|
op: 'wechat_mp_media_upload_failed',
|
|
233
|
-
endpoint: this.#options.config.
|
|
234
|
+
endpoint: this.#options.config.id,
|
|
234
235
|
error: error instanceof Error ? error.message : String(error),
|
|
235
236
|
}));
|
|
236
237
|
const alt = typeof data.alt === 'string' && data.alt ? data.alt : `[${seg.type}]`;
|
|
@@ -294,7 +295,7 @@ export class WeChatMpEndpoint {
|
|
|
294
295
|
if (data.access_token) {
|
|
295
296
|
this.#accessToken = data.access_token;
|
|
296
297
|
this.#tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000;
|
|
297
|
-
logger.debug(formatCompact({ op: 'token_refresh' }));
|
|
298
|
+
this.#logger.debug(formatCompact({ op: 'token_refresh' }));
|
|
298
299
|
return;
|
|
299
300
|
}
|
|
300
301
|
throw new Error(data.errmsg
|
|
@@ -305,7 +306,7 @@ export class WeChatMpEndpoint {
|
|
|
305
306
|
this.#tokenRefreshTimer = setInterval(() => {
|
|
306
307
|
if (Date.now() >= this.#tokenExpireTime) {
|
|
307
308
|
void this.#refreshAccessToken().catch((error) => {
|
|
308
|
-
logger.error('Failed to refresh access token in timer:', error);
|
|
309
|
+
this.#logger.error('Failed to refresh access token in timer:', error);
|
|
309
310
|
});
|
|
310
311
|
}
|
|
311
312
|
}, 3_600_000);
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
5
|
import type { IncomingMessage } from 'node:http';
|
|
6
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
7
|
export interface WeChatMpAdapterConfig {
|
|
7
|
-
readonly
|
|
8
|
+
readonly id?: string;
|
|
8
9
|
readonly appId?: string;
|
|
9
10
|
readonly appSecret?: string;
|
|
10
11
|
readonly token?: string;
|
|
@@ -31,7 +32,7 @@ export interface WeChatMpAdapterConfig {
|
|
|
31
32
|
}
|
|
32
33
|
export interface ResolvedWeChatMpConfig {
|
|
33
34
|
readonly context: 'wechat-mp';
|
|
34
|
-
readonly
|
|
35
|
+
readonly id: string;
|
|
35
36
|
readonly appId: string;
|
|
36
37
|
readonly appSecret: string;
|
|
37
38
|
readonly token: string;
|
|
@@ -101,6 +102,11 @@ export declare function parseXMLMessage(xmlString: string): Promise<WeChatMessag
|
|
|
101
102
|
export declare function decryptMessage(encryptedXml: string, msgSignature: string, timestamp: string, nonce: string, token: string, encodingAESKey: string, appId: string): Promise<string>;
|
|
102
103
|
export declare function encryptMessage(replyXml: string, token: string, encodingAESKey: string, appId: string, requestTimestamp?: string): string;
|
|
103
104
|
export declare function buildTextReply(wechatMsg: Pick<WeChatMessage, 'FromUserName' | 'ToUserName'>, content: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
107
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
108
|
+
*/
|
|
109
|
+
export declare function wechatMpInboundConversation(endpointKey: string, msg: WeChatMessage): ConversationRef;
|
|
104
110
|
/**
|
|
105
111
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
106
112
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/lib/protocol.js
CHANGED
|
@@ -12,8 +12,8 @@ export function resolveWeChatMpConfig(config = {}) {
|
|
|
12
12
|
if (!appId || !appSecret || !token) {
|
|
13
13
|
throw new TypeError('WeChat MP adapter requires appId + appSecret + token (plugins.<key> or endpoints with context: wechat-mp)');
|
|
14
14
|
}
|
|
15
|
-
const
|
|
16
|
-
|| (typeof entry?.
|
|
15
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
16
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
17
17
|
|| process.env.WECHAT_BOT_NAME
|
|
18
18
|
|| 'wechat-mp-bot';
|
|
19
19
|
const path = config.path ?? entry?.path ?? '/wechat/webhook';
|
|
@@ -26,7 +26,7 @@ export function resolveWeChatMpConfig(config = {}) {
|
|
|
26
26
|
?? 4500;
|
|
27
27
|
return {
|
|
28
28
|
context: 'wechat-mp',
|
|
29
|
-
|
|
29
|
+
id,
|
|
30
30
|
appId,
|
|
31
31
|
appSecret,
|
|
32
32
|
token,
|
|
@@ -170,6 +170,17 @@ export function buildTextReply(wechatMsg, content) {
|
|
|
170
170
|
'</xml>',
|
|
171
171
|
].join('');
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
175
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
176
|
+
*/
|
|
177
|
+
export function wechatMpInboundConversation(endpointKey, msg) {
|
|
178
|
+
return {
|
|
179
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
180
|
+
kind: 'private',
|
|
181
|
+
id: msg.FromUserName,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
173
184
|
/**
|
|
174
185
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
175
186
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/lib/webhook.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
2
|
import { getPassiveReplyCapture, runWithPassiveReplyCapture, } from './passive-reply.js';
|
|
3
|
-
import { buildTextReply, computeSignatureHash, decryptEchostr, decryptMessage, encryptMessage, formatInboundContent, formatInboundId, isEncryptedEchostr, normalizeEchostrParam, parseXMLMessage, queryParam, readTextBody, resolveEventPassiveReply, verifySignature, } from './protocol.js';
|
|
3
|
+
import { buildTextReply, computeSignatureHash, decryptEchostr, decryptMessage, encryptMessage, formatInboundContent, formatInboundId, isEncryptedEchostr, normalizeEchostrParam, parseXMLMessage, queryParam, readTextBody, resolveEventPassiveReply, verifySignature, wechatMpInboundConversation, } from './protocol.js';
|
|
4
4
|
const logger = getLogger('wechat-mp');
|
|
5
5
|
export function registerWeChatMpWebhookRoutes(http, handler) {
|
|
6
6
|
const path = handler.config.path;
|
|
@@ -132,17 +132,17 @@ export async function handleWeChatMpMessage(request, response, url, handler) {
|
|
|
132
132
|
export async function collectPassiveReply(handler, wechatMsg) {
|
|
133
133
|
const timeoutMs = handler.config.passiveReplyTimeoutMs;
|
|
134
134
|
const text = await runWithPassiveReplyCapture(async () => {
|
|
135
|
+
const conversation = wechatMpInboundConversation(String(handler.id), wechatMsg);
|
|
135
136
|
await Promise.race([
|
|
136
137
|
handler.gateway.receive({
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
conversation,
|
|
139
|
+
message: { conversation, id: formatInboundId(wechatMsg) },
|
|
139
140
|
content: formatInboundContent(wechatMsg),
|
|
140
|
-
sender: wechatMsg.FromUserName,
|
|
141
|
-
|
|
141
|
+
sender: { id: wechatMsg.FromUserName },
|
|
142
|
+
endpointId: handler.config.id,
|
|
142
143
|
metadata: Object.freeze({
|
|
143
144
|
msgType: wechatMsg.MsgType,
|
|
144
145
|
event: wechatMsg.Event,
|
|
145
|
-
endpoint: handler.config.name,
|
|
146
146
|
toUserName: wechatMsg.ToUserName,
|
|
147
147
|
}),
|
|
148
148
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-wechat-mp",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zhin.js WeChat Official Account adapter for Plugin Runtime (HTTP webhook)",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -8,19 +8,20 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"axios": "^1.19.0",
|
|
10
10
|
"xml2js": "^0.6.2",
|
|
11
|
-
"@zhin.js/adapter": "1.1.
|
|
12
|
-
"@zhin.js/command": "1.0.
|
|
13
|
-
"@zhin.js/core": "1.5.
|
|
14
|
-
"@zhin.js/host-http": "1.0.
|
|
15
|
-
"@zhin.js/
|
|
16
|
-
"@zhin.js/
|
|
11
|
+
"@zhin.js/adapter": "1.1.7",
|
|
12
|
+
"@zhin.js/command": "1.0.9",
|
|
13
|
+
"@zhin.js/core": "1.5.4",
|
|
14
|
+
"@zhin.js/host-http": "1.0.8",
|
|
15
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
16
|
+
"@zhin.js/logger": "1.0.76",
|
|
17
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
17
18
|
},
|
|
18
19
|
"peerDependencies": {
|
|
19
|
-
"@zhin.js/adapter": "1.1.
|
|
20
|
-
"@zhin.js/core": "1.5.
|
|
21
|
-
"@zhin.js/host-http": "1.0.
|
|
22
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
23
|
-
"zhin.js": "6.0.
|
|
20
|
+
"@zhin.js/adapter": "1.1.7",
|
|
21
|
+
"@zhin.js/core": "1.5.4",
|
|
22
|
+
"@zhin.js/host-http": "1.0.8",
|
|
23
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
24
|
+
"zhin.js": "6.0.4"
|
|
24
25
|
},
|
|
25
26
|
"peerDependenciesMeta": {
|
|
26
27
|
"zhin.js": {
|
package/schema.json
CHANGED
|
@@ -32,16 +32,48 @@
|
|
|
32
32
|
"type": "number",
|
|
33
33
|
"default": 4500
|
|
34
34
|
},
|
|
35
|
+
"master": {
|
|
36
|
+
"type": [
|
|
37
|
+
"string",
|
|
38
|
+
"number"
|
|
39
|
+
],
|
|
40
|
+
"description": "框架 master(WeChat OpenID;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
41
|
+
},
|
|
42
|
+
"trusted": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": {
|
|
45
|
+
"type": [
|
|
46
|
+
"string",
|
|
47
|
+
"number"
|
|
48
|
+
],
|
|
49
|
+
"description": "Trusted WeChat OpenID"
|
|
50
|
+
},
|
|
51
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
52
|
+
},
|
|
35
53
|
"endpoints": {
|
|
36
54
|
"type": "array",
|
|
37
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
55
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
38
56
|
"items": {
|
|
39
57
|
"type": "object",
|
|
40
58
|
"additionalProperties": true,
|
|
41
59
|
"properties": {
|
|
42
|
-
"
|
|
43
|
-
"type":
|
|
44
|
-
|
|
60
|
+
"master": {
|
|
61
|
+
"type": [
|
|
62
|
+
"string",
|
|
63
|
+
"number"
|
|
64
|
+
],
|
|
65
|
+
"description": "本 endpoint 的框架 master(WeChat OpenID);覆盖顶层 master"
|
|
66
|
+
},
|
|
67
|
+
"trusted": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": {
|
|
70
|
+
"type": [
|
|
71
|
+
"string",
|
|
72
|
+
"number"
|
|
73
|
+
],
|
|
74
|
+
"description": "Trusted WeChat OpenID"
|
|
75
|
+
},
|
|
76
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
45
77
|
},
|
|
46
78
|
"appId": {
|
|
47
79
|
"type": "string",
|
|
@@ -58,10 +90,14 @@
|
|
|
58
90
|
"encodingAESKey": {
|
|
59
91
|
"type": "string",
|
|
60
92
|
"description": "WeChat MP encoding AES key"
|
|
93
|
+
},
|
|
94
|
+
"id": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "WeChat MP bot name"
|
|
61
97
|
}
|
|
62
98
|
},
|
|
63
99
|
"required": [
|
|
64
|
-
"
|
|
100
|
+
"id",
|
|
65
101
|
"appId",
|
|
66
102
|
"appSecret",
|
|
67
103
|
"token"
|
package/src/endpoint.ts
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* WeChatMpEndpoint — lifecycle, outbound, admit, access token refresh.
|
|
3
3
|
*/
|
|
4
4
|
import axios from 'axios';
|
|
5
|
-
import type { EndpointFriend, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
5
|
+
import type { EndpointFriend, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
6
6
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
7
7
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
|
-
import { formatCompact,
|
|
8
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
9
9
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
10
10
|
import {
|
|
11
11
|
extractOutboundText,
|
|
12
12
|
formatCustomerServiceBody,
|
|
13
13
|
formatInboundContent,
|
|
14
14
|
formatInboundId,
|
|
15
|
+
wechatMpInboundConversation,
|
|
15
16
|
type ResolvedWeChatMpConfig,
|
|
16
17
|
type TokenResponse,
|
|
17
18
|
type WeChatAPIResponse,
|
|
@@ -29,8 +30,6 @@ import {
|
|
|
29
30
|
} from './passive-reply.js';
|
|
30
31
|
import { registerWeChatMpWebhookRoutes } from './webhook.js';
|
|
31
32
|
|
|
32
|
-
const logger = getLogger('wechat-mp');
|
|
33
|
-
|
|
34
33
|
/** token 失效类错误码:40001/40014 invalid access_token、42001 access_token expired。 */
|
|
35
34
|
const TOKEN_INVALID_ERRCODES = new Set([40001, 40014, 42001]);
|
|
36
35
|
|
|
@@ -71,6 +70,8 @@ function defaultFetch(
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
export class WeChatMpEndpoint implements EndpointInstance {
|
|
73
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
74
|
+
|
|
74
75
|
readonly #options: WeChatMpEndpointOptions;
|
|
75
76
|
readonly #fetch: WeChatMpFetch;
|
|
76
77
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -85,6 +86,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
85
86
|
readonly management: EndpointManagement = createWeChatMpEndpointManagement(this);
|
|
86
87
|
|
|
87
88
|
constructor(options: WeChatMpEndpointOptions) {
|
|
89
|
+
this.#logger = getAdapterLogger('wechat-mp', options.config.id);
|
|
88
90
|
this.#options = options;
|
|
89
91
|
this.#fetch = options.fetch ?? defaultFetch;
|
|
90
92
|
}
|
|
@@ -128,14 +130,14 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
128
130
|
await this.#refreshAccessToken();
|
|
129
131
|
this.#routeReleases.push(...registerWeChatMpWebhookRoutes(this.#options.http, this));
|
|
130
132
|
this.#startTokenRefreshTimer();
|
|
131
|
-
logger.debug(formatCompact({
|
|
132
|
-
endpoint: this.#options.config.
|
|
133
|
+
this.#logger.debug(formatCompact({
|
|
134
|
+
endpoint: this.#options.config.id,
|
|
133
135
|
op: 'webhook',
|
|
134
136
|
path: this.#options.config.path,
|
|
135
137
|
}));
|
|
136
138
|
} catch (error) {
|
|
137
139
|
await this.stop();
|
|
138
|
-
logger.error('Failed to connect WeChat MP bot:', error);
|
|
140
|
+
this.#logger.error('Failed to connect WeChat MP bot:', error);
|
|
139
141
|
throw error;
|
|
140
142
|
}
|
|
141
143
|
}
|
|
@@ -156,10 +158,10 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
156
158
|
}
|
|
157
159
|
for (const release of this.#routeReleases.splice(0)) release();
|
|
158
160
|
this.#started = false;
|
|
159
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
161
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
async send({
|
|
164
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
163
165
|
if (getPassiveReplyCapture()) {
|
|
164
166
|
const text = extractOutboundText(payload);
|
|
165
167
|
recordPassiveReplyText(text);
|
|
@@ -167,14 +169,14 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
if (this.#options.config.replyMode === 'customer_service') {
|
|
170
|
-
return this.#sendCustomerService(
|
|
172
|
+
return this.#sendCustomerService(conversation.id, payload);
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
logger.warn(formatCompact({
|
|
175
|
+
this.#logger.warn(formatCompact({
|
|
174
176
|
op: 'send',
|
|
175
177
|
skip: 'passive_outside_webhook',
|
|
176
|
-
endpoint: this.#options.config.
|
|
177
|
-
target
|
|
178
|
+
endpoint: this.#options.config.id,
|
|
179
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
178
180
|
}));
|
|
179
181
|
return `passive_skipped_${Date.now()}`;
|
|
180
182
|
}
|
|
@@ -182,22 +184,22 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
182
184
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
183
185
|
admit(msg: WeChatMessage): void {
|
|
184
186
|
if (!this.#open) return;
|
|
187
|
+
const conversation = wechatMpInboundConversation(String(this.#options.id), msg);
|
|
185
188
|
void this.#options.gateway.receive({
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
conversation,
|
|
190
|
+
message: { conversation, id: formatInboundId(msg) },
|
|
188
191
|
content: formatInboundContent(msg),
|
|
189
|
-
sender: msg.FromUserName,
|
|
190
|
-
|
|
192
|
+
sender: { id: msg.FromUserName },
|
|
193
|
+
endpointId: this.#options.config.id,
|
|
191
194
|
metadata: Object.freeze({
|
|
192
195
|
msgType: msg.MsgType,
|
|
193
196
|
event: msg.Event,
|
|
194
|
-
endpoint: this.#options.config.name,
|
|
195
197
|
toUserName: msg.ToUserName,
|
|
196
198
|
}),
|
|
197
199
|
}).catch((err) => {
|
|
198
|
-
logger.warn(formatCompact({
|
|
200
|
+
this.#logger.warn(formatCompact({
|
|
199
201
|
op: 'wechat_mp_gateway_receive_failed',
|
|
200
|
-
target:
|
|
202
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
201
203
|
error: err instanceof Error ? err.message : String(err),
|
|
202
204
|
}));
|
|
203
205
|
});
|
|
@@ -213,7 +215,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
213
215
|
let result = await this.#postCustomerService(messageData);
|
|
214
216
|
if (result.errcode && TOKEN_INVALID_ERRCODES.has(Number(result.errcode))) {
|
|
215
217
|
// 对端提前作废 token(多端共用等):刷新后重试一次。
|
|
216
|
-
logger.warn(formatCompact({
|
|
218
|
+
this.#logger.warn(formatCompact({
|
|
217
219
|
op: 'wechat_mp_token_invalid_retry',
|
|
218
220
|
errcode: result.errcode,
|
|
219
221
|
}));
|
|
@@ -223,7 +225,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
223
225
|
if (result.errcode && result.errcode !== 0) {
|
|
224
226
|
throw new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`);
|
|
225
227
|
}
|
|
226
|
-
logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
|
|
228
|
+
this.#logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
|
|
227
229
|
return result.msgid?.toString() || `cs_${Date.now()}`;
|
|
228
230
|
}
|
|
229
231
|
|
|
@@ -249,9 +251,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
249
251
|
// 已物化(mediaId/media_id)的段透传;其余无 canonical 媒体引用,丢弃留痕
|
|
250
252
|
if (typeof data.mediaId === 'string' && data.mediaId) return item;
|
|
251
253
|
if (typeof data.media_id === 'string' && data.media_id) return item;
|
|
252
|
-
logger.warn(formatCompact({
|
|
254
|
+
this.#logger.warn(formatCompact({
|
|
253
255
|
op: 'wechat_mp_outbound_media_dropped',
|
|
254
|
-
endpoint: this.#options.config.
|
|
256
|
+
endpoint: this.#options.config.id,
|
|
255
257
|
type: seg.type,
|
|
256
258
|
reason: 'missing_media_ref',
|
|
257
259
|
}));
|
|
@@ -262,9 +264,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
262
264
|
return { type: seg.type, data: { mediaId: media.value } };
|
|
263
265
|
}
|
|
264
266
|
if (!uploadType) {
|
|
265
|
-
logger.warn(formatCompact({
|
|
267
|
+
this.#logger.warn(formatCompact({
|
|
266
268
|
op: 'wechat_mp_outbound_media_dropped',
|
|
267
|
-
endpoint: this.#options.config.
|
|
269
|
+
endpoint: this.#options.config.id,
|
|
268
270
|
type: seg.type,
|
|
269
271
|
reason: 'unsupported_segment_type',
|
|
270
272
|
}));
|
|
@@ -274,9 +276,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
274
276
|
const mediaId = await this.#uploadMedia(uploadType, media);
|
|
275
277
|
return { type: seg.type, data: { mediaId } };
|
|
276
278
|
} catch (error) {
|
|
277
|
-
logger.warn(formatCompact({
|
|
279
|
+
this.#logger.warn(formatCompact({
|
|
278
280
|
op: 'wechat_mp_media_upload_failed',
|
|
279
|
-
endpoint: this.#options.config.
|
|
281
|
+
endpoint: this.#options.config.id,
|
|
280
282
|
error: error instanceof Error ? error.message : String(error),
|
|
281
283
|
}));
|
|
282
284
|
const alt = typeof data.alt === 'string' && data.alt ? data.alt : `[${seg.type}]`;
|
|
@@ -349,7 +351,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
349
351
|
if (data.access_token) {
|
|
350
352
|
this.#accessToken = data.access_token;
|
|
351
353
|
this.#tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000;
|
|
352
|
-
logger.debug(formatCompact({ op: 'token_refresh' }));
|
|
354
|
+
this.#logger.debug(formatCompact({ op: 'token_refresh' }));
|
|
353
355
|
return;
|
|
354
356
|
}
|
|
355
357
|
throw new Error(
|
|
@@ -363,7 +365,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
363
365
|
this.#tokenRefreshTimer = setInterval(() => {
|
|
364
366
|
if (Date.now() >= this.#tokenExpireTime) {
|
|
365
367
|
void this.#refreshAccessToken().catch((error) => {
|
|
366
|
-
logger.error('Failed to refresh access token in timer:', error);
|
|
368
|
+
this.#logger.error('Failed to refresh access token in timer:', error);
|
|
367
369
|
});
|
|
368
370
|
}
|
|
369
371
|
}, 3_600_000);
|
package/src/protocol.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
import { createHash, createDecipheriv, createCipheriv, randomBytes } from 'node:crypto';
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
8
|
import * as xml2js from 'xml2js';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
|
|
10
11
|
export interface WeChatMpAdapterConfig {
|
|
11
|
-
readonly
|
|
12
|
+
readonly id?: string;
|
|
12
13
|
readonly appId?: string;
|
|
13
14
|
readonly appSecret?: string;
|
|
14
15
|
readonly token?: string;
|
|
@@ -36,7 +37,7 @@ export interface WeChatMpAdapterConfig {
|
|
|
36
37
|
|
|
37
38
|
export interface ResolvedWeChatMpConfig {
|
|
38
39
|
readonly context: 'wechat-mp';
|
|
39
|
-
readonly
|
|
40
|
+
readonly id: string;
|
|
40
41
|
readonly appId: string;
|
|
41
42
|
readonly appSecret: string;
|
|
42
43
|
readonly token: string;
|
|
@@ -98,8 +99,8 @@ export function resolveWeChatMpConfig(config: WeChatMpAdapterConfig = {}): Resol
|
|
|
98
99
|
'WeChat MP adapter requires appId + appSecret + token (plugins.<key> or endpoints with context: wechat-mp)',
|
|
99
100
|
);
|
|
100
101
|
}
|
|
101
|
-
const
|
|
102
|
-
|| (typeof entry?.
|
|
102
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
103
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
103
104
|
|| process.env.WECHAT_BOT_NAME
|
|
104
105
|
|| 'wechat-mp-bot';
|
|
105
106
|
const path = config.path ?? entry?.path ?? '/wechat/webhook';
|
|
@@ -112,7 +113,7 @@ export function resolveWeChatMpConfig(config: WeChatMpAdapterConfig = {}): Resol
|
|
|
112
113
|
?? 4500;
|
|
113
114
|
return {
|
|
114
115
|
context: 'wechat-mp',
|
|
115
|
-
|
|
116
|
+
id,
|
|
116
117
|
appId,
|
|
117
118
|
appSecret,
|
|
118
119
|
token,
|
|
@@ -310,6 +311,21 @@ export function buildTextReply(
|
|
|
310
311
|
].join('');
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
/**
|
|
315
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
316
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
317
|
+
*/
|
|
318
|
+
export function wechatMpInboundConversation(
|
|
319
|
+
endpointKey: string,
|
|
320
|
+
msg: WeChatMessage,
|
|
321
|
+
): ConversationRef {
|
|
322
|
+
return {
|
|
323
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
324
|
+
kind: 'private',
|
|
325
|
+
id: msg.FromUserName,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
313
329
|
/**
|
|
314
330
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
315
331
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/src/webhook.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
readTextBody,
|
|
26
26
|
resolveEventPassiveReply,
|
|
27
27
|
verifySignature,
|
|
28
|
+
wechatMpInboundConversation,
|
|
28
29
|
type ResolvedWeChatMpConfig,
|
|
29
30
|
type WeChatMessage,
|
|
30
31
|
} from './protocol.js';
|
|
@@ -218,17 +219,17 @@ export async function collectPassiveReply(
|
|
|
218
219
|
): Promise<string> {
|
|
219
220
|
const timeoutMs = handler.config.passiveReplyTimeoutMs;
|
|
220
221
|
const text = await runWithPassiveReplyCapture(async () => {
|
|
222
|
+
const conversation = wechatMpInboundConversation(String(handler.id), wechatMsg);
|
|
221
223
|
await Promise.race([
|
|
222
224
|
handler.gateway.receive({
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
conversation,
|
|
226
|
+
message: { conversation, id: formatInboundId(wechatMsg) },
|
|
225
227
|
content: formatInboundContent(wechatMsg),
|
|
226
|
-
sender: wechatMsg.FromUserName,
|
|
227
|
-
|
|
228
|
+
sender: { id: wechatMsg.FromUserName },
|
|
229
|
+
endpointId: handler.config.id,
|
|
228
230
|
metadata: Object.freeze({
|
|
229
231
|
msgType: wechatMsg.MsgType,
|
|
230
232
|
event: wechatMsg.Event,
|
|
231
|
-
endpoint: handler.config.name,
|
|
232
233
|
toUserName: wechatMsg.ToUserName,
|
|
233
234
|
}),
|
|
234
235
|
}),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|