skyloom 1.19.0 → 1.21.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 (47) hide show
  1. package/README.md +52 -6
  2. package/dist/cli/main.js +3 -0
  3. package/dist/cli/main.js.map +1 -1
  4. package/dist/core/agent.d.ts +4 -1
  5. package/dist/core/agent.d.ts.map +1 -1
  6. package/dist/core/agent.js +83 -64
  7. package/dist/core/agent.js.map +1 -1
  8. package/dist/gateway/channels/feishu.d.ts +19 -0
  9. package/dist/gateway/channels/feishu.d.ts.map +1 -0
  10. package/dist/gateway/channels/feishu.js +186 -0
  11. package/dist/gateway/channels/feishu.js.map +1 -0
  12. package/dist/gateway/channels/qq.d.ts +25 -0
  13. package/dist/gateway/channels/qq.d.ts.map +1 -0
  14. package/dist/gateway/channels/qq.js +177 -0
  15. package/dist/gateway/channels/qq.js.map +1 -0
  16. package/dist/gateway/channels/wecom.d.ts +26 -0
  17. package/dist/gateway/channels/wecom.d.ts.map +1 -0
  18. package/dist/gateway/channels/wecom.js +177 -0
  19. package/dist/gateway/channels/wecom.js.map +1 -0
  20. package/dist/gateway/gateway.d.ts +19 -0
  21. package/dist/gateway/gateway.d.ts.map +1 -0
  22. package/dist/gateway/gateway.js +152 -0
  23. package/dist/gateway/gateway.js.map +1 -0
  24. package/dist/gateway/helpers.d.ts +39 -0
  25. package/dist/gateway/helpers.d.ts.map +1 -0
  26. package/dist/gateway/helpers.js +81 -0
  27. package/dist/gateway/helpers.js.map +1 -0
  28. package/dist/gateway/registry.d.ts +12 -0
  29. package/dist/gateway/registry.d.ts.map +1 -0
  30. package/dist/gateway/registry.js +44 -0
  31. package/dist/gateway/registry.js.map +1 -0
  32. package/dist/gateway/types.d.ts +81 -0
  33. package/dist/gateway/types.d.ts.map +1 -0
  34. package/dist/gateway/types.js +14 -0
  35. package/dist/gateway/types.js.map +1 -0
  36. package/package.json +1 -1
  37. package/src/cli/main.ts +3 -0
  38. package/src/core/agent.ts +83 -62
  39. package/src/gateway/channels/feishu.ts +142 -0
  40. package/src/gateway/channels/qq.ts +140 -0
  41. package/src/gateway/channels/wecom.ts +142 -0
  42. package/src/gateway/gateway.ts +151 -0
  43. package/src/gateway/helpers.ts +82 -0
  44. package/src/gateway/registry.ts +45 -0
  45. package/src/gateway/types.ts +91 -0
  46. package/tests/agent.test.ts +45 -19
  47. package/tests/gateway.test.ts +221 -0
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * Feishu / Lark channel adapter.
4
+ *
5
+ * Inbound: the event-subscription webhook (v2 schema). Handles the
6
+ * url_verification challenge, optional AES-encrypted payloads, and the optional
7
+ * verification-token check, then normalizes im.message.receive_v1 events.
8
+ *
9
+ * Outbound: obtains a tenant_access_token (cached) and replies via the
10
+ * im/v1/messages API (text by default).
11
+ *
12
+ * Config (channels.feishu): { appId, appSecret, encryptKey?, verificationToken?,
13
+ * domain?: 'feishu'|'lark', agent? }. Env fallback: FEISHU_APP_ID,
14
+ * FEISHU_APP_SECRET, FEISHU_ENCRYPT_KEY, FEISHU_VERIFICATION_TOKEN.
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.decryptFeishu = decryptFeishu;
51
+ exports.createFeishuAdapter = createFeishuAdapter;
52
+ const crypto = __importStar(require("crypto"));
53
+ const logger_1 = require("../../core/logger");
54
+ const helpers_1 = require("../helpers");
55
+ const log = (0, logger_1.getLogger)('channel-feishu');
56
+ /** Decrypt a Feishu AES-256-CBC encrypted event body. */
57
+ function decryptFeishu(encrypt, encryptKey) {
58
+ const key = crypto.createHash('sha256').update(encryptKey).digest();
59
+ const data = Buffer.from(encrypt, 'base64');
60
+ const iv = data.subarray(0, 16);
61
+ const ciphertext = data.subarray(16);
62
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
63
+ decipher.setAutoPadding(false);
64
+ let out = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
65
+ // PKCS#7 unpad
66
+ const pad = out[out.length - 1];
67
+ if (pad > 0 && pad <= 16)
68
+ out = out.subarray(0, out.length - pad);
69
+ return out.toString('utf8');
70
+ }
71
+ function createFeishuAdapter(cfg, env) {
72
+ const appId = (0, helpers_1.resolveSecret)(cfg.appId, env, 'FEISHU_APP_ID');
73
+ const appSecret = (0, helpers_1.resolveSecret)(cfg.appSecret, env, 'FEISHU_APP_SECRET');
74
+ if (!appId || !appSecret)
75
+ return null; // not configured
76
+ const encryptKey = (0, helpers_1.resolveSecret)(cfg.encryptKey, env, 'FEISHU_ENCRYPT_KEY');
77
+ const verificationToken = (0, helpers_1.resolveSecret)(cfg.verificationToken, env, 'FEISHU_VERIFICATION_TOKEN');
78
+ const base = cfg.domain === 'lark' ? 'https://open.larksuite.com' : 'https://open.feishu.cn';
79
+ const tokenCache = new helpers_1.TokenCache(async () => {
80
+ const data = await (0, helpers_1.postJson)(`${base}/open-apis/auth/v3/tenant_access_token/internal`, {
81
+ app_id: appId, app_secret: appSecret,
82
+ });
83
+ if (data.code !== 0)
84
+ throw new Error(`feishu token error ${data.code}: ${data.msg}`);
85
+ return { token: data.tenant_access_token, expiresInSec: data.expire ?? 7200 };
86
+ });
87
+ // De-dupe redelivered events (Feishu retries on slow ack).
88
+ const seen = new Set();
89
+ const remember = (id) => {
90
+ if (!id)
91
+ return false;
92
+ if (seen.has(id))
93
+ return true;
94
+ seen.add(id);
95
+ if (seen.size > 2000)
96
+ seen.clear();
97
+ return false;
98
+ };
99
+ return {
100
+ id: 'feishu',
101
+ name: 'Feishu/Lark',
102
+ defaultAgent: cfg.agent || 'fair',
103
+ async handleWebhook(req) {
104
+ let payload;
105
+ try {
106
+ payload = JSON.parse(req.body.toString('utf8') || '{}');
107
+ }
108
+ catch {
109
+ return { response: { status: 400, body: 'bad json' } };
110
+ }
111
+ // Encrypted transport: { encrypt: "..." } → decrypt to the real payload.
112
+ if (payload.encrypt) {
113
+ if (!encryptKey)
114
+ return { response: { status: 400, body: 'encrypt key not configured' } };
115
+ try {
116
+ payload = JSON.parse(decryptFeishu(payload.encrypt, encryptKey));
117
+ }
118
+ catch (e) {
119
+ log.warn('feishu_decrypt_failed', { error: String(e) });
120
+ return { response: { status: 400, body: 'decrypt failed' } };
121
+ }
122
+ }
123
+ // URL verification handshake.
124
+ if (payload.type === 'url_verification') {
125
+ if (verificationToken && payload.token && payload.token !== verificationToken) {
126
+ return { response: { status: 403, body: 'bad token' } };
127
+ }
128
+ return { response: { status: 200, contentType: 'application/json', body: JSON.stringify({ challenge: payload.challenge }) } };
129
+ }
130
+ // Verification token check (v2 puts it in header.token).
131
+ const token = payload.header?.token ?? payload.token;
132
+ if (verificationToken && token && token !== verificationToken) {
133
+ return { response: { status: 403, body: 'bad token' } };
134
+ }
135
+ const eventId = payload.header?.event_id;
136
+ if (remember(eventId))
137
+ return {}; // duplicate redelivery
138
+ const eventType = payload.header?.event_type ?? payload.event?.type;
139
+ if (eventType !== 'im.message.receive_v1')
140
+ return {}; // only handle message receipts
141
+ const message = payload.event?.message;
142
+ if (!message)
143
+ return {};
144
+ const chatId = message.chat_id;
145
+ const msgType = message.message_type;
146
+ let text = '';
147
+ if (msgType === 'text') {
148
+ try {
149
+ text = JSON.parse(message.content || '{}').text || '';
150
+ }
151
+ catch {
152
+ text = '';
153
+ }
154
+ // Strip @mentions like "@_user_1 ".
155
+ text = text.replace(/@_user_\d+/g, '').trim();
156
+ }
157
+ else {
158
+ text = `[${msgType} 消息]`;
159
+ }
160
+ const senderId = payload.event?.sender?.sender_id?.open_id || payload.event?.sender?.sender_id?.user_id || 'unknown';
161
+ return {
162
+ message: {
163
+ channel: 'feishu',
164
+ conversationId: chatId || senderId,
165
+ userId: senderId,
166
+ text,
167
+ replyTo: { channel: 'feishu', chatId },
168
+ raw: payload,
169
+ },
170
+ };
171
+ },
172
+ async send(target, text) {
173
+ const chatId = target.chatId;
174
+ if (!chatId)
175
+ return;
176
+ const token = await tokenCache.get();
177
+ const data = await (0, helpers_1.postJson)(`${base}/open-apis/im/v1/messages?receive_id_type=chat_id`, { receive_id: chatId, msg_type: 'text', content: JSON.stringify({ text }) }, { headers: { Authorization: `Bearer ${token}` } });
178
+ if (data.code !== 0) {
179
+ if (data.code === 99991663 || data.code === 99991661)
180
+ tokenCache.invalidate(); // token expired
181
+ throw new Error(`feishu send error ${data.code}: ${data.msg}`);
182
+ }
183
+ },
184
+ };
185
+ }
186
+ //# sourceMappingURL=feishu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../src/gateway/channels/feishu.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUH,sCAYC;AAED,kDAwGC;AA9HD,+CAAiC;AACjC,8CAA8C;AAC9C,wCAAiE;AAGjE,MAAM,GAAG,GAAG,IAAA,kBAAS,EAAC,gBAAgB,CAAC,CAAC;AAExC,yDAAyD;AACzD,SAAgB,aAAa,CAAC,OAAe,EAAE,UAAkB;IAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,CAAC;IACpE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACjE,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACzE,eAAe;IACf,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE;QAAE,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,SAAgB,mBAAmB,CAAC,GAAQ,EAAE,GAAsB;IAClE,MAAM,KAAK,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;IACzE,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;IAExD,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAC5E,MAAM,iBAAiB,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,iBAAiB,EAAE,GAAG,EAAE,2BAA2B,CAAC,CAAC;IACjG,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,wBAAwB,CAAC;IAE7F,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC,KAAK,IAAI,EAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAA,kBAAQ,EAAC,GAAG,IAAI,iDAAiD,EAAE;YACpF,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS;SACrC,CAAC,CAAC;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACrF,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,2DAA2D;IAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAW,EAAE;QACvC,IAAI,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QACtB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACb,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,aAAa;QACnB,YAAY,EAAE,GAAG,CAAC,KAAK,IAAI,MAAM;QAEjC,KAAK,CAAC,aAAa,CAAC,GAAe;YACjC,IAAI,OAAY,CAAC;YACjB,IAAI,CAAC;gBAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC;YAAC,CAAC;YAElI,yEAAyE;YACzE,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,CAAC,UAAU;oBAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,4BAA4B,EAAE,EAAE,CAAC;gBAC1F,IAAI,CAAC;oBAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;gBAAC,CAAC;gBACzE,OAAO,CAAC,EAAE,CAAC;oBAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;gBAAC,CAAC;YACtI,CAAC;YAED,8BAA8B;YAC9B,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACxC,IAAI,iBAAiB,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,iBAAiB,EAAE,CAAC;oBAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;gBAC1D,CAAC;gBACD,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YAChI,CAAC;YAED,yDAAyD;YACzD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;YACrD,IAAI,iBAAiB,IAAI,KAAK,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;gBAC9D,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;YAC1D,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC;YACzC,IAAI,QAAQ,CAAC,OAAO,CAAC;gBAAE,OAAO,EAAE,CAAC,CAAC,uBAAuB;YAEzD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;YACpE,IAAI,SAAS,KAAK,uBAAuB;gBAAE,OAAO,EAAE,CAAC,CAAC,+BAA+B;YAErF,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;YACvC,IAAI,CAAC,OAAO;gBAAE,OAAO,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAiB,CAAC;YACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAsB,CAAC;YAC/C,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvB,IAAI,CAAC;oBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC;oBAAC,IAAI,GAAG,EAAE,CAAC;gBAAC,CAAC;gBACnF,oCAAoC;gBACpC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAChD,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,IAAI,OAAO,MAAM,CAAC;YAC3B,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,IAAI,SAAS,CAAC;YAErH,OAAO;gBACL,OAAO,EAAE;oBACP,OAAO,EAAE,QAAQ;oBACjB,cAAc,EAAE,MAAM,IAAI,QAAQ;oBAClC,MAAM,EAAE,QAAQ;oBAChB,IAAI;oBACJ,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE;oBACtC,GAAG,EAAE,OAAO;iBACb;aACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,MAAmB,EAAE,IAAY;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAgB,CAAC;YACvC,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,MAAM,IAAA,kBAAQ,EACzB,GAAG,IAAI,mDAAmD,EAC1D,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAC3E,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE,CAClD,CAAC;YACF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACpB,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;oBAAE,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,gBAAgB;gBAC/F,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * QQ official-bot channel adapter (webhook mode, QQ 频道/群 机器人).
3
+ *
4
+ * Auth/crypto: QQ uses Ed25519. The signing seed is the bot secret repeated to
5
+ * 32 bytes. Two webhook concerns:
6
+ * - validation (op 13): the platform sends { d: { plain_token, event_ts } };
7
+ * we reply { plain_token, signature } where signature = ed25519(event_ts +
8
+ * plain_token).
9
+ * - event signature: each push carries X-Signature-Ed25519 (hex) and
10
+ * X-Signature-Timestamp; verify ed25519 over (timestamp + body).
11
+ *
12
+ * Inbound message events: GROUP_AT_MESSAGE_CREATE / C2C_MESSAGE_CREATE /
13
+ * AT_MESSAGE_CREATE. Outbound: getAppAccessToken (cached) then the v2 messages
14
+ * API (passive reply via msg_id). Config (channels.qq): { appId, secret,
15
+ * agent? }. Env fallback: QQ_BOT_APPID, QQ_BOT_SECRET.
16
+ */
17
+ import type { ChannelAdapter } from '../types';
18
+ /** Repeat the bot secret to a 32-byte Ed25519 seed (QQ's scheme). */
19
+ export declare function qqSeed(secret: string): Buffer;
20
+ /** Sign `event_ts + plain_token` for the validation handshake; returns hex. */
21
+ export declare function qqSignValidation(secret: string, eventTs: string, plainToken: string): string;
22
+ /** Verify an event push signature (hex) over `timestamp + body`. */
23
+ export declare function qqVerify(secret: string, timestamp: string, body: Buffer, signatureHex: string): boolean;
24
+ export declare function createQQAdapter(cfg: any, env: NodeJS.ProcessEnv): ChannelAdapter | null;
25
+ //# sourceMappingURL=qq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq.d.ts","sourceRoot":"","sources":["../../../src/gateway/channels/qq.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,UAAU,CAAC;AAMxF,qEAAqE;AACrE,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI7C;AAMD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG5F;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CASvG;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,IAAI,CAoFvF"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * QQ official-bot channel adapter (webhook mode, QQ 频道/群 机器人).
4
+ *
5
+ * Auth/crypto: QQ uses Ed25519. The signing seed is the bot secret repeated to
6
+ * 32 bytes. Two webhook concerns:
7
+ * - validation (op 13): the platform sends { d: { plain_token, event_ts } };
8
+ * we reply { plain_token, signature } where signature = ed25519(event_ts +
9
+ * plain_token).
10
+ * - event signature: each push carries X-Signature-Ed25519 (hex) and
11
+ * X-Signature-Timestamp; verify ed25519 over (timestamp + body).
12
+ *
13
+ * Inbound message events: GROUP_AT_MESSAGE_CREATE / C2C_MESSAGE_CREATE /
14
+ * AT_MESSAGE_CREATE. Outbound: getAppAccessToken (cached) then the v2 messages
15
+ * API (passive reply via msg_id). Config (channels.qq): { appId, secret,
16
+ * agent? }. Env fallback: QQ_BOT_APPID, QQ_BOT_SECRET.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.qqSeed = qqSeed;
53
+ exports.qqSignValidation = qqSignValidation;
54
+ exports.qqVerify = qqVerify;
55
+ exports.createQQAdapter = createQQAdapter;
56
+ const crypto = __importStar(require("crypto"));
57
+ const logger_1 = require("../../core/logger");
58
+ const helpers_1 = require("../helpers");
59
+ const log = (0, logger_1.getLogger)('channel-qq');
60
+ const PKCS8_ED25519_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
61
+ /** Repeat the bot secret to a 32-byte Ed25519 seed (QQ's scheme). */
62
+ function qqSeed(secret) {
63
+ let s = secret;
64
+ while (s.length < 32)
65
+ s = s + s;
66
+ return Buffer.from(s.slice(0, 32), 'utf8');
67
+ }
68
+ function privKeyFromSeed(seed) {
69
+ return crypto.createPrivateKey({ key: Buffer.concat([PKCS8_ED25519_PREFIX, seed]), format: 'der', type: 'pkcs8' });
70
+ }
71
+ /** Sign `event_ts + plain_token` for the validation handshake; returns hex. */
72
+ function qqSignValidation(secret, eventTs, plainToken) {
73
+ const priv = privKeyFromSeed(qqSeed(secret));
74
+ return crypto.sign(null, Buffer.from(eventTs + plainToken, 'utf8'), priv).toString('hex');
75
+ }
76
+ /** Verify an event push signature (hex) over `timestamp + body`. */
77
+ function qqVerify(secret, timestamp, body, signatureHex) {
78
+ try {
79
+ const pub = crypto.createPublicKey(privKeyFromSeed(qqSeed(secret)));
80
+ const msg = Buffer.concat([Buffer.from(timestamp, 'utf8'), body]);
81
+ return crypto.verify(null, msg, pub, Buffer.from(signatureHex, 'hex'));
82
+ }
83
+ catch (e) {
84
+ log.warn('qq_verify_error', { error: String(e) });
85
+ return false;
86
+ }
87
+ }
88
+ function createQQAdapter(cfg, env) {
89
+ const appId = (0, helpers_1.resolveSecret)(cfg.appId != null ? String(cfg.appId) : undefined, env, 'QQ_BOT_APPID');
90
+ const secret = (0, helpers_1.resolveSecret)(cfg.secret, env, 'QQ_BOT_SECRET');
91
+ if (!appId || !secret)
92
+ return null;
93
+ const tokenCache = new helpers_1.TokenCache(async () => {
94
+ const data = await (0, helpers_1.postJson)('https://bots.qq.com/app/getAppAccessToken', {
95
+ appId, clientSecret: secret,
96
+ });
97
+ if (!data.access_token)
98
+ throw new Error(`qq token error: ${JSON.stringify(data).slice(0, 120)}`);
99
+ return { token: data.access_token, expiresInSec: Number(data.expires_in) || 7200 };
100
+ });
101
+ const authHeaders = async () => ({ Authorization: `QQBot ${await tokenCache.get()}`, 'X-Union-Appid': appId });
102
+ return {
103
+ id: 'qq',
104
+ name: 'QQ Bot',
105
+ defaultAgent: cfg.agent || 'fair',
106
+ async handleWebhook(req) {
107
+ let payload;
108
+ try {
109
+ payload = JSON.parse(req.body.toString('utf8') || '{}');
110
+ }
111
+ catch {
112
+ return { response: { status: 400, body: 'bad json' } };
113
+ }
114
+ // Validation handshake (op 13) — no signature header on this one.
115
+ if (payload.op === 13 && payload.d?.plain_token && payload.d?.event_ts) {
116
+ const signature = qqSignValidation(secret, String(payload.d.event_ts), String(payload.d.plain_token));
117
+ return { response: { status: 200, contentType: 'application/json', body: JSON.stringify({ plain_token: payload.d.plain_token, signature }) } };
118
+ }
119
+ // Verify the event push signature.
120
+ const sig = req.headers['x-signature-ed25519'] || '';
121
+ const ts = req.headers['x-signature-timestamp'] || '';
122
+ if (sig && ts && !qqVerify(secret, ts, req.body, sig)) {
123
+ return { response: { status: 403, body: 'bad signature' } };
124
+ }
125
+ if (payload.op !== 0)
126
+ return { response: { status: 200, body: '' } }; // not a dispatch
127
+ const t = payload.t;
128
+ const d = payload.d || {};
129
+ const content = String(d.content || '').replace(/<@!?\d+>/g, '').trim();
130
+ const msgId = d.id;
131
+ let replyTo = null;
132
+ if (t === 'GROUP_AT_MESSAGE_CREATE')
133
+ replyTo = { channel: 'qq', kind: 'group', groupOpenid: d.group_openid, msgId };
134
+ else if (t === 'C2C_MESSAGE_CREATE')
135
+ replyTo = { channel: 'qq', kind: 'c2c', userOpenid: d.author?.user_openid, msgId };
136
+ else if (t === 'AT_MESSAGE_CREATE' || t === 'MESSAGE_CREATE')
137
+ replyTo = { channel: 'qq', kind: 'channel', channelId: d.channel_id, msgId };
138
+ if (!replyTo || !content)
139
+ return { response: { status: 200, body: '' } };
140
+ const userId = d.author?.user_openid || d.author?.id || d.author?.member_openid || 'unknown';
141
+ return {
142
+ response: { status: 200, body: '' },
143
+ message: {
144
+ channel: 'qq',
145
+ conversationId: replyTo.groupOpenid || replyTo.channelId || userId,
146
+ userId,
147
+ text: content,
148
+ replyTo,
149
+ raw: payload,
150
+ },
151
+ };
152
+ },
153
+ async send(target, text) {
154
+ const headers = { ...(await authHeaders()), 'Content-Type': 'application/json' };
155
+ const msgId = target.msgId;
156
+ const payload = { msg_type: 0, content: text };
157
+ if (msgId)
158
+ payload.msg_id = msgId; // passive reply within the allowed window
159
+ let url;
160
+ if (target.kind === 'group')
161
+ url = `https://api.sgroup.qq.com/v2/groups/${target.groupOpenid}/messages`;
162
+ else if (target.kind === 'c2c')
163
+ url = `https://api.sgroup.qq.com/v2/users/${target.userOpenid}/messages`;
164
+ else
165
+ url = `https://api.sgroup.qq.com/channels/${target.channelId}/messages`;
166
+ try {
167
+ await (0, helpers_1.postJson)(url, payload, { headers });
168
+ }
169
+ catch (e) {
170
+ if (e?.response?.status === 401)
171
+ tokenCache.invalidate();
172
+ throw new Error(`qq send error: ${e?.response?.status || ''} ${String(e?.message || e).slice(0, 120)}`);
173
+ }
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=qq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq.js","sourceRoot":"","sources":["../../../src/gateway/channels/qq.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WeCom (企业微信) channel adapter.
3
+ *
4
+ * Uses the official "receive messages" callback with the standard WeWork
5
+ * crypto: msg_signature = sha1(sort(token, timestamp, nonce, echostr|encrypt)),
6
+ * and AES-256-CBC (PKCS7) where the key is base64(EncodingAESKey + "=") and the
7
+ * plaintext is [16B random][4B big-endian msg-len][msg][receiveid].
8
+ *
9
+ * Inbound is XML. GET verifies the callback URL (echo the decrypted echostr);
10
+ * POST carries the encrypted message. We extract text from <Content>.
11
+ *
12
+ * Outbound uses the application message API (message/send) with the agent's
13
+ * gettoken. Config (channels.wecom): { corpId, corpSecret, token, encodingAesKey,
14
+ * agentId, agent? }. Env fallback: WECOM_CORP_ID, WECOM_CORP_SECRET,
15
+ * WECOM_TOKEN, WECOM_AES_KEY, WECOM_AGENT_ID.
16
+ */
17
+ import type { ChannelAdapter } from '../types';
18
+ /** WeWork msg_signature: sha1 over the sorted concatenation. */
19
+ export declare function wecomSignature(token: string, timestamp: string, nonce: string, encrypt: string): string;
20
+ /** Decrypt a WeWork AES message. Returns { message, receiveId }. */
21
+ export declare function decryptWecom(encrypt: string, encodingAesKey: string): {
22
+ message: string;
23
+ receiveId: string;
24
+ };
25
+ export declare function createWecomAdapter(cfg: any, env: NodeJS.ProcessEnv): ChannelAdapter | null;
26
+ //# sourceMappingURL=wecom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wecom.d.ts","sourceRoot":"","sources":["../../../src/gateway/channels/wecom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,UAAU,CAAC;AAIxF,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGvG;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAe5G;AASD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,IAAI,CAsF1F"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * WeCom (企业微信) channel adapter.
4
+ *
5
+ * Uses the official "receive messages" callback with the standard WeWork
6
+ * crypto: msg_signature = sha1(sort(token, timestamp, nonce, echostr|encrypt)),
7
+ * and AES-256-CBC (PKCS7) where the key is base64(EncodingAESKey + "=") and the
8
+ * plaintext is [16B random][4B big-endian msg-len][msg][receiveid].
9
+ *
10
+ * Inbound is XML. GET verifies the callback URL (echo the decrypted echostr);
11
+ * POST carries the encrypted message. We extract text from <Content>.
12
+ *
13
+ * Outbound uses the application message API (message/send) with the agent's
14
+ * gettoken. Config (channels.wecom): { corpId, corpSecret, token, encodingAesKey,
15
+ * agentId, agent? }. Env fallback: WECOM_CORP_ID, WECOM_CORP_SECRET,
16
+ * WECOM_TOKEN, WECOM_AES_KEY, WECOM_AGENT_ID.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.wecomSignature = wecomSignature;
53
+ exports.decryptWecom = decryptWecom;
54
+ exports.createWecomAdapter = createWecomAdapter;
55
+ const crypto = __importStar(require("crypto"));
56
+ const logger_1 = require("../../core/logger");
57
+ const helpers_1 = require("../helpers");
58
+ const log = (0, logger_1.getLogger)('channel-wecom');
59
+ /** WeWork msg_signature: sha1 over the sorted concatenation. */
60
+ function wecomSignature(token, timestamp, nonce, encrypt) {
61
+ const arr = [token, timestamp, nonce, encrypt].sort();
62
+ return crypto.createHash('sha1').update(arr.join('')).digest('hex');
63
+ }
64
+ /** Decrypt a WeWork AES message. Returns { message, receiveId }. */
65
+ function decryptWecom(encrypt, encodingAesKey) {
66
+ const key = Buffer.from(encodingAesKey + '=', 'base64'); // 32 bytes
67
+ const iv = key.subarray(0, 16);
68
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
69
+ decipher.setAutoPadding(false);
70
+ let decrypted = Buffer.concat([decipher.update(Buffer.from(encrypt, 'base64')), decipher.final()]);
71
+ // PKCS7 unpad
72
+ const pad = decrypted[decrypted.length - 1];
73
+ if (pad > 0 && pad <= 32)
74
+ decrypted = decrypted.subarray(0, decrypted.length - pad);
75
+ // [16B random][4B msg len BE][msg][receiveid]
76
+ const content = decrypted.subarray(16);
77
+ const msgLen = content.readUInt32BE(0);
78
+ const message = content.subarray(4, 4 + msgLen).toString('utf8');
79
+ const receiveId = content.subarray(4 + msgLen).toString('utf8');
80
+ return { message, receiveId };
81
+ }
82
+ function xmlField(xml, tag) {
83
+ const cdata = xml.match(new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]></${tag}>`));
84
+ if (cdata)
85
+ return cdata[1];
86
+ const plain = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
87
+ return plain ? plain[1] : '';
88
+ }
89
+ function createWecomAdapter(cfg, env) {
90
+ const corpId = (0, helpers_1.resolveSecret)(cfg.corpId, env, 'WECOM_CORP_ID');
91
+ const corpSecret = (0, helpers_1.resolveSecret)(cfg.corpSecret, env, 'WECOM_CORP_SECRET');
92
+ const token = (0, helpers_1.resolveSecret)(cfg.token, env, 'WECOM_TOKEN');
93
+ const aesKey = (0, helpers_1.resolveSecret)(cfg.encodingAesKey, env, 'WECOM_AES_KEY');
94
+ const agentId = (0, helpers_1.resolveSecret)(cfg.agentId != null ? String(cfg.agentId) : undefined, env, 'WECOM_AGENT_ID');
95
+ if (!corpId || !corpSecret || !token || !aesKey)
96
+ return null;
97
+ const tokenCache = new helpers_1.TokenCache(async () => {
98
+ const data = await (0, helpers_1.getJson)(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${encodeURIComponent(corpId)}&corpsecret=${encodeURIComponent(corpSecret)}`);
99
+ if (data.errcode !== 0)
100
+ throw new Error(`wecom token error ${data.errcode}: ${data.errmsg}`);
101
+ return { token: data.access_token, expiresInSec: data.expires_in ?? 7200 };
102
+ });
103
+ const verify = (req, encrypt) => {
104
+ const sig = req.query.get('msg_signature') || '';
105
+ const ts = req.query.get('timestamp') || '';
106
+ const nonce = req.query.get('nonce') || '';
107
+ return sig === wecomSignature(token, ts, nonce, encrypt);
108
+ };
109
+ return {
110
+ id: 'wecom',
111
+ name: 'WeCom (企业微信)',
112
+ defaultAgent: cfg.agent || 'fair',
113
+ async handleWebhook(req) {
114
+ // URL verification: GET with echostr.
115
+ if (req.method === 'GET') {
116
+ const echostr = req.query.get('echostr') || '';
117
+ if (!verify(req, echostr))
118
+ return { response: { status: 403, body: 'bad signature' } };
119
+ try {
120
+ const { message } = decryptWecom(echostr, aesKey);
121
+ return { response: { status: 200, body: message } };
122
+ }
123
+ catch (e) {
124
+ log.warn('wecom_echostr_decrypt_failed', { error: String(e) });
125
+ return { response: { status: 400, body: 'decrypt failed' } };
126
+ }
127
+ }
128
+ // Message callback: POST with <Encrypt> XML.
129
+ const xml = req.body.toString('utf8');
130
+ const encrypt = xmlField(xml, 'Encrypt');
131
+ if (!encrypt)
132
+ return { response: { status: 400, body: 'no encrypt' } };
133
+ if (!verify(req, encrypt))
134
+ return { response: { status: 403, body: 'bad signature' } };
135
+ let inner;
136
+ try {
137
+ inner = decryptWecom(encrypt, aesKey).message;
138
+ }
139
+ catch (e) {
140
+ log.warn('wecom_decrypt_failed', { error: String(e) });
141
+ return { response: { status: 400, body: 'decrypt failed' } };
142
+ }
143
+ const msgType = xmlField(inner, 'MsgType');
144
+ const fromUser = xmlField(inner, 'FromUserName');
145
+ let text = '';
146
+ if (msgType === 'text')
147
+ text = xmlField(inner, 'Content').trim();
148
+ else
149
+ text = `[${msgType} 消息]`;
150
+ // Ack the callback immediately (empty 200); reply is pushed via the API.
151
+ return {
152
+ response: { status: 200, body: '' },
153
+ message: text ? {
154
+ channel: 'wecom',
155
+ conversationId: fromUser,
156
+ userId: fromUser,
157
+ text,
158
+ replyTo: { channel: 'wecom', toUser: fromUser },
159
+ raw: inner,
160
+ } : undefined,
161
+ };
162
+ },
163
+ async send(target, text) {
164
+ const toUser = target.toUser;
165
+ if (!toUser || !agentId)
166
+ return;
167
+ const accessToken = await tokenCache.get();
168
+ const data = await (0, helpers_1.postJson)(`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${encodeURIComponent(accessToken)}`, { touser: toUser, msgtype: 'text', agentid: Number(agentId), text: { content: text } });
169
+ if (data.errcode !== 0) {
170
+ if (data.errcode === 42001 || data.errcode === 40014)
171
+ tokenCache.invalidate();
172
+ throw new Error(`wecom send error ${data.errcode}: ${data.errmsg}`);
173
+ }
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=wecom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wecom.js","sourceRoot":"","sources":["../../../src/gateway/channels/wecom.ts"],"names":[],"mappings":"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"}