@zhin.js/adapter-wecom 2.0.2 → 2.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 +33 -0
- package/README.md +79 -226
- package/adapters/wecom.ts +27 -0
- package/agent/tools/get_dept_users.ts +2 -2
- package/agent/tools/get_user.ts +2 -2
- package/agent/tools/list_departments.ts +2 -2
- package/agent/tools/send_text.ts +2 -2
- package/lib/endpoint.d.ts +46 -0
- package/lib/endpoint.js +205 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -0
- package/lib/platform-permit.d.ts +15 -0
- package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
- package/lib/protocol.d.ts +96 -0
- package/lib/protocol.js +252 -0
- package/lib/webhook.d.ts +14 -0
- package/lib/webhook.js +86 -0
- package/lib/wecom-agent-deps.d.ts +17 -0
- package/lib/wecom-agent-deps.js +30 -0
- package/package.json +45 -20
- package/plugin.ts +12 -0
- package/schema.json +24 -0
- package/src/endpoint.ts +196 -584
- package/src/index.ts +48 -53
- package/src/platform-permit.ts +1 -1
- package/src/protocol.ts +362 -0
- package/src/webhook.ts +130 -0
- package/src/wecom-agent-deps.ts +37 -9
- package/lib/agent/tools/get_dept_users.js +0 -18
- package/lib/agent/tools/get_dept_users.js.map +0 -1
- package/lib/agent/tools/get_user.js +0 -17
- package/lib/agent/tools/get_user.js.map +0 -1
- package/lib/agent/tools/list_departments.js +0 -18
- package/lib/agent/tools/list_departments.js.map +0 -1
- package/lib/agent/tools/send_text.js +0 -19
- package/lib/agent/tools/send_text.js.map +0 -1
- package/lib/src/adapter.js +0 -22
- package/lib/src/adapter.js.map +0 -1
- package/lib/src/endpoint.js +0 -602
- package/lib/src/endpoint.js.map +0 -1
- package/lib/src/index.js +0 -41
- package/lib/src/index.js.map +0 -1
- package/lib/src/platform-permit.js.map +0 -1
- package/lib/src/segment-mapper.js +0 -2
- package/lib/src/segment-mapper.js.map +0 -1
- package/lib/src/types.js +0 -5
- package/lib/src/types.js.map +0 -1
- package/lib/src/wecom-agent-deps.js +0 -10
- package/lib/src/wecom-agent-deps.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -28
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -51
package/lib/webhook.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
|
+
import { decryptMessage, extractEncryptFromXml, normalizeEchostrParam, parseXmlMessage, queryParam, readTextBody, verifySignature, } from './protocol.js';
|
|
3
|
+
const logger = getLogger('wecom');
|
|
4
|
+
export function registerWecomWebhookRoutes(http, handler) {
|
|
5
|
+
const path = handler.config.webhookPath;
|
|
6
|
+
return [
|
|
7
|
+
http.route('GET', path, (request, response, url) => {
|
|
8
|
+
handleWecomVerificationRequest(request, response, url, handler);
|
|
9
|
+
}, { summary: 'WeCom URL verification', tags: ['wecom'] }),
|
|
10
|
+
http.route('POST', path, async (request, response, url) => {
|
|
11
|
+
await handleWecomWebhookRequest(request, response, url, handler);
|
|
12
|
+
}, { summary: 'WeCom inbound webhook', tags: ['wecom'] }),
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
export function handleWecomVerificationRequest(_request, response, url, handler) {
|
|
16
|
+
try {
|
|
17
|
+
const msgSignature = queryParam(url.searchParams.get('msg_signature'));
|
|
18
|
+
const timestamp = queryParam(url.searchParams.get('timestamp'));
|
|
19
|
+
const nonce = queryParam(url.searchParams.get('nonce'));
|
|
20
|
+
const echostr = normalizeEchostrParam(queryParam(url.searchParams.get('echostr')));
|
|
21
|
+
const { token, encodingAESKey, corpId } = handler.config;
|
|
22
|
+
if (!msgSignature || !timestamp || !nonce || !echostr) {
|
|
23
|
+
response.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
24
|
+
response.end('Missing required query parameters');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!verifySignature(token, timestamp, nonce, echostr, msgSignature)) {
|
|
28
|
+
logger.warn(formatCompact({ op: 'verify', ok: false, error: 'invalid signature' }));
|
|
29
|
+
response.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
30
|
+
response.end('Forbidden');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const decrypted = decryptMessage(echostr, encodingAESKey, corpId);
|
|
34
|
+
if (!decrypted) {
|
|
35
|
+
response.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
36
|
+
response.end('Decryption failed');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
40
|
+
response.end(decrypted);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error('URL verification error:', error);
|
|
44
|
+
response.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
45
|
+
response.end('Internal Server Error');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function handleWecomWebhookRequest(request, response, url, handler) {
|
|
49
|
+
try {
|
|
50
|
+
const msgSignature = queryParam(url.searchParams.get('msg_signature'));
|
|
51
|
+
const timestamp = queryParam(url.searchParams.get('timestamp'));
|
|
52
|
+
const nonce = queryParam(url.searchParams.get('nonce'));
|
|
53
|
+
const { token, encodingAESKey, corpId } = handler.config;
|
|
54
|
+
const rawBody = await readTextBody(request);
|
|
55
|
+
const encrypted = extractEncryptFromXml(rawBody);
|
|
56
|
+
if (!encrypted) {
|
|
57
|
+
logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'no Encrypt field' }));
|
|
58
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
59
|
+
response.end('success');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!verifySignature(token, timestamp, nonce, encrypted, msgSignature)) {
|
|
63
|
+
logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid signature' }));
|
|
64
|
+
response.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
65
|
+
response.end('Forbidden');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const decryptedXml = decryptMessage(encrypted, encodingAESKey, corpId);
|
|
69
|
+
if (!decryptedXml) {
|
|
70
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
71
|
+
response.end('success');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const message = parseXmlMessage(decryptedXml);
|
|
75
|
+
if (message && handler.isOpen) {
|
|
76
|
+
handler.admit(message);
|
|
77
|
+
}
|
|
78
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
79
|
+
response.end('success');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
logger.error('Webhook error:', error);
|
|
83
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
84
|
+
response.end('success');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for wecom (get_user / departments / send_text).
|
|
3
|
+
* Endpoints register themselves on start; tools look up by endpoint id.
|
|
4
|
+
*/
|
|
5
|
+
export interface WecomAgentEndpoint {
|
|
6
|
+
getUserInfo(userId: string): Promise<unknown>;
|
|
7
|
+
getDepartmentUsers(deptId: number): Promise<unknown[]>;
|
|
8
|
+
getDepartmentList(deptId?: number): Promise<unknown[]>;
|
|
9
|
+
sendTextMessage(userId: string, content: string): Promise<boolean>;
|
|
10
|
+
}
|
|
11
|
+
export interface WecomAgentDeps {
|
|
12
|
+
getEndpoint: (endpointId: string) => WecomAgentEndpoint;
|
|
13
|
+
}
|
|
14
|
+
export declare function registerWecomAgentEndpoint(endpointId: string, endpoint: WecomAgentEndpoint): () => void;
|
|
15
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
|
+
export declare function setWecomAgentDeps(deps: WecomAgentDeps | null): void;
|
|
17
|
+
export declare function getWecomAgentDeps(): WecomAgentDeps;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool deps for wecom (get_user / departments / send_text).
|
|
3
|
+
* Endpoints register themselves on start; tools look up by endpoint id.
|
|
4
|
+
*/
|
|
5
|
+
const endpoints = new Map();
|
|
6
|
+
let override = null;
|
|
7
|
+
export function registerWecomAgentEndpoint(endpointId, endpoint) {
|
|
8
|
+
endpoints.set(endpointId, endpoint);
|
|
9
|
+
return () => {
|
|
10
|
+
if (endpoints.get(endpointId) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointId);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
|
+
export function setWecomAgentDeps(deps) {
|
|
17
|
+
override = deps;
|
|
18
|
+
}
|
|
19
|
+
export function getWecomAgentDeps() {
|
|
20
|
+
if (override)
|
|
21
|
+
return override;
|
|
22
|
+
return {
|
|
23
|
+
getEndpoint(endpointId) {
|
|
24
|
+
const endpoint = endpoints.get(endpointId);
|
|
25
|
+
if (!endpoint)
|
|
26
|
+
throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
27
|
+
return endpoint;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-wecom",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Zhin.js adapter for
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "Zhin.js WeCom (企业微信) adapter for Plugin Runtime (HTTP webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -33,24 +33,25 @@
|
|
|
33
33
|
"type": "git",
|
|
34
34
|
"directory": "plugins/adapters/wecom"
|
|
35
35
|
},
|
|
36
|
-
"
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"zhin.js": "4.1.2",
|
|
43
|
-
"@zhin.js/agent": "1.0.3"
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@zhin.js/adapter": "1.0.1",
|
|
38
|
+
"@zhin.js/core": "1.3.5",
|
|
39
|
+
"@zhin.js/host-http": "1.0.1",
|
|
40
|
+
"@zhin.js/logger": "1.0.75",
|
|
41
|
+
"@zhin.js/plugin-runtime": "1.0.1"
|
|
44
42
|
},
|
|
45
43
|
"peerDependencies": {
|
|
46
44
|
"zod": "^4.0.0",
|
|
47
|
-
"@zhin.js/
|
|
48
|
-
"zhin.js": "
|
|
49
|
-
"@zhin.js/
|
|
45
|
+
"@zhin.js/adapter": "1.0.1",
|
|
46
|
+
"@zhin.js/agent": "1.0.4",
|
|
47
|
+
"@zhin.js/core": "1.3.5",
|
|
48
|
+
"@zhin.js/host-http": "1.0.1",
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.0.1",
|
|
50
|
+
"zhin.js": "4.1.3"
|
|
50
51
|
},
|
|
51
52
|
"peerDependenciesMeta": {
|
|
52
|
-
"
|
|
53
|
-
"optional":
|
|
53
|
+
"zhin.js": {
|
|
54
|
+
"optional": true
|
|
54
55
|
},
|
|
55
56
|
"@zhin.js/agent": {
|
|
56
57
|
"optional": true
|
|
@@ -59,13 +60,23 @@
|
|
|
59
60
|
"optional": true
|
|
60
61
|
}
|
|
61
62
|
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^26.1.0",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"vitest": "^4.1.10",
|
|
67
|
+
"zod": "^4.4.3",
|
|
68
|
+
"@zhin.js/agent": "1.0.4",
|
|
69
|
+
"zhin.js": "4.1.3"
|
|
70
|
+
},
|
|
62
71
|
"files": [
|
|
72
|
+
"adapters",
|
|
73
|
+
"plugin.ts",
|
|
74
|
+
"schema.json",
|
|
63
75
|
"src",
|
|
64
76
|
"lib",
|
|
65
|
-
"
|
|
77
|
+
"agent",
|
|
66
78
|
"README.md",
|
|
67
|
-
"CHANGELOG.md"
|
|
68
|
-
"agent"
|
|
79
|
+
"CHANGELOG.md"
|
|
69
80
|
],
|
|
70
81
|
"publishConfig": {
|
|
71
82
|
"access": "public",
|
|
@@ -74,9 +85,23 @@
|
|
|
74
85
|
"engines": {
|
|
75
86
|
"node": "^20.19.0 || >=22.12.0"
|
|
76
87
|
},
|
|
88
|
+
"zhin": {
|
|
89
|
+
"protocol": 1,
|
|
90
|
+
"type": "plugin",
|
|
91
|
+
"entry": "./plugin.ts",
|
|
92
|
+
"engine": "^1.0.0",
|
|
93
|
+
"runtime": "trusted",
|
|
94
|
+
"features": [
|
|
95
|
+
{
|
|
96
|
+
"package": "@zhin.js/adapter",
|
|
97
|
+
"api": "^1.0.0"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"plugins": []
|
|
101
|
+
},
|
|
77
102
|
"scripts": {
|
|
78
|
-
"build": "
|
|
103
|
+
"build": "tsc",
|
|
79
104
|
"clean": "rimraf lib",
|
|
80
|
-
"
|
|
105
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/wecom/tests"
|
|
81
106
|
}
|
|
82
107
|
}
|
package/plugin.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { registerWecomPlatformPermitChecker } from './src/platform-permit.js';
|
|
3
|
+
|
|
4
|
+
export default definePlugin({
|
|
5
|
+
name: 'wecom',
|
|
6
|
+
metadata: {
|
|
7
|
+
displayName: 'WeCom (企业微信) Adapter',
|
|
8
|
+
},
|
|
9
|
+
setup() {
|
|
10
|
+
return registerWecomPlatformPermitChecker();
|
|
11
|
+
},
|
|
12
|
+
});
|
package/schema.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"name": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"default": "wecom-bot"
|
|
9
|
+
},
|
|
10
|
+
"corpId": { "type": "string" },
|
|
11
|
+
"agentSecret": { "type": "string" },
|
|
12
|
+
"token": { "type": "string" },
|
|
13
|
+
"encodingAESKey": { "type": "string" },
|
|
14
|
+
"webhookPath": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"default": "/wecom/callback"
|
|
17
|
+
},
|
|
18
|
+
"apiBaseUrl": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "https://qyapi.weixin.qq.com"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": ["corpId", "agentSecret", "token", "encodingAESKey"]
|
|
24
|
+
}
|