@zhin.js/adapter-wecom 4.0.2 → 4.0.5
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 +95 -0
- package/README.md +2 -2
- package/adapters/wecom.js +2 -2
- package/adapters/wecom.ts +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +38 -28
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +8 -2
- package/lib/protocol.js +14 -3
- package/lib/wecom-agent-deps.d.ts +2 -2
- package/lib/wecom-agent-deps.js +7 -7
- package/package.json +17 -15
- package/plugin.js +6 -3
- package/schema.json +41 -5
- package/src/endpoint.ts +40 -29
- package/src/index.ts +0 -1
- package/src/platform-permit.ts +4 -8
- package/src/protocol.ts +18 -5
- package/src/wecom-agent-deps.ts +8 -8
- /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,100 @@
|
|
|
1
1
|
# @zhin.js/adapter-wecom
|
|
2
2
|
|
|
3
|
+
## 4.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
|
|
66
|
+
## 4.0.4
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- f8c7a54: fix: im
|
|
71
|
+
- Updated dependencies [f8c7a54]
|
|
72
|
+
- @zhin.js/logger@1.0.76
|
|
73
|
+
- @zhin.js/host-http@1.0.7
|
|
74
|
+
- @zhin.js/adapter@1.1.6
|
|
75
|
+
- @zhin.js/agent@1.1.4
|
|
76
|
+
- @zhin.js/command@1.0.8
|
|
77
|
+
- @zhin.js/core@1.5.3
|
|
78
|
+
- @zhin.js/im-contract@1.0.2
|
|
79
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
80
|
+
- zhin.js@6.0.3
|
|
81
|
+
|
|
82
|
+
## 4.0.3
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [afc0e66]
|
|
87
|
+
- Updated dependencies [2e41ad5]
|
|
88
|
+
- Updated dependencies [9f57124]
|
|
89
|
+
- @zhin.js/core@1.5.2
|
|
90
|
+
- @zhin.js/adapter@1.1.5
|
|
91
|
+
- @zhin.js/im-contract@1.0.1
|
|
92
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
93
|
+
- @zhin.js/command@1.0.7
|
|
94
|
+
- @zhin.js/host-http@1.0.6
|
|
95
|
+
- @zhin.js/agent@1.1.3
|
|
96
|
+
- zhin.js@6.0.2
|
|
97
|
+
|
|
3
98
|
## 4.0.2
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ pnpm add @zhin.js/adapter-wecom
|
|
|
23
23
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
24
24
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
25
25
|
|
|
26
|
-
入站:`gateway.receive({
|
|
27
|
-
出站:`send({
|
|
26
|
+
入站:`gateway.receive({ conversation, message: { conversation, id: MsgId }, content: text, sender, metadata })`(`conversation` 为 `ConversationRef`,`@chatroom` → kind `group`,其余 → kind `private`)
|
|
27
|
+
出站:`send({ conversation, payload })` → 企业微信 `message/send` API
|
|
28
28
|
|
|
29
29
|
入站 `metadata.mentioned`:**未接线**。企业微信应用消息回调的 XML 事件不含 mentions/@ 字段,回调里的 `ToUserName` 是 CorpID(企业 ID)而非可比较的 bot 用户 id,配置中也没有 bot id/name 可作可靠判据,故无法可靠识别 @ 机器人。
|
|
30
30
|
|
package/adapters/wecom.js
CHANGED
|
@@ -21,8 +21,8 @@ export default defineAdapter({
|
|
|
21
21
|
create(context) {
|
|
22
22
|
const config = resolveWecomConfig(context.config);
|
|
23
23
|
// 注册到插件运行时状态(wecom.endpoint list 的"运行中"数据源)
|
|
24
|
-
context.use(wecomRuntimeStateToken).endpoints.set(config.
|
|
25
|
-
|
|
24
|
+
context.use(wecomRuntimeStateToken).endpoints.set(config.id, {
|
|
25
|
+
id: config.id,
|
|
26
26
|
mode: 'webhook',
|
|
27
27
|
});
|
|
28
28
|
return new WecomEndpoint({
|
package/adapters/wecom.ts
CHANGED
|
@@ -26,8 +26,8 @@ export default defineAdapter<WecomAdapterConfig>({
|
|
|
26
26
|
create(context) {
|
|
27
27
|
const config = resolveWecomConfig(context.config);
|
|
28
28
|
// 注册到插件运行时状态(wecom.endpoint list 的"运行中"数据源)
|
|
29
|
-
context.use(wecomRuntimeStateToken).endpoints.set(config.
|
|
30
|
-
|
|
29
|
+
context.use(wecomRuntimeStateToken).endpoints.set(config.id, {
|
|
30
|
+
id: config.id,
|
|
31
31
|
mode: 'webhook',
|
|
32
32
|
});
|
|
33
33
|
return new WecomEndpoint({
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WecomEndpoint — lifecycle, outbound send, inbound admit, OpenAPI helpers for agent tools.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
7
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -38,10 +38,7 @@ export declare class WecomEndpoint implements EndpointInstance {
|
|
|
38
38
|
open(): void;
|
|
39
39
|
close(): void;
|
|
40
40
|
stop(): Promise<void>;
|
|
41
|
-
send({
|
|
42
|
-
readonly target: string;
|
|
43
|
-
readonly payload: unknown;
|
|
44
|
-
}): Promise<string>;
|
|
41
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
45
42
|
recallMessage(messageId: string): Promise<void>;
|
|
46
43
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
47
44
|
admit(msg: WecomMessage): void;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { formatCompact,
|
|
1
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
2
2
|
import { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
|
|
3
3
|
import { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
|
|
4
|
-
import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, } from './protocol.js';
|
|
4
|
+
import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, wecomInboundConversation, } from './protocol.js';
|
|
5
5
|
import { registerWecomWebhookRoutes } from './webhook.js';
|
|
6
|
-
const logger = getLogger('wecom');
|
|
7
6
|
/**
|
|
8
7
|
* 企业微信服务端 API 无 bot 群列表/群成员列表接口(客户群接口属「客户联系」
|
|
9
8
|
* 独立授权域,非 bot 社交面),好友/频道概念亦不存在;
|
|
10
9
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
11
10
|
*/
|
|
12
11
|
export class WecomEndpoint {
|
|
12
|
+
#logger;
|
|
13
13
|
#options;
|
|
14
14
|
#fetch;
|
|
15
15
|
#routeReleases = [];
|
|
@@ -19,6 +19,7 @@ export class WecomEndpoint {
|
|
|
19
19
|
#started = false;
|
|
20
20
|
#unregisterAgent;
|
|
21
21
|
constructor(options) {
|
|
22
|
+
this.#logger = getAdapterLogger('wecom', options.config.id);
|
|
22
23
|
this.#options = options;
|
|
23
24
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
24
25
|
}
|
|
@@ -35,17 +36,17 @@ export class WecomEndpoint {
|
|
|
35
36
|
this.#started = true;
|
|
36
37
|
try {
|
|
37
38
|
await this.#refreshAccessToken();
|
|
38
|
-
this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.
|
|
39
|
+
this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
|
|
39
40
|
this.#routeReleases.push(...registerWecomWebhookRoutes(this.#options.http, this));
|
|
40
|
-
logger.debug(formatCompact({
|
|
41
|
-
endpoint: this.#options.config.
|
|
41
|
+
this.#logger.debug(formatCompact({
|
|
42
|
+
endpoint: this.#options.config.id,
|
|
42
43
|
op: 'webhook',
|
|
43
44
|
path: this.#options.config.webhookPath,
|
|
44
45
|
}));
|
|
45
46
|
}
|
|
46
47
|
catch (error) {
|
|
47
48
|
await this.stop();
|
|
48
|
-
logger.error('Failed to connect WeCom endpoint:', error);
|
|
49
|
+
this.#logger.error('Failed to connect WeCom endpoint:', error);
|
|
49
50
|
throw error;
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -62,12 +63,12 @@ export class WecomEndpoint {
|
|
|
62
63
|
this.#unregisterAgent?.();
|
|
63
64
|
this.#unregisterAgent = undefined;
|
|
64
65
|
this.#started = false;
|
|
65
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
66
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
66
67
|
}
|
|
67
|
-
async send({
|
|
68
|
+
async send({ conversation, payload }) {
|
|
68
69
|
const materialized = await this.#materializeOutboundMedia(payload);
|
|
69
70
|
const content = formatOutboundBody(materialized);
|
|
70
|
-
const body = buildSendRequestBody(
|
|
71
|
+
const body = buildSendRequestBody(conversation.id, content,
|
|
71
72
|
// Legacy field: historically written into agentid (preserved for cutover).
|
|
72
73
|
this.#options.config.agentSecret);
|
|
73
74
|
const data = await this.#request('/cgi-bin/message/send', {
|
|
@@ -77,7 +78,8 @@ export class WecomEndpoint {
|
|
|
77
78
|
if (data.errcode !== 0) {
|
|
78
79
|
throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
|
|
79
80
|
}
|
|
80
|
-
logger.debug(formatCompact({ op: 'send',
|
|
81
|
+
this.#logger.debug(formatCompact({ op: 'send', to: `${conversation.kind}:${conversation.id}`,
|
|
82
|
+
}));
|
|
81
83
|
return data.msgid || `${Date.now()}`;
|
|
82
84
|
}
|
|
83
85
|
async recallMessage(messageId) {
|
|
@@ -106,9 +108,9 @@ export class WecomEndpoint {
|
|
|
106
108
|
const data = seg.data ?? {};
|
|
107
109
|
const media = readOutboundImageMedia(data);
|
|
108
110
|
if (!media) {
|
|
109
|
-
logger.warn(formatCompact({
|
|
111
|
+
this.#logger.warn(formatCompact({
|
|
110
112
|
op: 'wecom_outbound_media_dropped',
|
|
111
|
-
endpoint: this.#options.config.
|
|
113
|
+
endpoint: this.#options.config.id,
|
|
112
114
|
type: 'image',
|
|
113
115
|
reason: 'missing_media_ref',
|
|
114
116
|
}));
|
|
@@ -122,9 +124,9 @@ export class WecomEndpoint {
|
|
|
122
124
|
return { type: 'image', data: { media_id: mediaId } };
|
|
123
125
|
}
|
|
124
126
|
catch (error) {
|
|
125
|
-
logger.warn(formatCompact({
|
|
127
|
+
this.#logger.warn(formatCompact({
|
|
126
128
|
op: 'wecom_media_upload_failed',
|
|
127
|
-
endpoint: this.#options.config.
|
|
129
|
+
endpoint: this.#options.config.id,
|
|
128
130
|
error: error instanceof Error ? error.message : String(error),
|
|
129
131
|
}));
|
|
130
132
|
const alt = typeof data.alt === 'string' && data.alt ? data.alt : '[image]';
|
|
@@ -150,24 +152,24 @@ export class WecomEndpoint {
|
|
|
150
152
|
if (!this.#open)
|
|
151
153
|
return;
|
|
152
154
|
const chatType = resolveChatType(msg.FromUserName);
|
|
155
|
+
const conversation = wecomInboundConversation(String(this.#options.id), msg);
|
|
153
156
|
void this.#options.gateway.receive({
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
conversation,
|
|
158
|
+
message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
|
|
156
159
|
content: formatInboundContent(msg),
|
|
157
|
-
sender: msg.FromUserName,
|
|
158
|
-
|
|
160
|
+
sender: { id: msg.FromUserName },
|
|
161
|
+
endpointId: this.#options.config.id,
|
|
159
162
|
metadata: Object.freeze({
|
|
160
163
|
msgType: msg.MsgType,
|
|
161
164
|
event: msg.Event,
|
|
162
165
|
chatType,
|
|
163
|
-
endpoint: this.#options.config.name,
|
|
164
166
|
toUserName: msg.ToUserName,
|
|
165
167
|
agentId: msg.AgentID,
|
|
166
168
|
}),
|
|
167
169
|
}).catch((err) => {
|
|
168
|
-
logger.warn(formatCompact({
|
|
170
|
+
this.#logger.warn(formatCompact({
|
|
169
171
|
op: 'wecom_gateway_receive_failed',
|
|
170
|
-
target:
|
|
172
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
171
173
|
error: err instanceof Error ? err.message : String(err),
|
|
172
174
|
}));
|
|
173
175
|
});
|
|
@@ -182,7 +184,7 @@ export class WecomEndpoint {
|
|
|
182
184
|
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
183
185
|
}
|
|
184
186
|
catch (error) {
|
|
185
|
-
logger.error('Failed to get user info:', error);
|
|
187
|
+
this.#logger.error('Failed to get user info:', error);
|
|
186
188
|
return null;
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -196,7 +198,7 @@ export class WecomEndpoint {
|
|
|
196
198
|
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
197
199
|
}
|
|
198
200
|
catch (error) {
|
|
199
|
-
logger.error('Failed to get department users:', error);
|
|
201
|
+
this.#logger.error('Failed to get department users:', error);
|
|
200
202
|
return [];
|
|
201
203
|
}
|
|
202
204
|
}
|
|
@@ -210,17 +212,25 @@ export class WecomEndpoint {
|
|
|
210
212
|
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
211
213
|
}
|
|
212
214
|
catch (error) {
|
|
213
|
-
logger.error('Failed to get department list:', error);
|
|
215
|
+
this.#logger.error('Failed to get department list:', error);
|
|
214
216
|
return [];
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
async sendTextMessage(userId, content) {
|
|
218
220
|
try {
|
|
219
|
-
|
|
221
|
+
const endpointKey = String(this.#options.id);
|
|
222
|
+
await this.send({
|
|
223
|
+
conversation: {
|
|
224
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
225
|
+
kind: resolveChatType(userId),
|
|
226
|
+
id: userId,
|
|
227
|
+
},
|
|
228
|
+
payload: content,
|
|
229
|
+
});
|
|
220
230
|
return true;
|
|
221
231
|
}
|
|
222
232
|
catch (error) {
|
|
223
|
-
logger.error('Failed to send text message:', error);
|
|
233
|
+
this.#logger.error('Failed to send text message:', error);
|
|
224
234
|
return false;
|
|
225
235
|
}
|
|
226
236
|
}
|
|
@@ -269,7 +279,7 @@ export class WecomEndpoint {
|
|
|
269
279
|
expires_in: data.expires_in ?? 7200,
|
|
270
280
|
timestamp: Date.now(),
|
|
271
281
|
};
|
|
272
|
-
logger.debug('Access token refreshed successfully');
|
|
282
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
273
283
|
return;
|
|
274
284
|
}
|
|
275
285
|
throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, type AccessToken, type ResolvedWecomConfig, type WecomAdapterConfig, type WecomApiResponse, type WecomMessage, type WecomSendBody, type WecomWireSegment, } from './protocol.js';
|
|
2
2
|
export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, type WecomAgentDeps, type WecomAgentEndpoint, } from './wecom-agent-deps.js';
|
|
3
|
-
export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit,
|
|
3
|
+
export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
|
|
4
4
|
export { WecomEndpoint, type WecomEndpointOptions, type WecomFetch, } from './endpoint.js';
|
|
5
5
|
export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, type MediaBinary, type WecomMediaUploadResult, } from './media-upload.js';
|
|
6
6
|
export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, type WecomWebhookHandler, } from './webhook.js';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, } from './protocol.js';
|
|
2
2
|
export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, } from './wecom-agent-deps.js';
|
|
3
|
-
export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit,
|
|
3
|
+
export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
|
|
4
4
|
export { WecomEndpoint, } from './endpoint.js';
|
|
5
5
|
export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
|
|
6
6
|
export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, } from './webhook.js';
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 企业微信 WeCom platform permit
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function wecomGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeWecomSenderForPermit(input: {
|
|
@@ -11,5 +11,4 @@ export declare function normalizeWecomSenderForPermit(input: {
|
|
|
11
11
|
role?: string;
|
|
12
12
|
permissions?: string[];
|
|
13
13
|
};
|
|
14
|
-
export declare function checkWecomPlatformPermit(perm: string,
|
|
15
|
-
export declare function registerWecomPlatformPermitChecker(): () => void;
|
|
14
|
+
export declare function checkWecomPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 企业微信 WeCom platform permit
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'wecom';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -22,10 +18,9 @@ export function normalizeWecomSenderForPermit(input) {
|
|
|
22
18
|
}
|
|
23
19
|
return { role: 'member', permissions: [] };
|
|
24
20
|
}
|
|
25
|
-
export function checkWecomPlatformPermit(perm,
|
|
26
|
-
const
|
|
27
|
-
const permissions = sender
|
|
28
|
-
const role = sender.role;
|
|
21
|
+
export function checkWecomPlatformPermit(perm, subject) {
|
|
22
|
+
const role = subject.sender?.role?.[0];
|
|
23
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
29
24
|
const has = (t) => permissions.includes(t);
|
|
30
25
|
switch (perm) {
|
|
31
26
|
case 'chat_owner':
|
|
@@ -36,6 +31,3 @@ export function checkWecomPlatformPermit(perm, message) {
|
|
|
36
31
|
return false;
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
|
-
export function registerWecomPlatformPermitChecker() {
|
|
40
|
-
return registerPlatformPermitChecker(ADAPTER, checkWecomPlatformPermit);
|
|
41
|
-
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
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
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
7
8
|
export interface WecomAdapterConfig {
|
|
8
|
-
readonly
|
|
9
|
+
readonly id?: string;
|
|
9
10
|
readonly corpId?: string;
|
|
10
11
|
readonly agentSecret?: string;
|
|
11
12
|
readonly token?: string;
|
|
@@ -19,7 +20,7 @@ export interface WecomAdapterConfig {
|
|
|
19
20
|
}
|
|
20
21
|
export interface ResolvedWecomConfig {
|
|
21
22
|
readonly context: 'wecom';
|
|
22
|
-
readonly
|
|
23
|
+
readonly id: string;
|
|
23
24
|
readonly corpId: string;
|
|
24
25
|
readonly agentSecret: string;
|
|
25
26
|
readonly token: string;
|
|
@@ -86,6 +87,11 @@ export declare function parseXmlMessage(xml: string): WecomMessage | null;
|
|
|
86
87
|
/** Build inbound text for MessageGateway.receive. */
|
|
87
88
|
export declare function formatInboundContent(msg: WecomMessage): string;
|
|
88
89
|
export declare function resolveChatType(fromUserName: string): 'group' | 'private';
|
|
90
|
+
/**
|
|
91
|
+
* 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
|
|
92
|
+
* `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
|
|
93
|
+
*/
|
|
94
|
+
export declare function wecomInboundConversation(endpointKey: string, msg: WecomMessage): ConversationRef;
|
|
89
95
|
/**
|
|
90
96
|
* Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
|
|
91
97
|
*/
|
package/lib/protocol.js
CHANGED
|
@@ -14,13 +14,13 @@ export function resolveWecomConfig(config = {}) {
|
|
|
14
14
|
if (!corpId || !agentSecret || !token || !encodingAESKey) {
|
|
15
15
|
throw new TypeError('WeCom adapter requires corpId + agentSecret + token + encodingAESKey (plugins.<key> or endpoints with context: wecom)');
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
|| (typeof entry?.
|
|
17
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
18
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
19
19
|
|| process.env.WECOM_BOT_NAME
|
|
20
20
|
|| 'wecom-bot';
|
|
21
21
|
return {
|
|
22
22
|
context: 'wecom',
|
|
23
|
-
|
|
23
|
+
id,
|
|
24
24
|
corpId,
|
|
25
25
|
agentSecret,
|
|
26
26
|
token,
|
|
@@ -155,6 +155,17 @@ export function formatInboundContent(msg) {
|
|
|
155
155
|
export function resolveChatType(fromUserName) {
|
|
156
156
|
return fromUserName.endsWith('@chatroom') ? 'group' : 'private';
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
|
|
160
|
+
* `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
|
|
161
|
+
*/
|
|
162
|
+
export function wecomInboundConversation(endpointKey, msg) {
|
|
163
|
+
return {
|
|
164
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
165
|
+
kind: resolveChatType(msg.FromUserName),
|
|
166
|
+
id: msg.FromUserName,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
158
169
|
/**
|
|
159
170
|
* Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
|
|
160
171
|
*/
|
|
@@ -9,9 +9,9 @@ export interface WecomAgentEndpoint {
|
|
|
9
9
|
sendTextMessage(userId: string, content: string): Promise<boolean>;
|
|
10
10
|
}
|
|
11
11
|
export interface WecomAgentDeps {
|
|
12
|
-
getEndpoint: (
|
|
12
|
+
getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
|
|
13
13
|
}
|
|
14
|
-
export declare function registerWecomAgentEndpoint(
|
|
14
|
+
export declare function registerWecomAgentEndpoint(endpointKey: string, endpoint: WecomAgentEndpoint): () => void;
|
|
15
15
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
16
|
export declare function setWecomAgentDeps(deps: WecomAgentDeps | null): void;
|
|
17
17
|
export declare function getWecomAgentDeps(): WecomAgentDeps;
|
package/lib/wecom-agent-deps.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerWecomAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerWecomAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -20,10 +20,10 @@ export function getWecomAgentDeps() {
|
|
|
20
20
|
if (override)
|
|
21
21
|
return override;
|
|
22
22
|
return {
|
|
23
|
-
getEndpoint(
|
|
24
|
-
const endpoint = endpoints.get(
|
|
23
|
+
getEndpoint(endpointKey) {
|
|
24
|
+
const endpoint = endpoints.get(endpointKey);
|
|
25
25
|
if (!endpoint)
|
|
26
|
-
throw new Error(`Endpoint ${
|
|
26
|
+
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
27
27
|
return endpoint;
|
|
28
28
|
},
|
|
29
29
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-wecom",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"description": "Zhin.js WeCom (企业微信) adapter for Plugin Runtime (HTTP webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,21 +34,23 @@
|
|
|
34
34
|
"directory": "plugins/adapters/wecom"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/command": "1.0.
|
|
39
|
-
"@zhin.js/core": "1.5.
|
|
40
|
-
"@zhin.js/host-http": "1.0.
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
37
|
+
"@zhin.js/adapter": "1.1.7",
|
|
38
|
+
"@zhin.js/command": "1.0.9",
|
|
39
|
+
"@zhin.js/core": "1.5.4",
|
|
40
|
+
"@zhin.js/host-http": "1.0.8",
|
|
41
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
42
|
+
"@zhin.js/logger": "1.0.76",
|
|
43
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "1.1.
|
|
47
|
-
"@zhin.js/agent": "1.1.
|
|
48
|
-
"@zhin.js/core": "1.5.
|
|
49
|
-
"@zhin.js/host-http": "1.0.
|
|
50
|
-
"@zhin.js/
|
|
51
|
-
"zhin.js": "
|
|
47
|
+
"@zhin.js/adapter": "1.1.7",
|
|
48
|
+
"@zhin.js/agent": "1.1.5",
|
|
49
|
+
"@zhin.js/core": "1.5.4",
|
|
50
|
+
"@zhin.js/host-http": "1.0.8",
|
|
51
|
+
"@zhin.js/permission": "1.0.1",
|
|
52
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
53
|
+
"zhin.js": "6.0.4"
|
|
52
54
|
},
|
|
53
55
|
"peerDependenciesMeta": {
|
|
54
56
|
"zhin.js": {
|
|
@@ -66,8 +68,8 @@
|
|
|
66
68
|
"typescript": "^6.0.3",
|
|
67
69
|
"vitest": "^4.1.10",
|
|
68
70
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/agent": "1.1.
|
|
70
|
-
"zhin.js": "6.0.
|
|
71
|
+
"@zhin.js/agent": "1.1.5",
|
|
72
|
+
"zhin.js": "6.0.4"
|
|
71
73
|
},
|
|
72
74
|
"files": [
|
|
73
75
|
"adapters",
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
3
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
|
+
import { checkWecomPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { wecomRuntimeStateToken } from "./lib/wecom-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'wecom',
|
|
@@ -9,8 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'WeCom (企业微信) Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(wecom.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(wecomRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('wecom', checkWecomPlatformPermit);
|
|
17
|
+
}
|
|
15
18
|
},
|
|
16
19
|
});
|
package/schema.json
CHANGED
|
@@ -11,16 +11,48 @@
|
|
|
11
11
|
"type": "string",
|
|
12
12
|
"default": "https://qyapi.weixin.qq.com"
|
|
13
13
|
},
|
|
14
|
+
"master": {
|
|
15
|
+
"type": [
|
|
16
|
+
"string",
|
|
17
|
+
"number"
|
|
18
|
+
],
|
|
19
|
+
"description": "框架 master(WeCom userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
20
|
+
},
|
|
21
|
+
"trusted": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": [
|
|
25
|
+
"string",
|
|
26
|
+
"number"
|
|
27
|
+
],
|
|
28
|
+
"description": "Trusted WeCom userid"
|
|
29
|
+
},
|
|
30
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
31
|
+
},
|
|
14
32
|
"endpoints": {
|
|
15
33
|
"type": "array",
|
|
16
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
34
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
17
35
|
"items": {
|
|
18
36
|
"type": "object",
|
|
19
37
|
"additionalProperties": true,
|
|
20
38
|
"properties": {
|
|
21
|
-
"
|
|
22
|
-
"type":
|
|
23
|
-
|
|
39
|
+
"master": {
|
|
40
|
+
"type": [
|
|
41
|
+
"string",
|
|
42
|
+
"number"
|
|
43
|
+
],
|
|
44
|
+
"description": "本 endpoint 的框架 master(WeCom userid);覆盖顶层 master"
|
|
45
|
+
},
|
|
46
|
+
"trusted": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": [
|
|
50
|
+
"string",
|
|
51
|
+
"number"
|
|
52
|
+
],
|
|
53
|
+
"description": "Trusted WeCom userid"
|
|
54
|
+
},
|
|
55
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
24
56
|
},
|
|
25
57
|
"corpId": {
|
|
26
58
|
"type": "string",
|
|
@@ -37,10 +69,14 @@
|
|
|
37
69
|
"encodingAESKey": {
|
|
38
70
|
"type": "string",
|
|
39
71
|
"description": "WeCom encoding AES key"
|
|
72
|
+
},
|
|
73
|
+
"id": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "WeCom bot name"
|
|
40
76
|
}
|
|
41
77
|
},
|
|
42
78
|
"required": [
|
|
43
|
-
"
|
|
79
|
+
"id",
|
|
44
80
|
"corpId",
|
|
45
81
|
"agentSecret",
|
|
46
82
|
"token",
|
package/src/endpoint.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WecomEndpoint — lifecycle, outbound send, inbound admit, OpenAPI helpers for agent tools.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
7
|
-
import { formatCompact,
|
|
7
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
8
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
9
9
|
import { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
|
|
10
10
|
import {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
formatInboundContent,
|
|
19
19
|
formatOutboundBody,
|
|
20
20
|
resolveChatType,
|
|
21
|
+
wecomInboundConversation,
|
|
21
22
|
type AccessToken,
|
|
22
23
|
type ResolvedWecomConfig,
|
|
23
24
|
type WecomApiResponse,
|
|
@@ -25,8 +26,6 @@ import {
|
|
|
25
26
|
} from './protocol.js';
|
|
26
27
|
import { registerWecomWebhookRoutes } from './webhook.js';
|
|
27
28
|
|
|
28
|
-
const logger = getLogger('wecom');
|
|
29
|
-
|
|
30
29
|
export type WecomFetch = (
|
|
31
30
|
url: string,
|
|
32
31
|
init?: {
|
|
@@ -55,6 +54,8 @@ export interface WecomEndpointOptions {
|
|
|
55
54
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
56
55
|
*/
|
|
57
56
|
export class WecomEndpoint implements EndpointInstance {
|
|
57
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
58
|
+
|
|
58
59
|
readonly #options: WecomEndpointOptions;
|
|
59
60
|
readonly #fetch: WecomFetch;
|
|
60
61
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -65,6 +66,7 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
65
66
|
#unregisterAgent?: () => void;
|
|
66
67
|
|
|
67
68
|
constructor(options: WecomEndpointOptions) {
|
|
69
|
+
this.#logger = getAdapterLogger('wecom', options.config.id);
|
|
68
70
|
this.#options = options;
|
|
69
71
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
70
72
|
}
|
|
@@ -83,16 +85,16 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
83
85
|
this.#started = true;
|
|
84
86
|
try {
|
|
85
87
|
await this.#refreshAccessToken();
|
|
86
|
-
this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.
|
|
88
|
+
this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
|
|
87
89
|
this.#routeReleases.push(...registerWecomWebhookRoutes(this.#options.http, this));
|
|
88
|
-
logger.debug(formatCompact({
|
|
89
|
-
endpoint: this.#options.config.
|
|
90
|
+
this.#logger.debug(formatCompact({
|
|
91
|
+
endpoint: this.#options.config.id,
|
|
90
92
|
op: 'webhook',
|
|
91
93
|
path: this.#options.config.webhookPath,
|
|
92
94
|
}));
|
|
93
95
|
} catch (error) {
|
|
94
96
|
await this.stop();
|
|
95
|
-
logger.error('Failed to connect WeCom endpoint:', error);
|
|
97
|
+
this.#logger.error('Failed to connect WeCom endpoint:', error);
|
|
96
98
|
throw error;
|
|
97
99
|
}
|
|
98
100
|
}
|
|
@@ -111,14 +113,14 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
111
113
|
this.#unregisterAgent?.();
|
|
112
114
|
this.#unregisterAgent = undefined;
|
|
113
115
|
this.#started = false;
|
|
114
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
116
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
async send({
|
|
119
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
118
120
|
const materialized = await this.#materializeOutboundMedia(payload);
|
|
119
121
|
const content = formatOutboundBody(materialized);
|
|
120
122
|
const body = buildSendRequestBody(
|
|
121
|
-
|
|
123
|
+
conversation.id,
|
|
122
124
|
content,
|
|
123
125
|
// Legacy field: historically written into agentid (preserved for cutover).
|
|
124
126
|
this.#options.config.agentSecret,
|
|
@@ -130,7 +132,8 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
130
132
|
if (data.errcode !== 0) {
|
|
131
133
|
throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
|
|
132
134
|
}
|
|
133
|
-
logger.debug(formatCompact({ op: 'send',
|
|
135
|
+
this.#logger.debug(formatCompact({ op: 'send', to: `${conversation.kind}:${conversation.id}`,
|
|
136
|
+
}));
|
|
134
137
|
return (data.msgid as string) || `${Date.now()}`;
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -157,9 +160,9 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
157
160
|
const data = seg.data ?? {};
|
|
158
161
|
const media = readOutboundImageMedia(data);
|
|
159
162
|
if (!media) {
|
|
160
|
-
logger.warn(formatCompact({
|
|
163
|
+
this.#logger.warn(formatCompact({
|
|
161
164
|
op: 'wecom_outbound_media_dropped',
|
|
162
|
-
endpoint: this.#options.config.
|
|
165
|
+
endpoint: this.#options.config.id,
|
|
163
166
|
type: 'image',
|
|
164
167
|
reason: 'missing_media_ref',
|
|
165
168
|
}));
|
|
@@ -172,9 +175,9 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
172
175
|
const mediaId = await this.#uploadMedia('image', media);
|
|
173
176
|
return { type: 'image', data: { media_id: mediaId } };
|
|
174
177
|
} catch (error) {
|
|
175
|
-
logger.warn(formatCompact({
|
|
178
|
+
this.#logger.warn(formatCompact({
|
|
176
179
|
op: 'wecom_media_upload_failed',
|
|
177
|
-
endpoint: this.#options.config.
|
|
180
|
+
endpoint: this.#options.config.id,
|
|
178
181
|
error: error instanceof Error ? error.message : String(error),
|
|
179
182
|
}));
|
|
180
183
|
const alt = typeof data.alt === 'string' && data.alt ? data.alt : '[image]';
|
|
@@ -200,24 +203,24 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
200
203
|
admit(msg: WecomMessage): void {
|
|
201
204
|
if (!this.#open) return;
|
|
202
205
|
const chatType = resolveChatType(msg.FromUserName);
|
|
206
|
+
const conversation = wecomInboundConversation(String(this.#options.id), msg);
|
|
203
207
|
void this.#options.gateway.receive({
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
conversation,
|
|
209
|
+
message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
|
|
206
210
|
content: formatInboundContent(msg),
|
|
207
|
-
sender: msg.FromUserName,
|
|
208
|
-
|
|
211
|
+
sender: { id: msg.FromUserName },
|
|
212
|
+
endpointId: this.#options.config.id,
|
|
209
213
|
metadata: Object.freeze({
|
|
210
214
|
msgType: msg.MsgType,
|
|
211
215
|
event: msg.Event,
|
|
212
216
|
chatType,
|
|
213
|
-
endpoint: this.#options.config.name,
|
|
214
217
|
toUserName: msg.ToUserName,
|
|
215
218
|
agentId: msg.AgentID,
|
|
216
219
|
}),
|
|
217
220
|
}).catch((err) => {
|
|
218
|
-
logger.warn(formatCompact({
|
|
221
|
+
this.#logger.warn(formatCompact({
|
|
219
222
|
op: 'wecom_gateway_receive_failed',
|
|
220
|
-
target:
|
|
223
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
221
224
|
error: err instanceof Error ? err.message : String(err),
|
|
222
225
|
}));
|
|
223
226
|
});
|
|
@@ -231,7 +234,7 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
231
234
|
if (data.errcode === 0) return data;
|
|
232
235
|
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
233
236
|
} catch (error) {
|
|
234
|
-
logger.error('Failed to get user info:', error);
|
|
237
|
+
this.#logger.error('Failed to get user info:', error);
|
|
235
238
|
return null;
|
|
236
239
|
}
|
|
237
240
|
}
|
|
@@ -244,7 +247,7 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
244
247
|
if (data.errcode === 0) return (data.userlist as unknown[]) || [];
|
|
245
248
|
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
246
249
|
} catch (error) {
|
|
247
|
-
logger.error('Failed to get department users:', error);
|
|
250
|
+
this.#logger.error('Failed to get department users:', error);
|
|
248
251
|
return [];
|
|
249
252
|
}
|
|
250
253
|
}
|
|
@@ -257,17 +260,25 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
257
260
|
if (data.errcode === 0) return (data.department as unknown[]) || [];
|
|
258
261
|
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
259
262
|
} catch (error) {
|
|
260
|
-
logger.error('Failed to get department list:', error);
|
|
263
|
+
this.#logger.error('Failed to get department list:', error);
|
|
261
264
|
return [];
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
async sendTextMessage(userId: string, content: string): Promise<boolean> {
|
|
266
269
|
try {
|
|
267
|
-
|
|
270
|
+
const endpointKey = String(this.#options.id);
|
|
271
|
+
await this.send({
|
|
272
|
+
conversation: {
|
|
273
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
274
|
+
kind: resolveChatType(userId),
|
|
275
|
+
id: userId,
|
|
276
|
+
},
|
|
277
|
+
payload: content,
|
|
278
|
+
});
|
|
268
279
|
return true;
|
|
269
280
|
} catch (error) {
|
|
270
|
-
logger.error('Failed to send text message:', error);
|
|
281
|
+
this.#logger.error('Failed to send text message:', error);
|
|
271
282
|
return false;
|
|
272
283
|
}
|
|
273
284
|
}
|
|
@@ -330,7 +341,7 @@ export class WecomEndpoint implements EndpointInstance {
|
|
|
330
341
|
expires_in: data.expires_in ?? 7200,
|
|
331
342
|
timestamp: Date.now(),
|
|
332
343
|
};
|
|
333
|
-
logger.debug('Access token refreshed successfully');
|
|
344
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
334
345
|
return;
|
|
335
346
|
}
|
|
336
347
|
throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
|
package/src/index.ts
CHANGED
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 企业微信 WeCom platform permit
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'wecom';
|
|
7
7
|
|
|
@@ -31,10 +31,9 @@ export function normalizeWecomSenderForPermit(input: {
|
|
|
31
31
|
return { role: 'member', permissions: [] };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function checkWecomPlatformPermit(perm: string,
|
|
35
|
-
const
|
|
36
|
-
const permissions = sender
|
|
37
|
-
const role = sender.role;
|
|
34
|
+
export function checkWecomPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
35
|
+
const role = subject.sender?.role?.[0];
|
|
36
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
38
37
|
const has = (t: string) => permissions.includes(t);
|
|
39
38
|
|
|
40
39
|
switch (perm) {
|
|
@@ -47,6 +46,3 @@ export function checkWecomPlatformPermit(perm: string, message: Message<any>): b
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export function registerWecomPlatformPermitChecker(): () => void {
|
|
51
|
-
return registerPlatformPermitChecker(ADAPTER, checkWecomPlatformPermit);
|
|
52
|
-
}
|
package/src/protocol.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { createHash, createDecipheriv, timingSafeEqual } from 'node:crypto';
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
8
9
|
|
|
9
10
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
10
11
|
export interface WecomAdapterConfig {
|
|
11
|
-
readonly
|
|
12
|
+
readonly id?: string;
|
|
12
13
|
readonly corpId?: string;
|
|
13
14
|
readonly agentSecret?: string;
|
|
14
15
|
readonly token?: string;
|
|
@@ -23,7 +24,7 @@ export interface WecomAdapterConfig {
|
|
|
23
24
|
|
|
24
25
|
export interface ResolvedWecomConfig {
|
|
25
26
|
readonly context: 'wecom';
|
|
26
|
-
readonly
|
|
27
|
+
readonly id: string;
|
|
27
28
|
readonly corpId: string;
|
|
28
29
|
readonly agentSecret: string;
|
|
29
30
|
readonly token: string;
|
|
@@ -96,13 +97,13 @@ export function resolveWecomConfig(config: WecomAdapterConfig = {}): ResolvedWec
|
|
|
96
97
|
'WeCom adapter requires corpId + agentSecret + token + encodingAESKey (plugins.<key> or endpoints with context: wecom)',
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
|
-
const
|
|
100
|
-
|| (typeof entry?.
|
|
100
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
101
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
101
102
|
|| process.env.WECOM_BOT_NAME
|
|
102
103
|
|| 'wecom-bot';
|
|
103
104
|
return {
|
|
104
105
|
context: 'wecom',
|
|
105
|
-
|
|
106
|
+
id,
|
|
106
107
|
corpId,
|
|
107
108
|
agentSecret,
|
|
108
109
|
token,
|
|
@@ -255,6 +256,18 @@ export function resolveChatType(fromUserName: string): 'group' | 'private' {
|
|
|
255
256
|
return fromUserName.endsWith('@chatroom') ? 'group' : 'private';
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
/**
|
|
260
|
+
* 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
|
|
261
|
+
* `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
|
|
262
|
+
*/
|
|
263
|
+
export function wecomInboundConversation(endpointKey: string, msg: WecomMessage): ConversationRef {
|
|
264
|
+
return {
|
|
265
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
266
|
+
kind: resolveChatType(msg.FromUserName),
|
|
267
|
+
id: msg.FromUserName,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
258
271
|
/**
|
|
259
272
|
* Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
|
|
260
273
|
*/
|
package/src/wecom-agent-deps.ts
CHANGED
|
@@ -11,20 +11,20 @@ export interface WecomAgentEndpoint {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface WecomAgentDeps {
|
|
14
|
-
getEndpoint: (
|
|
14
|
+
getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const endpoints = new Map<string, WecomAgentEndpoint>();
|
|
18
18
|
let override: WecomAgentDeps | null = null;
|
|
19
19
|
|
|
20
20
|
export function registerWecomAgentEndpoint(
|
|
21
|
-
|
|
21
|
+
endpointKey: string,
|
|
22
22
|
endpoint: WecomAgentEndpoint,
|
|
23
23
|
): () => void {
|
|
24
|
-
endpoints.set(
|
|
24
|
+
endpoints.set(endpointKey, endpoint);
|
|
25
25
|
return () => {
|
|
26
|
-
if (endpoints.get(
|
|
27
|
-
endpoints.delete(
|
|
26
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
27
|
+
endpoints.delete(endpointKey);
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -37,9 +37,9 @@ export function setWecomAgentDeps(deps: WecomAgentDeps | null): void {
|
|
|
37
37
|
export function getWecomAgentDeps(): WecomAgentDeps {
|
|
38
38
|
if (override) return override;
|
|
39
39
|
return {
|
|
40
|
-
getEndpoint(
|
|
41
|
-
const endpoint = endpoints.get(
|
|
42
|
-
if (!endpoint) throw new Error(`Endpoint ${
|
|
40
|
+
getEndpoint(endpointKey) {
|
|
41
|
+
const endpoint = endpoints.get(endpointKey);
|
|
42
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
43
43
|
return endpoint;
|
|
44
44
|
},
|
|
45
45
|
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|