skyloom 1.20.0 → 1.22.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.
- package/README.md +46 -0
- package/dist/cli/main.js +3 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/gateway/channels/feishu.d.ts +19 -0
- package/dist/gateway/channels/feishu.d.ts.map +1 -0
- package/dist/gateway/channels/feishu.js +290 -0
- package/dist/gateway/channels/feishu.js.map +1 -0
- package/dist/gateway/channels/qq.d.ts +25 -0
- package/dist/gateway/channels/qq.d.ts.map +1 -0
- package/dist/gateway/channels/qq.js +187 -0
- package/dist/gateway/channels/qq.js.map +1 -0
- package/dist/gateway/channels/wecom.d.ts +26 -0
- package/dist/gateway/channels/wecom.d.ts.map +1 -0
- package/dist/gateway/channels/wecom.js +196 -0
- package/dist/gateway/channels/wecom.js.map +1 -0
- package/dist/gateway/gateway.d.ts +19 -0
- package/dist/gateway/gateway.d.ts.map +1 -0
- package/dist/gateway/gateway.js +177 -0
- package/dist/gateway/gateway.js.map +1 -0
- package/dist/gateway/helpers.d.ts +39 -0
- package/dist/gateway/helpers.d.ts.map +1 -0
- package/dist/gateway/helpers.js +81 -0
- package/dist/gateway/helpers.js.map +1 -0
- package/dist/gateway/registry.d.ts +12 -0
- package/dist/gateway/registry.d.ts.map +1 -0
- package/dist/gateway/registry.js +44 -0
- package/dist/gateway/registry.js.map +1 -0
- package/dist/gateway/types.d.ts +104 -0
- package/dist/gateway/types.d.ts.map +1 -0
- package/dist/gateway/types.js +26 -0
- package/dist/gateway/types.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/main.ts +3 -0
- package/src/gateway/channels/feishu.ts +242 -0
- package/src/gateway/channels/qq.ts +151 -0
- package/src/gateway/channels/wecom.ts +151 -0
- package/src/gateway/gateway.ts +178 -0
- package/src/gateway/helpers.ts +82 -0
- package/src/gateway/registry.ts +45 -0
- package/src/gateway/types.ts +125 -0
- package/tests/gateway.test.ts +293 -0
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
// 'card' replies render as an interactive card (supports streaming patches);
|
|
80
|
+
// 'raw' forces plain text; 'auto' (default) uses a card so streaming works.
|
|
81
|
+
const renderMode = cfg.renderMode || 'auto';
|
|
82
|
+
const useCard = renderMode === 'card' || renderMode === 'auto';
|
|
83
|
+
const tokenCache = new helpers_1.TokenCache(async () => {
|
|
84
|
+
const data = await (0, helpers_1.postJson)(`${base}/open-apis/auth/v3/tenant_access_token/internal`, {
|
|
85
|
+
app_id: appId, app_secret: appSecret,
|
|
86
|
+
});
|
|
87
|
+
if (data.code !== 0)
|
|
88
|
+
throw new Error(`feishu token error ${data.code}: ${data.msg}`);
|
|
89
|
+
return { token: data.tenant_access_token, expiresInSec: data.expire ?? 7200 };
|
|
90
|
+
});
|
|
91
|
+
const authHeader = async () => ({ Authorization: `Bearer ${await tokenCache.get()}` });
|
|
92
|
+
const onTokenError = (code) => { if (code === 99991663 || code === 99991661)
|
|
93
|
+
tokenCache.invalidate(); };
|
|
94
|
+
/** A minimal interactive card carrying a single markdown body. */
|
|
95
|
+
const cardContent = (text) => JSON.stringify({
|
|
96
|
+
config: { wide_screen_mode: true, update_multi: true },
|
|
97
|
+
elements: [{ tag: 'markdown', content: text || ' ' }],
|
|
98
|
+
});
|
|
99
|
+
/** Create a card message in a chat; returns its message_id for later patches. */
|
|
100
|
+
const createCard = async (chatId, text) => {
|
|
101
|
+
const data = await (0, helpers_1.postJson)(`${base}/open-apis/im/v1/messages?receive_id_type=chat_id`, { receive_id: chatId, msg_type: 'interactive', content: cardContent(text) }, { headers: await authHeader() });
|
|
102
|
+
if (data.code !== 0) {
|
|
103
|
+
onTokenError(data.code);
|
|
104
|
+
throw new Error(`feishu card create ${data.code}: ${data.msg}`);
|
|
105
|
+
}
|
|
106
|
+
return data.data?.message_id || null;
|
|
107
|
+
};
|
|
108
|
+
/** Patch an existing card message with new content. */
|
|
109
|
+
const patchCard = async (messageId, text) => {
|
|
110
|
+
const data = await (0, helpers_1.postJson)(`${base}/open-apis/im/v1/messages/${messageId}`, { content: cardContent(text) }, { headers: await authHeader() }).catch((e) => ({ code: -1, msg: String(e) }));
|
|
111
|
+
if (data && data.code !== 0)
|
|
112
|
+
onTokenError(data.code);
|
|
113
|
+
};
|
|
114
|
+
// De-dupe redelivered events (Feishu retries on slow ack).
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const remember = (id) => {
|
|
117
|
+
if (!id)
|
|
118
|
+
return false;
|
|
119
|
+
if (seen.has(id))
|
|
120
|
+
return true;
|
|
121
|
+
seen.add(id);
|
|
122
|
+
if (seen.size > 2000)
|
|
123
|
+
seen.clear();
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
id: 'feishu',
|
|
128
|
+
name: 'Feishu/Lark',
|
|
129
|
+
defaultAgent: cfg.agent || 'fair',
|
|
130
|
+
async handleWebhook(req) {
|
|
131
|
+
let payload;
|
|
132
|
+
try {
|
|
133
|
+
payload = JSON.parse(req.body.toString('utf8') || '{}');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { response: { status: 400, body: 'bad json' } };
|
|
137
|
+
}
|
|
138
|
+
// Encrypted transport: { encrypt: "..." } → decrypt to the real payload.
|
|
139
|
+
if (payload.encrypt) {
|
|
140
|
+
if (!encryptKey)
|
|
141
|
+
return { response: { status: 400, body: 'encrypt key not configured' } };
|
|
142
|
+
try {
|
|
143
|
+
payload = JSON.parse(decryptFeishu(payload.encrypt, encryptKey));
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
log.warn('feishu_decrypt_failed', { error: String(e) });
|
|
147
|
+
return { response: { status: 400, body: 'decrypt failed' } };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// URL verification handshake.
|
|
151
|
+
if (payload.type === 'url_verification') {
|
|
152
|
+
if (verificationToken && payload.token && payload.token !== verificationToken) {
|
|
153
|
+
return { response: { status: 403, body: 'bad token' } };
|
|
154
|
+
}
|
|
155
|
+
return { response: { status: 200, contentType: 'application/json', body: JSON.stringify({ challenge: payload.challenge }) } };
|
|
156
|
+
}
|
|
157
|
+
// Verification token check (v2 puts it in header.token).
|
|
158
|
+
const token = payload.header?.token ?? payload.token;
|
|
159
|
+
if (verificationToken && token && token !== verificationToken) {
|
|
160
|
+
return { response: { status: 403, body: 'bad token' } };
|
|
161
|
+
}
|
|
162
|
+
const eventId = payload.header?.event_id;
|
|
163
|
+
if (remember(eventId))
|
|
164
|
+
return {}; // duplicate redelivery
|
|
165
|
+
const eventType = payload.header?.event_type ?? payload.event?.type;
|
|
166
|
+
if (eventType !== 'im.message.receive_v1')
|
|
167
|
+
return {}; // only handle message receipts
|
|
168
|
+
const message = payload.event?.message;
|
|
169
|
+
if (!message)
|
|
170
|
+
return {};
|
|
171
|
+
const chatId = message.chat_id;
|
|
172
|
+
const msgType = message.message_type;
|
|
173
|
+
let text = '';
|
|
174
|
+
const media = [];
|
|
175
|
+
let content = {};
|
|
176
|
+
try {
|
|
177
|
+
content = JSON.parse(message.content || '{}');
|
|
178
|
+
}
|
|
179
|
+
catch { /* ignore */ }
|
|
180
|
+
switch (msgType) {
|
|
181
|
+
case 'text':
|
|
182
|
+
text = (content.text || '').replace(/@_user_\d+/g, '').trim(); // strip @mentions
|
|
183
|
+
break;
|
|
184
|
+
case 'image':
|
|
185
|
+
media.push({ kind: 'image', ref: content.image_key });
|
|
186
|
+
break;
|
|
187
|
+
case 'audio':
|
|
188
|
+
media.push({ kind: 'audio', ref: content.file_key });
|
|
189
|
+
break;
|
|
190
|
+
case 'media': // short video
|
|
191
|
+
media.push({ kind: 'video', ref: content.file_key, filename: content.file_name });
|
|
192
|
+
break;
|
|
193
|
+
case 'file':
|
|
194
|
+
media.push({ kind: 'file', ref: content.file_key, filename: content.file_name });
|
|
195
|
+
break;
|
|
196
|
+
case 'sticker':
|
|
197
|
+
media.push({ kind: 'sticker', ref: content.file_key });
|
|
198
|
+
break;
|
|
199
|
+
case 'post': { // rich text: pull plain text + embedded images
|
|
200
|
+
const blocks = content?.content;
|
|
201
|
+
if (Array.isArray(blocks)) {
|
|
202
|
+
for (const row of blocks) {
|
|
203
|
+
for (const el of row || []) {
|
|
204
|
+
if (el?.tag === 'text' && el.text)
|
|
205
|
+
text += el.text;
|
|
206
|
+
else if (el?.tag === 'a' && el.text)
|
|
207
|
+
text += el.text;
|
|
208
|
+
else if (el?.tag === 'img' && el.image_key)
|
|
209
|
+
media.push({ kind: 'image', ref: el.image_key });
|
|
210
|
+
}
|
|
211
|
+
text += '\n';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
text = text.trim();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
default:
|
|
218
|
+
text = `[${msgType} 消息]`;
|
|
219
|
+
}
|
|
220
|
+
const senderId = payload.event?.sender?.sender_id?.open_id || payload.event?.sender?.sender_id?.user_id || 'unknown';
|
|
221
|
+
return {
|
|
222
|
+
message: {
|
|
223
|
+
channel: 'feishu',
|
|
224
|
+
conversationId: chatId || senderId,
|
|
225
|
+
userId: senderId,
|
|
226
|
+
text,
|
|
227
|
+
media: media.length ? media : undefined,
|
|
228
|
+
replyTo: { channel: 'feishu', chatId },
|
|
229
|
+
raw: payload,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
async send(target, text) {
|
|
234
|
+
const chatId = target.chatId;
|
|
235
|
+
if (!chatId)
|
|
236
|
+
return;
|
|
237
|
+
if (useCard) {
|
|
238
|
+
await createCard(chatId, text);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
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: await authHeader() });
|
|
242
|
+
if (data.code !== 0) {
|
|
243
|
+
onTokenError(data.code);
|
|
244
|
+
throw new Error(`feishu send error ${data.code}: ${data.msg}`);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
// Streaming reply: post a placeholder card, then patch it as text arrives —
|
|
248
|
+
// throttled (≥600ms apart) to stay well under Feishu's update rate limit.
|
|
249
|
+
async sendStreaming(target, chunks) {
|
|
250
|
+
const chatId = target.chatId;
|
|
251
|
+
if (!chatId)
|
|
252
|
+
return;
|
|
253
|
+
if (!useCard) { // plain-text mode can't patch; collect then send once
|
|
254
|
+
let all = '';
|
|
255
|
+
for await (const c of chunks)
|
|
256
|
+
all += c;
|
|
257
|
+
await this.send(target, all.trim() || '(无回复)');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
let messageId = null;
|
|
261
|
+
let acc = '';
|
|
262
|
+
let lastPatch = 0;
|
|
263
|
+
let dirty = false;
|
|
264
|
+
const MIN_INTERVAL = 600;
|
|
265
|
+
try {
|
|
266
|
+
messageId = await createCard(chatId, '思考中…');
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
log.warn('feishu_card_create_failed', { error: String(e) });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!messageId)
|
|
273
|
+
return;
|
|
274
|
+
for await (const chunk of chunks) {
|
|
275
|
+
acc += chunk;
|
|
276
|
+
dirty = true;
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
if (now - lastPatch >= MIN_INTERVAL) {
|
|
279
|
+
lastPatch = now;
|
|
280
|
+
dirty = false;
|
|
281
|
+
await patchCard(messageId, acc);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Final flush so the last tokens always land.
|
|
285
|
+
if (dirty || acc)
|
|
286
|
+
await patchCard(messageId, acc.trim() || '(无回复)');
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=feishu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../src/gateway/channels/feishu.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,EAA4D,MAAM,UAAU,CAAC;AAMzG,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,CA+FvF"}
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
// QQ delivers images/files as an attachments array on the event.
|
|
139
|
+
const media = [];
|
|
140
|
+
for (const att of (Array.isArray(d.attachments) ? d.attachments : [])) {
|
|
141
|
+
const ct = String(att?.content_type || '');
|
|
142
|
+
const kind = ct.startsWith('image') ? 'image'
|
|
143
|
+
: ct.startsWith('audio') || ct.startsWith('voice') ? 'audio'
|
|
144
|
+
: ct.startsWith('video') ? 'video' : 'file';
|
|
145
|
+
media.push({ kind, ref: att?.id, filename: att?.filename, mimeType: att?.content_type, url: att?.url });
|
|
146
|
+
}
|
|
147
|
+
if (!replyTo || (!content && media.length === 0))
|
|
148
|
+
return { response: { status: 200, body: '' } };
|
|
149
|
+
const userId = d.author?.user_openid || d.author?.id || d.author?.member_openid || 'unknown';
|
|
150
|
+
return {
|
|
151
|
+
response: { status: 200, body: '' },
|
|
152
|
+
message: {
|
|
153
|
+
channel: 'qq',
|
|
154
|
+
conversationId: replyTo.groupOpenid || replyTo.channelId || userId,
|
|
155
|
+
userId,
|
|
156
|
+
text: content,
|
|
157
|
+
media: media.length ? media : undefined,
|
|
158
|
+
replyTo,
|
|
159
|
+
raw: payload,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
async send(target, text) {
|
|
164
|
+
const headers = { ...(await authHeaders()), 'Content-Type': 'application/json' };
|
|
165
|
+
const msgId = target.msgId;
|
|
166
|
+
const payload = { msg_type: 0, content: text };
|
|
167
|
+
if (msgId)
|
|
168
|
+
payload.msg_id = msgId; // passive reply within the allowed window
|
|
169
|
+
let url;
|
|
170
|
+
if (target.kind === 'group')
|
|
171
|
+
url = `https://api.sgroup.qq.com/v2/groups/${target.groupOpenid}/messages`;
|
|
172
|
+
else if (target.kind === 'c2c')
|
|
173
|
+
url = `https://api.sgroup.qq.com/v2/users/${target.userOpenid}/messages`;
|
|
174
|
+
else
|
|
175
|
+
url = `https://api.sgroup.qq.com/channels/${target.channelId}/messages`;
|
|
176
|
+
try {
|
|
177
|
+
await (0, helpers_1.postJson)(url, payload, { headers });
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
if (e?.response?.status === 401)
|
|
181
|
+
tokenCache.invalidate();
|
|
182
|
+
throw new Error(`qq send error: ${e?.response?.status || ''} ${String(e?.message || e).slice(0, 120)}`);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# 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,EAA4D,MAAM,UAAU,CAAC;AAIzG,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,CA+F1F"}
|