@zhin.js/adapter-wecom 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +80 -226
  3. package/adapters/wecom.ts +27 -0
  4. package/agent/tools/get_dept_users.ts +2 -2
  5. package/agent/tools/get_user.ts +2 -2
  6. package/agent/tools/list_departments.ts +2 -2
  7. package/agent/tools/send_text.ts +2 -2
  8. package/lib/endpoint.d.ts +46 -0
  9. package/lib/endpoint.js +205 -0
  10. package/lib/index.d.ts +5 -0
  11. package/lib/index.js +5 -0
  12. package/lib/platform-permit.d.ts +15 -0
  13. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  14. package/lib/protocol.d.ts +96 -0
  15. package/lib/protocol.js +252 -0
  16. package/lib/webhook.d.ts +14 -0
  17. package/lib/webhook.js +86 -0
  18. package/lib/wecom-agent-deps.d.ts +17 -0
  19. package/lib/wecom-agent-deps.js +30 -0
  20. package/package.json +45 -20
  21. package/plugin.ts +12 -0
  22. package/schema.json +60 -0
  23. package/src/endpoint.ts +196 -584
  24. package/src/index.ts +48 -53
  25. package/src/platform-permit.ts +1 -1
  26. package/src/protocol.ts +362 -0
  27. package/src/webhook.ts +130 -0
  28. package/src/wecom-agent-deps.ts +37 -9
  29. package/lib/agent/tools/get_dept_users.js +0 -18
  30. package/lib/agent/tools/get_dept_users.js.map +0 -1
  31. package/lib/agent/tools/get_user.js +0 -17
  32. package/lib/agent/tools/get_user.js.map +0 -1
  33. package/lib/agent/tools/list_departments.js +0 -18
  34. package/lib/agent/tools/list_departments.js.map +0 -1
  35. package/lib/agent/tools/send_text.js +0 -19
  36. package/lib/agent/tools/send_text.js.map +0 -1
  37. package/lib/src/adapter.js +0 -22
  38. package/lib/src/adapter.js.map +0 -1
  39. package/lib/src/endpoint.js +0 -602
  40. package/lib/src/endpoint.js.map +0 -1
  41. package/lib/src/index.js +0 -41
  42. package/lib/src/index.js.map +0 -1
  43. package/lib/src/platform-permit.js.map +0 -1
  44. package/lib/src/segment-mapper.js +0 -2
  45. package/lib/src/segment-mapper.js.map +0 -1
  46. package/lib/src/types.js +0 -5
  47. package/lib/src/types.js.map +0 -1
  48. package/lib/src/wecom-agent-deps.js +0 -10
  49. package/lib/src/wecom-agent-deps.js.map +0 -1
  50. package/plugin.yml +0 -3
  51. package/src/adapter.ts +0 -28
  52. package/src/segment-mapper.ts +0 -1
  53. 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.2",
4
- "description": "Zhin.js adapter for WeCom (企业微信)",
3
+ "version": "3.0.0",
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
- "devDependencies": {
37
- "@types/koa": "^3.0.3",
38
- "@types/node": "^26.1.0",
39
- "typescript": "^6.0.3",
40
- "zod": "^4.4.3",
41
- "@zhin.js/host-router": "2.0.3",
42
- "zhin.js": "4.1.2",
43
- "@zhin.js/agent": "1.0.3"
36
+ "dependencies": {
37
+ "@zhin.js/adapter": "1.1.0",
38
+ "@zhin.js/core": "1.4.0",
39
+ "@zhin.js/host-http": "1.0.2",
40
+ "@zhin.js/logger": "1.0.75",
41
+ "@zhin.js/plugin-runtime": "1.1.0"
44
42
  },
45
43
  "peerDependencies": {
46
44
  "zod": "^4.0.0",
47
- "@zhin.js/host-router": "2.0.3",
48
- "zhin.js": "4.1.2",
49
- "@zhin.js/agent": "1.0.3"
45
+ "@zhin.js/adapter": "1.1.0",
46
+ "@zhin.js/agent": "1.0.5",
47
+ "@zhin.js/core": "1.4.0",
48
+ "@zhin.js/host-http": "1.0.2",
49
+ "@zhin.js/plugin-runtime": "1.1.0",
50
+ "zhin.js": "5.0.0"
50
51
  },
51
52
  "peerDependenciesMeta": {
52
- "@zhin.js/host-router": {
53
- "optional": false
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.5",
69
+ "zhin.js": "5.0.0"
70
+ },
62
71
  "files": [
72
+ "adapters",
73
+ "plugin.ts",
74
+ "schema.json",
63
75
  "src",
64
76
  "lib",
65
- "plugin.yml",
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": "pnpm build:node",
103
+ "build": "tsc",
79
104
  "clean": "rimraf lib",
80
- "build:node": "tsc"
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,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "webhookPath": {
7
+ "type": "string",
8
+ "default": "/wecom/callback"
9
+ },
10
+ "apiBaseUrl": {
11
+ "type": "string",
12
+ "default": "https://qyapi.weixin.qq.com"
13
+ },
14
+ "endpoints": {
15
+ "type": "array",
16
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
17
+ "items": {
18
+ "type": "object",
19
+ "additionalProperties": true,
20
+ "properties": {
21
+ "name": {
22
+ "type": "string",
23
+ "description": "WeCom bot name"
24
+ },
25
+ "corpId": {
26
+ "type": "string",
27
+ "description": "WeCom corp ID"
28
+ },
29
+ "agentSecret": {
30
+ "type": "string",
31
+ "description": "WeCom agent secret"
32
+ },
33
+ "token": {
34
+ "type": "string",
35
+ "description": "WeCom callback token"
36
+ },
37
+ "encodingAESKey": {
38
+ "type": "string",
39
+ "description": "WeCom encoding AES key"
40
+ }
41
+ },
42
+ "required": [
43
+ "name",
44
+ "corpId",
45
+ "agentSecret",
46
+ "token",
47
+ "encodingAESKey"
48
+ ]
49
+ }
50
+ },
51
+ "commandPrefix": {
52
+ "type": "string",
53
+ "default": "",
54
+ "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
55
+ }
56
+ },
57
+ "required": [
58
+ "endpoints"
59
+ ]
60
+ }