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.
- package/README.md +52 -6
- package/dist/cli/main.js +3 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/core/agent.d.ts +4 -1
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +83 -64
- package/dist/core/agent.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 +186 -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 +177 -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 +177 -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 +152 -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 +81 -0
- package/dist/gateway/types.d.ts.map +1 -0
- package/dist/gateway/types.js +14 -0
- package/dist/gateway/types.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/main.ts +3 -0
- package/src/core/agent.ts +83 -62
- package/src/gateway/channels/feishu.ts +142 -0
- package/src/gateway/channels/qq.ts +140 -0
- package/src/gateway/channels/wecom.ts +142 -0
- package/src/gateway/gateway.ts +151 -0
- package/src/gateway/helpers.ts +82 -0
- package/src/gateway/registry.ts +45 -0
- package/src/gateway/types.ts +91 -0
- package/tests/agent.test.ts +45 -19
- 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":"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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,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":";AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUH,wCAGC;AAGD,oCAeC;AASD,gDAsFC;AA5HD,+CAAiC;AACjC,8CAA8C;AAC9C,wCAA0E;AAG1E,MAAM,GAAG,GAAG,IAAA,kBAAS,EAAC,eAAe,CAAC,CAAC;AAEvC,gEAAgE;AAChE,SAAgB,cAAc,CAAC,KAAa,EAAE,SAAiB,EAAE,KAAa,EAAE,OAAe;IAC7F,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC;AAED,oEAAoE;AACpE,SAAgB,YAAY,CAAC,OAAe,EAAE,cAAsB;IAClE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW;IACpE,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/B,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,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnG,cAAc;IACd,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC5C,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE;QAAE,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACpF,8CAA8C;IAC9C,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW;IACxC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,sCAAsC,GAAG,GAAG,CAAC,CAAC,CAAC;IACzF,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,kBAAkB,GAAG,GAAG,CAAC,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,SAAgB,kBAAkB,CAAC,GAAQ,EAAE,GAAsB;IACjE,MAAM,MAAM,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,cAAc,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,IAAA,uBAAa,EAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC5G,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAE7D,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC,KAAK,IAAI,EAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAA,iBAAO,EACxB,uDAAuD,kBAAkB,CAAC,MAAM,CAAC,eAAe,kBAAkB,CAAC,UAAU,CAAC,EAAE,CACjI,CAAC;QACF,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7F,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,CAAC,GAAe,EAAE,OAAe,EAAW,EAAE;QAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAC3C,OAAO,GAAG,KAAK,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,cAAc;QACpB,YAAY,EAAE,GAAG,CAAC,KAAK,IAAI,MAAM;QAEjC,KAAK,CAAC,aAAa,CAAC,GAAe;YACjC,sCAAsC;YACtC,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC;oBAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,CAAC;gBACvF,IAAI,CAAC;oBACH,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBAClD,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;gBACtD,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBAC/D,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;gBAC/D,CAAC;YACH,CAAC;YAED,6CAA6C;YAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACzC,IAAI,CAAC,OAAO;gBAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC;YACvE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,CAAC;YAEvF,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBAAC,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC;YAAC,CAAC;YACtD,OAAO,CAAC,EAAE,CAAC;gBAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;YAAC,CAAC;YAEnI,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;YACjD,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,IAAI,OAAO,KAAK,MAAM;gBAAE,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;;gBAC5D,IAAI,GAAG,IAAI,OAAO,MAAM,CAAC;YAE9B,yEAAyE;YACzE,OAAO;gBACL,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;gBACnC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;oBACd,OAAO,EAAE,OAAO;oBAChB,cAAc,EAAE,QAAQ;oBACxB,MAAM,EAAE,QAAQ;oBAChB,IAAI;oBACJ,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;oBAC/C,GAAG,EAAE,KAAK;iBACX,CAAC,CAAC,CAAC,SAAS;aACd,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,MAAmB,EAAE,IAAY;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAgB,CAAC;YACvC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;gBAAE,OAAO;YAChC,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,IAAA,kBAAQ,EACzB,iEAAiE,kBAAkB,CAAC,WAAW,CAAC,EAAE,EAClG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CACvF,CAAC;YACF,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;oBAAE,UAAU,CAAC,UAAU,EAAE,CAAC;gBAC9E,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|