@love19861018/wecom-openclaw-plugin 1.0.11 → 1.0.13
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/dist/index.cjs.js +1 -1945
- package/dist/index.esm.js +1 -1941
- package/package.json +3 -2
package/dist/index.cjs.js
CHANGED
|
@@ -1,1945 +1 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var pluginSdk = require('openclaw/plugin-sdk');
|
|
6
|
-
var aibotNodeSdk = require('@wecom/aibot-node-sdk');
|
|
7
|
-
var fileType = require('file-type');
|
|
8
|
-
var os = require('node:os');
|
|
9
|
-
var path = require('node:path');
|
|
10
|
-
|
|
11
|
-
let runtime = null;
|
|
12
|
-
function setWeComRuntime(r) {
|
|
13
|
-
runtime = r;
|
|
14
|
-
}
|
|
15
|
-
function getWeComRuntime() {
|
|
16
|
-
if (!runtime) {
|
|
17
|
-
throw new Error("WeCom runtime not initialized - plugin not registered");
|
|
18
|
-
}
|
|
19
|
-
return runtime;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 企业微信渠道常量定义
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* 企业微信渠道 ID
|
|
27
|
-
*/
|
|
28
|
-
const CHANNEL_ID = "wecom";
|
|
29
|
-
/**
|
|
30
|
-
* 企业微信 WebSocket 命令枚举
|
|
31
|
-
*/
|
|
32
|
-
var WeComCommand;
|
|
33
|
-
(function (WeComCommand) {
|
|
34
|
-
/** 认证订阅 */
|
|
35
|
-
WeComCommand["SUBSCRIBE"] = "aibot_subscribe";
|
|
36
|
-
/** 心跳 */
|
|
37
|
-
WeComCommand["PING"] = "ping";
|
|
38
|
-
/** 企业微信推送消息 */
|
|
39
|
-
WeComCommand["AIBOT_CALLBACK"] = "aibot_callback";
|
|
40
|
-
/** clawdbot 响应消息 */
|
|
41
|
-
WeComCommand["AIBOT_RESPONSE"] = "aibot_response";
|
|
42
|
-
})(WeComCommand || (WeComCommand = {}));
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// 超时和重试配置
|
|
45
|
-
// ============================================================================
|
|
46
|
-
/** 图片下载超时时间(毫秒) */
|
|
47
|
-
const IMAGE_DOWNLOAD_TIMEOUT_MS = 30000;
|
|
48
|
-
/** 文件下载超时时间(毫秒) */
|
|
49
|
-
const FILE_DOWNLOAD_TIMEOUT_MS = 60000;
|
|
50
|
-
/** 消息发送超时时间(毫秒) */
|
|
51
|
-
const REPLY_SEND_TIMEOUT_MS = 15000;
|
|
52
|
-
/** 消息处理总超时时间(毫秒) */
|
|
53
|
-
const MESSAGE_PROCESS_TIMEOUT_MS = 5 * 60 * 1000;
|
|
54
|
-
/** WebSocket 心跳间隔(毫秒) */
|
|
55
|
-
const WS_HEARTBEAT_INTERVAL_MS = 30000;
|
|
56
|
-
/** WebSocket 最大重连次数 */
|
|
57
|
-
const WS_MAX_RECONNECT_ATTEMPTS = 100;
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// 消息状态管理配置
|
|
60
|
-
// ============================================================================
|
|
61
|
-
/** messageStates Map 条目的最大 TTL(毫秒),防止内存泄漏 */
|
|
62
|
-
const MESSAGE_STATE_TTL_MS = 10 * 60 * 1000;
|
|
63
|
-
/** messageStates Map 清理间隔(毫秒) */
|
|
64
|
-
const MESSAGE_STATE_CLEANUP_INTERVAL_MS = 60000;
|
|
65
|
-
/** messageStates Map 最大条目数 */
|
|
66
|
-
const MESSAGE_STATE_MAX_SIZE = 500;
|
|
67
|
-
// ============================================================================
|
|
68
|
-
// 消息模板
|
|
69
|
-
// ============================================================================
|
|
70
|
-
/** "思考中"流式消息占位内容 */
|
|
71
|
-
const THINKING_MESSAGE = "<think></think>";
|
|
72
|
-
/** 仅包含图片时的消息占位符 */
|
|
73
|
-
const MEDIA_IMAGE_PLACEHOLDER = "<media:image>";
|
|
74
|
-
/** 仅包含文件时的消息占位符 */
|
|
75
|
-
const MEDIA_DOCUMENT_PLACEHOLDER = "<media:document>";
|
|
76
|
-
// ============================================================================
|
|
77
|
-
// 默认值
|
|
78
|
-
// ============================================================================
|
|
79
|
-
/** 默认媒体大小上限(MB) */
|
|
80
|
-
const DEFAULT_MEDIA_MAX_MB = 5;
|
|
81
|
-
/** 文本分块大小上限 */
|
|
82
|
-
const TEXT_CHUNK_LIMIT = 4000;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 企业微信消息内容解析模块
|
|
86
|
-
*
|
|
87
|
-
* 负责从 WsFrame 中提取文本、图片、引用等内容
|
|
88
|
-
*/
|
|
89
|
-
// ============================================================================
|
|
90
|
-
// 解析函数
|
|
91
|
-
// ============================================================================
|
|
92
|
-
/**
|
|
93
|
-
* 解析消息内容(支持单条消息、图文混排和引用消息)
|
|
94
|
-
* @returns 提取的文本数组、图片URL数组和引用消息内容
|
|
95
|
-
*/
|
|
96
|
-
function parseMessageContent(body) {
|
|
97
|
-
const textParts = [];
|
|
98
|
-
const imageUrls = [];
|
|
99
|
-
const imageAesKeys = new Map();
|
|
100
|
-
const fileUrls = [];
|
|
101
|
-
const fileAesKeys = new Map();
|
|
102
|
-
let quoteContent;
|
|
103
|
-
// 处理图文混排消息
|
|
104
|
-
if (body.msgtype === "mixed" && body.mixed?.msg_item) {
|
|
105
|
-
for (const item of body.mixed.msg_item) {
|
|
106
|
-
if (item.msgtype === "text" && item.text?.content) {
|
|
107
|
-
textParts.push(item.text.content);
|
|
108
|
-
}
|
|
109
|
-
else if (item.msgtype === "image" && item.image?.url) {
|
|
110
|
-
imageUrls.push(item.image.url);
|
|
111
|
-
if (item.image.aeskey) {
|
|
112
|
-
imageAesKeys.set(item.image.url, item.image.aeskey);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// 处理单条消息
|
|
119
|
-
if (body.text?.content) {
|
|
120
|
-
textParts.push(body.text.content);
|
|
121
|
-
}
|
|
122
|
-
// 处理语音消息(语音转文字后的文本内容)
|
|
123
|
-
if (body.msgtype === "voice" && body.voice?.content) {
|
|
124
|
-
textParts.push(body.voice.content);
|
|
125
|
-
}
|
|
126
|
-
if (body.image?.url) {
|
|
127
|
-
imageUrls.push(body.image.url);
|
|
128
|
-
if (body.image.aeskey) {
|
|
129
|
-
imageAesKeys.set(body.image.url, body.image.aeskey);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// 处理文件消息
|
|
133
|
-
if (body.msgtype === "file" && body.file?.url) {
|
|
134
|
-
fileUrls.push(body.file.url);
|
|
135
|
-
if (body.file.aeskey) {
|
|
136
|
-
fileAesKeys.set(body.file.url, body.file.aeskey);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// 处理引用消息
|
|
141
|
-
if (body.quote) {
|
|
142
|
-
if (body.quote.msgtype === "text" && body.quote.text?.content) {
|
|
143
|
-
quoteContent = body.quote.text.content;
|
|
144
|
-
}
|
|
145
|
-
else if (body.quote.msgtype === "voice" && body.quote.voice?.content) {
|
|
146
|
-
quoteContent = body.quote.voice.content;
|
|
147
|
-
}
|
|
148
|
-
else if (body.quote.msgtype === "image" && body.quote.image?.url) {
|
|
149
|
-
// 引用的图片消息:将图片 URL 加入下载列表
|
|
150
|
-
imageUrls.push(body.quote.image.url);
|
|
151
|
-
if (body.quote.image.aeskey) {
|
|
152
|
-
imageAesKeys.set(body.quote.image.url, body.quote.image.aeskey);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (body.quote.msgtype === "file" && body.quote.file?.url) {
|
|
156
|
-
// 引用的文件消息:将文件 URL 加入下载列表
|
|
157
|
-
fileUrls.push(body.quote.file.url);
|
|
158
|
-
if (body.quote.file.aeskey) {
|
|
159
|
-
fileAesKeys.set(body.quote.file.url, body.quote.file.aeskey);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 超时控制工具模块
|
|
168
|
-
*
|
|
169
|
-
* 为异步操作提供统一的超时保护机制
|
|
170
|
-
*/
|
|
171
|
-
/**
|
|
172
|
-
* 为 Promise 添加超时保护
|
|
173
|
-
*
|
|
174
|
-
* @param promise - 原始 Promise
|
|
175
|
-
* @param timeoutMs - 超时时间(毫秒)
|
|
176
|
-
* @param message - 超时错误消息
|
|
177
|
-
* @returns 带超时保护的 Promise
|
|
178
|
-
*/
|
|
179
|
-
function withTimeout(promise, timeoutMs, message) {
|
|
180
|
-
if (timeoutMs <= 0 || !Number.isFinite(timeoutMs)) {
|
|
181
|
-
return promise;
|
|
182
|
-
}
|
|
183
|
-
let timeoutId;
|
|
184
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
185
|
-
timeoutId = setTimeout(() => {
|
|
186
|
-
reject(new TimeoutError(message ?? `Operation timed out after ${timeoutMs}ms`));
|
|
187
|
-
}, timeoutMs);
|
|
188
|
-
});
|
|
189
|
-
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
190
|
-
clearTimeout(timeoutId);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 超时错误类型
|
|
195
|
-
*/
|
|
196
|
-
class TimeoutError extends Error {
|
|
197
|
-
constructor(message) {
|
|
198
|
-
super(message);
|
|
199
|
-
this.name = "TimeoutError";
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 企业微信消息发送模块
|
|
205
|
-
*
|
|
206
|
-
* 负责通过 WSClient 发送回复消息,包含超时保护
|
|
207
|
-
*/
|
|
208
|
-
// ============================================================================
|
|
209
|
-
// 消息发送
|
|
210
|
-
// ============================================================================
|
|
211
|
-
/**
|
|
212
|
-
* 发送企业微信回复消息
|
|
213
|
-
* 供 monitor 内部和 channel outbound 使用
|
|
214
|
-
*
|
|
215
|
-
* @returns messageId (streamId)
|
|
216
|
-
*/
|
|
217
|
-
async function sendWeComReply(params) {
|
|
218
|
-
const { wsClient, frame, text, runtime, finish = true, streamId: existingStreamId } = params;
|
|
219
|
-
if (!text) {
|
|
220
|
-
return "";
|
|
221
|
-
}
|
|
222
|
-
const streamId = existingStreamId || aibotNodeSdk.generateReqId("stream");
|
|
223
|
-
if (!wsClient.isConnected) {
|
|
224
|
-
runtime.error?.(`[WeCom] WSClient not connected, cannot send reply`);
|
|
225
|
-
throw new Error("WSClient not connected");
|
|
226
|
-
}
|
|
227
|
-
// 使用 SDK 的 replyStream 方法发送消息,带超时保护
|
|
228
|
-
await withTimeout(wsClient.replyStream(frame, streamId, text, finish), REPLY_SEND_TIMEOUT_MS, `Reply send timed out (streamId=${streamId})`);
|
|
229
|
-
runtime.log?.(`[WeCom] Sent reply: streamId=${streamId}, finish=${finish}`);
|
|
230
|
-
return streamId;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* 企业微信媒体(图片)下载和保存模块
|
|
235
|
-
*
|
|
236
|
-
* 负责下载、检测格式、保存图片到本地,包含超时保护
|
|
237
|
-
*/
|
|
238
|
-
// ============================================================================
|
|
239
|
-
// 图片格式检测辅助函数(基于 file-type 包)
|
|
240
|
-
// ============================================================================
|
|
241
|
-
/**
|
|
242
|
-
* 检查 Buffer 是否为有效的图片格式
|
|
243
|
-
*/
|
|
244
|
-
async function isImageBuffer(data) {
|
|
245
|
-
const type = await fileType.fileTypeFromBuffer(data);
|
|
246
|
-
return type?.mime.startsWith("image/") ?? false;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* 检测 Buffer 的图片内容类型
|
|
250
|
-
*/
|
|
251
|
-
async function detectImageContentType(data) {
|
|
252
|
-
const type = await fileType.fileTypeFromBuffer(data);
|
|
253
|
-
if (type?.mime.startsWith("image/")) {
|
|
254
|
-
return type.mime;
|
|
255
|
-
}
|
|
256
|
-
return "application/octet-stream";
|
|
257
|
-
}
|
|
258
|
-
// ============================================================================
|
|
259
|
-
// 图片下载和保存
|
|
260
|
-
// ============================================================================
|
|
261
|
-
/**
|
|
262
|
-
* 下载并保存所有图片到本地,每张图片的下载带超时保护
|
|
263
|
-
*/
|
|
264
|
-
async function downloadAndSaveImages(params) {
|
|
265
|
-
const { imageUrls, config, runtime, wsClient } = params;
|
|
266
|
-
const core = getWeComRuntime();
|
|
267
|
-
const mediaList = [];
|
|
268
|
-
for (const imageUrl of imageUrls) {
|
|
269
|
-
try {
|
|
270
|
-
runtime.log?.(`[WeCom] Downloading image from: ${imageUrl}`);
|
|
271
|
-
const mediaMaxMb = config.agents?.defaults?.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
|
|
272
|
-
const maxBytes = mediaMaxMb * 1024 * 1024;
|
|
273
|
-
let imageBuffer;
|
|
274
|
-
let imageContentType;
|
|
275
|
-
let originalFilename;
|
|
276
|
-
const imageAesKey = params.imageAesKeys?.get(imageUrl);
|
|
277
|
-
try {
|
|
278
|
-
// 优先使用 SDK 的 downloadFile 方法下载(带超时保护)
|
|
279
|
-
const result = await withTimeout(wsClient.downloadFile(imageUrl, imageAesKey), IMAGE_DOWNLOAD_TIMEOUT_MS, `Image download timed out: ${imageUrl}`);
|
|
280
|
-
imageBuffer = result.buffer;
|
|
281
|
-
originalFilename = result.filename;
|
|
282
|
-
imageContentType = await detectImageContentType(imageBuffer);
|
|
283
|
-
runtime.log?.(`[WeCom] Image downloaded via SDK: size=${imageBuffer.length}, contentType=${imageContentType}${originalFilename ? `, filename=${originalFilename}` : ""}`);
|
|
284
|
-
}
|
|
285
|
-
catch (sdkError) {
|
|
286
|
-
// 如果 SDK 方法失败,回退到原有方式(带超时保护)
|
|
287
|
-
runtime.log?.(`[WeCom] SDK download failed, falling back to manual download: ${String(sdkError)}`);
|
|
288
|
-
const fetched = await withTimeout(core.channel.media.fetchRemoteMedia({ url: imageUrl }), IMAGE_DOWNLOAD_TIMEOUT_MS, `Manual image download timed out: ${imageUrl}`);
|
|
289
|
-
runtime.log?.(`[WeCom] Image fetched: contentType=${fetched.contentType}, size=${fetched.buffer.length}, first4Bytes=${fetched.buffer.slice(0, 4).toString("hex")}`);
|
|
290
|
-
imageBuffer = fetched.buffer;
|
|
291
|
-
imageContentType = fetched.contentType ?? "application/octet-stream";
|
|
292
|
-
const isValidImage = await isImageBuffer(fetched.buffer);
|
|
293
|
-
if (!isValidImage) {
|
|
294
|
-
runtime.log?.(`[WeCom] WARN: Image does not appear to be a valid image format`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const saved = await core.channel.media.saveMediaBuffer(imageBuffer, imageContentType, "inbound", maxBytes, originalFilename);
|
|
298
|
-
mediaList.push({ path: saved.path, contentType: saved.contentType });
|
|
299
|
-
runtime.log?.(`[WeCom] Image saved to ${saved.path}, finalContentType=${saved.contentType}`);
|
|
300
|
-
}
|
|
301
|
-
catch (err) {
|
|
302
|
-
runtime.error?.(`[WeCom] Failed to download image: ${String(err)}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return mediaList;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* 下载并保存所有文件到本地,每个文件的下载带超时保护
|
|
309
|
-
*/
|
|
310
|
-
async function downloadAndSaveFiles(params) {
|
|
311
|
-
const { fileUrls, config, runtime, wsClient } = params;
|
|
312
|
-
const core = getWeComRuntime();
|
|
313
|
-
const mediaList = [];
|
|
314
|
-
for (const fileUrl of fileUrls) {
|
|
315
|
-
try {
|
|
316
|
-
runtime.log?.(`[WeCom] Downloading file from: ${fileUrl}`);
|
|
317
|
-
const mediaMaxMb = config.agents?.defaults?.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
|
|
318
|
-
const maxBytes = mediaMaxMb * 1024 * 1024;
|
|
319
|
-
let fileBuffer;
|
|
320
|
-
let fileContentType;
|
|
321
|
-
let originalFilename;
|
|
322
|
-
const fileAesKey = params.fileAesKeys?.get(fileUrl);
|
|
323
|
-
try {
|
|
324
|
-
// 使用 SDK 的 downloadFile 方法下载(带超时保护)
|
|
325
|
-
const result = await withTimeout(wsClient.downloadFile(fileUrl, fileAesKey), FILE_DOWNLOAD_TIMEOUT_MS, `File download timed out: ${fileUrl}`);
|
|
326
|
-
fileBuffer = result.buffer;
|
|
327
|
-
originalFilename = result.filename;
|
|
328
|
-
// 检测文件类型
|
|
329
|
-
const type = await fileType.fileTypeFromBuffer(fileBuffer);
|
|
330
|
-
fileContentType = type?.mime ?? "application/octet-stream";
|
|
331
|
-
runtime.log?.(`[WeCom] File downloaded via SDK: size=${fileBuffer.length}, contentType=${fileContentType}${originalFilename ? `, filename=${originalFilename}` : ""}`);
|
|
332
|
-
}
|
|
333
|
-
catch (sdkError) {
|
|
334
|
-
// 如果 SDK 方法失败,回退到 fetchRemoteMedia(带超时保护)
|
|
335
|
-
runtime.log?.(`[WeCom] SDK file download failed, falling back to manual download: ${String(sdkError)}`);
|
|
336
|
-
const fetched = await withTimeout(core.channel.media.fetchRemoteMedia({ url: fileUrl }), FILE_DOWNLOAD_TIMEOUT_MS, `Manual file download timed out: ${fileUrl}`);
|
|
337
|
-
runtime.log?.(`[WeCom] File fetched: contentType=${fetched.contentType}, size=${fetched.buffer.length}`);
|
|
338
|
-
fileBuffer = fetched.buffer;
|
|
339
|
-
fileContentType = fetched.contentType ?? "application/octet-stream";
|
|
340
|
-
}
|
|
341
|
-
const saved = await core.channel.media.saveMediaBuffer(fileBuffer, fileContentType, "inbound", maxBytes, originalFilename);
|
|
342
|
-
mediaList.push({ path: saved.path, contentType: saved.contentType });
|
|
343
|
-
runtime.log?.(`[WeCom] File saved to ${saved.path}, finalContentType=${saved.contentType}`);
|
|
344
|
-
}
|
|
345
|
-
catch (err) {
|
|
346
|
-
runtime.error?.(`[WeCom] Failed to download file: ${String(err)}`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return mediaList;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* 企业微信群组访问控制模块
|
|
354
|
-
*
|
|
355
|
-
* 负责群组策略检查(groupPolicy、群组白名单、群内发送者白名单)
|
|
356
|
-
*/
|
|
357
|
-
// ============================================================================
|
|
358
|
-
// 内部辅助函数
|
|
359
|
-
// ============================================================================
|
|
360
|
-
/**
|
|
361
|
-
* 解析企业微信群组配置
|
|
362
|
-
*/
|
|
363
|
-
function resolveWeComGroupConfig(params) {
|
|
364
|
-
const groups = params.cfg?.groups ?? {};
|
|
365
|
-
const wildcard = groups["*"];
|
|
366
|
-
const groupId = params.groupId?.trim();
|
|
367
|
-
if (!groupId) {
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
const direct = groups[groupId];
|
|
371
|
-
if (direct) {
|
|
372
|
-
return direct;
|
|
373
|
-
}
|
|
374
|
-
const lowered = groupId.toLowerCase();
|
|
375
|
-
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
|
|
376
|
-
if (matchKey) {
|
|
377
|
-
return groups[matchKey];
|
|
378
|
-
}
|
|
379
|
-
return wildcard;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* 检查群组是否在允许列表中
|
|
383
|
-
*/
|
|
384
|
-
function isWeComGroupAllowed(params) {
|
|
385
|
-
const { groupPolicy } = params;
|
|
386
|
-
if (groupPolicy === "disabled") {
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
if (groupPolicy === "open") {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
// allowlist 模式:检查群组是否在允许列表中
|
|
393
|
-
const normalizedAllowFrom = params.allowFrom.map((entry) => {
|
|
394
|
-
let normalized = String(entry).replace(new RegExp(`^${CHANNEL_ID}:`, "i"), "").trim();
|
|
395
|
-
// 兼容 botId:groupId 格式,移除 botId
|
|
396
|
-
if (normalized.includes(":")) {
|
|
397
|
-
const parts = normalized.split(":");
|
|
398
|
-
normalized = parts.slice(1).join(":");
|
|
399
|
-
}
|
|
400
|
-
return normalized;
|
|
401
|
-
});
|
|
402
|
-
if (normalizedAllowFrom.includes("*")) {
|
|
403
|
-
return true;
|
|
404
|
-
}
|
|
405
|
-
const normalizedGroupId = params.groupId.trim();
|
|
406
|
-
return normalizedAllowFrom.some((entry) => entry === normalizedGroupId || entry.toLowerCase() === normalizedGroupId.toLowerCase());
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* 检查发送者是否在允许列表中
|
|
410
|
-
*/
|
|
411
|
-
function isSenderAllowed(senderId, allowFrom) {
|
|
412
|
-
if (allowFrom.length === 0) {
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
const list = allowFrom.map((v) => String(v));
|
|
416
|
-
if (list.includes("*")) {
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
return list.some((entry) => {
|
|
420
|
-
// 兼容多种格式:wecom:bot-id:user-id, wecom:user-id, user:user-id, user-id
|
|
421
|
-
let normalized = entry.replace(new RegExp(`^${CHANNEL_ID}:`, "i"), "").trim();
|
|
422
|
-
// 如果仍然包含冒号,可能是 accountId:userId 格式,再次移除前面的部分
|
|
423
|
-
if (normalized.includes(":")) {
|
|
424
|
-
const parts = normalized.split(":");
|
|
425
|
-
// 如果 parts[0] 看起来像是一个 botId (由 onboarding 生成的)
|
|
426
|
-
normalized = parts.slice(1).join(":");
|
|
427
|
-
}
|
|
428
|
-
return normalized === senderId || normalized === `user:${senderId}` || entry === senderId;
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* 检查群组内发送者是否在允许列表中
|
|
433
|
-
*/
|
|
434
|
-
function isGroupSenderAllowed(params) {
|
|
435
|
-
const { senderId, groupId, wecomConfig } = params;
|
|
436
|
-
const groupConfig = resolveWeComGroupConfig({
|
|
437
|
-
cfg: wecomConfig,
|
|
438
|
-
groupId,
|
|
439
|
-
});
|
|
440
|
-
const perGroupSenderAllowFrom = (groupConfig?.allowFrom ?? []).map((v) => String(v));
|
|
441
|
-
if (perGroupSenderAllowFrom.length === 0) {
|
|
442
|
-
return true;
|
|
443
|
-
}
|
|
444
|
-
return isSenderAllowed(senderId, perGroupSenderAllowFrom);
|
|
445
|
-
}
|
|
446
|
-
// ============================================================================
|
|
447
|
-
// 公开 API
|
|
448
|
-
// ============================================================================
|
|
449
|
-
/**
|
|
450
|
-
* 检查群组策略访问控制
|
|
451
|
-
* @returns 检查结果,包含是否允许继续处理
|
|
452
|
-
*/
|
|
453
|
-
function checkGroupPolicy(params) {
|
|
454
|
-
const { chatId, senderId, account, config, runtime } = params;
|
|
455
|
-
// 使用当前账户的配置,而不是全局根级配置
|
|
456
|
-
const wecomConfig = account.config;
|
|
457
|
-
const defaultGroupPolicy = config.channels?.defaults?.groupPolicy;
|
|
458
|
-
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
|
459
|
-
const groupAllowFrom = wecomConfig.groupAllowFrom ?? [];
|
|
460
|
-
const groupAllowed = isWeComGroupAllowed({
|
|
461
|
-
groupPolicy,
|
|
462
|
-
allowFrom: groupAllowFrom,
|
|
463
|
-
groupId: chatId,
|
|
464
|
-
});
|
|
465
|
-
if (!groupAllowed) {
|
|
466
|
-
runtime.log?.(`[WeCom] Group ${chatId} not allowed (groupPolicy=${groupPolicy})`);
|
|
467
|
-
return { allowed: false };
|
|
468
|
-
}
|
|
469
|
-
const senderAllowed = isGroupSenderAllowed({
|
|
470
|
-
senderId,
|
|
471
|
-
groupId: chatId,
|
|
472
|
-
wecomConfig,
|
|
473
|
-
});
|
|
474
|
-
if (!senderAllowed) {
|
|
475
|
-
runtime.log?.(`[WeCom] Sender ${senderId} not in group ${chatId} sender allowlist`);
|
|
476
|
-
return { allowed: false };
|
|
477
|
-
}
|
|
478
|
-
return { allowed: true };
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* 企业微信 DM(私聊)访问控制模块
|
|
483
|
-
*
|
|
484
|
-
* 负责私聊策略检查、配对流程
|
|
485
|
-
*/
|
|
486
|
-
// ============================================================================
|
|
487
|
-
// 公开 API
|
|
488
|
-
// ============================================================================
|
|
489
|
-
/**
|
|
490
|
-
* 检查 DM Policy 访问控制
|
|
491
|
-
* @returns 检查结果,包含是否允许继续处理
|
|
492
|
-
*/
|
|
493
|
-
async function checkDmPolicy(params) {
|
|
494
|
-
const { senderId, isGroup, account, wsClient, frame, runtime } = params;
|
|
495
|
-
const core = getWeComRuntime();
|
|
496
|
-
// 群聊消息不检查 DM Policy
|
|
497
|
-
if (isGroup) {
|
|
498
|
-
return { allowed: true };
|
|
499
|
-
}
|
|
500
|
-
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
501
|
-
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
|
502
|
-
// 如果 dmPolicy 是 disabled,直接拒绝
|
|
503
|
-
if (dmPolicy === "disabled") {
|
|
504
|
-
runtime.log?.(`[WeCom] Blocked DM from ${senderId} (dmPolicy=disabled)`);
|
|
505
|
-
return { allowed: false };
|
|
506
|
-
}
|
|
507
|
-
// 如果是 open 模式,允许所有人
|
|
508
|
-
if (dmPolicy === "open") {
|
|
509
|
-
return { allowed: true };
|
|
510
|
-
}
|
|
511
|
-
// OpenClaw <= 2026.2.19 signature: readAllowFromStore(channel, env?, accountId?)
|
|
512
|
-
const oldStoreAllowFrom = await core.channel.pairing.readAllowFromStore('wecom', undefined, account.accountId).catch(() => []);
|
|
513
|
-
// Compatibility fallback for newer OpenClaw implementations.
|
|
514
|
-
const newStoreAllowFrom = await core.channel.pairing
|
|
515
|
-
.readAllowFromStore({ channel: CHANNEL_ID, accountId: account.accountId })
|
|
516
|
-
.catch(() => []);
|
|
517
|
-
// 检查发送者是否在允许列表中
|
|
518
|
-
const storeAllowFrom = [...oldStoreAllowFrom, ...newStoreAllowFrom];
|
|
519
|
-
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
|
520
|
-
const senderAllowedResult = isSenderAllowed(senderId, effectiveAllowFrom);
|
|
521
|
-
if (senderAllowedResult) {
|
|
522
|
-
return { allowed: true };
|
|
523
|
-
}
|
|
524
|
-
// 处理未授权用户
|
|
525
|
-
if (dmPolicy === "pairing") {
|
|
526
|
-
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
|
527
|
-
channel: CHANNEL_ID,
|
|
528
|
-
id: senderId,
|
|
529
|
-
accountId: account.accountId,
|
|
530
|
-
meta: { name: senderId },
|
|
531
|
-
});
|
|
532
|
-
if (created) {
|
|
533
|
-
runtime.log?.(`[WeCom] Pairing request created for sender=${senderId}`);
|
|
534
|
-
try {
|
|
535
|
-
await sendWeComReply({
|
|
536
|
-
wsClient,
|
|
537
|
-
frame,
|
|
538
|
-
text: `[机器人: ${account.tinyId}] ` + core.channel.pairing.buildPairingReply({
|
|
539
|
-
channel: CHANNEL_ID,
|
|
540
|
-
idLine: `您的企业微信用户ID: ${senderId}`,
|
|
541
|
-
code,
|
|
542
|
-
}),
|
|
543
|
-
runtime,
|
|
544
|
-
finish: true,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
catch (err) {
|
|
548
|
-
runtime.error?.(`[WeCom] Failed to send pairing reply to ${senderId}: ${String(err)}`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
runtime.log?.(`[WeCom] Pairing request already exists for sender=${senderId}`);
|
|
553
|
-
}
|
|
554
|
-
return { allowed: false, pairingSent: created };
|
|
555
|
-
}
|
|
556
|
-
// allowlist 模式:直接拒绝未授权用户
|
|
557
|
-
runtime.log?.(`[WeCom] Blocked unauthorized sender ${senderId} (dmPolicy=${dmPolicy})`);
|
|
558
|
-
return { allowed: false };
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// ============================================================================
|
|
562
|
-
// 常量
|
|
563
|
-
// ============================================================================
|
|
564
|
-
const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
|
|
565
|
-
const DEFAULT_MEMORY_MAX_SIZE = 200;
|
|
566
|
-
const DEFAULT_FILE_MAX_ENTRIES = 500;
|
|
567
|
-
const DEFAULT_FLUSH_DEBOUNCE_MS = 1000;
|
|
568
|
-
const DEFAULT_LOCK_OPTIONS = {
|
|
569
|
-
stale: 60000,
|
|
570
|
-
retries: {
|
|
571
|
-
retries: 6,
|
|
572
|
-
factor: 1.35,
|
|
573
|
-
minTimeout: 8,
|
|
574
|
-
maxTimeout: 180,
|
|
575
|
-
randomize: true,
|
|
576
|
-
},
|
|
577
|
-
};
|
|
578
|
-
// ============================================================================
|
|
579
|
-
// 状态目录解析
|
|
580
|
-
// ============================================================================
|
|
581
|
-
function resolveStateDirFromEnv(env = process.env) {
|
|
582
|
-
const stateOverride = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
583
|
-
if (stateOverride) {
|
|
584
|
-
return stateOverride;
|
|
585
|
-
}
|
|
586
|
-
if (env.VITEST || env.NODE_ENV === "test") {
|
|
587
|
-
return path.join(os.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
|
588
|
-
}
|
|
589
|
-
return path.join(os.homedir(), ".openclaw");
|
|
590
|
-
}
|
|
591
|
-
function resolveReqIdFilePath(accountId) {
|
|
592
|
-
const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
593
|
-
return path.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
|
|
594
|
-
}
|
|
595
|
-
// ============================================================================
|
|
596
|
-
// 核心实现
|
|
597
|
-
// ============================================================================
|
|
598
|
-
function createPersistentReqIdStore(accountId, options) {
|
|
599
|
-
const ttlMs = DEFAULT_TTL_MS;
|
|
600
|
-
const memoryMaxSize = DEFAULT_MEMORY_MAX_SIZE;
|
|
601
|
-
const fileMaxEntries = DEFAULT_FILE_MAX_ENTRIES;
|
|
602
|
-
const flushDebounceMs = DEFAULT_FLUSH_DEBOUNCE_MS;
|
|
603
|
-
const filePath = resolveReqIdFilePath(accountId);
|
|
604
|
-
// 内存层:chatId → ReqIdEntry
|
|
605
|
-
const memory = new Map();
|
|
606
|
-
// 防抖写入相关
|
|
607
|
-
let dirty = false;
|
|
608
|
-
let flushTimer = null;
|
|
609
|
-
// ========== 内部辅助函数 ==========
|
|
610
|
-
/** 检查条目是否过期 */
|
|
611
|
-
function isExpired(entry, now) {
|
|
612
|
-
return now - entry.ts >= ttlMs;
|
|
613
|
-
}
|
|
614
|
-
/** 验证磁盘条目的合法性 */
|
|
615
|
-
function isValidEntry(entry) {
|
|
616
|
-
return (typeof entry === "object" &&
|
|
617
|
-
entry !== null &&
|
|
618
|
-
typeof entry.reqId === "string" &&
|
|
619
|
-
typeof entry.ts === "number" &&
|
|
620
|
-
Number.isFinite(entry.ts));
|
|
621
|
-
}
|
|
622
|
-
/** 清理磁盘数据中的无效值,返回干净的 Record */
|
|
623
|
-
function sanitizeData(value) {
|
|
624
|
-
if (!value || typeof value !== "object") {
|
|
625
|
-
return {};
|
|
626
|
-
}
|
|
627
|
-
const out = {};
|
|
628
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
629
|
-
if (isValidEntry(entry)) {
|
|
630
|
-
out[key] = entry;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return out;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* 内存容量控制:淘汰最旧的条目。
|
|
637
|
-
* 利用 Map 的插入顺序 + touch(先 delete 再 set) 实现类 LRU 效果。
|
|
638
|
-
*/
|
|
639
|
-
function pruneMemory() {
|
|
640
|
-
if (memory.size <= memoryMaxSize)
|
|
641
|
-
return;
|
|
642
|
-
const sorted = [...memory.entries()].sort((a, b) => a[1].ts - b[1].ts);
|
|
643
|
-
const toRemove = sorted.slice(0, memory.size - memoryMaxSize);
|
|
644
|
-
for (const [key] of toRemove) {
|
|
645
|
-
memory.delete(key);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
/** 磁盘数据容量控制:先清过期,再按时间淘汰超量 */
|
|
649
|
-
function pruneFileData(data, now) {
|
|
650
|
-
{
|
|
651
|
-
for (const [key, entry] of Object.entries(data)) {
|
|
652
|
-
if (now - entry.ts >= ttlMs) {
|
|
653
|
-
delete data[key];
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
const keys = Object.keys(data);
|
|
658
|
-
if (keys.length <= fileMaxEntries)
|
|
659
|
-
return;
|
|
660
|
-
keys
|
|
661
|
-
.sort((a, b) => data[a].ts - data[b].ts)
|
|
662
|
-
.slice(0, keys.length - fileMaxEntries)
|
|
663
|
-
.forEach((key) => delete data[key]);
|
|
664
|
-
}
|
|
665
|
-
/** 防抖写入磁盘 */
|
|
666
|
-
function scheduleDiskFlush() {
|
|
667
|
-
dirty = true;
|
|
668
|
-
if (flushTimer)
|
|
669
|
-
return;
|
|
670
|
-
flushTimer = setTimeout(async () => {
|
|
671
|
-
flushTimer = null;
|
|
672
|
-
if (!dirty)
|
|
673
|
-
return;
|
|
674
|
-
await flushToDisk();
|
|
675
|
-
}, flushDebounceMs);
|
|
676
|
-
}
|
|
677
|
-
/** 立即写入磁盘(带文件锁,参考 createPersistentDedupe 的 checkAndRecordInner) */
|
|
678
|
-
async function flushToDisk() {
|
|
679
|
-
dirty = false;
|
|
680
|
-
const now = Date.now();
|
|
681
|
-
try {
|
|
682
|
-
await pluginSdk.withFileLock(filePath, DEFAULT_LOCK_OPTIONS, async () => {
|
|
683
|
-
// 读取现有磁盘数据并合并
|
|
684
|
-
const { value } = await pluginSdk.readJsonFileWithFallback(filePath, {});
|
|
685
|
-
const data = sanitizeData(value);
|
|
686
|
-
// 将内存中未过期的数据合并到磁盘数据(内存优先)
|
|
687
|
-
for (const [chatId, entry] of memory) {
|
|
688
|
-
if (!isExpired(entry, now)) {
|
|
689
|
-
data[chatId] = entry;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
// 清理过期和超量
|
|
693
|
-
pruneFileData(data, now);
|
|
694
|
-
// 原子写入
|
|
695
|
-
await pluginSdk.writeJsonFileAtomically(filePath, data);
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
catch (error) {
|
|
699
|
-
// 磁盘写入失败不影响内存使用,降级到纯内存模式
|
|
700
|
-
// console.error(`[WeCom] reqid-store: flush to disk failed: ${String(error)}`);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
// ========== 公开 API ==========
|
|
704
|
-
function set(chatId, reqId) {
|
|
705
|
-
const entry = { reqId, ts: Date.now() };
|
|
706
|
-
// touch:先删再设,保持 Map 插入顺序(类 LRU)
|
|
707
|
-
memory.delete(chatId);
|
|
708
|
-
memory.set(chatId, entry);
|
|
709
|
-
pruneMemory();
|
|
710
|
-
scheduleDiskFlush();
|
|
711
|
-
}
|
|
712
|
-
async function get(chatId) {
|
|
713
|
-
const now = Date.now();
|
|
714
|
-
// 1. 先查内存
|
|
715
|
-
const memEntry = memory.get(chatId);
|
|
716
|
-
if (memEntry && !isExpired(memEntry, now)) {
|
|
717
|
-
return memEntry.reqId;
|
|
718
|
-
}
|
|
719
|
-
if (memEntry) {
|
|
720
|
-
memory.delete(chatId); // 过期则删除
|
|
721
|
-
}
|
|
722
|
-
// 2. 内存 miss,回查磁盘并回填内存
|
|
723
|
-
try {
|
|
724
|
-
const { value } = await pluginSdk.readJsonFileWithFallback(filePath, {});
|
|
725
|
-
const data = sanitizeData(value);
|
|
726
|
-
const diskEntry = data[chatId];
|
|
727
|
-
if (diskEntry && !isExpired(diskEntry, now)) {
|
|
728
|
-
// 回填内存
|
|
729
|
-
memory.set(chatId, diskEntry);
|
|
730
|
-
return diskEntry.reqId;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
catch {
|
|
734
|
-
// 磁盘读取失败,降级返回 undefined
|
|
735
|
-
}
|
|
736
|
-
return undefined;
|
|
737
|
-
}
|
|
738
|
-
function getSync(chatId) {
|
|
739
|
-
const now = Date.now();
|
|
740
|
-
const entry = memory.get(chatId);
|
|
741
|
-
if (entry && !isExpired(entry, now)) {
|
|
742
|
-
return entry.reqId;
|
|
743
|
-
}
|
|
744
|
-
if (entry) {
|
|
745
|
-
memory.delete(chatId);
|
|
746
|
-
}
|
|
747
|
-
return undefined;
|
|
748
|
-
}
|
|
749
|
-
function del(chatId) {
|
|
750
|
-
memory.delete(chatId);
|
|
751
|
-
scheduleDiskFlush();
|
|
752
|
-
}
|
|
753
|
-
async function warmup(onError) {
|
|
754
|
-
const now = Date.now();
|
|
755
|
-
try {
|
|
756
|
-
const { value } = await pluginSdk.readJsonFileWithFallback(filePath, {});
|
|
757
|
-
const data = sanitizeData(value);
|
|
758
|
-
let loaded = 0;
|
|
759
|
-
for (const [chatId, entry] of Object.entries(data)) {
|
|
760
|
-
if (!isExpired(entry, now)) {
|
|
761
|
-
memory.set(chatId, entry);
|
|
762
|
-
loaded++;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
pruneMemory();
|
|
766
|
-
return loaded;
|
|
767
|
-
}
|
|
768
|
-
catch (error) {
|
|
769
|
-
onError?.(error);
|
|
770
|
-
return 0;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
async function flush() {
|
|
774
|
-
if (flushTimer) {
|
|
775
|
-
clearTimeout(flushTimer);
|
|
776
|
-
flushTimer = null;
|
|
777
|
-
}
|
|
778
|
-
await flushToDisk();
|
|
779
|
-
}
|
|
780
|
-
function clearMemory() {
|
|
781
|
-
memory.clear();
|
|
782
|
-
}
|
|
783
|
-
function memorySize() {
|
|
784
|
-
return memory.size;
|
|
785
|
-
}
|
|
786
|
-
return {
|
|
787
|
-
set,
|
|
788
|
-
get,
|
|
789
|
-
getSync,
|
|
790
|
-
delete: del,
|
|
791
|
-
warmup,
|
|
792
|
-
flush,
|
|
793
|
-
clearMemory,
|
|
794
|
-
memorySize,
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* 企业微信全局状态管理模块
|
|
800
|
-
*
|
|
801
|
-
* 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储
|
|
802
|
-
* 解决全局 Map 的内存泄漏问题
|
|
803
|
-
*/
|
|
804
|
-
// ============================================================================
|
|
805
|
-
// WSClient 实例管理
|
|
806
|
-
// ============================================================================
|
|
807
|
-
/** WSClient 实例管理 */
|
|
808
|
-
const wsClientInstances = new Map();
|
|
809
|
-
/**
|
|
810
|
-
* 获取指定账户的 WSClient 实例
|
|
811
|
-
*/
|
|
812
|
-
function getWeComWebSocket(accountId = pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
813
|
-
return wsClientInstances.get(accountId) ?? null;
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* 设置指定账户的 WSClient 实例
|
|
817
|
-
*/
|
|
818
|
-
function setWeComWebSocket(accountId, client) {
|
|
819
|
-
wsClientInstances.set(accountId, client);
|
|
820
|
-
}
|
|
821
|
-
/** 消息状态管理 */
|
|
822
|
-
const messageStates = new Map();
|
|
823
|
-
/** 定期清理定时器 */
|
|
824
|
-
let cleanupTimer = null;
|
|
825
|
-
/** 启动计数器,确保只有在没有账户运行时才停止清理 */
|
|
826
|
-
let runningAccountsCount = 0;
|
|
827
|
-
/**
|
|
828
|
-
* 启动消息状态定期清理(自动 TTL 清理 + 容量限制)
|
|
829
|
-
*/
|
|
830
|
-
function startMessageStateCleanup() {
|
|
831
|
-
runningAccountsCount++;
|
|
832
|
-
if (cleanupTimer)
|
|
833
|
-
return;
|
|
834
|
-
cleanupTimer = setInterval(() => {
|
|
835
|
-
pruneMessageStates();
|
|
836
|
-
}, MESSAGE_STATE_CLEANUP_INTERVAL_MS);
|
|
837
|
-
// 允许进程退出时不阻塞
|
|
838
|
-
if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
839
|
-
cleanupTimer.unref();
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* 停止消息状态定期清理
|
|
844
|
-
*/
|
|
845
|
-
function stopMessageStateCleanup() {
|
|
846
|
-
runningAccountsCount = Math.max(0, runningAccountsCount - 1);
|
|
847
|
-
if (runningAccountsCount === 0 && cleanupTimer) {
|
|
848
|
-
clearInterval(cleanupTimer);
|
|
849
|
-
cleanupTimer = null;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* 清理过期和超量的消息状态条目
|
|
854
|
-
*/
|
|
855
|
-
function pruneMessageStates() {
|
|
856
|
-
const now = Date.now();
|
|
857
|
-
// 1. 清理过期条目
|
|
858
|
-
for (const [key, entry] of messageStates) {
|
|
859
|
-
if (now - entry.createdAt >= MESSAGE_STATE_TTL_MS) {
|
|
860
|
-
messageStates.delete(key);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
// 2. 容量限制:如果仍超过最大条目数,按时间淘汰最旧的
|
|
864
|
-
if (messageStates.size > MESSAGE_STATE_MAX_SIZE) {
|
|
865
|
-
const sorted = [...messageStates.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
866
|
-
const toRemove = sorted.slice(0, messageStates.size - MESSAGE_STATE_MAX_SIZE);
|
|
867
|
-
for (const [key] of toRemove) {
|
|
868
|
-
messageStates.delete(key);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* 设置消息状态
|
|
874
|
-
*/
|
|
875
|
-
function setMessageState(messageId, state) {
|
|
876
|
-
messageStates.set(messageId, {
|
|
877
|
-
state,
|
|
878
|
-
createdAt: Date.now(),
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* 删除消息状态
|
|
883
|
-
*/
|
|
884
|
-
function deleteMessageState(messageId) {
|
|
885
|
-
messageStates.delete(messageId);
|
|
886
|
-
}
|
|
887
|
-
// ============================================================================
|
|
888
|
-
// ReqId 持久化存储管理(按 accountId 隔离)
|
|
889
|
-
// ============================================================================
|
|
890
|
-
/**
|
|
891
|
-
* ReqId 持久化存储管理
|
|
892
|
-
* 参考 createPersistentDedupe 模式:内存 + 磁盘双层、文件锁、原子写入、TTL 过期、防抖写入
|
|
893
|
-
* 重启后可从磁盘恢复,确保主动推送消息时能获取到 reqId
|
|
894
|
-
*/
|
|
895
|
-
const reqIdStores = new Map();
|
|
896
|
-
function getOrCreateReqIdStore(accountId) {
|
|
897
|
-
let store = reqIdStores.get(accountId);
|
|
898
|
-
if (!store) {
|
|
899
|
-
store = createPersistentReqIdStore(accountId);
|
|
900
|
-
reqIdStores.set(accountId, store);
|
|
901
|
-
}
|
|
902
|
-
return store;
|
|
903
|
-
}
|
|
904
|
-
// ============================================================================
|
|
905
|
-
// ReqId 操作函数
|
|
906
|
-
// ============================================================================
|
|
907
|
-
/**
|
|
908
|
-
* 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)
|
|
909
|
-
*/
|
|
910
|
-
function setReqIdForChat(chatId, reqId, accountId = pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
911
|
-
getOrCreateReqIdStore(accountId).set(chatId, reqId);
|
|
912
|
-
}
|
|
913
|
-
/**
|
|
914
|
-
* 启动时预热 reqId 缓存(从磁盘加载到内存)
|
|
915
|
-
*/
|
|
916
|
-
async function warmupReqIdStore(accountId = pluginSdk.DEFAULT_ACCOUNT_ID, log) {
|
|
917
|
-
const store = getOrCreateReqIdStore(accountId);
|
|
918
|
-
return store.warmup((error) => {
|
|
919
|
-
log?.(`[WeCom] reqid-store warmup error: ${String(error)}`);
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
// ============================================================================
|
|
923
|
-
// 全局 cleanup(断开连接时释放所有资源)
|
|
924
|
-
// ============================================================================
|
|
925
|
-
/**
|
|
926
|
-
* 清理指定账户的所有状态
|
|
927
|
-
*/
|
|
928
|
-
async function cleanupAccount(accountId) {
|
|
929
|
-
// 同时清理 accountId 和可能存在的 tinyId 映射
|
|
930
|
-
const wsClient = wsClientInstances.get(accountId);
|
|
931
|
-
if (wsClient) {
|
|
932
|
-
// 1. 显式断开连接,防止连接泄露
|
|
933
|
-
try {
|
|
934
|
-
wsClient.disconnect();
|
|
935
|
-
}
|
|
936
|
-
catch (err) {
|
|
937
|
-
// 忽略断开时的错误
|
|
938
|
-
}
|
|
939
|
-
// 2. 清理所有指向该实例的键
|
|
940
|
-
for (const [key, client] of wsClientInstances.entries()) {
|
|
941
|
-
if (client === wsClient) {
|
|
942
|
-
wsClientInstances.delete(key);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const store = reqIdStores.get(accountId);
|
|
947
|
-
if (store) {
|
|
948
|
-
await store.flush();
|
|
949
|
-
reqIdStores.delete(accountId);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* 企业微信 WebSocket 监控器主模块
|
|
955
|
-
*
|
|
956
|
-
* 负责:
|
|
957
|
-
* - 建立和管理 WebSocket 连接
|
|
958
|
-
* - 协调消息处理流程(解析→策略检查→下载图片→路由回复)
|
|
959
|
-
* - 资源生命周期管理
|
|
960
|
-
*
|
|
961
|
-
* 子模块:
|
|
962
|
-
* - message-parser.ts : 消息内容解析
|
|
963
|
-
* - message-sender.ts : 消息发送(带超时保护)
|
|
964
|
-
* - media-handler.ts : 图片下载和保存(带超时保护)
|
|
965
|
-
* - group-policy.ts : 群组访问控制
|
|
966
|
-
* - dm-policy.ts : 私聊访问控制
|
|
967
|
-
* - state-manager.ts : 全局状态管理(带 TTL 清理)
|
|
968
|
-
* - timeout.ts : 超时工具
|
|
969
|
-
*/
|
|
970
|
-
// ============================================================================
|
|
971
|
-
// 消息上下文构建
|
|
972
|
-
// ============================================================================
|
|
973
|
-
/**
|
|
974
|
-
* 构建消息上下文
|
|
975
|
-
*/
|
|
976
|
-
function buildMessageContext(frame, account, config, text, mediaList, quoteContent) {
|
|
977
|
-
const core = getWeComRuntime();
|
|
978
|
-
const body = frame.body;
|
|
979
|
-
const chatId = body.chatid || body.from.userid;
|
|
980
|
-
const chatType = body.chattype === "group" ? "group" : "direct";
|
|
981
|
-
// 构建会话标签
|
|
982
|
-
const fromLabel = chatType === "group"
|
|
983
|
-
? `[${account.name}] group:${chatId}`
|
|
984
|
-
: `[${account.name}] user:${body.from.userid}`;
|
|
985
|
-
// 当只有媒体没有文本时,使用占位符标识媒体类型
|
|
986
|
-
const hasImages = mediaList.some((m) => m.contentType?.startsWith("image/"));
|
|
987
|
-
const messageBody = text || (mediaList.length > 0 ? (hasImages ? MEDIA_IMAGE_PLACEHOLDER : MEDIA_DOCUMENT_PLACEHOLDER) : "");
|
|
988
|
-
// 构建多媒体数组
|
|
989
|
-
const mediaPaths = mediaList.length > 0 ? mediaList.map((m) => m.path) : undefined;
|
|
990
|
-
const mediaTypes = mediaList.length > 0
|
|
991
|
-
? mediaList.map((m) => m.contentType).filter(Boolean)
|
|
992
|
-
: undefined;
|
|
993
|
-
// 构建账户标识符(用于隔离不同机器人的会话)
|
|
994
|
-
// 采用标准的 3 段式标识符:channel:accountId:chatId
|
|
995
|
-
// 这里 accountId 使用 tinyId,确保多用户 x 多机器人场景下的 SessionKey 唯一
|
|
996
|
-
const accountPrefix = `${CHANNEL_ID}:${account.tinyId}`;
|
|
997
|
-
// 构建标准消息上下文
|
|
998
|
-
return core.channel.reply.finalizeInboundContext({
|
|
999
|
-
SessionKey: 'agent:' + 'agent_' + account.tinyId + ":" + account.accountId,
|
|
1000
|
-
Body: messageBody,
|
|
1001
|
-
RawBody: messageBody,
|
|
1002
|
-
CommandBody: messageBody,
|
|
1003
|
-
MessageSid: body.msgid,
|
|
1004
|
-
From: chatType === "group" ? `${accountPrefix}:group:${chatId}` : `${accountPrefix}:${body.from.userid}`,
|
|
1005
|
-
To: `${accountPrefix}:${chatId}`,
|
|
1006
|
-
SenderId: body.from.userid,
|
|
1007
|
-
AccountId: account.accountId,
|
|
1008
|
-
ChatType: chatType,
|
|
1009
|
-
ConversationLabel: fromLabel,
|
|
1010
|
-
Timestamp: Date.now(),
|
|
1011
|
-
Provider: CHANNEL_ID,
|
|
1012
|
-
Surface: CHANNEL_ID,
|
|
1013
|
-
OriginatingChannel: CHANNEL_ID,
|
|
1014
|
-
OriginatingTo: `${accountPrefix}:${chatId}`,
|
|
1015
|
-
CommandAuthorized: true,
|
|
1016
|
-
ResponseUrl: body.response_url,
|
|
1017
|
-
ReqId: frame.headers.req_id,
|
|
1018
|
-
WeComFrame: frame,
|
|
1019
|
-
MediaPath: mediaList[0]?.path,
|
|
1020
|
-
MediaType: mediaList[0]?.contentType,
|
|
1021
|
-
MediaPaths: mediaPaths,
|
|
1022
|
-
MediaTypes: mediaTypes,
|
|
1023
|
-
MediaUrls: mediaPaths,
|
|
1024
|
-
ReplyToBody: quoteContent,
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
// ============================================================================
|
|
1028
|
-
// 消息处理和回复
|
|
1029
|
-
// ============================================================================
|
|
1030
|
-
/**
|
|
1031
|
-
* 发送"思考中"消息
|
|
1032
|
-
*/
|
|
1033
|
-
async function sendThinkingReply(params) {
|
|
1034
|
-
const { wsClient, frame, streamId, runtime } = params;
|
|
1035
|
-
runtime.log?.(`[WeCom] Sending thinking message`);
|
|
1036
|
-
try {
|
|
1037
|
-
await sendWeComReply({
|
|
1038
|
-
wsClient,
|
|
1039
|
-
frame,
|
|
1040
|
-
text: THINKING_MESSAGE,
|
|
1041
|
-
runtime,
|
|
1042
|
-
finish: false,
|
|
1043
|
-
streamId,
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
catch (err) {
|
|
1047
|
-
runtime.error?.(`[WeCom] Failed to send thinking message: ${String(err)}`);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* 路由消息到核心处理流程并处理回复
|
|
1052
|
-
*/
|
|
1053
|
-
async function routeAndDispatchMessage(params) {
|
|
1054
|
-
const { ctxPayload, config, wsClient, frame, state, runtime, onCleanup } = params;
|
|
1055
|
-
const core = getWeComRuntime();
|
|
1056
|
-
// 防止 onCleanup 被多次调用(onError 回调与 catch 块可能重复触发)
|
|
1057
|
-
let cleanedUp = false;
|
|
1058
|
-
const safeCleanup = () => {
|
|
1059
|
-
if (!cleanedUp) {
|
|
1060
|
-
cleanedUp = true;
|
|
1061
|
-
onCleanup();
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
runtime.error?.(`[Anfioo] ${JSON.stringify(config, null, 2)}`);
|
|
1065
|
-
// 第二个参数 null 表示不使用替换函数
|
|
1066
|
-
// 第三个参数 2 表示缩进2个空格
|
|
1067
|
-
try {
|
|
1068
|
-
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1069
|
-
ctx: ctxPayload,
|
|
1070
|
-
cfg: config,
|
|
1071
|
-
dispatcherOptions: {
|
|
1072
|
-
deliver: async (payload, info) => {
|
|
1073
|
-
state.accumulatedText += payload.text;
|
|
1074
|
-
if (info.kind !== "final") {
|
|
1075
|
-
await sendWeComReply({
|
|
1076
|
-
wsClient,
|
|
1077
|
-
frame,
|
|
1078
|
-
text: state.accumulatedText,
|
|
1079
|
-
runtime,
|
|
1080
|
-
finish: false,
|
|
1081
|
-
streamId: state.streamId,
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
onError: (err, info) => {
|
|
1086
|
-
runtime.error?.(`[WeCom] ${info.kind} reply failed: ${String(err)}`);
|
|
1087
|
-
// 仅记录错误,不立即 cleanup,让外层 try/catch 统一处理最终回复和 cleanup
|
|
1088
|
-
},
|
|
1089
|
-
},
|
|
1090
|
-
});
|
|
1091
|
-
// 发送最终消息
|
|
1092
|
-
if (state.accumulatedText) {
|
|
1093
|
-
await sendWeComReply({
|
|
1094
|
-
wsClient,
|
|
1095
|
-
frame,
|
|
1096
|
-
text: state.accumulatedText,
|
|
1097
|
-
runtime,
|
|
1098
|
-
finish: true,
|
|
1099
|
-
streamId: state.streamId,
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
safeCleanup();
|
|
1103
|
-
}
|
|
1104
|
-
catch (err) {
|
|
1105
|
-
runtime.error?.(`[WeCom] Failed to process message: ${String(err)}`);
|
|
1106
|
-
safeCleanup();
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* 处理企业微信消息(主函数)
|
|
1111
|
-
*
|
|
1112
|
-
* 处理流程:
|
|
1113
|
-
* 1. 解析消息内容(文本、图片、引用)
|
|
1114
|
-
* 2. 群组策略检查(仅群聊)
|
|
1115
|
-
* 3. DM Policy 访问控制检查(仅私聊)
|
|
1116
|
-
* 4. 下载并保存图片
|
|
1117
|
-
* 5. 初始化消息状态
|
|
1118
|
-
* 6. 发送"思考中"消息
|
|
1119
|
-
* 7. 路由消息到核心处理流程
|
|
1120
|
-
*
|
|
1121
|
-
* 整体带超时保护,防止单条消息处理阻塞过久
|
|
1122
|
-
*/
|
|
1123
|
-
async function processWeComMessage(params) {
|
|
1124
|
-
const { frame, account, config, runtime, wsClient } = params;
|
|
1125
|
-
const body = frame.body;
|
|
1126
|
-
const chatId = body.chatid || body.from.userid;
|
|
1127
|
-
const chatType = body.chattype === "group" ? "group" : "direct";
|
|
1128
|
-
const messageId = body.msgid;
|
|
1129
|
-
const reqId = frame.headers.req_id;
|
|
1130
|
-
// Step 1: 解析消息内容
|
|
1131
|
-
const { textParts, imageUrls, imageAesKeys, fileUrls, fileAesKeys, quoteContent } = parseMessageContent(body);
|
|
1132
|
-
let text = textParts.join("\n").trim();
|
|
1133
|
-
// 群聊中移除 @机器人 的提及标记
|
|
1134
|
-
if (body.chattype === "group") {
|
|
1135
|
-
text = text.replace(/@\S+/g, "").trim();
|
|
1136
|
-
}
|
|
1137
|
-
// 如果文本为空但存在引用消息,使用引用消息内容
|
|
1138
|
-
if (!text && quoteContent) {
|
|
1139
|
-
text = quoteContent;
|
|
1140
|
-
runtime.log?.("[WeCom] Using quote content as message body (user only mentioned bot)");
|
|
1141
|
-
}
|
|
1142
|
-
// 如果既没有文本也没有图片也没有文件也没有引用内容,则跳过
|
|
1143
|
-
if (!text && imageUrls.length === 0 && fileUrls.length === 0) {
|
|
1144
|
-
runtime.log?.("[WeCom] Skipping empty message (no text, image, file or quote)");
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
runtime.log?.(`[WeCom] Processing ${chatType} message from chat: ${chatId} user: ${body.from.userid} reqId: ${reqId}${imageUrls.length > 0 ? ` (with ${imageUrls.length} image(s))` : ""}${fileUrls.length > 0 ? ` (with ${fileUrls.length} file(s))` : ""}${quoteContent ? ` (with quote)` : ""}`);
|
|
1148
|
-
// Step 2: 群组策略检查(仅群聊)
|
|
1149
|
-
if (chatType === "group") {
|
|
1150
|
-
const groupPolicyResult = checkGroupPolicy({
|
|
1151
|
-
chatId,
|
|
1152
|
-
senderId: body.from.userid,
|
|
1153
|
-
account,
|
|
1154
|
-
config,
|
|
1155
|
-
runtime,
|
|
1156
|
-
});
|
|
1157
|
-
if (!groupPolicyResult.allowed) {
|
|
1158
|
-
return;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
// Step 3: DM Policy 访问控制检查(仅私聊)
|
|
1162
|
-
const dmPolicyResult = await checkDmPolicy({
|
|
1163
|
-
senderId: body.from.userid,
|
|
1164
|
-
isGroup: chatType === "group",
|
|
1165
|
-
account,
|
|
1166
|
-
wsClient,
|
|
1167
|
-
frame,
|
|
1168
|
-
runtime,
|
|
1169
|
-
});
|
|
1170
|
-
if (!dmPolicyResult.allowed) {
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
// Step 4: 下载并保存图片和文件
|
|
1174
|
-
const [imageMediaList, fileMediaList] = await Promise.all([
|
|
1175
|
-
downloadAndSaveImages({
|
|
1176
|
-
imageUrls,
|
|
1177
|
-
imageAesKeys,
|
|
1178
|
-
account,
|
|
1179
|
-
config,
|
|
1180
|
-
runtime,
|
|
1181
|
-
wsClient,
|
|
1182
|
-
}),
|
|
1183
|
-
downloadAndSaveFiles({
|
|
1184
|
-
fileUrls,
|
|
1185
|
-
fileAesKeys,
|
|
1186
|
-
account,
|
|
1187
|
-
config,
|
|
1188
|
-
runtime,
|
|
1189
|
-
wsClient,
|
|
1190
|
-
}),
|
|
1191
|
-
]);
|
|
1192
|
-
const mediaList = [...imageMediaList, ...fileMediaList];
|
|
1193
|
-
// Step 5: 初始化消息状态
|
|
1194
|
-
setReqIdForChat(chatId, reqId, account.accountId);
|
|
1195
|
-
const streamId = aibotNodeSdk.generateReqId("stream");
|
|
1196
|
-
const state = { accumulatedText: "", streamId };
|
|
1197
|
-
setMessageState(messageId, state);
|
|
1198
|
-
const cleanupState = () => {
|
|
1199
|
-
deleteMessageState(messageId);
|
|
1200
|
-
};
|
|
1201
|
-
// Step 6: 发送"思考中"消息
|
|
1202
|
-
const shouldSendThinking = account.sendThinkingMessage ?? true;
|
|
1203
|
-
if (shouldSendThinking) {
|
|
1204
|
-
await sendThinkingReply({ wsClient, frame, streamId, runtime });
|
|
1205
|
-
}
|
|
1206
|
-
// Step 7: 构建上下文并路由到核心处理流程(带整体超时保护)
|
|
1207
|
-
const ctxPayload = buildMessageContext(frame, account, config, text, mediaList, quoteContent);
|
|
1208
|
-
try {
|
|
1209
|
-
await withTimeout(routeAndDispatchMessage({
|
|
1210
|
-
ctxPayload,
|
|
1211
|
-
config,
|
|
1212
|
-
wsClient,
|
|
1213
|
-
frame,
|
|
1214
|
-
state,
|
|
1215
|
-
runtime,
|
|
1216
|
-
onCleanup: cleanupState,
|
|
1217
|
-
}), MESSAGE_PROCESS_TIMEOUT_MS, `Message processing timed out (msgId=${messageId})`);
|
|
1218
|
-
}
|
|
1219
|
-
catch (err) {
|
|
1220
|
-
runtime.error?.(`[WeCom] Message processing failed or timed out: ${String(err)}`);
|
|
1221
|
-
cleanupState();
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
// ============================================================================
|
|
1225
|
-
// 创建 SDK Logger 适配器
|
|
1226
|
-
// ============================================================================
|
|
1227
|
-
/**
|
|
1228
|
-
* 创建适配 RuntimeEnv 的 Logger
|
|
1229
|
-
*/
|
|
1230
|
-
function createSdkLogger(runtime, accountId) {
|
|
1231
|
-
return {
|
|
1232
|
-
debug: (message, ...args) => {
|
|
1233
|
-
runtime.log?.(`[${accountId}] ${message}`, ...args);
|
|
1234
|
-
},
|
|
1235
|
-
info: (message, ...args) => {
|
|
1236
|
-
runtime.log?.(`[${accountId}] ${message}`, ...args);
|
|
1237
|
-
},
|
|
1238
|
-
warn: (message, ...args) => {
|
|
1239
|
-
runtime.log?.(`[${accountId}] WARN: ${message}`, ...args);
|
|
1240
|
-
},
|
|
1241
|
-
error: (message, ...args) => {
|
|
1242
|
-
runtime.error?.(`[${accountId}] ${message}`, ...args);
|
|
1243
|
-
},
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
// ============================================================================
|
|
1247
|
-
// 主函数
|
|
1248
|
-
// ============================================================================
|
|
1249
|
-
/**
|
|
1250
|
-
* 监听企业微信 WebSocket 连接
|
|
1251
|
-
* 使用 aibot-node-sdk 简化连接管理
|
|
1252
|
-
*/
|
|
1253
|
-
async function monitorWeComProvider(options) {
|
|
1254
|
-
const { account, config, runtime, abortSignal } = options;
|
|
1255
|
-
runtime.log?.(`[${account.accountId}] Initializing WSClient with SDK...`);
|
|
1256
|
-
// 启动消息状态定期清理
|
|
1257
|
-
startMessageStateCleanup();
|
|
1258
|
-
return new Promise((resolve, reject) => {
|
|
1259
|
-
const logger = createSdkLogger(runtime, account.accountId);
|
|
1260
|
-
const wsClient = new aibotNodeSdk.WSClient({
|
|
1261
|
-
botId: account.botId,
|
|
1262
|
-
secret: account.secret,
|
|
1263
|
-
wsUrl: account.websocketUrl,
|
|
1264
|
-
logger,
|
|
1265
|
-
heartbeatInterval: WS_HEARTBEAT_INTERVAL_MS,
|
|
1266
|
-
maxReconnectAttempts: WS_MAX_RECONNECT_ATTEMPTS,
|
|
1267
|
-
});
|
|
1268
|
-
// 清理函数:确保所有资源被释放
|
|
1269
|
-
const cleanup = async () => {
|
|
1270
|
-
stopMessageStateCleanup();
|
|
1271
|
-
await cleanupAccount(account.accountId);
|
|
1272
|
-
};
|
|
1273
|
-
// 处理中止信号
|
|
1274
|
-
if (abortSignal) {
|
|
1275
|
-
abortSignal.addEventListener("abort", async () => {
|
|
1276
|
-
runtime.log?.(`[${account.accountId}] Connection aborted`);
|
|
1277
|
-
await cleanup();
|
|
1278
|
-
resolve();
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
// 监听连接事件
|
|
1282
|
-
wsClient.on("connected", () => {
|
|
1283
|
-
runtime.log?.(`[${account.accountId}] WebSocket connected`);
|
|
1284
|
-
});
|
|
1285
|
-
// 监听认证成功事件
|
|
1286
|
-
wsClient.on("authenticated", () => {
|
|
1287
|
-
runtime.log?.(`[${account.accountId}] Authentication successful`);
|
|
1288
|
-
// 建立多重映射:accountId (可能是 tinyId 或 "main") 和真实的 botId 都能找到该实例
|
|
1289
|
-
setWeComWebSocket(account.accountId, wsClient);
|
|
1290
|
-
// 如果 account.accountId 不是 botId,也建立 botId 的映射
|
|
1291
|
-
if (account.botId && account.botId !== account.accountId) {
|
|
1292
|
-
setWeComWebSocket(account.botId, wsClient);
|
|
1293
|
-
}
|
|
1294
|
-
// 特殊处理:如果 account.accountId 是 tinyId,也建立映射(冗余但安全)
|
|
1295
|
-
if (account.tinyId && account.tinyId !== account.accountId && account.tinyId !== account.botId) {
|
|
1296
|
-
setWeComWebSocket(account.tinyId, wsClient);
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
|
-
// 监听断开事件
|
|
1300
|
-
wsClient.on("disconnected", (reason) => {
|
|
1301
|
-
runtime.log?.(`[${account.accountId}] WebSocket disconnected: ${reason}`);
|
|
1302
|
-
});
|
|
1303
|
-
// 监听重连事件
|
|
1304
|
-
wsClient.on("reconnecting", (attempt) => {
|
|
1305
|
-
runtime.log?.(`[${account.accountId}] Reconnecting attempt ${attempt}...`);
|
|
1306
|
-
});
|
|
1307
|
-
// 监听错误事件
|
|
1308
|
-
wsClient.on("error", (error) => {
|
|
1309
|
-
runtime.error?.(`[${account.accountId}] WebSocket error: ${error.message}`);
|
|
1310
|
-
// 认证失败时拒绝 Promise
|
|
1311
|
-
if (error.message.includes("Authentication failed")) {
|
|
1312
|
-
cleanup().finally(() => reject(error));
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
// 监听所有消息
|
|
1316
|
-
wsClient.on("message", async (frame) => {
|
|
1317
|
-
try {
|
|
1318
|
-
await processWeComMessage({
|
|
1319
|
-
frame,
|
|
1320
|
-
account,
|
|
1321
|
-
config,
|
|
1322
|
-
runtime,
|
|
1323
|
-
wsClient,
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
catch (err) {
|
|
1327
|
-
runtime.error?.(`[${account.accountId}] Failed to process message: ${String(err)}`);
|
|
1328
|
-
}
|
|
1329
|
-
});
|
|
1330
|
-
// 启动前预热 reqId 缓存,确保完成后再建立连接,避免 getSync 在预热完成前返回 undefined
|
|
1331
|
-
warmupReqIdStore(account.accountId, (...args) => runtime.log?.(...args))
|
|
1332
|
-
.then((count) => {
|
|
1333
|
-
runtime.log?.(`[${account.accountId}] Warmed up ${count} reqId entries from disk`);
|
|
1334
|
-
})
|
|
1335
|
-
.catch((err) => {
|
|
1336
|
-
runtime.error?.(`[${account.accountId}] Failed to warmup reqId store: ${String(err)}`);
|
|
1337
|
-
})
|
|
1338
|
-
.finally(() => {
|
|
1339
|
-
// 无论预热成功或失败,都建立连接
|
|
1340
|
-
wsClient.connect();
|
|
1341
|
-
});
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* 企业微信公共工具函数
|
|
1347
|
-
*/
|
|
1348
|
-
const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
|
|
1349
|
-
/**
|
|
1350
|
-
* 解析所有企业微信账户 ID
|
|
1351
|
-
*/
|
|
1352
|
-
function listWeComAccountIds(cfg) {
|
|
1353
|
-
const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
|
|
1354
|
-
const ids = new Set();
|
|
1355
|
-
// 仅从 bots 数组中解析
|
|
1356
|
-
if (Array.isArray(wecomConfig.bots)) {
|
|
1357
|
-
for (const bot of wecomConfig.bots) {
|
|
1358
|
-
const id = (bot.tinyId || bot.botId)?.trim();
|
|
1359
|
-
if (id) {
|
|
1360
|
-
ids.add(id);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
return Array.from(ids);
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* 解析企业微信账户配置
|
|
1368
|
-
*/
|
|
1369
|
-
function resolveWeComAccount(cfg, accountId = pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
1370
|
-
const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
|
|
1371
|
-
// 1. 查找匹配的机器人配置
|
|
1372
|
-
let targetConfig;
|
|
1373
|
-
if (accountId === pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
1374
|
-
// 默认账户直接取 bots 数组中第一个
|
|
1375
|
-
if (Array.isArray(wecomConfig.bots) && wecomConfig.bots.length > 0) {
|
|
1376
|
-
targetConfig = wecomConfig.bots[0];
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
else {
|
|
1380
|
-
// 根据 tinyId 或 botId 查找
|
|
1381
|
-
targetConfig = wecomConfig.bots?.find((b) => b.tinyId === accountId || b.botId === accountId);
|
|
1382
|
-
}
|
|
1383
|
-
// 如果最后还是没找到任何配置,就返回一个默认空配置
|
|
1384
|
-
const finalConfig = targetConfig ?? {};
|
|
1385
|
-
return {
|
|
1386
|
-
accountId: accountId === pluginSdk.DEFAULT_ACCOUNT_ID ? (finalConfig.tinyId || finalConfig.botId || pluginSdk.DEFAULT_ACCOUNT_ID) : accountId,
|
|
1387
|
-
tinyId: finalConfig.tinyId || finalConfig.botId || accountId,
|
|
1388
|
-
name: finalConfig.name ?? "企业微信",
|
|
1389
|
-
enabled: finalConfig.enabled ?? false,
|
|
1390
|
-
websocketUrl: finalConfig.websocketUrl || DefaultWsUrl,
|
|
1391
|
-
botId: finalConfig.botId ?? "",
|
|
1392
|
-
secret: finalConfig.secret ?? "",
|
|
1393
|
-
sendThinkingMessage: finalConfig.sendThinkingMessage ?? true,
|
|
1394
|
-
config: finalConfig,
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
/**
|
|
1398
|
-
* 设置企业微信账户配置
|
|
1399
|
-
*/
|
|
1400
|
-
function setWeComAccount(cfg, account, accountId = pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
1401
|
-
const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
|
|
1402
|
-
const bots = Array.isArray(wecomConfig.bots) ? [...wecomConfig.bots] : [];
|
|
1403
|
-
let index = -1;
|
|
1404
|
-
if (accountId === pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
1405
|
-
// 如果是设置默认账户且数组不为空,更新第一个
|
|
1406
|
-
if (bots.length > 0) {
|
|
1407
|
-
index = 0;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
else {
|
|
1411
|
-
// 根据 accountId 查找
|
|
1412
|
-
index = bots.findIndex((b) => b.tinyId === accountId || b.botId === accountId);
|
|
1413
|
-
}
|
|
1414
|
-
const newBotConfig = {
|
|
1415
|
-
...(index >= 0 ? bots[index] : {}),
|
|
1416
|
-
...account,
|
|
1417
|
-
};
|
|
1418
|
-
// 确保 ID 字段正确
|
|
1419
|
-
if (!newBotConfig.botId && accountId !== pluginSdk.DEFAULT_ACCOUNT_ID) {
|
|
1420
|
-
newBotConfig.botId = accountId;
|
|
1421
|
-
}
|
|
1422
|
-
if (index >= 0) {
|
|
1423
|
-
bots[index] = newBotConfig;
|
|
1424
|
-
}
|
|
1425
|
-
else {
|
|
1426
|
-
bots.push(newBotConfig);
|
|
1427
|
-
}
|
|
1428
|
-
return {
|
|
1429
|
-
...cfg,
|
|
1430
|
-
channels: {
|
|
1431
|
-
...cfg.channels,
|
|
1432
|
-
[CHANNEL_ID]: {
|
|
1433
|
-
bots,
|
|
1434
|
-
},
|
|
1435
|
-
},
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* 企业微信 onboarding adapter for CLI setup wizard.
|
|
1441
|
-
*/
|
|
1442
|
-
const channel = CHANNEL_ID;
|
|
1443
|
-
/**
|
|
1444
|
-
* 提示输入 Tiny ID
|
|
1445
|
-
*/
|
|
1446
|
-
async function promptTinyId(prompter, account) {
|
|
1447
|
-
const value = await prompter.text({
|
|
1448
|
-
message: "机器人 Tiny ID (短 ID,用于标识机器人,可不填则默认使用 Bot ID)",
|
|
1449
|
-
initialValue: account?.tinyId === account?.botId ? "" : (account?.tinyId ?? ""),
|
|
1450
|
-
placeholder: "例如: bot1",
|
|
1451
|
-
});
|
|
1452
|
-
return String(value ?? "").trim();
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* 提示输入 Bot ID
|
|
1456
|
-
*/
|
|
1457
|
-
async function promptBotId(prompter, account) {
|
|
1458
|
-
return String(await prompter.text({
|
|
1459
|
-
message: "企业微信机器人 Bot ID",
|
|
1460
|
-
initialValue: account?.botId ?? "",
|
|
1461
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
1462
|
-
})).trim();
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* 提示输入 Secret
|
|
1466
|
-
*/
|
|
1467
|
-
async function promptSecret(prompter, account) {
|
|
1468
|
-
return String(await prompter.text({
|
|
1469
|
-
message: "企业微信机器人 Secret",
|
|
1470
|
-
initialValue: account?.secret ?? "",
|
|
1471
|
-
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
1472
|
-
})).trim();
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* 设置企业微信 dmPolicy
|
|
1476
|
-
*/
|
|
1477
|
-
function setWeComDmPolicy(cfg, dmPolicy) {
|
|
1478
|
-
const accountIds = listWeComAccountIds(cfg);
|
|
1479
|
-
let updatedCfg = cfg;
|
|
1480
|
-
// 如果有多个机器人,统一设置所有机器人的私信策略
|
|
1481
|
-
for (const accountId of accountIds) {
|
|
1482
|
-
const account = resolveWeComAccount(updatedCfg, accountId);
|
|
1483
|
-
const existingAllowFrom = account.config.allowFrom ?? [];
|
|
1484
|
-
const allowFrom = dmPolicy === "open"
|
|
1485
|
-
? pluginSdk.addWildcardAllowFrom(existingAllowFrom.map((x) => String(x)))
|
|
1486
|
-
: existingAllowFrom.map((x) => String(x));
|
|
1487
|
-
updatedCfg = setWeComAccount(updatedCfg, {
|
|
1488
|
-
dmPolicy,
|
|
1489
|
-
allowFrom,
|
|
1490
|
-
}, accountId);
|
|
1491
|
-
}
|
|
1492
|
-
return updatedCfg;
|
|
1493
|
-
}
|
|
1494
|
-
const dmPolicy = {
|
|
1495
|
-
label: "企业微信",
|
|
1496
|
-
channel,
|
|
1497
|
-
policyKey: `channels.${CHANNEL_ID}.dmPolicy`,
|
|
1498
|
-
allowFromKey: `channels.${CHANNEL_ID}.allowFrom`,
|
|
1499
|
-
getCurrent: (cfg) => {
|
|
1500
|
-
// 默认返回主账号的策略
|
|
1501
|
-
const account = resolveWeComAccount(cfg);
|
|
1502
|
-
return account.config.dmPolicy ?? "pairing";
|
|
1503
|
-
},
|
|
1504
|
-
setPolicy: (cfg, policy) => {
|
|
1505
|
-
return setWeComDmPolicy(cfg, policy);
|
|
1506
|
-
},
|
|
1507
|
-
promptAllowFrom: async ({ cfg, prompter }) => {
|
|
1508
|
-
const account = resolveWeComAccount(cfg);
|
|
1509
|
-
const existingAllowFrom = account.config.allowFrom ?? [];
|
|
1510
|
-
const entry = await prompter.text({
|
|
1511
|
-
message: "企业微信允许来源(用户ID或群组ID,每行一个,推荐用于安全控制)",
|
|
1512
|
-
placeholder: "user123 或 group456",
|
|
1513
|
-
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
|
|
1514
|
-
});
|
|
1515
|
-
const allowFrom = String(entry ?? "")
|
|
1516
|
-
.split(/[\n,;]+/g)
|
|
1517
|
-
.map((s) => s.trim())
|
|
1518
|
-
.filter(Boolean);
|
|
1519
|
-
// 设置所有机器人的允许来源
|
|
1520
|
-
const accountIds = listWeComAccountIds(cfg);
|
|
1521
|
-
let updatedCfg = cfg;
|
|
1522
|
-
for (const accountId of accountIds) {
|
|
1523
|
-
updatedCfg = setWeComAccount(updatedCfg, { allowFrom }, accountId);
|
|
1524
|
-
}
|
|
1525
|
-
return updatedCfg;
|
|
1526
|
-
},
|
|
1527
|
-
};
|
|
1528
|
-
const wecomOnboardingAdapter = {
|
|
1529
|
-
channel,
|
|
1530
|
-
getStatus: async ({ cfg }) => {
|
|
1531
|
-
const accountIds = listWeComAccountIds(cfg);
|
|
1532
|
-
const configured = accountIds.length > 0;
|
|
1533
|
-
return {
|
|
1534
|
-
channel,
|
|
1535
|
-
configured,
|
|
1536
|
-
statusLines: [
|
|
1537
|
-
`企业微信: ${configured ? `已配置 ${accountIds.length} 个机器人` : "未配置"}`
|
|
1538
|
-
],
|
|
1539
|
-
selectionHint: configured ? "已配置" : "需要设置",
|
|
1540
|
-
};
|
|
1541
|
-
},
|
|
1542
|
-
configure: async ({ cfg, prompter }) => {
|
|
1543
|
-
// 强制进入多机器人管理模式
|
|
1544
|
-
const accountIds = listWeComAccountIds(cfg);
|
|
1545
|
-
const options = [
|
|
1546
|
-
...accountIds.map(id => {
|
|
1547
|
-
const acc = resolveWeComAccount(cfg, id);
|
|
1548
|
-
return { value: id, label: `机器人: ${acc.tinyId} (${acc.botId.substring(0, 8)}...)` };
|
|
1549
|
-
}),
|
|
1550
|
-
{ value: "__add_new__", label: "+ 新增机器人" },
|
|
1551
|
-
];
|
|
1552
|
-
const selection = await prompter.select({
|
|
1553
|
-
message: "选择要配置的机器人",
|
|
1554
|
-
options,
|
|
1555
|
-
});
|
|
1556
|
-
let targetAccount = null;
|
|
1557
|
-
let accountIdToUpdate = "";
|
|
1558
|
-
if (selection !== "__add_new__") {
|
|
1559
|
-
targetAccount = resolveWeComAccount(cfg, selection);
|
|
1560
|
-
accountIdToUpdate = selection;
|
|
1561
|
-
}
|
|
1562
|
-
const tinyId = await promptTinyId(prompter, targetAccount);
|
|
1563
|
-
const botId = await promptBotId(prompter, targetAccount);
|
|
1564
|
-
const secret = await promptSecret(prompter, targetAccount);
|
|
1565
|
-
// 如果是新增,accountIdToUpdate 应该是新输入的 tinyId 或 botId
|
|
1566
|
-
if (selection === "__add_new__") {
|
|
1567
|
-
accountIdToUpdate = tinyId || botId;
|
|
1568
|
-
}
|
|
1569
|
-
return {
|
|
1570
|
-
cfg: setWeComAccount(cfg, {
|
|
1571
|
-
tinyId: tinyId || undefined,
|
|
1572
|
-
botId,
|
|
1573
|
-
secret,
|
|
1574
|
-
enabled: true,
|
|
1575
|
-
dmPolicy: targetAccount?.config?.dmPolicy ?? "pairing",
|
|
1576
|
-
allowFrom: targetAccount?.config?.allowFrom ?? [],
|
|
1577
|
-
}, accountIdToUpdate)
|
|
1578
|
-
};
|
|
1579
|
-
},
|
|
1580
|
-
dmPolicy,
|
|
1581
|
-
disable: (cfg) => {
|
|
1582
|
-
return setWeComAccount(cfg, { enabled: false });
|
|
1583
|
-
},
|
|
1584
|
-
};
|
|
1585
|
-
|
|
1586
|
-
/**
|
|
1587
|
-
* 使用 SDK 的 sendMessage 主动发送企业微信消息
|
|
1588
|
-
* 无需依赖 reqId,直接向指定会话推送消息
|
|
1589
|
-
*/
|
|
1590
|
-
async function sendWeComMessage({ to, content, accountId, }) {
|
|
1591
|
-
// 解析标识符:wecom:accountId:chatId 或 wecom:chatId 或 原始 chatId
|
|
1592
|
-
// 注意:chatId 本身可能包含冒号(如 group:xxxx)
|
|
1593
|
-
const parts = to.split(':');
|
|
1594
|
-
let finalAccountId = accountId;
|
|
1595
|
-
let chatId = to;
|
|
1596
|
-
if (parts[0]?.toLowerCase() === CHANNEL_ID) {
|
|
1597
|
-
if (parts.length >= 3) {
|
|
1598
|
-
// 3 段及以上:wecom:accountId:chatId...
|
|
1599
|
-
finalAccountId = parts[1];
|
|
1600
|
-
chatId = parts.slice(2).join(':');
|
|
1601
|
-
}
|
|
1602
|
-
else if (parts.length === 2) {
|
|
1603
|
-
// 2 段:wecom:chatId
|
|
1604
|
-
chatId = parts[1];
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
// 进一步解析 chatId:移除可能存在的 group: 前缀(由 monitor.ts 中 From 生成)
|
|
1608
|
-
if (chatId.startsWith('group:')) {
|
|
1609
|
-
chatId = chatId.substring(6);
|
|
1610
|
-
}
|
|
1611
|
-
const resolvedAccountId = finalAccountId ?? pluginSdk.DEFAULT_ACCOUNT_ID;
|
|
1612
|
-
console.log(`[WeCom] sendWeComMessage: ${JSON.stringify({ to, content, accountId, finalAccountId, chatId, resolvedAccountId })}`);
|
|
1613
|
-
// 获取 WSClient 实例
|
|
1614
|
-
const wsClient = getWeComWebSocket(resolvedAccountId);
|
|
1615
|
-
if (!wsClient) {
|
|
1616
|
-
throw new Error(`WSClient not connected for account ${resolvedAccountId}`);
|
|
1617
|
-
}
|
|
1618
|
-
// 使用 SDK 的 sendMessage 主动发送 markdown 消息
|
|
1619
|
-
const result = await wsClient.sendMessage(chatId, {
|
|
1620
|
-
msgtype: 'markdown',
|
|
1621
|
-
markdown: { content },
|
|
1622
|
-
});
|
|
1623
|
-
const messageId = result?.headers?.req_id ?? `wecom-${Date.now()}`;
|
|
1624
|
-
console.log(`[WeCom] Sent message to ${chatId} via ${resolvedAccountId}, messageId=${messageId}`);
|
|
1625
|
-
return {
|
|
1626
|
-
channel: CHANNEL_ID,
|
|
1627
|
-
messageId,
|
|
1628
|
-
chatId,
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
// 企业微信频道元数据
|
|
1632
|
-
const meta = {
|
|
1633
|
-
id: CHANNEL_ID,
|
|
1634
|
-
label: "企业微信",
|
|
1635
|
-
selectionLabel: "企业微信 (WeCom)",
|
|
1636
|
-
detailLabel: "企业微信智能机器人",
|
|
1637
|
-
docsPath: `/channels/${CHANNEL_ID}`,
|
|
1638
|
-
docsLabel: CHANNEL_ID,
|
|
1639
|
-
blurb: "企业微信智能机器人接入插件",
|
|
1640
|
-
systemImage: "message.fill",
|
|
1641
|
-
};
|
|
1642
|
-
const wecomPlugin = {
|
|
1643
|
-
id: CHANNEL_ID,
|
|
1644
|
-
meta: {
|
|
1645
|
-
...meta,
|
|
1646
|
-
quickstartAllowFrom: true,
|
|
1647
|
-
},
|
|
1648
|
-
pairing: {
|
|
1649
|
-
idLabel: "wecomUserId",
|
|
1650
|
-
normalizeAllowEntry: (entry) => entry.replace(new RegExp(`^(${CHANNEL_ID}|user):`, "i"), "").trim(),
|
|
1651
|
-
notifyApproval: async ({ cfg, id }) => {
|
|
1652
|
-
// sendWeComMessage({
|
|
1653
|
-
// to: id,
|
|
1654
|
-
// content: " pairing approved",
|
|
1655
|
-
// accountId: cfg.accountId,
|
|
1656
|
-
// });
|
|
1657
|
-
console.log(`[WeCom] Pairing approved for user: ${id}`);
|
|
1658
|
-
},
|
|
1659
|
-
},
|
|
1660
|
-
onboarding: wecomOnboardingAdapter,
|
|
1661
|
-
capabilities: {
|
|
1662
|
-
chatTypes: ["direct", "group"],
|
|
1663
|
-
reactions: false,
|
|
1664
|
-
threads: false,
|
|
1665
|
-
media: true,
|
|
1666
|
-
nativeCommands: false,
|
|
1667
|
-
blockStreaming: true,
|
|
1668
|
-
},
|
|
1669
|
-
reload: { configPrefixes: [`channels.${CHANNEL_ID}`] },
|
|
1670
|
-
config: {
|
|
1671
|
-
// 列出所有账户 ID
|
|
1672
|
-
listAccountIds: (cfg) => listWeComAccountIds(cfg),
|
|
1673
|
-
// 解析账户配置
|
|
1674
|
-
resolveAccount: (cfg, accountId) => resolveWeComAccount(cfg, accountId),
|
|
1675
|
-
// 获取默认账户 ID
|
|
1676
|
-
defaultAccountId: () => pluginSdk.DEFAULT_ACCOUNT_ID,
|
|
1677
|
-
// 设置账户启用状态
|
|
1678
|
-
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
|
1679
|
-
return setWeComAccount(cfg, { enabled }, accountId);
|
|
1680
|
-
},
|
|
1681
|
-
// 删除账户
|
|
1682
|
-
deleteAccount: ({ cfg, accountId }) => {
|
|
1683
|
-
const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
|
|
1684
|
-
const bots = (wecomConfig.bots ?? []).filter((b) => b.tinyId !== accountId && b.botId !== accountId);
|
|
1685
|
-
return {
|
|
1686
|
-
...cfg,
|
|
1687
|
-
channels: {
|
|
1688
|
-
...cfg.channels,
|
|
1689
|
-
[CHANNEL_ID]: {
|
|
1690
|
-
...wecomConfig,
|
|
1691
|
-
bots,
|
|
1692
|
-
},
|
|
1693
|
-
},
|
|
1694
|
-
};
|
|
1695
|
-
},
|
|
1696
|
-
// 检查是否已配置
|
|
1697
|
-
isConfigured: (account) => Boolean(account.botId?.trim() && account.secret?.trim()),
|
|
1698
|
-
// 描述账户信息
|
|
1699
|
-
describeAccount: (account) => ({
|
|
1700
|
-
accountId: account.accountId,
|
|
1701
|
-
name: account.name,
|
|
1702
|
-
enabled: account.enabled,
|
|
1703
|
-
configured: Boolean(account.botId?.trim() && account.secret?.trim()),
|
|
1704
|
-
botId: account.botId,
|
|
1705
|
-
websocketUrl: account.websocketUrl,
|
|
1706
|
-
}),
|
|
1707
|
-
// 解析允许来源列表
|
|
1708
|
-
resolveAllowFrom: ({ cfg, accountId }) => {
|
|
1709
|
-
const account = resolveWeComAccount(cfg, accountId);
|
|
1710
|
-
return (account.config.allowFrom ?? []).map((entry) => String(entry));
|
|
1711
|
-
},
|
|
1712
|
-
// 格式化允许来源列表
|
|
1713
|
-
formatAllowFrom: ({ allowFrom }) => allowFrom
|
|
1714
|
-
.map((entry) => String(entry).trim())
|
|
1715
|
-
.filter(Boolean),
|
|
1716
|
-
},
|
|
1717
|
-
security: {
|
|
1718
|
-
resolveDmPolicy: ({ account }) => {
|
|
1719
|
-
// 动态生成路径以匹配多机器人配置
|
|
1720
|
-
// 使用真实的 botId 来生成配置路径,因为 bots 数组中的项是通过 botId 标识的
|
|
1721
|
-
const isDefault = account.accountId === pluginSdk.DEFAULT_ACCOUNT_ID;
|
|
1722
|
-
const basePath = isDefault
|
|
1723
|
-
? `channels.${CHANNEL_ID}.`
|
|
1724
|
-
: `channels.${CHANNEL_ID}.bots[botId=${account.botId}].`;
|
|
1725
|
-
return {
|
|
1726
|
-
policy: account.config.dmPolicy ?? "pairing",
|
|
1727
|
-
allowFrom: account.config.allowFrom ?? [],
|
|
1728
|
-
policyPath: `${basePath}dmPolicy`,
|
|
1729
|
-
allowFromPath: `${basePath}allowFrom`,
|
|
1730
|
-
approveHint: pluginSdk.formatPairingApproveHint(CHANNEL_ID),
|
|
1731
|
-
normalizeEntry: (raw) => raw.replace(new RegExp(`^${CHANNEL_ID}:`, "i"), "").trim(),
|
|
1732
|
-
};
|
|
1733
|
-
},
|
|
1734
|
-
collectWarnings: ({ account, cfg }) => {
|
|
1735
|
-
const warnings = [];
|
|
1736
|
-
const isDefault = account.accountId === pluginSdk.DEFAULT_ACCOUNT_ID;
|
|
1737
|
-
const basePath = isDefault
|
|
1738
|
-
? `channels.${CHANNEL_ID}.`
|
|
1739
|
-
: `channels.${CHANNEL_ID}.bots[botId=${account.botId}].`;
|
|
1740
|
-
// DM 策略警告
|
|
1741
|
-
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
1742
|
-
if (dmPolicy === "open") {
|
|
1743
|
-
const hasWildcard = (account.config.allowFrom ?? []).some((entry) => String(entry).trim() === "*");
|
|
1744
|
-
if (!hasWildcard) {
|
|
1745
|
-
warnings.push(`- 企业微信私信:dmPolicy="open" 但 allowFrom 未包含 "*"。任何人都可以发消息,但允许列表为空可能导致意外行为。建议设置 ${basePath}allowFrom=["*"] 或使用 dmPolicy="pairing"。`);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
// 群组策略警告
|
|
1749
|
-
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
1750
|
-
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
|
1751
|
-
// const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
|
|
1752
|
-
// providerConfigPresent: true,
|
|
1753
|
-
// groupPolicy: account.config.groupPolicy,
|
|
1754
|
-
// defaultGroupPolicy,
|
|
1755
|
-
// });
|
|
1756
|
-
if (groupPolicy === "open") {
|
|
1757
|
-
warnings.push(`- 企业微信群组:groupPolicy="open" 允许所有群组中的成员触发。设置 ${basePath}groupPolicy="allowlist" + ${basePath}groupAllowFrom 来限制群组。`);
|
|
1758
|
-
}
|
|
1759
|
-
return warnings;
|
|
1760
|
-
},
|
|
1761
|
-
},
|
|
1762
|
-
messaging: {
|
|
1763
|
-
normalizeTarget: (target) => {
|
|
1764
|
-
const trimmed = target.trim();
|
|
1765
|
-
if (!trimmed)
|
|
1766
|
-
return undefined;
|
|
1767
|
-
return trimmed;
|
|
1768
|
-
},
|
|
1769
|
-
targetResolver: {
|
|
1770
|
-
looksLikeId: (id) => {
|
|
1771
|
-
const trimmed = id?.trim();
|
|
1772
|
-
return Boolean(trimmed);
|
|
1773
|
-
},
|
|
1774
|
-
hint: "<userId|groupId>",
|
|
1775
|
-
},
|
|
1776
|
-
},
|
|
1777
|
-
directory: {
|
|
1778
|
-
self: async () => null,
|
|
1779
|
-
listPeers: async () => [],
|
|
1780
|
-
listGroups: async () => [],
|
|
1781
|
-
},
|
|
1782
|
-
outbound: {
|
|
1783
|
-
deliveryMode: "direct",
|
|
1784
|
-
chunker: (text, limit) => getWeComRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
1785
|
-
textChunkLimit: TEXT_CHUNK_LIMIT,
|
|
1786
|
-
sendText: async ({ to, text, accountId, ...rest }) => {
|
|
1787
|
-
console.log(`[WeCom] sendText: ${JSON.stringify({ to, text, accountId, ...rest })}`);
|
|
1788
|
-
return sendWeComMessage({ to, content: text, accountId: accountId ?? undefined });
|
|
1789
|
-
},
|
|
1790
|
-
sendMedia: async ({ to, text, mediaUrl, accountId, ...rest }) => {
|
|
1791
|
-
console.log(`[WeCom] sendMedia: ${JSON.stringify({ to, text, mediaUrl, accountId, ...rest })}`);
|
|
1792
|
-
const content = `Sending attachments is not supported yet\n${text ? `${text}\n${mediaUrl}` : (mediaUrl ?? "")}`;
|
|
1793
|
-
return sendWeComMessage({ to, content, accountId: accountId ?? undefined });
|
|
1794
|
-
},
|
|
1795
|
-
},
|
|
1796
|
-
status: {
|
|
1797
|
-
defaultRuntime: {
|
|
1798
|
-
accountId: pluginSdk.DEFAULT_ACCOUNT_ID,
|
|
1799
|
-
running: false,
|
|
1800
|
-
lastStartAt: null,
|
|
1801
|
-
lastStopAt: null,
|
|
1802
|
-
lastError: null,
|
|
1803
|
-
},
|
|
1804
|
-
collectStatusIssues: (accounts) => accounts.flatMap((entry) => {
|
|
1805
|
-
const accountId = String(entry.accountId ?? pluginSdk.DEFAULT_ACCOUNT_ID);
|
|
1806
|
-
const enabled = entry.enabled !== false;
|
|
1807
|
-
const configured = entry.configured === true;
|
|
1808
|
-
if (!enabled) {
|
|
1809
|
-
return [];
|
|
1810
|
-
}
|
|
1811
|
-
const issues = [];
|
|
1812
|
-
if (!configured) {
|
|
1813
|
-
issues.push({
|
|
1814
|
-
channel: CHANNEL_ID,
|
|
1815
|
-
accountId,
|
|
1816
|
-
kind: "config",
|
|
1817
|
-
message: "企业微信机器人 ID 或 Secret 未配置",
|
|
1818
|
-
fix: "Run: openclaw channels add wecom --bot-id <id> --secret <secret>",
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
1821
|
-
return issues;
|
|
1822
|
-
}),
|
|
1823
|
-
buildChannelSummary: ({ snapshot }) => ({
|
|
1824
|
-
configured: snapshot.configured ?? false,
|
|
1825
|
-
running: snapshot.running ?? false,
|
|
1826
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
1827
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
1828
|
-
lastError: snapshot.lastError ?? null,
|
|
1829
|
-
}),
|
|
1830
|
-
probeAccount: async () => {
|
|
1831
|
-
return { ok: true, status: 200 };
|
|
1832
|
-
},
|
|
1833
|
-
buildAccountSnapshot: ({ account, runtime }) => {
|
|
1834
|
-
const configured = Boolean(account.botId?.trim() &&
|
|
1835
|
-
account.secret?.trim());
|
|
1836
|
-
return {
|
|
1837
|
-
accountId: account.accountId,
|
|
1838
|
-
name: account.name,
|
|
1839
|
-
enabled: account.enabled,
|
|
1840
|
-
configured,
|
|
1841
|
-
running: runtime?.running ?? false,
|
|
1842
|
-
lastStartAt: runtime?.lastStartAt ?? null,
|
|
1843
|
-
lastStopAt: runtime?.lastStopAt ?? null,
|
|
1844
|
-
lastError: runtime?.lastError ?? null,
|
|
1845
|
-
};
|
|
1846
|
-
},
|
|
1847
|
-
},
|
|
1848
|
-
gateway: {
|
|
1849
|
-
startAccount: async (ctx) => {
|
|
1850
|
-
const account = ctx.account;
|
|
1851
|
-
// 启动 WebSocket 监听
|
|
1852
|
-
return monitorWeComProvider({
|
|
1853
|
-
account,
|
|
1854
|
-
config: ctx.cfg,
|
|
1855
|
-
runtime: ctx.runtime,
|
|
1856
|
-
abortSignal: ctx.abortSignal,
|
|
1857
|
-
});
|
|
1858
|
-
},
|
|
1859
|
-
logoutAccount: async ({ cfg }) => {
|
|
1860
|
-
const nextCfg = { ...cfg };
|
|
1861
|
-
const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
|
|
1862
|
-
let cleared = false;
|
|
1863
|
-
let changed = false;
|
|
1864
|
-
// 如果有机器人列表,清空它
|
|
1865
|
-
if (Array.isArray(wecomConfig.bots) && wecomConfig.bots.length > 0) {
|
|
1866
|
-
delete wecomConfig.bots;
|
|
1867
|
-
cleared = true;
|
|
1868
|
-
changed = true;
|
|
1869
|
-
}
|
|
1870
|
-
if (changed) {
|
|
1871
|
-
if (Object.keys(wecomConfig).length > 0) {
|
|
1872
|
-
nextCfg.channels = { ...nextCfg.channels, [CHANNEL_ID]: wecomConfig };
|
|
1873
|
-
}
|
|
1874
|
-
else {
|
|
1875
|
-
const nextChannels = { ...nextCfg.channels };
|
|
1876
|
-
delete nextChannels[CHANNEL_ID];
|
|
1877
|
-
if (Object.keys(nextChannels).length > 0) {
|
|
1878
|
-
nextCfg.channels = nextChannels;
|
|
1879
|
-
}
|
|
1880
|
-
else {
|
|
1881
|
-
delete nextCfg.channels;
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
await getWeComRuntime().config.writeConfigFile(nextCfg);
|
|
1885
|
-
}
|
|
1886
|
-
const accountIds = listWeComAccountIds(changed ? nextCfg : cfg);
|
|
1887
|
-
const loggedOut = accountIds.length === 0;
|
|
1888
|
-
return { cleared, envToken: false, loggedOut };
|
|
1889
|
-
},
|
|
1890
|
-
},
|
|
1891
|
-
};
|
|
1892
|
-
|
|
1893
|
-
const botSchema = {
|
|
1894
|
-
type: "object",
|
|
1895
|
-
properties: {
|
|
1896
|
-
enabled: { type: "boolean", title: "启用" },
|
|
1897
|
-
name: { type: "string", title: "名称" },
|
|
1898
|
-
tinyId: { type: "string", title: "Tiny ID (短 ID)" },
|
|
1899
|
-
botId: { type: "string", title: "Bot ID" },
|
|
1900
|
-
secret: { type: "string", title: "Secret" },
|
|
1901
|
-
websocketUrl: { type: "string", title: "WebSocket URL" },
|
|
1902
|
-
dmPolicy: {
|
|
1903
|
-
type: "string",
|
|
1904
|
-
title: "私信策略",
|
|
1905
|
-
enum: ["pairing", "open", "allowlist", "disabled"],
|
|
1906
|
-
},
|
|
1907
|
-
allowFrom: {
|
|
1908
|
-
type: "array",
|
|
1909
|
-
title: "允许来源",
|
|
1910
|
-
items: { type: "string" },
|
|
1911
|
-
},
|
|
1912
|
-
groupPolicy: {
|
|
1913
|
-
type: "string",
|
|
1914
|
-
title: "群组策略",
|
|
1915
|
-
enum: ["open", "allowlist", "disabled"],
|
|
1916
|
-
},
|
|
1917
|
-
groupAllowFrom: {
|
|
1918
|
-
type: "array",
|
|
1919
|
-
title: "允许群组",
|
|
1920
|
-
items: { type: "string" },
|
|
1921
|
-
},
|
|
1922
|
-
sendThinkingMessage: { type: "boolean", title: "发送思考中消息" },
|
|
1923
|
-
},
|
|
1924
|
-
};
|
|
1925
|
-
const plugin = {
|
|
1926
|
-
id: "wecom-openclaw-plugin",
|
|
1927
|
-
name: "企业微信Bots",
|
|
1928
|
-
description: "企业微信Bots OpenClaw 插件",
|
|
1929
|
-
configSchema: {
|
|
1930
|
-
type: "object",
|
|
1931
|
-
properties: {
|
|
1932
|
-
bots: {
|
|
1933
|
-
type: "array",
|
|
1934
|
-
title: "机器人列表",
|
|
1935
|
-
items: botSchema,
|
|
1936
|
-
},
|
|
1937
|
-
},
|
|
1938
|
-
},
|
|
1939
|
-
register(api) {
|
|
1940
|
-
setWeComRuntime(api.runtime);
|
|
1941
|
-
api.registerChannel({ plugin: wecomPlugin });
|
|
1942
|
-
},
|
|
1943
|
-
};
|
|
1944
|
-
|
|
1945
|
-
exports.default = plugin;
|
|
1
|
+
'use strict';const _0x5d3b3c=_0x1273;(function(_0x19bd7c,_0x32d32f){const _0x32c216=_0x1273,_0x93f099=_0x19bd7c();while(!![]){try{const _0x58500d=parseInt(_0x32c216(0x159))/0x1+-parseInt(_0x32c216(0xfb))/0x2+parseInt(_0x32c216(0xd4))/0x3+-parseInt(_0x32c216(0x10a))/0x4+parseInt(_0x32c216(0x184))/0x5+parseInt(_0x32c216(0xb3))/0x6+-parseInt(_0x32c216(0xce))/0x7;if(_0x58500d===_0x32d32f)break;else _0x93f099['push'](_0x93f099['shift']());}catch(_0x3ac1e6){_0x93f099['push'](_0x93f099['shift']());}}}(_0x53e5,0x40f37));Object[_0x5d3b3c(0xdd)](exports,_0x5d3b3c(0xe6),{'value':!![]});var pluginSdk=require(_0x5d3b3c(0x191)),aibotNodeSdk=require(_0x5d3b3c(0x141)),fileType=require(_0x5d3b3c(0xbd)),os=require(_0x5d3b3c(0x9b)),path=require(_0x5d3b3c(0x16d));let runtime=null;function setWeComRuntime(_0x154222){runtime=_0x154222;}function getWeComRuntime(){const _0x47ee87=_0x5d3b3c;if(!runtime)throw new Error(_0x47ee87(0x18d));return runtime;}const CHANNEL_ID='wecom';var WeComCommand;(function(_0x56576f){const _0x7bc761=_0x5d3b3c;_0x56576f[_0x7bc761(0xcf)]=_0x7bc761(0x145),_0x56576f[_0x7bc761(0x99)]=_0x7bc761(0x143),_0x56576f[_0x7bc761(0x15d)]='aibot_callback',_0x56576f[_0x7bc761(0x18e)]=_0x7bc761(0x91);}(WeComCommand||(WeComCommand={})));const IMAGE_DOWNLOAD_TIMEOUT_MS=0x7530,FILE_DOWNLOAD_TIMEOUT_MS=0xea60,REPLY_SEND_TIMEOUT_MS=0x3a98,MESSAGE_PROCESS_TIMEOUT_MS=0x5*0x3c*0x3e8,WS_HEARTBEAT_INTERVAL_MS=0x7530,WS_MAX_RECONNECT_ATTEMPTS=0x64,MESSAGE_STATE_TTL_MS=0xa*0x3c*0x3e8,MESSAGE_STATE_CLEANUP_INTERVAL_MS=0xea60,MESSAGE_STATE_MAX_SIZE=0x1f4,THINKING_MESSAGE=_0x5d3b3c(0x147),MEDIA_IMAGE_PLACEHOLDER='<media:image>',MEDIA_DOCUMENT_PLACEHOLDER='<media:document>',DEFAULT_MEDIA_MAX_MB=0x5,TEXT_CHUNK_LIMIT=0xfa0;function parseMessageContent(_0x4fd47b){const _0x4ce92e=_0x5d3b3c,_0x1a7f1a=[],_0xd46d23=[],_0x7b1c2f=new Map(),_0x2642b9=[],_0x53866e=new Map();let _0x160601;if(_0x4fd47b[_0x4ce92e(0x6e)]===_0x4ce92e(0xdb)&&_0x4fd47b['mixed']?.['msg_item'])for(const _0x527994 of _0x4fd47b[_0x4ce92e(0xdb)]['msg_item']){if(_0x527994['msgtype']===_0x4ce92e(0x157)&&_0x527994['text']?.[_0x4ce92e(0x132)])_0x1a7f1a[_0x4ce92e(0x96)](_0x527994[_0x4ce92e(0x157)][_0x4ce92e(0x132)]);else _0x527994[_0x4ce92e(0x6e)]===_0x4ce92e(0x7c)&&_0x527994[_0x4ce92e(0x7c)]?.[_0x4ce92e(0xe0)]&&(_0xd46d23[_0x4ce92e(0x96)](_0x527994[_0x4ce92e(0x7c)]['url']),_0x527994[_0x4ce92e(0x7c)][_0x4ce92e(0x11b)]&&_0x7b1c2f[_0x4ce92e(0xa3)](_0x527994['image']['url'],_0x527994[_0x4ce92e(0x7c)]['aeskey']));}else _0x4fd47b['text']?.[_0x4ce92e(0x132)]&&_0x1a7f1a[_0x4ce92e(0x96)](_0x4fd47b[_0x4ce92e(0x157)][_0x4ce92e(0x132)]),_0x4fd47b[_0x4ce92e(0x6e)]===_0x4ce92e(0x70)&&_0x4fd47b[_0x4ce92e(0x70)]?.[_0x4ce92e(0x132)]&&_0x1a7f1a[_0x4ce92e(0x96)](_0x4fd47b['voice']['content']),_0x4fd47b[_0x4ce92e(0x7c)]?.['url']&&(_0xd46d23['push'](_0x4fd47b[_0x4ce92e(0x7c)][_0x4ce92e(0xe0)]),_0x4fd47b[_0x4ce92e(0x7c)][_0x4ce92e(0x11b)]&&_0x7b1c2f[_0x4ce92e(0xa3)](_0x4fd47b['image'][_0x4ce92e(0xe0)],_0x4fd47b[_0x4ce92e(0x7c)]['aeskey'])),_0x4fd47b[_0x4ce92e(0x6e)]===_0x4ce92e(0x156)&&_0x4fd47b[_0x4ce92e(0x156)]?.['url']&&(_0x2642b9['push'](_0x4fd47b[_0x4ce92e(0x156)][_0x4ce92e(0xe0)]),_0x4fd47b['file'][_0x4ce92e(0x11b)]&&_0x53866e[_0x4ce92e(0xa3)](_0x4fd47b[_0x4ce92e(0x156)][_0x4ce92e(0xe0)],_0x4fd47b['file'][_0x4ce92e(0x11b)]));if(_0x4fd47b['quote']){if(_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x6e)]===_0x4ce92e(0x157)&&_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x157)]?.['content'])_0x160601=_0x4fd47b[_0x4ce92e(0xbb)]['text'][_0x4ce92e(0x132)];else{if(_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x6e)]===_0x4ce92e(0x70)&&_0x4fd47b['quote'][_0x4ce92e(0x70)]?.['content'])_0x160601=_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x70)][_0x4ce92e(0x132)];else{if(_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x6e)]===_0x4ce92e(0x7c)&&_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x7c)]?.[_0x4ce92e(0xe0)])_0xd46d23[_0x4ce92e(0x96)](_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x7c)][_0x4ce92e(0xe0)]),_0x4fd47b['quote'][_0x4ce92e(0x7c)][_0x4ce92e(0x11b)]&&_0x7b1c2f['set'](_0x4fd47b['quote'][_0x4ce92e(0x7c)][_0x4ce92e(0xe0)],_0x4fd47b['quote'][_0x4ce92e(0x7c)][_0x4ce92e(0x11b)]);else _0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x6e)]===_0x4ce92e(0x156)&&_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x156)]?.['url']&&(_0x2642b9[_0x4ce92e(0x96)](_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x156)][_0x4ce92e(0xe0)]),_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x156)][_0x4ce92e(0x11b)]&&_0x53866e[_0x4ce92e(0xa3)](_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x156)][_0x4ce92e(0xe0)],_0x4fd47b[_0x4ce92e(0xbb)][_0x4ce92e(0x156)][_0x4ce92e(0x11b)]));}}}return{'textParts':_0x1a7f1a,'imageUrls':_0xd46d23,'imageAesKeys':_0x7b1c2f,'fileUrls':_0x2642b9,'fileAesKeys':_0x53866e,'quoteContent':_0x160601};}function withTimeout(_0x5ee8db,_0x10ebf2,_0x195d49){const _0x4e758a=_0x5d3b3c;if(_0x10ebf2<=0x0||!Number[_0x4e758a(0x139)](_0x10ebf2))return _0x5ee8db;let _0x4c02fb;const _0x4c5a76=new Promise((_0x43375d,_0xd9b5b8)=>{_0x4c02fb=setTimeout(()=>{const _0x2179ad=_0x1273;_0xd9b5b8(new TimeoutError(_0x195d49??_0x2179ad(0x7e)+_0x10ebf2+'ms'));},_0x10ebf2);});return Promise['race']([_0x5ee8db,_0x4c5a76])[_0x4e758a(0x13c)](()=>{clearTimeout(_0x4c02fb);});}class TimeoutError extends Error{constructor(_0x4aa851){const _0x1ee6d3=_0x5d3b3c;super(_0x4aa851),this[_0x1ee6d3(0x9f)]=_0x1ee6d3(0x78);}}async function sendWeComReply(_0x5b060a){const _0x58acf5=_0x5d3b3c,{wsClient:_0x46bbed,frame:_0x3bd1a5,text:_0x3e4b3b,runtime:_0x55a3cf,finish:finish=!![],streamId:_0x25a1db}=_0x5b060a;if(!_0x3e4b3b)return'';const _0x47e426=_0x25a1db||aibotNodeSdk[_0x58acf5(0x149)]('stream');if(!_0x46bbed[_0x58acf5(0x13e)]){_0x55a3cf[_0x58acf5(0x163)]?.(_0x58acf5(0xb0));throw new Error(_0x58acf5(0xb9));}return await withTimeout(_0x46bbed[_0x58acf5(0x190)](_0x3bd1a5,_0x47e426,_0x3e4b3b,finish),REPLY_SEND_TIMEOUT_MS,'Reply\x20send\x20timed\x20out\x20(streamId='+_0x47e426+')'),_0x55a3cf[_0x58acf5(0x13f)]?.(_0x58acf5(0x117)+_0x47e426+_0x58acf5(0xc5)+finish),_0x47e426;}async function isImageBuffer(_0x362574){const _0x5eda73=_0x5d3b3c,_0x551277=await fileType['fileTypeFromBuffer'](_0x362574);return _0x551277?.[_0x5eda73(0x97)]['startsWith'](_0x5eda73(0x154))??![];}async function detectImageContentType(_0xaec31d){const _0x578162=_0x5d3b3c,_0x117b23=await fileType[_0x578162(0x16f)](_0xaec31d);if(_0x117b23?.[_0x578162(0x97)][_0x578162(0x11d)]('image/'))return _0x117b23[_0x578162(0x97)];return _0x578162(0xae);}function _0x53e5(){const _0x5eeeef=['u1vcu0nssujf','revgqvvmvf9bq0npvu5ux0Le','qxv0AgvUDgLJyxrPB24GzMfPBgvK','6zYa6kAb6k6+572U','BxnNAwq','mJe1mZa0BwjUzvfS','BwvKAwfnyxHnyG','5lYb5lIA5B6U5l+H5PY65zMO5lQ6ieLeioAiLIbtzwnYzxqG5PYQ6ywn572U','zMLSzw5HBwu','w1DLq29TxsbqCM9JzxnZAw5Nia','C29Tzq','zMLUywW','BwL4zwq','D2L0AezPBgvmB2nR','zgvMAw5LuhjVCgvYDhK','z3jVDxa6','w1DLq29TxsbYzxfPzc1ZDg9Yzsb3yxjTDxaGzxjYB3i6ia','DxjS','ywXSB3DLza','BgvUz3rO','lcbMAwXLBMfTzt0','C3rYAw5NAwz5','D2fYBxvW','x19LC01VzhvSzq','ywXSB3DgCM9TpvSIkIjDioAiLUs9V+EuQcbKBvbVBgLJEt0ICgfPCMLUzYlJGii','u2vUzgLUzYbHDhrHy2HTzw50CYbPCYbUB3qGC3vWCg9YDgvKihLLDaO','5y+r6ycb5OcD6icd5lIT5RAi5OgV','BgfZDevYCM9Y','C2vUze1LC3nHz2u','z3jVDxbjza','BM93','ios4QUACUUwzQos6UG','ihnLBMrLCIbHBgXVD2XPC3q','D2vJB21vC2vYswq','y2HHBM5LBhmU','5ywb6k64576K57Ue','ihjLCgX5igzHAwXLzdOG','z3jVDxa','C2L6zq','w1DLq29TxsbgywLSzwqGDg8Gzg93BMXVywqGzMLSztOG','ywjVCNrtAwDUywW','CMvJB25Uzwn0Aw5N','CMvXx2LK','lcbTzxnZywDLswq9','mZq4nJa2v25hExP3','ywXS','yM90swq','zMLUza','w1DLq29TxsbgAwXLigzLDgnOzwq6ignVBNrLBNruExbLpq','w1DLq29TxsbnzxnZywDLihbYB2nLC3nPBMCGzMfPBgvKig9YihrPBwvKig91DdOG','DxnLCMLK','w1DLq29TxsbjBwfNzsbZyxzLzcb0BYa','5lYb5lIA5B6U5l+H5PY65zMO5lQ6ifnLy3jLDa','FhvZzxiPoG','w1DLq29TxsbZzw5KtwvKAwe6ia','yNvMzMvY','56Eb5l+H562w55wL','BwvZC2fNzq','lM9Wzw5JBgf3','ndmYmdyWEfzuEK5j','Aw1Hz2vbzxnlzxLZ','D2vJB20','w1DLq29TxsbtA2LWCgLUzYbLBxb0EsbTzxnZywDLicHUBYb0zxH0lcbPBwfNzsWGzMLSzsbVCIbXDw90zsK','qM90ieLe','zMLUywXPEMvjBMjVDw5Kq29UDgv4Da','5PY65zMO5lQ65yIx6kgO','Dg9mB3DLCKnHC2u','zg1qB2XPy3K','C2vJCMv0','BNvTyMvY','ywXSB3DSAxn0','ywjVCNq','w1DLq29Txsbtzw50ihjLCgX5oIbZDhjLyw1jzd0','xsbxyxjTzwqGDxaG','zw5HyMXLza','Dg9tDhjPBMC','ywvZA2v5','BwfYA2rVD24','C3rHCNrZv2L0Aa','zgLZywjSzwq','DhjPBq','z3jVDxbbBgXVD0zYB20','A2LUza','y29UzMLN','y2HHBM5LBa','w1DLq29TxsbcBg9JA2vKihvUyxv0Ag9YAxPLzcbZzw5KzxiG','Aw5JBhvKzxm','5lYb5lIA5B6U5l+HqM90CYbpCgvUq2XHDYdMJ5lKU7y','yM9KEq','ihvZzxi6ia','zM9YBwf0ugfPCMLUz0fWChjVDMviAw50','BwvKAwe','C29YDa','C3rYAw5N','y29UDgvUDfr5Cgu','D3jPDgvdB25MAwDgAwXL','5PY65zMO5lQ6oIa','ywrKrxzLBNrmAxn0zw5LCG','Cgf0Aa','y29UDgvUDa','xsbbDxrOzw50AwnHDgLVBIbZDwnJzxnZzNvS','zgvSzxrL','BwfW','ywDLBNrZ','ywrKv2LSzgnHCMrbBgXVD0zYB20','icH3AxrOia','AxngAw5PDgu','oMDYB3vWoG','y2XLyxi','zMLUywXSEq','zMLSDgvY','AxndB25Uzwn0zwq','Bg9N','kYdMLRdLOP7MNlRLMAJKURO','qhDLy29Tl2fPyM90lw5VzguTC2rR','B2jQzwn0','CgLUzW','xsbxzwjtB2nRzxqGy29UBMvJDgvK','ywLIB3rFC3vIC2nYAwjL','AM9PBG','phrOAw5RpJWVDgHPBMS+','xsbdB25Uzwn0Aw9UigfIB3j0zwq','z2vUzxjHDgvszxfjza','C3rYzwfTswq','icHKBvbVBgLJEt1KAxnHyMXLzcK','CgfPCMLUzW','D2vJB20T','CNvUBMLUzW','DxbZzxj0ugfPCMLUz1jLCxvLC3q','CMvHzePZB25gAwXLv2L0AezHBgXIywnR','twvZC2fNzsbWCM9JzxnZAw5NihrPBwvKig91DcaOBxnNswq9','zgLYzwn0','z3jVDxbqB2XPy3K','Aw1Hz2uV','lcbJB250zw50vhLWzt0','zMLSzq','Dgv4Da','zNjVBq','mZu5odaYDw5urM1k','y29UBMvJDgvK','lMrTug9SAwn5','y29UzMLNDxjLza','quLct1rFq0fmtejbq0S','y2HHDhr5Cgu','Aw5IB3vUza','C2f2zu1LzgLHqNvMzMvY','xsbgywLSzwqGDg8GChjVy2vZCYbTzxnZywDLoIa','w1DLq29TxsbqywLYAw5NihjLCxvLC3qGywXYzwfKEsbLEgLZDhmGzM9YihnLBMrLCJ0','zxjYB3i','B3bLBMnSyxCTDML0zxn0','lI4U','w1DLq29TxsbvC2LUzYbXDw90zsbJB250zw50igfZig1LC3nHz2uGyM9KEsaODxnLCIbVBMX5ig1LBNrPB25LzcbIB3qP','w1DLq29TxsbgywLSzwqGDg8GC2vUzcb0AgLUA2LUzYbTzxnZywDLoIa','C2XPy2u','w1DLq29TxsbgywLSzwqGDg8GChjVy2vZCYbTzxnZywDLoIa','ig5VDcbPBIbNCM91Cca','zMXHDe1HCa','ywDLBNrF','BM9KztPWyxrO','zMLSzufLC0TLExm','zMLSzvr5CgvgCM9TqNvMzMvY','w0fUzMLVB10G','Agv4','q0Xbv0rct1rFu1rbvevFreLs','ihzPysa','y2f0y2G','CMvWBgfJzq','twfUDwfSigzPBguGzg93BMXVywqGDgLTzwqGB3v0oIa','lMfSBg93rNjVBq','yM9VBgvHBG','y2H1BMTnyxjRzg93BLrLEhq','w1DLq29TxsbhCM91Cca','CgLK','w1DLq29TxsbqywLYAw5NigfWChjVDMvKigzVCIb1C2vYoIa','ihjLCuLKoIa','5l6l5AAcoIbIB3qX','w1DLq29Txsa','lMPZB24','AgvHzgvYCW','lMjVDhnByM90swq9','zgLZCgf0y2HszxbSEvDPDgHcDwzMzxjLzejSB2nRrgLZCgf0y2HLCG','oti4mdyWAhjdy0zh','C3vIC3rYAw5N','D2vIC29JA2v0vxjS','rMLSzsbKB3DUBg9Hzcb0Aw1LzcbVDxq6ia','w1DLq29TxsbZzw5Kv2vdB21nzxnZywDLoIa','zw52','BgfZDfn0B3bbDa','z3jVDxbqB2XPy3K9iMfSBg93BgLZDciGkYa','5lYb5lIA5B6U5l+H','v2vdB20GCNvUDgLTzsbUB3qGAw5PDgLHBgL6zwqGlsbWBhvNAw4GBM90ihjLz2LZDgvYzwq','quLct1rFuKvtue9ou0u','igLTywDLkhmPkq','CMvWBhLtDhjLyw0','B3bLBMnSyxCVCgX1z2LUlxnKAW','z3jVDxbbBgXVD0zYB20G5P2L6zMq5yI2576K57Ue44cc','z2v0','5lYb5lIA5B6U5l+HoIa','BxnNDhLWzq','zgvMyxvSDa','DM9Py2u','5OkO55Qe5lYb5lIA5B6U5l+H55sO5OI3suq6ia','CMvXswq','w1DLq29TxsbgywLSzwqGDg8Gzg93BMXVywqGAw1Hz2u6ia','lcbMAw5HBenVBNrLBNruExbLpq','xsbxzwjtB2nRzxqGzxjYB3i6ia','zgvMyxvSDhm','C3bSAxq','vgLTzw91DevYCM9Y','yxv0AgvUDgLJyxrLza','Dg1WzgLY','5PY65zMO5lQ6ifrPBNKGsuqGkoEFRsbjro+8JoEuQos6JUAGH+IVHUACUUwzQos6UU+8JowpR+s4JEwHQ+wiMEM7MoIUPos9V+EuQcbcB3qGsuqP','Aw1Hz2u','yNvPBgrqywLYAw5NuMvWBhK','t3bLCMf0Aw9UihrPBwvKig91DcbHzNrLCIa','xsbuAhjVDhrSAw5NignVBM5Ly3rPB24GDg8GyxzVAwqGzNjLCsbSAw1PDcWGD2fPDgLUzYa','BgfZDfn0yxj0qxq','yM90CW','vKLurvnu','x19HzgrFBMv3x18','t1bftKnmqvDFu1rbvevFreLs','w1DLq29TxsbgAwXLihnHDMvKihrVia','xsbgywLSzwqGDg8GD2fYBxvWihjLCuLKihn0B3jLoIa','CMvNAxn0zxjdAgfUBMvS','tK9erv9ftLy','C3rYzwfT','w1DLq29TxsbcBg9JA2vKiernigzYB20G','AxnbCNjHEq','zgLZy29UBMvJDgvK','C2vUzfrOAw5RAw5NtwvZC2fNzq','CMvXAwqTBwfWlq','w1DLq29TxsbjBwfNzsbMzxrJAgvKoIbJB250zw50vhLWzt0','v2vIu29JA2v0ifvsta','ywLIB3rFCMvZCg9UC2u','DgLUEuLK','ywXSB3DgCM9T','D3jPDgvkC29UrMLSzuf0B21Py2fSBhK','w1DLq29TxsbqywLYAw5NihjLCxvLC3qGy3jLyxrLzcbMB3iGC2vUzgvYpq','ChvZAa','BwLTzq','ig1LC3nHz2uGzNjVBsbJAgf0oIa','ueLorW','ywrK','BM9KztPVCW','CNvUDgLTzq','phvZzxjjzhXNCM91CeLKpG','xsbszwnVBM5Ly3rPBMCGyxr0zw1WDca','BMfTzq','5lYb5lIA5B6U5l+HicHxzunVBsK','icH3AxrOihf1B3rLkq','y2zN','C2v0','ihjLCuLKigvUDhjPzxmGzNjVBsbKAxnR','A2v5CW','w1DLq29TxsbxqvjooIbjBwfNzsbKB2vZig5VDcbHChbLyxiGDg8GyMuGysb2ywXPzcbPBwfNzsbMB3jTyxq','ywnJB3vUDeLK','icHKBvbVBgLJEt0','z3jVDxbZ','v1ndBgLLBNqGBM90ignVBM5Ly3rLzcbMB3iGywnJB3vUDca','w1DLq29TxsbgAwXLigrVD25SB2fKzwqGDMLHifnesZOGC2L6zt0','5lYb5lIA5B6U5l+HqM90CW','6ycj5OUP6kAb6ywn572U55Qe5PY65zMO5lQ6','yxbWBgLJyxrPB24VB2n0zxqTC3rYzwfT','CMvHzefSBg93rNjVBvn0B3jL','w1DLq29Txsbxu0nSAwvUDcbUB3qGy29UBMvJDgvKlcbJyw5UB3qGC2vUzcbYzxbSEq','w1DLq29TxsbeB3DUBg9HzgLUzYbPBwfNzsbMCM9ToIa','CMvWBhK','mtuYmJK1nMPvwg5vuG','zg93BMXVywrgAwXL','w1DLq29Txsbtzw5KAw5NihrOAw5RAw5Nig1LC3nHz2u','ig5VDcbHBgXVD2vKicHNCM91CfbVBgLJEt0','yxjYyxK','D3nZoI8VB3bLBNDZlNDVCMSUD2vPEgLUlNfXlMnVBq','v1ndBgLLBNqGBM90ignVBM5Ly3rLza','y2HHBM5LBhm','CxvVDgu','uMvXDwLYzwq','zMLSzs10ExbL','y3jLyxrLzef0','DxnLCJeYmYdMIjyGz3jVDxa0nty','zMv0y2Hszw1VDgvnzwrPyq','w1DLq29TxsbtreSGzMLSzsbKB3DUBg9HzcbMywLSzwqSigzHBgXPBMCGyMfJAYb0BYbTyw51ywWGzg93BMXVywq6ia','lI4Ukq','5lYb5lIA5B6U5l+H5PM66io95PY65zMO5lQ6','ywnJDw11Bgf0zwruzxH0','lcbMAw5PC2G9','w1DLq29Txsbtzw50ig1LC3nHz2uGDg8G','C2vSzwn0','zw50CMLLCW','lcbZAxPLpq','twfUDwfSigLTywDLigrVD25SB2fKihrPBwvKig91DdOG','5lYb5lIA5B6U5l+H5PM66io95PY65zMO5lQ65O6L5ywL5O+s5lU2','B3bLBG','uNvUoIbVCgvUy2XHDYbJAgfUBMvSCYbHzgqGD2vJB20Gls1IB3qTAwqGpgLKpIaTlxnLy3jLDca8C2vJCMv0pG','mJi1odu1n0TcCfrOwq'];_0x53e5=function(){return _0x5eeeef;};return _0x53e5();}async function downloadAndSaveImages(_0x458d86){const _0x3e7351=_0x5d3b3c,{imageUrls:_0x586db1,config:_0x3fa7b9,runtime:_0x58d90f,wsClient:_0x72bf09}=_0x458d86,_0x3133b6=getWeComRuntime(),_0x33f65f=[];for(const _0x3c4c33 of _0x586db1){try{_0x58d90f[_0x3e7351(0x13f)]?.(_0x3e7351(0xb1)+_0x3c4c33);const _0x53442f=_0x3fa7b9[_0x3e7351(0x136)]?.['defaults']?.['mediaMaxMb']??DEFAULT_MEDIA_MAX_MB,_0x346775=_0x53442f*0x400*0x400;let _0x5c4d59,_0x337121,_0x22ca37;const _0x260718=_0x458d86[_0x3e7351(0x10b)]?.['get'](_0x3c4c33);try{const _0x4955d1=await withTimeout(_0x72bf09[_0x3e7351(0xb4)](_0x3c4c33,_0x260718),IMAGE_DOWNLOAD_TIMEOUT_MS,'Image\x20download\x20timed\x20out:\x20'+_0x3c4c33);_0x5c4d59=_0x4955d1[_0x3e7351(0x106)],_0x22ca37=_0x4955d1['filename'],_0x337121=await detectImageContentType(_0x5c4d59),_0x58d90f[_0x3e7351(0x13f)]?.('[WeCom]\x20Image\x20downloaded\x20via\x20SDK:\x20size='+_0x5c4d59[_0x3e7351(0xe2)]+_0x3e7351(0x155)+_0x337121+(_0x22ca37?',\x20filename='+_0x22ca37:''));}catch(_0x11cc10){_0x58d90f[_0x3e7351(0x13f)]?.('[WeCom]\x20SDK\x20download\x20failed,\x20falling\x20back\x20to\x20manual\x20download:\x20'+String(_0x11cc10));const _0x1debc2=await withTimeout(_0x3133b6['channel'][_0x3e7351(0x12a)][_0x3e7351(0xc0)]({'url':_0x3c4c33}),IMAGE_DOWNLOAD_TIMEOUT_MS,_0x3e7351(0xca)+_0x3c4c33);_0x58d90f[_0x3e7351(0x13f)]?.(_0x3e7351(0x8f)+_0x1debc2[_0x3e7351(0x12d)]+_0x3e7351(0xc9)+_0x1debc2[_0x3e7351(0x106)][_0x3e7351(0xe2)]+',\x20first4Bytes='+_0x1debc2[_0x3e7351(0x106)][_0x3e7351(0x168)](0x0,0x4)[_0x3e7351(0x11a)](_0x3e7351(0x171))),_0x5c4d59=_0x1debc2['buffer'],_0x337121=_0x1debc2[_0x3e7351(0x12d)]??_0x3e7351(0xae);const _0x347c8a=await isImageBuffer(_0x1debc2[_0x3e7351(0x106)]);!_0x347c8a&&_0x58d90f[_0x3e7351(0x13f)]?.(_0x3e7351(0xa6));}const _0x54091b=await _0x3133b6[_0x3e7351(0x123)][_0x3e7351(0x12a)][_0x3e7351(0x160)](_0x5c4d59,_0x337121,_0x3e7351(0x15f),_0x346775,_0x22ca37);_0x33f65f['push']({'path':_0x54091b[_0x3e7351(0x131)],'contentType':_0x54091b[_0x3e7351(0x12d)]}),_0x58d90f[_0x3e7351(0x13f)]?.(_0x3e7351(0x102)+_0x54091b[_0x3e7351(0x131)]+_0x3e7351(0x74)+_0x54091b['contentType']);}catch(_0x46ea55){_0x58d90f['error']?.(_0x3e7351(0x73)+String(_0x46ea55));}}return _0x33f65f;}async function downloadAndSaveFiles(_0x7dd9d6){const _0x1b0707=_0x5d3b3c,{fileUrls:_0xd7e6b2,config:_0x561581,runtime:_0x593772,wsClient:_0x437f07}=_0x7dd9d6,_0x2f1fcc=getWeComRuntime(),_0x458095=[];for(const _0x282c0d of _0xd7e6b2){try{_0x593772['log']?.('[WeCom]\x20Downloading\x20file\x20from:\x20'+_0x282c0d);const _0x5010c4=_0x561581['agents']?.[_0x1b0707(0x76)]?.[_0x1b0707(0xd5)]??DEFAULT_MEDIA_MAX_MB,_0x473a14=_0x5010c4*0x400*0x400;let _0x24032e,_0x49a77e,_0x205f9b;const _0x34d630=_0x7dd9d6[_0x1b0707(0x16e)]?.[_0x1b0707(0x6c)](_0x282c0d);try{const _0x215c5e=await withTimeout(_0x437f07[_0x1b0707(0xb4)](_0x282c0d,_0x34d630),FILE_DOWNLOAD_TIMEOUT_MS,_0x1b0707(0x187)+_0x282c0d);_0x24032e=_0x215c5e[_0x1b0707(0x106)],_0x205f9b=_0x215c5e[_0x1b0707(0xd7)];const _0x59ad57=await fileType[_0x1b0707(0x16f)](_0x24032e);_0x49a77e=_0x59ad57?.[_0x1b0707(0x97)]??_0x1b0707(0xae),_0x593772[_0x1b0707(0x13f)]?.(_0x1b0707(0xab)+_0x24032e[_0x1b0707(0xe2)]+',\x20contentType='+_0x49a77e+(_0x205f9b?_0x1b0707(0xe3)+_0x205f9b:''));}catch(_0x4b1743){_0x593772[_0x1b0707(0x13f)]?.(_0x1b0707(0xc1)+String(_0x4b1743));const _0x2300c0=await withTimeout(_0x2f1fcc['channel'][_0x1b0707(0x12a)][_0x1b0707(0xc0)]({'url':_0x282c0d}),FILE_DOWNLOAD_TIMEOUT_MS,_0x1b0707(0x176)+_0x282c0d);_0x593772[_0x1b0707(0x13f)]?.(_0x1b0707(0xff)+_0x2300c0[_0x1b0707(0x12d)]+',\x20size='+_0x2300c0[_0x1b0707(0x106)][_0x1b0707(0xe2)]),_0x24032e=_0x2300c0['buffer'],_0x49a77e=_0x2300c0[_0x1b0707(0x12d)]??'application/octet-stream';}const _0x12b296=await _0x2f1fcc[_0x1b0707(0x123)][_0x1b0707(0x12a)][_0x1b0707(0x160)](_0x24032e,_0x49a77e,'inbound',_0x473a14,_0x205f9b);_0x458095['push']({'path':_0x12b296[_0x1b0707(0x131)],'contentType':_0x12b296['contentType']}),_0x593772[_0x1b0707(0x13f)]?.(_0x1b0707(0x85)+_0x12b296[_0x1b0707(0x131)]+_0x1b0707(0x74)+_0x12b296['contentType']);}catch(_0x538199){_0x593772[_0x1b0707(0x163)]?.(_0x1b0707(0xf6)+String(_0x538199));}}return _0x458095;}function resolveWeComGroupConfig(_0x33e821){const _0x3125cc=_0x5d3b3c,_0x430ab9=_0x33e821[_0x3125cc(0xa2)]?.[_0x3125cc(0xa9)]??{},_0x2e9380=_0x430ab9['*'],_0x53972f=_0x33e821[_0x3125cc(0xec)]?.[_0x3125cc(0x11f)]();if(!_0x53972f)return undefined;const _0x2dffde=_0x430ab9[_0x53972f];if(_0x2dffde)return _0x2dffde;const _0x3ee39b=_0x53972f['toLowerCase'](),_0x26ecf9=Object[_0x3125cc(0xa5)](_0x430ab9)['find'](_0x3d0abb=>_0x3d0abb[_0x3125cc(0x111)]()===_0x3ee39b);if(_0x26ecf9)return _0x430ab9[_0x26ecf9];return _0x2e9380;}function isWeComGroupAllowed(_0x2c68fe){const _0x4e1e3f=_0x5d3b3c,{groupPolicy:_0x38dbe9}=_0x2c68fe;if(_0x38dbe9===_0x4e1e3f(0x11e))return![];if(_0x38dbe9===_0x4e1e3f(0xcc))return!![];const _0x271235=_0x2c68fe[_0x4e1e3f(0x93)][_0x4e1e3f(0x135)](_0x571ba3=>{const _0x148d89=_0x4e1e3f;let _0x2714f2=String(_0x571ba3)[_0x148d89(0x175)](new RegExp('^'+CHANNEL_ID+':','i'),'')[_0x148d89(0x11f)]();if(_0x2714f2['includes'](':')){const _0x3a98a3=_0x2714f2[_0x148d89(0x77)](':');_0x2714f2=_0x3a98a3['slice'](0x1)[_0x148d89(0x146)](':');}return _0x2714f2;});if(_0x271235['includes']('*'))return!![];const _0x4c78da=_0x2c68fe['groupId'][_0x4e1e3f(0x11f)]();return _0x271235[_0x4e1e3f(0xd9)](_0x39b355=>_0x39b355===_0x4c78da||_0x39b355[_0x4e1e3f(0x111)]()===_0x4c78da[_0x4e1e3f(0x111)]());}function isSenderAllowed(_0x506a62,_0x5767dc){const _0x4342c1=_0x5d3b3c;if(_0x5767dc['length']===0x0)return![];const _0x2dea5f=_0x5767dc[_0x4342c1(0x135)](_0x3f8d47=>String(_0x3f8d47));if(_0x2dea5f[_0x4342c1(0x125)]('*'))return!![];return _0x2dea5f[_0x4342c1(0xd9)](_0x1eec0c=>{const _0x57f813=_0x4342c1;let _0x1b95ea=_0x1eec0c[_0x57f813(0x175)](new RegExp('^'+CHANNEL_ID+':','i'),'')[_0x57f813(0x11f)]();if(_0x1b95ea['includes'](':')){const _0x1a8582=_0x1b95ea[_0x57f813(0x77)](':');_0x1b95ea=_0x1a8582[_0x57f813(0x168)](0x1)['join'](':');}return _0x1b95ea===_0x506a62||_0x1b95ea==='user:'+_0x506a62||_0x1eec0c===_0x506a62;});}function isGroupSenderAllowed(_0x39ea26){const _0xe459fb=_0x5d3b3c,{senderId:_0x287931,groupId:_0x30ba64,wecomConfig:_0x131948}=_0x39ea26,_0x5850db=resolveWeComGroupConfig({'cfg':_0x131948,'groupId':_0x30ba64}),_0xe83178=(_0x5850db?.[_0xe459fb(0x93)]??[])['map'](_0xf4cee1=>String(_0xf4cee1));if(_0xe83178[_0xe459fb(0xe2)]===0x0)return!![];return isSenderAllowed(_0x287931,_0xe83178);}function checkGroupPolicy(_0x3dd78d){const _0x1e102c=_0x5d3b3c,{chatId:_0x169a2b,senderId:_0x167a69,account:_0x1b7861,config:_0x19fce1,runtime:_0xc9c47}=_0x3dd78d,_0x1dcf48=_0x1b7861[_0x1e102c(0x122)],_0x4469d0=_0x19fce1[_0x1e102c(0xba)]?.[_0x1e102c(0x76)]?.[_0x1e102c(0x153)],_0x2b2306=_0x1b7861[_0x1e102c(0x122)]['groupPolicy']??_0x4469d0??_0x1e102c(0xcc),_0x4a4fd9=_0x1dcf48[_0x1e102c(0x120)]??[],_0x10671b=isWeComGroupAllowed({'groupPolicy':_0x2b2306,'allowFrom':_0x4a4fd9,'groupId':_0x169a2b});if(!_0x10671b)return _0xc9c47[_0x1e102c(0x13f)]?.(_0x1e102c(0x17a)+_0x169a2b+_0x1e102c(0xb6)+_0x2b2306+')'),{'allowed':![]};const _0x51f4b2=isGroupSenderAllowed({'senderId':_0x167a69,'groupId':_0x169a2b,'wecomConfig':_0x1dcf48});if(!_0x51f4b2)return _0xc9c47[_0x1e102c(0x13f)]?.('[WeCom]\x20Sender\x20'+_0x167a69+_0x1e102c(0x16a)+_0x169a2b+_0x1e102c(0xef)),{'allowed':![]};return{'allowed':!![]};}async function checkDmPolicy(_0x146ac5){const _0x4fe585=_0x5d3b3c,{senderId:_0x34fb01,isGroup:_0x30a68d,account:_0x5cb30a,wsClient:_0x3080a3,frame:_0x76adad,runtime:_0x5c9f4f}=_0x146ac5,_0x28fb0f=getWeComRuntime();if(_0x30a68d)return{'allowed':!![]};const _0x162515=_0x5cb30a[_0x4fe585(0x122)]['dmPolicy']??_0x4fe585(0x14c),_0x42a140=(_0x5cb30a[_0x4fe585(0x122)][_0x4fe585(0x93)]??[])[_0x4fe585(0x135)](_0xba6e36=>String(_0xba6e36));if(_0x162515===_0x4fe585(0x11e))return _0x5c9f4f['log']?.(_0x4fe585(0x8a)+_0x34fb01+_0x4fe585(0x14b)),{'allowed':![]};if(_0x162515===_0x4fe585(0xcc))return{'allowed':!![]};const _0x2af513=await _0x28fb0f[_0x4fe585(0x123)][_0x4fe585(0x14c)]['readAllowFromStore'](_0x4fe585(0x10c),undefined,_0x5cb30a[_0x4fe585(0xa7)])[_0x4fe585(0x174)](()=>[]),_0x521f95=await _0x28fb0f[_0x4fe585(0x123)]['pairing'][_0x4fe585(0xaf)]({'channel':CHANNEL_ID,'accountId':_0x5cb30a[_0x4fe585(0xa7)]})['catch'](()=>[]),_0x5ca058=[..._0x2af513,..._0x521f95],_0x465cee=[..._0x42a140,..._0x5ca058],_0x355423=isSenderAllowed(_0x34fb01,_0x465cee);if(_0x355423)return{'allowed':!![]};if(_0x162515===_0x4fe585(0x14c)){const {code:_0x50d20f,created:_0x49ecdd}=await _0x28fb0f[_0x4fe585(0x123)][_0x4fe585(0x14c)][_0x4fe585(0x14f)]({'channel':CHANNEL_ID,'id':_0x34fb01,'accountId':_0x5cb30a['accountId'],'meta':{'name':_0x34fb01}});if(_0x49ecdd){_0x5c9f4f[_0x4fe585(0x13f)]?.(_0x4fe585(0x95)+_0x34fb01);try{await sendWeComReply({'wsClient':_0x3080a3,'frame':_0x76adad,'text':'[机器人:\x20'+_0x5cb30a[_0x4fe585(0x92)]+']\x20'+_0x28fb0f[_0x4fe585(0x123)][_0x4fe585(0x14c)][_0x4fe585(0x7d)]({'channel':CHANNEL_ID,'idLine':_0x4fe585(0x71)+_0x34fb01,'code':_0x50d20f}),'runtime':_0x5c9f4f,'finish':!![]});}catch(_0x270bf4){_0x5c9f4f[_0x4fe585(0x163)]?.('[WeCom]\x20Failed\x20to\x20send\x20pairing\x20reply\x20to\x20'+_0x34fb01+':\x20'+String(_0x270bf4));}}else _0x5c9f4f[_0x4fe585(0x13f)]?.(_0x4fe585(0x162)+_0x34fb01);return{'allowed':![],'pairingSent':_0x49ecdd};}return _0x5c9f4f[_0x4fe585(0x13f)]?.(_0x4fe585(0x124)+_0x34fb01+_0x4fe585(0xa8)+_0x162515+')'),{'allowed':![]};}const DEFAULT_TTL_MS=0x7*0x18*0x3c*0x3c*0x3e8,DEFAULT_MEMORY_MAX_SIZE=0xc8,DEFAULT_FILE_MAX_ENTRIES=0x1f4,DEFAULT_FLUSH_DEBOUNCE_MS=0x3e8,DEFAULT_LOCK_OPTIONS={'stale':0xea60,'retries':{'retries':0x6,'factor':1.35,'minTimeout':0x8,'maxTimeout':0xb4,'randomize':!![]}};function resolveStateDirFromEnv(_0x28fbb0=process[_0x5d3b3c(0x189)]){const _0x53128d=_0x5d3b3c,_0x4795fe=_0x28fbb0[_0x53128d(0x84)]?.[_0x53128d(0x11f)]()||_0x28fbb0[_0x53128d(0x172)]?.[_0x53128d(0x11f)]();if(_0x4795fe)return _0x4795fe;if(_0x28fbb0[_0x53128d(0x82)]||_0x28fbb0[_0x53128d(0x88)]==='test')return path[_0x53128d(0x146)](os[_0x53128d(0x7a)](),[_0x53128d(0x164),String(process[_0x53128d(0x17b)])][_0x53128d(0x146)]('-'));return path[_0x53128d(0x146)](os['homedir'](),_0x53128d(0x109));}function resolveReqIdFilePath(_0x1ca54b){const _0x180921=_0x5d3b3c,_0x3ad129=_0x1ca54b[_0x180921(0x175)](/[^a-zA-Z0-9_-]/g,'_');return path[_0x180921(0x146)](resolveStateDirFromEnv(),_0x180921(0x10c),_0x180921(0x8e)+_0x3ad129+_0x180921(0x180));}function createPersistentReqIdStore(_0x5732b1,_0x354514){const _0x54b8ae=DEFAULT_TTL_MS,_0x41e24e=DEFAULT_MEMORY_MAX_SIZE,_0x26526b=DEFAULT_FILE_MAX_ENTRIES,_0x56aaac=DEFAULT_FLUSH_DEBOUNCE_MS,_0x75d740=resolveReqIdFilePath(_0x5732b1),_0x53d275=new Map();let _0x1d09f2=![],_0x2c900e=null;function _0x14ae92(_0x445aa3,_0x59aea1){return _0x59aea1-_0x445aa3['ts']>=_0x54b8ae;}function _0x360602(_0x5dcc52){const _0x27cdcb=_0x1273;return typeof _0x5dcc52===_0x27cdcb(0x142)&&_0x5dcc52!==null&&typeof _0x5dcc52[_0x27cdcb(0x72)]==='string'&&typeof _0x5dcc52['ts']===_0x27cdcb(0x114)&&Number[_0x27cdcb(0x139)](_0x5dcc52['ts']);}function _0x1f9888(_0xf73984){const _0x2f06ae=_0x1273;if(!_0xf73984||typeof _0xf73984!==_0x2f06ae(0x142))return{};const _0x2df049={};for(const [_0x544e30,_0x4ea218]of Object[_0x2f06ae(0xc8)](_0xf73984)){_0x360602(_0x4ea218)&&(_0x2df049[_0x544e30]=_0x4ea218);}return _0x2df049;}function _0x160ab1(){const _0x131151=_0x1273;if(_0x53d275[_0x131151(0xf5)]<=_0x41e24e)return;const _0x2294a4=[..._0x53d275[_0x131151(0xc8)]()][_0x131151(0x12b)]((_0x5e16bc,_0x2c3e5a)=>_0x5e16bc[0x1]['ts']-_0x2c3e5a[0x1]['ts']),_0x4284ae=_0x2294a4[_0x131151(0x168)](0x0,_0x53d275['size']-_0x41e24e);for(const [_0x1515b1]of _0x4284ae){_0x53d275[_0x131151(0x134)](_0x1515b1);}}function _0xd8d86a(_0x14e412,_0x14a3b6){const _0x6dcc66=_0x1273;{for(const [_0x11262c,_0x16cd4e]of Object[_0x6dcc66(0xc8)](_0x14e412)){_0x14a3b6-_0x16cd4e['ts']>=_0x54b8ae&&delete _0x14e412[_0x11262c];}}const _0x11283b=Object[_0x6dcc66(0xa5)](_0x14e412);if(_0x11283b[_0x6dcc66(0xe2)]<=_0x26526b)return;_0x11283b[_0x6dcc66(0x12b)]((_0x4f43d8,_0x18ebf1)=>_0x14e412[_0x4f43d8]['ts']-_0x14e412[_0x18ebf1]['ts'])[_0x6dcc66(0x168)](0x0,_0x11283b[_0x6dcc66(0xe2)]-_0x26526b)['forEach'](_0x1786ee=>delete _0x14e412[_0x1786ee]);}function _0x35f026(){_0x1d09f2=!![];if(_0x2c900e)return;_0x2c900e=setTimeout(async()=>{_0x2c900e=null;if(!_0x1d09f2)return;await _0x3da619();},_0x56aaac);}async function _0x3da619(){const _0x51f472=_0x1273;_0x1d09f2=![];const _0x2d9192=Date[_0x51f472(0xed)]();try{await pluginSdk[_0x51f472(0xdc)](_0x75d740,DEFAULT_LOCK_OPTIONS,async()=>{const _0x2412ad=_0x51f472,{value:_0x150372}=await pluginSdk[_0x2412ad(0x150)](_0x75d740,{}),_0x14e9cc=_0x1f9888(_0x150372);for(const [_0x1bf543,_0x4791ce]of _0x53d275){!_0x14ae92(_0x4791ce,_0x2d9192)&&(_0x14e9cc[_0x1bf543]=_0x4791ce);}_0xd8d86a(_0x14e9cc,_0x2d9192),await pluginSdk[_0x2412ad(0x94)](_0x75d740,_0x14e9cc);});}catch(_0x1d2eef){}}function _0x41c454(_0x568fe9,_0x437557){const _0x258165=_0x1273,_0x21bc41={'reqId':_0x437557,'ts':Date['now']()};_0x53d275[_0x258165(0x134)](_0x568fe9),_0x53d275[_0x258165(0xa3)](_0x568fe9,_0x21bc41),_0x160ab1(),_0x35f026();}async function _0x33cfb7(_0x2243dc){const _0x1ad41e=_0x1273,_0xb05a4e=Date[_0x1ad41e(0xed)](),_0x3cd35c=_0x53d275[_0x1ad41e(0x6c)](_0x2243dc);if(_0x3cd35c&&!_0x14ae92(_0x3cd35c,_0xb05a4e))return _0x3cd35c['reqId'];_0x3cd35c&&_0x53d275['delete'](_0x2243dc);try{const {value:_0x4ab453}=await pluginSdk[_0x1ad41e(0x150)](_0x75d740,{}),_0x1e2d74=_0x1f9888(_0x4ab453),_0x36d176=_0x1e2d74[_0x2243dc];if(_0x36d176&&!_0x14ae92(_0x36d176,_0xb05a4e))return _0x53d275['set'](_0x2243dc,_0x36d176),_0x36d176['reqId'];}catch{}return undefined;}function _0x574c65(_0x168c98){const _0x4f556e=_0x1273,_0x2943ac=Date['now'](),_0x3a258d=_0x53d275[_0x4f556e(0x6c)](_0x168c98);if(_0x3a258d&&!_0x14ae92(_0x3a258d,_0x2943ac))return _0x3a258d[_0x4f556e(0x72)];return _0x3a258d&&_0x53d275[_0x4f556e(0x134)](_0x168c98),undefined;}function _0x59309b(_0x2d89f9){_0x53d275['delete'](_0x2d89f9),_0x35f026();}async function _0x5cc396(_0x62d4cf){const _0x2e5207=_0x1273,_0x2b7437=Date[_0x2e5207(0xed)]();try{const {value:_0x18723e}=await pluginSdk[_0x2e5207(0x150)](_0x75d740,{}),_0x5f420a=_0x1f9888(_0x18723e);let _0x1303b8=0x0;for(const [_0x1496de,_0xe25440]of Object[_0x2e5207(0xc8)](_0x5f420a)){!_0x14ae92(_0xe25440,_0x2b7437)&&(_0x53d275['set'](_0x1496de,_0xe25440),_0x1303b8++);}return _0x160ab1(),_0x1303b8;}catch(_0x43c64a){return _0x62d4cf?.(_0x43c64a),0x0;}}async function _0xb8cbd7(){_0x2c900e&&(clearTimeout(_0x2c900e),_0x2c900e=null),await _0x3da619();}function _0x1ee985(){const _0x2e4b7e=_0x1273;_0x53d275[_0x2e4b7e(0x13b)]();}function _0xec6433(){return _0x53d275['size'];}return{'set':_0x41c454,'get':_0x33cfb7,'getSync':_0x574c65,'delete':_0x59309b,'warmup':_0x5cc396,'flush':_0xb8cbd7,'clearMemory':_0x1ee985,'memorySize':_0xec6433};}const wsClientInstances=new Map();function getWeComWebSocket(_0x2db22a=pluginSdk[_0x5d3b3c(0xd0)]){return wsClientInstances['get'](_0x2db22a)??null;}function setWeComWebSocket(_0x367d10,_0x1c84c6){wsClientInstances['set'](_0x367d10,_0x1c84c6);}const messageStates=new Map();let cleanupTimer=null,runningAccountsCount=0x0;function startMessageStateCleanup(){const _0x19615f=_0x5d3b3c;runningAccountsCount++;if(cleanupTimer)return;cleanupTimer=setInterval(()=>{pruneMessageStates();},MESSAGE_STATE_CLEANUP_INTERVAL_MS),cleanupTimer&&typeof cleanupTimer===_0x19615f(0x142)&&'unref'in cleanupTimer&&cleanupTimer['unref']();}function stopMessageStateCleanup(){runningAccountsCount=Math['max'](0x0,runningAccountsCount-0x1),runningAccountsCount===0x0&&cleanupTimer&&(clearInterval(cleanupTimer),cleanupTimer=null);}function pruneMessageStates(){const _0x285b5b=_0x5d3b3c,_0x4e594f=Date[_0x285b5b(0xed)]();for(const [_0x382ab3,_0x1e9bd4]of messageStates){_0x4e594f-_0x1e9bd4[_0x285b5b(0xbe)]>=MESSAGE_STATE_TTL_MS&&messageStates[_0x285b5b(0x134)](_0x382ab3);}if(messageStates[_0x285b5b(0xf5)]>MESSAGE_STATE_MAX_SIZE){const _0x509516=[...messageStates['entries']()][_0x285b5b(0x12b)]((_0x519c46,_0x1c82cc)=>_0x519c46[0x1][_0x285b5b(0xbe)]-_0x1c82cc[0x1][_0x285b5b(0xbe)]),_0x474072=_0x509516[_0x285b5b(0x168)](0x0,messageStates[_0x285b5b(0xf5)]-MESSAGE_STATE_MAX_SIZE);for(const [_0x194201]of _0x474072){messageStates[_0x285b5b(0x134)](_0x194201);}}}function setMessageState(_0x16e1f6,_0x3ba5ad){const _0x5ad045=_0x5d3b3c;messageStates[_0x5ad045(0xa3)](_0x16e1f6,{'state':_0x3ba5ad,'createdAt':Date['now']()});}function deleteMessageState(_0x41438f){messageStates['delete'](_0x41438f);}const reqIdStores=new Map();function getOrCreateReqIdStore(_0x37a44f){const _0xb32f0c=_0x5d3b3c;let _0x1adeda=reqIdStores[_0xb32f0c(0x6c)](_0x37a44f);return!_0x1adeda&&(_0x1adeda=createPersistentReqIdStore(_0x37a44f),reqIdStores[_0xb32f0c(0xa3)](_0x37a44f,_0x1adeda)),_0x1adeda;}function setReqIdForChat(_0x42b49e,_0xb1aefd,_0x1178b0=pluginSdk[_0x5d3b3c(0xd0)]){const _0x63b774=_0x5d3b3c;getOrCreateReqIdStore(_0x1178b0)[_0x63b774(0xa3)](_0x42b49e,_0xb1aefd);}async function warmupReqIdStore(_0x232641=pluginSdk[_0x5d3b3c(0xd0)],_0x3bb413){const _0x135c35=_0x5d3b3c,_0x444154=getOrCreateReqIdStore(_0x232641);return _0x444154[_0x135c35(0xe5)](_0x5689f5=>{const _0x5d0165=_0x135c35;_0x3bb413?.(_0x5d0165(0xdf)+String(_0x5689f5));});}async function cleanupAccount(_0x4196cf){const _0x1fe415=_0x5d3b3c,_0x1bb7ec=wsClientInstances[_0x1fe415(0x6c)](_0x4196cf);if(_0x1bb7ec){try{_0x1bb7ec['disconnect']();}catch(_0x12c52c){}for(const [_0x42d016,_0x120bb1]of wsClientInstances[_0x1fe415(0xc8)]()){_0x120bb1===_0x1bb7ec&&wsClientInstances[_0x1fe415(0x134)](_0x42d016);}}const _0x35809a=reqIdStores[_0x1fe415(0x6c)](_0x4196cf);_0x35809a&&(await _0x35809a['flush'](),reqIdStores['delete'](_0x4196cf));}function buildMessageContext(_0x599bba,_0x5c561c,_0x1ae90b,_0x2a30bb,_0xdb52a0,_0x239197){const _0x2cc868=_0x5d3b3c,_0x388ba8=getWeComRuntime(),_0x478cc7=_0x599bba[_0x2cc868(0x127)],_0x1743e7=_0x478cc7['chatid']||_0x478cc7['from'][_0x2cc868(0x101)],_0x1e5656=_0x478cc7['chattype']===_0x2cc868(0xf4)?'group':_0x2cc868(0x152),_0x4293c9=_0x1e5656===_0x2cc868(0xf4)?'['+_0x5c561c[_0x2cc868(0x9f)]+']\x20group:'+_0x1743e7:'['+_0x5c561c[_0x2cc868(0x9f)]+']\x20user:'+_0x478cc7[_0x2cc868(0x158)]['userid'],_0x4157a4=_0xdb52a0[_0x2cc868(0xd9)](_0x4343e0=>_0x4343e0[_0x2cc868(0x12d)]?.['startsWith'](_0x2cc868(0x154))),_0x4e7d27=_0x2a30bb||(_0xdb52a0[_0x2cc868(0xe2)]>0x0?_0x4157a4?MEDIA_IMAGE_PLACEHOLDER:MEDIA_DOCUMENT_PLACEHOLDER:''),_0x41a362=_0xdb52a0[_0x2cc868(0xe2)]>0x0?_0xdb52a0['map'](_0x13a862=>_0x13a862[_0x2cc868(0x131)]):undefined,_0x1b62a9=_0xdb52a0[_0x2cc868(0xe2)]>0x0?_0xdb52a0[_0x2cc868(0x135)](_0x3be15a=>_0x3be15a[_0x2cc868(0x12d)])[_0x2cc868(0x13d)](Boolean):undefined,_0x12c39b=CHANNEL_ID+':'+_0x5c561c[_0x2cc868(0x92)];return _0x388ba8['channel'][_0x2cc868(0xb2)][_0x2cc868(0x10f)]({'SessionKey':'agent:'+_0x2cc868(0x16c)+_0x5c561c['tinyId']+':'+_0x5c561c[_0x2cc868(0xa7)],'Body':_0x4e7d27,'RawBody':_0x4e7d27,'CommandBody':_0x4e7d27,'MessageSid':_0x478cc7[_0x2cc868(0xd3)],'From':_0x1e5656===_0x2cc868(0xf4)?_0x12c39b+_0x2cc868(0x13a)+_0x1743e7:_0x12c39b+':'+_0x478cc7[_0x2cc868(0x158)][_0x2cc868(0x101)],'To':_0x12c39b+':'+_0x1743e7,'SenderId':_0x478cc7[_0x2cc868(0x158)][_0x2cc868(0x101)],'AccountId':_0x5c561c[_0x2cc868(0xa7)],'ChatType':_0x1e5656,'ConversationLabel':_0x4293c9,'Timestamp':Date[_0x2cc868(0xed)](),'Provider':CHANNEL_ID,'Surface':CHANNEL_ID,'OriginatingChannel':CHANNEL_ID,'OriginatingTo':_0x12c39b+':'+_0x1743e7,'CommandAuthorized':!![],'ResponseUrl':_0x478cc7['response_url'],'ReqId':_0x599bba[_0x2cc868(0x181)]['req_id'],'WeComFrame':_0x599bba,'MediaPath':_0xdb52a0[0x0]?.['path'],'MediaType':_0xdb52a0[0x0]?.[_0x2cc868(0x12d)],'MediaPaths':_0x41a362,'MediaTypes':_0x1b62a9,'MediaUrls':_0x41a362,'ReplyToBody':_0x239197});}function _0x1273(_0x52dd91,_0x4c05e0){_0x52dd91=_0x52dd91-0x6b;const _0x53e5ff=_0x53e5();let _0x1273e3=_0x53e5ff[_0x52dd91];if(_0x1273['mKlrps']===undefined){var _0x4637ef=function(_0x40601d){const _0x178b7c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x154222='',_0x56576f='';for(let _0x4fd47b=0x0,_0x1a7f1a,_0xd46d23,_0x7b1c2f=0x0;_0xd46d23=_0x40601d['charAt'](_0x7b1c2f++);~_0xd46d23&&(_0x1a7f1a=_0x4fd47b%0x4?_0x1a7f1a*0x40+_0xd46d23:_0xd46d23,_0x4fd47b++%0x4)?_0x154222+=String['fromCharCode'](0xff&_0x1a7f1a>>(-0x2*_0x4fd47b&0x6)):0x0){_0xd46d23=_0x178b7c['indexOf'](_0xd46d23);}for(let _0x2642b9=0x0,_0x53866e=_0x154222['length'];_0x2642b9<_0x53866e;_0x2642b9++){_0x56576f+='%'+('00'+_0x154222['charCodeAt'](_0x2642b9)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x56576f);};_0x1273['HsZWil']=_0x4637ef,_0x1273['zCcZjn']={},_0x1273['mKlrps']=!![];}const _0x3be881=_0x53e5ff[0x0],_0x549d01=_0x52dd91+_0x3be881,_0x3d3788=_0x1273['zCcZjn'][_0x549d01];return!_0x3d3788?(_0x1273e3=_0x1273['HsZWil'](_0x1273e3),_0x1273['zCcZjn'][_0x549d01]=_0x1273e3):_0x1273e3=_0x3d3788,_0x1273e3;}async function sendThinkingReply(_0x1c928d){const _0x41a177=_0x5d3b3c,{wsClient:_0x1c9dca,frame:_0x5a3dc7,streamId:_0x22ce66,runtime:_0x55bfd6}=_0x1c928d;_0x55bfd6[_0x41a177(0x13f)]?.(_0x41a177(0xb5));try{await sendWeComReply({'wsClient':_0x1c9dca,'frame':_0x5a3dc7,'text':THINKING_MESSAGE,'runtime':_0x55bfd6,'finish':![],'streamId':_0x22ce66});}catch(_0x3b01ee){_0x55bfd6['error']?.(_0x41a177(0x167)+String(_0x3b01ee));}}async function routeAndDispatchMessage(_0x39135a){const _0x272b9a=_0x5d3b3c,{ctxPayload:_0x2c2e7f,config:_0x316d06,wsClient:_0x46a32b,frame:_0x587075,state:_0x31c039,runtime:_0x42efc9,onCleanup:_0x5c919c}=_0x39135a,_0x409136=getWeComRuntime();let _0x264c94=![];const _0x186541=()=>{!_0x264c94&&(_0x264c94=!![],_0x5c919c());};_0x42efc9[_0x272b9a(0x163)]?.(_0x272b9a(0x170)+JSON[_0x272b9a(0xe4)](_0x316d06,null,0x2));try{await _0x409136[_0x272b9a(0x123)][_0x272b9a(0xb2)][_0x272b9a(0x183)]({'ctx':_0x2c2e7f,'cfg':_0x316d06,'dispatcherOptions':{'deliver':async(_0x5c138e,_0x321428)=>{const _0x21e423=_0x272b9a;_0x31c039[_0x21e423(0xc4)]+=_0x5c138e[_0x21e423(0x157)],_0x321428[_0x21e423(0x121)]!==_0x21e423(0xda)&&await sendWeComReply({'wsClient':_0x46a32b,'frame':_0x587075,'text':_0x31c039[_0x21e423(0xc4)],'runtime':_0x42efc9,'finish':![],'streamId':_0x31c039['streamId']});},'onError':(_0x2f2789,_0x2f2943)=>{const _0x5832ac=_0x272b9a;_0x42efc9[_0x5832ac(0x163)]?.(_0x5832ac(0x17f)+_0x2f2943[_0x5832ac(0x121)]+_0x5832ac(0xf3)+String(_0x2f2789));}}}),_0x31c039['accumulatedText']&&await sendWeComReply({'wsClient':_0x46a32b,'frame':_0x587075,'text':_0x31c039[_0x272b9a(0xc4)],'runtime':_0x42efc9,'finish':!![],'streamId':_0x31c039[_0x272b9a(0x14a)]}),_0x186541();}catch(_0x4251d1){_0x42efc9[_0x272b9a(0x163)]?.(_0x272b9a(0x169)+String(_0x4251d1)),_0x186541();}}async function processWeComMessage(_0x57a4e7){const _0x4957e9=_0x5d3b3c,{frame:_0x4629ac,account:_0x2671d4,config:_0x319362,runtime:_0x5a7795,wsClient:_0x5c659d}=_0x57a4e7,_0x18a983=_0x4629ac['body'],_0x255c4f=_0x18a983['chatid']||_0x18a983[_0x4957e9(0x158)][_0x4957e9(0x101)],_0x18cd7e=_0x18a983[_0x4957e9(0x15e)]===_0x4957e9(0xf4)?'group':_0x4957e9(0x152),_0xb70830=_0x18a983['msgid'],_0x796d09=_0x4629ac['headers'][_0x4957e9(0xf9)],{textParts:_0x3aedc0,imageUrls:_0x5a33cd,imageAesKeys:_0x1bf6c8,fileUrls:_0x5d682e,fileAesKeys:_0x1c07e1,quoteContent:_0x531cde}=parseMessageContent(_0x18a983);let _0x209e16=_0x3aedc0['join']('\x0a')[_0x4957e9(0x11f)]();_0x18a983[_0x4957e9(0x15e)]===_0x4957e9(0xf4)&&(_0x209e16=_0x209e16['replace'](/@\S+/g,'')['trim']());!_0x209e16&&_0x531cde&&(_0x209e16=_0x531cde,_0x5a7795['log']?.(_0x4957e9(0x166)));if(!_0x209e16&&_0x5a33cd[_0x4957e9(0xe2)]===0x0&&_0x5d682e[_0x4957e9(0xe2)]===0x0){_0x5a7795[_0x4957e9(0x13f)]?.(_0x4957e9(0x10d));return;}_0x5a7795[_0x4957e9(0x13f)]?.(_0x4957e9(0xd8)+_0x18cd7e+_0x4957e9(0x98)+_0x255c4f+_0x4957e9(0x128)+_0x18a983['from'][_0x4957e9(0x101)]+_0x4957e9(0x17d)+_0x796d09+(_0x5a33cd[_0x4957e9(0xe2)]>0x0?'\x20(with\x20'+_0x5a33cd[_0x4957e9(0xe2)]+_0x4957e9(0x18f):'')+(_0x5d682e[_0x4957e9(0xe2)]>0x0?_0x4957e9(0x138)+_0x5d682e[_0x4957e9(0xe2)]+'\x20file(s))':'')+(_0x531cde?_0x4957e9(0xa1):''));if(_0x18cd7e==='group'){const _0x336931=checkGroupPolicy({'chatId':_0x255c4f,'senderId':_0x18a983[_0x4957e9(0x158)][_0x4957e9(0x101)],'account':_0x2671d4,'config':_0x319362,'runtime':_0x5a7795});if(!_0x336931['allowed'])return;}const _0x39ffd2=await checkDmPolicy({'senderId':_0x18a983[_0x4957e9(0x158)][_0x4957e9(0x101)],'isGroup':_0x18cd7e===_0x4957e9(0xf4),'account':_0x2671d4,'wsClient':_0x5c659d,'frame':_0x4629ac,'runtime':_0x5a7795});if(!_0x39ffd2[_0x4957e9(0xe1)])return;const [_0x2c3424,_0x4095ad]=await Promise[_0x4957e9(0xfc)]([downloadAndSaveImages({'imageUrls':_0x5a33cd,'imageAesKeys':_0x1bf6c8,'account':_0x2671d4,'config':_0x319362,'runtime':_0x5a7795,'wsClient':_0x5c659d}),downloadAndSaveFiles({'fileUrls':_0x5d682e,'fileAesKeys':_0x1c07e1,'account':_0x2671d4,'config':_0x319362,'runtime':_0x5a7795,'wsClient':_0x5c659d})]),_0x37a034=[..._0x2c3424,..._0x4095ad];setReqIdForChat(_0x255c4f,_0x796d09,_0x2671d4[_0x4957e9(0xa7)]);const _0x2a1866=aibotNodeSdk[_0x4957e9(0x149)](_0x4957e9(0x89)),_0x534f80={'accumulatedText':'','streamId':_0x2a1866};setMessageState(_0xb70830,_0x534f80);const _0xabc575=()=>{deleteMessageState(_0xb70830);},_0x41f419=_0x2671d4[_0x4957e9(0x8d)]??!![];_0x41f419&&await sendThinkingReply({'wsClient':_0x5c659d,'frame':_0x4629ac,'streamId':_0x2a1866,'runtime':_0x5a7795});const _0x56d958=buildMessageContext(_0x4629ac,_0x2671d4,_0x319362,_0x209e16,_0x37a034,_0x531cde);try{await withTimeout(routeAndDispatchMessage({'ctxPayload':_0x56d958,'config':_0x319362,'wsClient':_0x5c659d,'frame':_0x4629ac,'state':_0x534f80,'runtime':_0x5a7795,'onCleanup':_0xabc575}),MESSAGE_PROCESS_TIMEOUT_MS,_0x4957e9(0x151)+_0xb70830+')');}catch(_0x2b91d3){_0x5a7795[_0x4957e9(0x163)]?.(_0x4957e9(0x100)+String(_0x2b91d3)),_0xabc575();}}function createSdkLogger(_0x57862b,_0x460107){return{'debug':(_0x4f577a,..._0x37a658)=>{const _0x5d7eb2=_0x1273;_0x57862b[_0x5d7eb2(0x13f)]?.('['+_0x460107+']\x20'+_0x4f577a,..._0x37a658);},'info':(_0x27dacd,..._0x2ff6a6)=>{const _0x34aef9=_0x1273;_0x57862b[_0x34aef9(0x13f)]?.('['+_0x460107+']\x20'+_0x27dacd,..._0x2ff6a6);},'warn':(_0x367bc0,..._0x454340)=>{const _0xa8c74f=_0x1273;_0x57862b[_0xa8c74f(0x13f)]?.('['+_0x460107+']\x20WARN:\x20'+_0x367bc0,..._0x454340);},'error':(_0x2d0a16,..._0x6139e0)=>{const _0x29d7e7=_0x1273;_0x57862b[_0x29d7e7(0x163)]?.('['+_0x460107+']\x20'+_0x2d0a16,..._0x6139e0);}};}let nextConnectAllowedAt=0x0;const CONNECT_THROTTLE_MS=0x7d0;async function monitorWeComProvider(_0x6e5e43){const _0x17a2bf=_0x5d3b3c,{account:_0x3be28e,config:_0x2fd24b,runtime:_0x84aa69,abortSignal:_0x5caf7e}=_0x6e5e43;return _0x84aa69[_0x17a2bf(0x13f)]?.('['+_0x3be28e[_0x17a2bf(0xa7)]+']\x20Initializing\x20WSClient\x20with\x20SDK...'),startMessageStateCleanup(),new Promise((_0x28a91c,_0x1ff1f0)=>{const _0x5dced1=_0x17a2bf,_0x2f16ce=createSdkLogger(_0x84aa69,_0x3be28e['accountId']),_0xb7a20f=new aibotNodeSdk['WSClient']({'botId':_0x3be28e[_0x5dced1(0xfd)],'secret':_0x3be28e['secret'],'wsUrl':_0x3be28e[_0x5dced1(0x186)],'logger':_0x2f16ce,'heartbeatInterval':WS_HEARTBEAT_INTERVAL_MS,'maxReconnectAttempts':WS_MAX_RECONNECT_ATTEMPTS}),_0x1f920b=async()=>{const _0x3a4d40=_0x5dced1;stopMessageStateCleanup(),await cleanupAccount(_0x3be28e[_0x3a4d40(0xa7)]);};_0x5caf7e&&_0x5caf7e[_0x5dced1(0x130)](_0x5dced1(0x116),async()=>{const _0x479e05=_0x5dced1;_0x84aa69['log']?.('['+_0x3be28e[_0x479e05(0xa7)]+_0x479e05(0x148)),await _0x1f920b(),_0x28a91c();}),_0xb7a20f['on'](_0x5dced1(0x15a),()=>{const _0x4444b4=_0x5dced1;_0x84aa69[_0x4444b4(0x13f)]?.('['+_0x3be28e[_0x4444b4(0xa7)]+_0x4444b4(0x144));}),_0xb7a20f['on'](_0x5dced1(0x79),()=>{const _0x23321a=_0x5dced1;_0x84aa69[_0x23321a(0x13f)]?.('['+_0x3be28e[_0x23321a(0xa7)]+_0x23321a(0x133)),setWeComWebSocket(_0x3be28e[_0x23321a(0xa7)],_0xb7a20f),_0x3be28e[_0x23321a(0xfd)]&&_0x3be28e[_0x23321a(0xfd)]!==_0x3be28e[_0x23321a(0xa7)]&&setWeComWebSocket(_0x3be28e[_0x23321a(0xfd)],_0xb7a20f),_0x3be28e['tinyId']&&_0x3be28e[_0x23321a(0x92)]!==_0x3be28e[_0x23321a(0xa7)]&&_0x3be28e[_0x23321a(0x92)]!==_0x3be28e[_0x23321a(0xfd)]&&setWeComWebSocket(_0x3be28e['tinyId'],_0xb7a20f);}),_0xb7a20f['on'](_0x5dced1(0x8c),_0x4e416d=>{const _0x5eaf27=_0x5dced1;_0x84aa69[_0x5eaf27(0x13f)]?.('['+_0x3be28e['accountId']+']\x20WebSocket\x20disconnected:\x20'+_0x4e416d);}),_0xb7a20f['on'](_0x5dced1(0xf8),_0x959e50=>{const _0x104d7b=_0x5dced1;_0x84aa69['log']?.('['+_0x3be28e[_0x104d7b(0xa7)]+_0x104d7b(0x9e)+_0x959e50+_0x104d7b(0x165));}),_0xb7a20f['on']('error',_0x47a816=>{const _0xe08206=_0x5dced1;_0x84aa69[_0xe08206(0x163)]?.('['+_0x3be28e['accountId']+_0xe08206(0x75)+_0x47a816[_0xe08206(0x108)]),_0x47a816['message'][_0xe08206(0x125)](_0xe08206(0xd1))&&_0x1f920b()[_0xe08206(0x13c)](()=>_0x1ff1f0(_0x47a816));}),_0xb7a20f['on'](_0x5dced1(0x108),async _0x43f272=>{const _0x4c2aa1=_0x5dced1;try{await processWeComMessage({'frame':_0x43f272,'account':_0x3be28e,'config':_0x2fd24b,'runtime':_0x84aa69,'wsClient':_0xb7a20f});}catch(_0x1b4127){_0x84aa69[_0x4c2aa1(0x163)]?.('['+_0x3be28e[_0x4c2aa1(0xa7)]+_0x4c2aa1(0x161)+String(_0x1b4127));}}),warmupReqIdStore(_0x3be28e[_0x5dced1(0xa7)],(..._0x3e8e7d)=>_0x84aa69['log']?.(..._0x3e8e7d))['then'](_0xfa976b=>{const _0x470b93=_0x5dced1;_0x84aa69[_0x470b93(0x13f)]?.('['+_0x3be28e[_0x470b93(0xa7)]+_0x470b93(0x118)+_0xfa976b+_0x470b93(0xa4));})[_0x5dced1(0x174)](_0x1f13a7=>{const _0x21eab9=_0x5dced1;_0x84aa69[_0x21eab9(0x163)]?.('['+_0x3be28e['accountId']+_0x21eab9(0x86)+String(_0x1f13a7));})['finally'](async()=>{const _0x32e815=_0x5dced1,_0x3edd3a=Date['now']();let _0x50f6a6=0x0;nextConnectAllowedAt===0x0||_0x3edd3a>=nextConnectAllowedAt?nextConnectAllowedAt=_0x3edd3a+CONNECT_THROTTLE_MS:(_0x50f6a6=nextConnectAllowedAt-_0x3edd3a,nextConnectAllowedAt+=CONNECT_THROTTLE_MS),_0x50f6a6>0x0&&(_0x84aa69[_0x32e815(0x13f)]?.('['+_0x3be28e[_0x32e815(0xa7)]+_0x32e815(0x7f)+_0x50f6a6+'ms...'),await new Promise(_0x5c19ba=>setTimeout(_0x5c19ba,_0x50f6a6))),_0xb7a20f['connect']();});});}const DefaultWsUrl=_0x5d3b3c(0xb8);function listWeComAccountIds(_0x1cb102){const _0x56be49=_0x5d3b3c,_0xa9c61d=_0x1cb102['channels']?.[CHANNEL_ID]??{},_0x44d95d=new Set();if(Array[_0x56be49(0x8b)](_0xa9c61d['bots']))for(const _0x56cb9c of _0xa9c61d['bots']){const _0x153ad3=(_0x56cb9c[_0x56be49(0x92)]||_0x56cb9c[_0x56be49(0xfd)])?.[_0x56be49(0x11f)]();_0x153ad3&&_0x44d95d[_0x56be49(0x9a)](_0x153ad3);}return Array[_0x56be49(0x158)](_0x44d95d);}function resolveWeComAccount(_0x312ab0,_0x19f741=pluginSdk[_0x5d3b3c(0xd0)]){const _0x89847c=_0x5d3b3c,_0x15553a=_0x312ab0['channels']?.[CHANNEL_ID]??{};let _0x305526;_0x19f741===pluginSdk[_0x89847c(0xd0)]?Array[_0x89847c(0x8b)](_0x15553a[_0x89847c(0x81)])&&_0x15553a[_0x89847c(0x81)][_0x89847c(0xe2)]>0x0&&(_0x305526=_0x15553a[_0x89847c(0x81)][0x0]):_0x305526=_0x15553a['bots']?.[_0x89847c(0xfe)](_0x101a9e=>_0x101a9e[_0x89847c(0x92)]===_0x19f741||_0x101a9e[_0x89847c(0xfd)]===_0x19f741);const _0xad6998=_0x305526??{};return{'accountId':_0x19f741===pluginSdk[_0x89847c(0xd0)]?_0xad6998[_0x89847c(0x92)]||_0xad6998[_0x89847c(0xfd)]||pluginSdk[_0x89847c(0xd0)]:_0x19f741,'tinyId':_0xad6998[_0x89847c(0x92)]||_0xad6998[_0x89847c(0xfd)]||_0x19f741,'name':_0xad6998['name']??_0x89847c(0x18c),'enabled':_0xad6998['enabled']??![],'websocketUrl':_0xad6998[_0x89847c(0x186)]||DefaultWsUrl,'botId':_0xad6998[_0x89847c(0xfd)]??'','secret':_0xad6998[_0x89847c(0x113)]??'','sendThinkingMessage':_0xad6998[_0x89847c(0x8d)]??!![],'config':_0xad6998};}function setWeComAccount(_0x56c296,_0x57c3e7,_0x445308=pluginSdk[_0x5d3b3c(0xd0)]){const _0x1c107c=_0x5d3b3c,_0x5576ca=_0x56c296[_0x1c107c(0xba)]?.[CHANNEL_ID]??{},_0x3b0727=Array['isArray'](_0x5576ca[_0x1c107c(0x81)])?[..._0x5576ca[_0x1c107c(0x81)]]:[];let _0x1143e2=-0x1;_0x445308===pluginSdk[_0x1c107c(0xd0)]?_0x3b0727[_0x1c107c(0xe2)]>0x0&&(_0x1143e2=0x0):_0x1143e2=_0x3b0727['findIndex'](_0x2ee887=>_0x2ee887[_0x1c107c(0x92)]===_0x445308||_0x2ee887[_0x1c107c(0xfd)]===_0x445308);const _0x10153b={..._0x1143e2>=0x0?_0x3b0727[_0x1143e2]:{},..._0x57c3e7};return!_0x10153b[_0x1c107c(0xfd)]&&_0x445308!==pluginSdk[_0x1c107c(0xd0)]&&(_0x10153b[_0x1c107c(0xfd)]=_0x445308),_0x1143e2>=0x0?_0x3b0727[_0x1143e2]=_0x10153b:_0x3b0727[_0x1c107c(0x96)](_0x10153b),{..._0x56c296,'channels':{..._0x56c296[_0x1c107c(0xba)],[CHANNEL_ID]:{'bots':_0x3b0727}}};}const channel=CHANNEL_ID;async function promptTinyId(_0x10f480,_0x562eb7){const _0x1e6187=_0x5d3b3c,_0xa82efc=await _0x10f480[_0x1e6187(0x157)]({'message':_0x1e6187(0x7b),'initialValue':_0x562eb7?.['tinyId']===_0x562eb7?.[_0x1e6187(0xfd)]?'':_0x562eb7?.[_0x1e6187(0x92)]??'','placeholder':_0x1e6187(0x17e)});return String(_0xa82efc??'')[_0x1e6187(0x11f)]();}async function promptBotId(_0x34ad7d,_0x331b3f){const _0x1d0690=_0x5d3b3c;return String(await _0x34ad7d[_0x1d0690(0x157)]({'message':'企业微信机器人\x20Bot\x20ID','initialValue':_0x331b3f?.[_0x1d0690(0xfd)]??'','validate':_0x37fb18=>_0x37fb18?.[_0x1d0690(0x11f)]()?undefined:_0x1d0690(0xbc)}))[_0x1d0690(0x11f)]();}async function promptSecret(_0x3cad7b,_0x3951ad){const _0x36d45d=_0x5d3b3c;return String(await _0x3cad7b[_0x36d45d(0x157)]({'message':_0x36d45d(0x103),'initialValue':_0x3951ad?.['secret']??'','validate':_0x103310=>_0x103310?.[_0x36d45d(0x11f)]()?undefined:_0x36d45d(0xbc)}))[_0x36d45d(0x11f)]();}function setWeComDmPolicy(_0x579aed,_0x38ffd4){const _0x10c5b1=_0x5d3b3c,_0x354fc2=listWeComAccountIds(_0x579aed);let _0x1e5c08=_0x579aed;for(const _0x153ea7 of _0x354fc2){const _0x52f152=resolveWeComAccount(_0x1e5c08,_0x153ea7),_0x1593c2=_0x52f152[_0x10c5b1(0x122)][_0x10c5b1(0x93)]??[],_0x5e8726=_0x38ffd4==='open'?pluginSdk[_0x10c5b1(0x137)](_0x1593c2[_0x10c5b1(0x135)](_0x2b7200=>String(_0x2b7200))):_0x1593c2[_0x10c5b1(0x135)](_0x59d8f3=>String(_0x59d8f3));_0x1e5c08=setWeComAccount(_0x1e5c08,{'dmPolicy':_0x38ffd4,'allowFrom':_0x5e8726},_0x153ea7);}return _0x1e5c08;}const dmPolicy={'label':'企业微信','channel':channel,'policyKey':_0x5d3b3c(0xf1)+CHANNEL_ID+_0x5d3b3c(0x15b),'allowFromKey':_0x5d3b3c(0xf1)+CHANNEL_ID+_0x5d3b3c(0x177),'getCurrent':_0x24f67a=>{const _0x14c7b9=_0x5d3b3c,_0x4c0deb=resolveWeComAccount(_0x24f67a);return _0x4c0deb['config'][_0x14c7b9(0x112)]??'pairing';},'setPolicy':(_0x42b833,_0x5e6021)=>{return setWeComDmPolicy(_0x42b833,_0x5e6021);},'promptAllowFrom':async({cfg:_0x1e99ca,prompter:_0x17b54f})=>{const _0x4d80ac=_0x5d3b3c,_0xb29726=resolveWeComAccount(_0x1e99ca),_0x27d7b5=_0xb29726['config'][_0x4d80ac(0x93)]??[],_0x46386d=await _0x17b54f[_0x4d80ac(0x157)]({'message':'企业微信允许来源(用户ID或群组ID,每行一个,推荐用于安全控制)','placeholder':_0x4d80ac(0xbf),'initialValue':_0x27d7b5[0x0]?String(_0x27d7b5[0x0]):undefined}),_0x211eec=String(_0x46386d??'')['split'](/[\n,;]+/g)[_0x4d80ac(0x135)](_0x5a4b1d=>_0x5a4b1d[_0x4d80ac(0x11f)]())[_0x4d80ac(0x13d)](Boolean),_0x1c4db7=listWeComAccountIds(_0x1e99ca);let _0x2a6ef4=_0x1e99ca;for(const _0x2d30a6 of _0x1c4db7){_0x2a6ef4=setWeComAccount(_0x2a6ef4,{'allowFrom':_0x211eec},_0x2d30a6);}return _0x2a6ef4;}},wecomOnboardingAdapter={'channel':channel,'getStatus':async({cfg:_0x246324})=>{const _0x5b3595=_0x5d3b3c,_0x5542e7=listWeComAccountIds(_0x246324),_0x47a2db=_0x5542e7[_0x5b3595(0xe2)]>0x0;return{'channel':channel,'configured':_0x47a2db,'statusLines':[_0x5b3595(0x6d)+(_0x47a2db?'已配置\x20'+_0x5542e7[_0x5b3595(0xe2)]+_0x5b3595(0xee):'未配置')],'selectionHint':_0x47a2db?'已配置':_0x5b3595(0xd2)};},'configure':async({cfg:_0x14cfe5,prompter:_0x355b56})=>{const _0x39e01a=_0x5d3b3c,_0x23b330=listWeComAccountIds(_0x14cfe5),_0x252d90=[..._0x23b330[_0x39e01a(0x135)](_0x4331f8=>{const _0x2108c1=_0x39e01a,_0x1cbb28=resolveWeComAccount(_0x14cfe5,_0x4331f8);return{'value':_0x4331f8,'label':_0x2108c1(0x12f)+_0x1cbb28[_0x2108c1(0x92)]+'\x20('+_0x1cbb28['botId'][_0x2108c1(0x185)](0x0,0x8)+_0x2108c1(0xc2)};}),{'value':_0x39e01a(0x83),'label':_0x39e01a(0x140)}],_0xbf9752=await _0x355b56[_0x39e01a(0xc7)]({'message':_0x39e01a(0xad),'options':_0x252d90});let _0x1792ac=null,_0x4c6966='';_0xbf9752!==_0x39e01a(0x83)&&(_0x1792ac=resolveWeComAccount(_0x14cfe5,_0xbf9752),_0x4c6966=_0xbf9752);const _0xc48a1d=await promptTinyId(_0x355b56,_0x1792ac),_0x29750e=await promptBotId(_0x355b56,_0x1792ac),_0x870820=await promptSecret(_0x355b56,_0x1792ac);return _0xbf9752===_0x39e01a(0x83)&&(_0x4c6966=_0xc48a1d||_0x29750e),{'cfg':setWeComAccount(_0x14cfe5,{'tinyId':_0xc48a1d||undefined,'botId':_0x29750e,'secret':_0x870820,'enabled':!![],'dmPolicy':_0x1792ac?.[_0x39e01a(0x122)]?.['dmPolicy']??'pairing','allowFrom':_0x1792ac?.[_0x39e01a(0x122)]?.[_0x39e01a(0x93)]??[]},_0x4c6966)};},'dmPolicy':dmPolicy,'disable':_0x37060c=>{return setWeComAccount(_0x37060c,{'enabled':![]});}};async function sendWeComMessage({to:_0x57b39c,content:_0x45114c,accountId:_0x27c404}){const _0x1f6834=_0x5d3b3c,_0x59b700=_0x57b39c[_0x1f6834(0x77)](':');let _0x8f0a1e=_0x27c404,_0x48a63f=_0x57b39c;if(_0x59b700[0x0]?.['toLowerCase']()===CHANNEL_ID){if(_0x59b700['length']>=0x3)_0x8f0a1e=_0x59b700[0x1],_0x48a63f=_0x59b700[_0x1f6834(0x168)](0x2)[_0x1f6834(0x146)](':');else _0x59b700[_0x1f6834(0xe2)]===0x2&&(_0x48a63f=_0x59b700[0x1]);}_0x48a63f[_0x1f6834(0x11d)](_0x1f6834(0xde))&&(_0x48a63f=_0x48a63f[_0x1f6834(0x185)](0x6));const _0x50ecb1=_0x8f0a1e??pluginSdk['DEFAULT_ACCOUNT_ID'];console[_0x1f6834(0x13f)](_0x1f6834(0x188)+JSON[_0x1f6834(0xe4)]({'to':_0x57b39c,'content':_0x45114c,'accountId':_0x27c404,'finalAccountId':_0x8f0a1e,'chatId':_0x48a63f,'resolvedAccountId':_0x50ecb1}));const _0x301baf=getWeComWebSocket(_0x50ecb1);if(!_0x301baf)throw new Error(_0x1f6834(0xaa)+_0x50ecb1);const _0x273c8d=await _0x301baf[_0x1f6834(0xeb)](_0x48a63f,{'msgtype':_0x1f6834(0x11c),'markdown':{'content':_0x45114c}}),_0x50c1b8=_0x273c8d?.[_0x1f6834(0x181)]?.[_0x1f6834(0xf9)]??_0x1f6834(0x14d)+Date[_0x1f6834(0xed)]();return console[_0x1f6834(0x13f)](_0x1f6834(0xc6)+_0x48a63f+_0x1f6834(0x173)+_0x50ecb1+_0x1f6834(0xfa)+_0x50c1b8),{'channel':CHANNEL_ID,'messageId':_0x50c1b8,'chatId':_0x48a63f};}const meta={'id':CHANNEL_ID,'label':_0x5d3b3c(0x18c),'selectionLabel':_0x5d3b3c(0xa0),'detailLabel':_0x5d3b3c(0xc3),'docsPath':'/channels/'+CHANNEL_ID,'docsLabel':CHANNEL_ID,'blurb':_0x5d3b3c(0xcb),'systemImage':'message.fill'},wecomPlugin={'id':CHANNEL_ID,'meta':{...meta,'quickstartAllowFrom':!![]},'pairing':{'idLabel':_0x5d3b3c(0xf0),'normalizeAllowEntry':_0x4c875f=>_0x4c875f[_0x5d3b3c(0x175)](new RegExp('^('+CHANNEL_ID+_0x5d3b3c(0x104),'i'),'')[_0x5d3b3c(0x11f)](),'notifyApproval':async({cfg:_0x53d92a,id:_0x86325c})=>{const _0x234373=_0x5d3b3c;console[_0x234373(0x13f)](_0x234373(0x17c)+_0x86325c);}},'onboarding':wecomOnboardingAdapter,'capabilities':{'chatTypes':[_0x5d3b3c(0x152),_0x5d3b3c(0xf4)],'reactions':![],'threads':![],'media':!![],'nativeCommands':![],'blockStreaming':!![]},'reload':{'configPrefixes':[_0x5d3b3c(0xf1)+CHANNEL_ID]},'config':{'listAccountIds':_0x70b3f4=>listWeComAccountIds(_0x70b3f4),'resolveAccount':(_0x5d9a52,_0x323d1b)=>resolveWeComAccount(_0x5d9a52,_0x323d1b),'defaultAccountId':()=>pluginSdk[_0x5d3b3c(0xd0)],'setAccountEnabled':({cfg:_0x25ce13,accountId:_0x534867,enabled:_0x384b2b})=>{return setWeComAccount(_0x25ce13,{'enabled':_0x384b2b},_0x534867);},'deleteAccount':({cfg:_0x20f2d6,accountId:_0x1dd37e})=>{const _0x413d75=_0x5d3b3c,_0x272b2c=_0x20f2d6[_0x413d75(0xba)]?.[CHANNEL_ID]??{},_0x5ca9a9=(_0x272b2c[_0x413d75(0x81)]??[])[_0x413d75(0x13d)](_0x5c77ae=>_0x5c77ae['tinyId']!==_0x1dd37e&&_0x5c77ae[_0x413d75(0xfd)]!==_0x1dd37e);return{..._0x20f2d6,'channels':{..._0x20f2d6[_0x413d75(0xba)],[CHANNEL_ID]:{..._0x272b2c,'bots':_0x5ca9a9}}};},'isConfigured':_0x2908cb=>Boolean(_0x2908cb[_0x5d3b3c(0xfd)]?.[_0x5d3b3c(0x11f)]()&&_0x2908cb[_0x5d3b3c(0x113)]?.[_0x5d3b3c(0x11f)]()),'describeAccount':_0x4a3ebc=>({'accountId':_0x4a3ebc[_0x5d3b3c(0xa7)],'name':_0x4a3ebc[_0x5d3b3c(0x9f)],'enabled':_0x4a3ebc['enabled'],'configured':Boolean(_0x4a3ebc[_0x5d3b3c(0xfd)]?.['trim']()&&_0x4a3ebc[_0x5d3b3c(0x113)]?.[_0x5d3b3c(0x11f)]()),'botId':_0x4a3ebc[_0x5d3b3c(0xfd)],'websocketUrl':_0x4a3ebc['websocketUrl']}),'resolveAllowFrom':({cfg:_0x2e8d7b,accountId:_0x73a4b3})=>{const _0x138a9b=resolveWeComAccount(_0x2e8d7b,_0x73a4b3);return(_0x138a9b['config']['allowFrom']??[])['map'](_0x4b607f=>String(_0x4b607f));},'formatAllowFrom':({allowFrom:_0x44d102})=>_0x44d102[_0x5d3b3c(0x135)](_0x3e4eba=>String(_0x3e4eba)[_0x5d3b3c(0x11f)]())[_0x5d3b3c(0x13d)](Boolean)},'security':{'resolveDmPolicy':({account:_0x201553})=>{const _0x4d9655=_0x5d3b3c,_0x447b09=_0x201553[_0x4d9655(0xa7)]===pluginSdk[_0x4d9655(0xd0)],_0xcd9d61=_0x447b09?'channels.'+CHANNEL_ID+'.':'channels.'+CHANNEL_ID+_0x4d9655(0x182)+_0x201553[_0x4d9655(0xfd)]+'].';return{'policy':_0x201553['config'][_0x4d9655(0x112)]??'pairing','allowFrom':_0x201553['config']['allowFrom']??[],'policyPath':_0xcd9d61+_0x4d9655(0x112),'allowFromPath':_0xcd9d61+_0x4d9655(0x93),'approveHint':pluginSdk[_0x4d9655(0x129)](CHANNEL_ID),'normalizeEntry':_0x591923=>_0x591923[_0x4d9655(0x175)](new RegExp('^'+CHANNEL_ID+':','i'),'')['trim']()};},'collectWarnings':({account:_0x5a8f9a,cfg:_0x5a8b3e})=>{const _0xfba2b0=_0x5d3b3c,_0x2f9407=[],_0x367fd6=_0x5a8f9a[_0xfba2b0(0xa7)]===pluginSdk[_0xfba2b0(0xd0)],_0x3e7c7b=_0x367fd6?'channels.'+CHANNEL_ID+'.':_0xfba2b0(0xf1)+CHANNEL_ID+'.bots[botId='+_0x5a8f9a[_0xfba2b0(0xfd)]+'].',_0x12656e=_0x5a8f9a[_0xfba2b0(0x122)]['dmPolicy']??_0xfba2b0(0x14c);if(_0x12656e===_0xfba2b0(0xcc)){const _0x4a383e=(_0x5a8f9a[_0xfba2b0(0x122)][_0xfba2b0(0x93)]??[])[_0xfba2b0(0xd9)](_0x2cc890=>String(_0x2cc890)[_0xfba2b0(0x11f)]()==='*');!_0x4a383e&&_0x2f9407[_0xfba2b0(0x96)]('-\x20企业微信私信:dmPolicy=\x22open\x22\x20但\x20allowFrom\x20未包含\x20\x22*\x22。任何人都可以发消息,但允许列表为空可能导致意外行为。建议设置\x20'+_0x3e7c7b+_0xfba2b0(0xe7));}const _0x4a8840=_0x5a8b3e[_0xfba2b0(0xba)]?.[_0xfba2b0(0x76)]?.[_0xfba2b0(0x153)],_0x2d21ee=_0x5a8f9a[_0xfba2b0(0x122)]['groupPolicy']??_0x4a8840??_0xfba2b0(0xcc);return _0x2d21ee===_0xfba2b0(0xcc)&&_0x2f9407[_0xfba2b0(0x96)]('-\x20企业微信群组:groupPolicy=\x22open\x22\x20允许所有群组中的成员触发。设置\x20'+_0x3e7c7b+_0xfba2b0(0x18b)+_0x3e7c7b+_0xfba2b0(0x6b)),_0x2f9407;}},'messaging':{'normalizeTarget':_0x54535e=>{const _0x15c242=_0x54535e['trim']();if(!_0x15c242)return undefined;return _0x15c242;},'targetResolver':{'looksLikeId':_0x4a9139=>{const _0x341660=_0x5d3b3c,_0x548ec5=_0x4a9139?.[_0x341660(0x11f)]();return Boolean(_0x548ec5);},'hint':_0x5d3b3c(0x9d)}},'directory':{'self':async()=>null,'listPeers':async()=>[],'listGroups':async()=>[]},'outbound':{'deliveryMode':_0x5d3b3c(0x152),'chunker':(_0x4da76b,_0x44cd24)=>getWeComRuntime()[_0x5d3b3c(0x123)][_0x5d3b3c(0x157)][_0x5d3b3c(0x179)](_0x4da76b,_0x44cd24),'textChunkLimit':TEXT_CHUNK_LIMIT,'sendText':async({to:_0x5341f9,text:_0x28eac0,accountId:_0x3578d5,..._0x28b212})=>{const _0x4f510d=_0x5d3b3c;return console[_0x4f510d(0x13f)]('[WeCom]\x20sendText:\x20'+JSON[_0x4f510d(0xe4)]({'to':_0x5341f9,'text':_0x28eac0,'accountId':_0x3578d5,..._0x28b212})),sendWeComMessage({'to':_0x5341f9,'content':_0x28eac0,'accountId':_0x3578d5??undefined});},'sendMedia':async({to:_0x53fb4c,text:_0x3a1aa2,mediaUrl:_0x1899ee,accountId:_0x81cf1a,..._0xc7aabe})=>{const _0x53f73f=_0x5d3b3c;console[_0x53f73f(0x13f)](_0x53f73f(0x105)+JSON[_0x53f73f(0xe4)]({'to':_0x53fb4c,'text':_0x3a1aa2,'mediaUrl':_0x1899ee,'accountId':_0x81cf1a,..._0xc7aabe}));const _0x4f9f41=_0x53f73f(0xe8)+(_0x3a1aa2?_0x3a1aa2+'\x0a'+_0x1899ee:_0x1899ee??'');return sendWeComMessage({'to':_0x53fb4c,'content':_0x4f9f41,'accountId':_0x81cf1a??undefined});}},'status':{'defaultRuntime':{'accountId':pluginSdk['DEFAULT_ACCOUNT_ID'],'running':![],'lastStartAt':null,'lastStopAt':null,'lastError':null},'collectStatusIssues':_0x375aff=>_0x375aff[_0x5d3b3c(0x16b)](_0x3c0db5=>{const _0xe3c39c=_0x5d3b3c,_0x2e1332=String(_0x3c0db5[_0xe3c39c(0xa7)]??pluginSdk[_0xe3c39c(0xd0)]),_0x2d22cf=_0x3c0db5[_0xe3c39c(0x119)]!==![],_0x4e17e1=_0x3c0db5[_0xe3c39c(0x15c)]===!![];if(!_0x2d22cf)return[];const _0x562560=[];return!_0x4e17e1&&_0x562560[_0xe3c39c(0x96)]({'channel':CHANNEL_ID,'accountId':_0x2e1332,'kind':_0xe3c39c(0x122),'message':_0xe3c39c(0xd6),'fix':_0xe3c39c(0xcd)}),_0x562560;}),'buildChannelSummary':({snapshot:_0x58d3f0})=>({'configured':_0x58d3f0[_0x5d3b3c(0x15c)]??![],'running':_0x58d3f0[_0x5d3b3c(0x14e)]??![],'lastStartAt':_0x58d3f0[_0x5d3b3c(0x80)]??null,'lastStopAt':_0x58d3f0[_0x5d3b3c(0x18a)]??null,'lastError':_0x58d3f0[_0x5d3b3c(0xea)]??null}),'probeAccount':async()=>{return{'ok':!![],'status':0xc8};},'buildAccountSnapshot':({account:_0x3914ef,runtime:_0x1227e9})=>{const _0x563f49=_0x5d3b3c,_0x349419=Boolean(_0x3914ef[_0x563f49(0xfd)]?.[_0x563f49(0x11f)]()&&_0x3914ef[_0x563f49(0x113)]?.[_0x563f49(0x11f)]());return{'accountId':_0x3914ef[_0x563f49(0xa7)],'name':_0x3914ef[_0x563f49(0x9f)],'enabled':_0x3914ef['enabled'],'configured':_0x349419,'running':_0x1227e9?.[_0x563f49(0x14e)]??![],'lastStartAt':_0x1227e9?.[_0x563f49(0x80)]??null,'lastStopAt':_0x1227e9?.[_0x563f49(0x18a)]??null,'lastError':_0x1227e9?.[_0x563f49(0xea)]??null};}},'gateway':{'startAccount':async _0x1f937e=>{const _0x2107da=_0x5d3b3c,_0x4ecc28=_0x1f937e['account'];return monitorWeComProvider({'account':_0x4ecc28,'config':_0x1f937e[_0x2107da(0xa2)],'runtime':_0x1f937e[_0x2107da(0x9c)],'abortSignal':_0x1f937e[_0x2107da(0xf7)]});},'logoutAccount':async({cfg:_0x1711c9})=>{const _0x2d3530=_0x5d3b3c,_0x31ae29={..._0x1711c9},_0xb8323=_0x1711c9['channels']?.[CHANNEL_ID]??{};let _0x39f80a=![],_0x598a73=![];Array[_0x2d3530(0x8b)](_0xb8323[_0x2d3530(0x81)])&&_0xb8323['bots']['length']>0x0&&(delete _0xb8323[_0x2d3530(0x81)],_0x39f80a=!![],_0x598a73=!![]);if(_0x598a73){if(Object[_0x2d3530(0xa5)](_0xb8323)[_0x2d3530(0xe2)]>0x0)_0x31ae29[_0x2d3530(0xba)]={..._0x31ae29[_0x2d3530(0xba)],[CHANNEL_ID]:_0xb8323};else{const _0x6975bf={..._0x31ae29['channels']};delete _0x6975bf[CHANNEL_ID],Object['keys'](_0x6975bf)['length']>0x0?_0x31ae29['channels']=_0x6975bf:delete _0x31ae29['channels'];}await getWeComRuntime()[_0x2d3530(0x122)][_0x2d3530(0x12e)](_0x31ae29);}const _0x2b688c=listWeComAccountIds(_0x598a73?_0x31ae29:_0x1711c9),_0x170a4b=_0x2b688c['length']===0x0;return{'cleared':_0x39f80a,'envToken':![],'loggedOut':_0x170a4b};}}},botSchema={'type':'object','properties':{'enabled':{'type':_0x5d3b3c(0x178),'title':'启用'},'name':{'type':_0x5d3b3c(0x12c),'title':'名称'},'tinyId':{'type':_0x5d3b3c(0x12c),'title':'Tiny\x20ID\x20(短\x20ID)'},'botId':{'type':_0x5d3b3c(0x12c),'title':_0x5d3b3c(0x10e)},'secret':{'type':_0x5d3b3c(0x12c),'title':'Secret'},'websocketUrl':{'type':_0x5d3b3c(0x12c),'title':_0x5d3b3c(0x90)},'dmPolicy':{'type':_0x5d3b3c(0x12c),'title':_0x5d3b3c(0x107),'enum':[_0x5d3b3c(0x14c),'open',_0x5d3b3c(0x115),'disabled']},'allowFrom':{'type':_0x5d3b3c(0xb7),'title':'允许来源','items':{'type':'string'}},'groupPolicy':{'type':_0x5d3b3c(0x12c),'title':'群组策略','enum':[_0x5d3b3c(0xcc),'allowlist',_0x5d3b3c(0x11e)]},'groupAllowFrom':{'type':_0x5d3b3c(0xb7),'title':_0x5d3b3c(0xf2),'items':{'type':'string'}},'sendThinkingMessage':{'type':_0x5d3b3c(0x178),'title':_0x5d3b3c(0xe9)}}},plugin={'id':'wecom-openclaw-plugin','name':_0x5d3b3c(0xac),'description':_0x5d3b3c(0x126),'configSchema':{'type':'object','properties':{'bots':{'type':_0x5d3b3c(0xb7),'title':_0x5d3b3c(0x110),'items':botSchema}}},'register'(_0x16afc8){const _0x49345c=_0x5d3b3c;setWeComRuntime(_0x16afc8[_0x49345c(0x9c)]),_0x16afc8[_0x49345c(0x87)]({'plugin':wecomPlugin});}};exports[_0x5d3b3c(0x6f)]=plugin;
|