koishi-plugin-share-links-analysis 0.8.0 → 0.8.2
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/lib/index.js +169 -47
- package/lib/types.d.ts +6 -0
- package/lib/utils.d.ts +3 -3
- package/lib/utils.js +39 -6
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -112,7 +112,42 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
112
112
|
userAgent: koishi_1.Schema.string().description("所有 API 请求所用的 User-Agent").default("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"),
|
|
113
113
|
debug: koishi_1.Schema.boolean().default(false).description("开启调试模式 (输出详细日志)"),
|
|
114
114
|
}).description("调试设置"),
|
|
115
|
+
koishi_1.Schema.object({
|
|
116
|
+
reportEnabled: koishi_1.Schema.boolean().default(false).description("开启性能数据上报"),
|
|
117
|
+
reportUrl: koishi_1.Schema.string().default("http://127.0.0.1:8080/").description("性能数据上报地址 (HTTP POST)"),
|
|
118
|
+
}).description("性能监控"),
|
|
115
119
|
]);
|
|
120
|
+
async function reportMetric(ctx, config, payload) {
|
|
121
|
+
if (!config.reportEnabled || !config.reportUrl)
|
|
122
|
+
return;
|
|
123
|
+
const data = {
|
|
124
|
+
app: "share_links_analysis", // 必填 app 标识
|
|
125
|
+
timestamp: Math.floor(Date.now() / 1000), // 可选时间戳
|
|
126
|
+
...payload
|
|
127
|
+
};
|
|
128
|
+
// 异步发送,不阻塞主流程
|
|
129
|
+
ctx.http.post(config.reportUrl, data).catch(e => {
|
|
130
|
+
// 仅在调试模式下打印上报错误,避免刷屏
|
|
131
|
+
if (config.debug) {
|
|
132
|
+
ctx.logger('share-links-analysis').warn(`性能数据上报失败: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function sendResult(ctx, session, config, result, logger) {
|
|
137
|
+
if (!session.channel) {
|
|
138
|
+
return await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
139
|
+
}
|
|
140
|
+
switch (config.useForward) {
|
|
141
|
+
case "plain":
|
|
142
|
+
return await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
143
|
+
case 'forward':
|
|
144
|
+
return await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, false);
|
|
145
|
+
case "mixed":
|
|
146
|
+
return await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, true);
|
|
147
|
+
default:
|
|
148
|
+
return { downloadTime: 0, sendTime: 0 };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
116
151
|
function apply(ctx, config) {
|
|
117
152
|
// 数据库模型定义
|
|
118
153
|
ctx.model.extend('sla_cookie_cache', {
|
|
@@ -146,6 +181,7 @@ function apply(ctx, config) {
|
|
|
146
181
|
if (config.debug) {
|
|
147
182
|
logger.level = 3; // Debug Level
|
|
148
183
|
}
|
|
184
|
+
const pendingChecks = new Map();
|
|
149
185
|
// 清理缓存函数
|
|
150
186
|
const cleanExpiredCache = async () => {
|
|
151
187
|
if (!config.enableCache || config.cacheExpiration <= 0)
|
|
@@ -236,7 +272,7 @@ function apply(ctx, config) {
|
|
|
236
272
|
});
|
|
237
273
|
// 清除缓存指令
|
|
238
274
|
cmd.subcommand('.clean', '清除所有缓存和文件', { authority: 3 })
|
|
239
|
-
.action(async (
|
|
275
|
+
.action(async () => {
|
|
240
276
|
await ctx.database.remove('sla_parse_cache', {});
|
|
241
277
|
const allFiles = await ctx.database.get('sla_file_cache', {});
|
|
242
278
|
for (const file of allFiles) {
|
|
@@ -245,7 +281,8 @@ function apply(ctx, config) {
|
|
|
245
281
|
await fs.promises.unlink(file.path);
|
|
246
282
|
}
|
|
247
283
|
}
|
|
248
|
-
catch {
|
|
284
|
+
catch {
|
|
285
|
+
}
|
|
249
286
|
}
|
|
250
287
|
await ctx.database.remove('sla_file_cache', {});
|
|
251
288
|
return '缓存及对应文件已清理。';
|
|
@@ -296,60 +333,145 @@ function apply(ctx, config) {
|
|
|
296
333
|
if (config.waitTip_Switch) {
|
|
297
334
|
await session.send(config.waitTip_Switch);
|
|
298
335
|
}
|
|
299
|
-
// ===
|
|
336
|
+
// === 性能统计变量 ===
|
|
337
|
+
const startTotal = Date.now();
|
|
338
|
+
let parseTime = 0;
|
|
339
|
+
let downloadTime = 0;
|
|
340
|
+
let sendTime = 0;
|
|
341
|
+
let isCache = false;
|
|
342
|
+
let status = "success";
|
|
343
|
+
let errorMsg = "";
|
|
344
|
+
let errorStack = "";
|
|
345
|
+
// === 缓存与并发控制逻辑 ===
|
|
300
346
|
let result = null;
|
|
301
347
|
const cacheKey = `${link.platform}:${link.id}`;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
348
|
+
try {
|
|
349
|
+
// 1. 查持久化缓存 (DB)
|
|
350
|
+
if (config.enableCache) {
|
|
351
|
+
const cached = await ctx.database.get('sla_parse_cache', cacheKey);
|
|
352
|
+
// 检查是否存在且未过期
|
|
353
|
+
if (cached.length > 0) {
|
|
354
|
+
const entry = cached[0];
|
|
355
|
+
const isExpired = config.cacheExpiration > 0 && (Date.now() - entry.created_at > config.cacheExpiration * 60 * 60 * 1000);
|
|
356
|
+
if (!isExpired) {
|
|
357
|
+
logger.debug(`使用缓存解析结果: ${cacheKey}`);
|
|
358
|
+
result = entry.data;
|
|
359
|
+
isCache = true;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// 过期删除
|
|
363
|
+
await ctx.database.remove('sla_parse_cache', { key: cacheKey });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// 2. 查内存任务队列
|
|
368
|
+
if (!result) {
|
|
369
|
+
if (pendingChecks.has(cacheKey)) {
|
|
370
|
+
logger.debug(`检测到正在进行的解析任务,正在等待合并结果: ${cacheKey}`);
|
|
371
|
+
// 如果有相同的任务正在进行,直接等待它的结果
|
|
372
|
+
try {
|
|
373
|
+
result = await pendingChecks.get(cacheKey) || null;
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
// 如果等待的任务失败了,这里也会捕获到
|
|
377
|
+
throw e;
|
|
378
|
+
}
|
|
311
379
|
}
|
|
312
380
|
else {
|
|
313
|
-
//
|
|
314
|
-
|
|
381
|
+
// 如果没有,创建一个新的 Promise 任务
|
|
382
|
+
const task = (async () => {
|
|
383
|
+
const t = Date.now();
|
|
384
|
+
try {
|
|
385
|
+
const res = await (0, core_1.processLink)(ctx, config, link, session);
|
|
386
|
+
// 解析成功且开启缓存,则写入 DB
|
|
387
|
+
if (res && config.enableCache) {
|
|
388
|
+
await ctx.database.upsert('sla_parse_cache', [{
|
|
389
|
+
key: cacheKey,
|
|
390
|
+
data: res,
|
|
391
|
+
created_at: Date.now()
|
|
392
|
+
}]);
|
|
393
|
+
}
|
|
394
|
+
return res;
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
logger.warn(`解析任务出错: ${e}`);
|
|
398
|
+
throw e;
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
// 执行上报
|
|
402
|
+
// 无论成功失败,都在 finally 中上报数据
|
|
403
|
+
parseTime = Date.now() - t;
|
|
404
|
+
}
|
|
405
|
+
})();
|
|
406
|
+
// 将任务存入 Map
|
|
407
|
+
pendingChecks.set(cacheKey, task);
|
|
408
|
+
try {
|
|
409
|
+
result = await task;
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
// 无论成功失败,任务结束后从 Map 中移除
|
|
413
|
+
pendingChecks.delete(cacheKey);
|
|
414
|
+
}
|
|
315
415
|
}
|
|
316
416
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
417
|
+
// === 逻辑结束 ===
|
|
418
|
+
if (result) {
|
|
419
|
+
lastProcessedUrls[channelId][link.url] = Date.now();
|
|
420
|
+
const stats = await sendResult(ctx, session, config, result, logger);
|
|
421
|
+
downloadTime = stats.downloadTime;
|
|
422
|
+
sendTime = stats.sendTime;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
status = "failed";
|
|
426
|
+
errorMsg = "parser_returned_null";
|
|
427
|
+
// 即使没有抛错,如果返回 null,也可以视为一种“软失败”,记录一下
|
|
328
428
|
}
|
|
429
|
+
linkCount++;
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
status = "error";
|
|
433
|
+
errorMsg = e.message || String(e);
|
|
434
|
+
errorStack = e.stack || String(e);
|
|
435
|
+
logger.warn(`处理异常: ${e}`);
|
|
329
436
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
437
|
+
finally {
|
|
438
|
+
// 4. 上报数据
|
|
439
|
+
const totalTime = Date.now() - startTotal;
|
|
440
|
+
// 获取内存使用情况 (MB)
|
|
441
|
+
const memoryUsage = process.memoryUsage();
|
|
442
|
+
const rssMB = (memoryUsage.rss / 1024 / 1024).toFixed(2);
|
|
443
|
+
// 截取堆栈前 1000 个字符,防止数据包过大(根据您的后端数据库限制调整)
|
|
444
|
+
const truncatedStack = errorStack.length > 1000 ? errorStack.substring(0, 1000) + "..." : errorStack;
|
|
445
|
+
reportMetric(ctx, config, {
|
|
446
|
+
type: "link_process", // 业务类型
|
|
447
|
+
// Tags
|
|
448
|
+
platform: link.platform,
|
|
449
|
+
status: status,
|
|
450
|
+
is_cache: isCache.toString(),
|
|
451
|
+
using_local: config.usingLocal.toString(),
|
|
452
|
+
user_id: session.userId || "unknown",
|
|
453
|
+
// [新增] 上下文信息,帮助定位是哪个群/频道
|
|
454
|
+
guild_id: session.guildId || "private",
|
|
455
|
+
channel_id: session.channelId || "unknown",
|
|
456
|
+
// [新增] 核心复现数据:具体的 URL
|
|
457
|
+
// 注意:如果您的后端是时序数据库(InfluxDB等),URL作为Tag可能会导致基数爆炸(High Cardinality)。
|
|
458
|
+
// 但根据您的描述是存入 tags 列用于筛选,且为内网服务,通常问题不大。
|
|
459
|
+
target_url: link.url,
|
|
460
|
+
// [新增] 错误信息
|
|
461
|
+
error_msg: errorMsg,
|
|
462
|
+
// 建议将堆栈放入 tags (如果数据库支持长文本) 或者单独的 log 字段
|
|
463
|
+
// 这里放入 tags 供查阅
|
|
464
|
+
error_stack: status === 'error' ? truncatedStack : "",
|
|
465
|
+
// === Metrics (数值/指标) ===
|
|
466
|
+
time_total_ms: totalTime,
|
|
467
|
+
time_parse_ms: parseTime,
|
|
468
|
+
time_download_ms: downloadTime,
|
|
469
|
+
time_send_ms: sendTime,
|
|
470
|
+
// 系统负载指标
|
|
471
|
+
memory_rss_mb: parseFloat(rssMB), // 当前进程内存占用
|
|
472
|
+
concurrent_tasks: pendingChecks.size, // 当前正在进行的并发解析数
|
|
473
|
+
});
|
|
334
474
|
}
|
|
335
|
-
linkCount++;
|
|
336
475
|
}
|
|
337
476
|
});
|
|
338
477
|
}
|
|
339
|
-
async function sendResult(ctx, session, config, result, logger) {
|
|
340
|
-
if (!session.channel) {
|
|
341
|
-
await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
switch (config.useForward) {
|
|
345
|
-
case "plain":
|
|
346
|
-
await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
347
|
-
return;
|
|
348
|
-
case 'forward':
|
|
349
|
-
await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, false);
|
|
350
|
-
return;
|
|
351
|
-
case "mixed":
|
|
352
|
-
await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, true);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
}
|
package/lib/types.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface PluginConfig {
|
|
|
43
43
|
localDownloadDir: string;
|
|
44
44
|
userAgent: string;
|
|
45
45
|
debug: boolean;
|
|
46
|
+
reportEnabled: boolean;
|
|
47
|
+
reportUrl: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SendResultStats {
|
|
50
|
+
downloadTime: number;
|
|
51
|
+
sendTime: number;
|
|
46
52
|
}
|
|
47
53
|
export interface BilibiliVideoInfo {
|
|
48
54
|
data: {
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParsedInfo, PluginConfig } from './types';
|
|
1
|
+
import { ParsedInfo, PluginConfig, SendResultStats } from './types';
|
|
2
2
|
import { Context, Logger, Session } from "koishi";
|
|
3
3
|
import { Agent as HttpAgent } from 'http';
|
|
4
4
|
import { Agent as HttpsAgent } from 'https';
|
|
@@ -19,5 +19,5 @@ export declare function getEffectiveSettings(ctx: Context, guildId: string | und
|
|
|
19
19
|
nsfw: boolean;
|
|
20
20
|
}>;
|
|
21
21
|
export declare function isUserAdmin(session: Session, userId: string): Promise<boolean>;
|
|
22
|
-
export declare function sendResult_plain(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<
|
|
23
|
-
export declare function sendResult_forward(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<
|
|
22
|
+
export declare function sendResult_plain(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<SendResultStats>;
|
|
23
|
+
export declare function sendResult_forward(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<SendResultStats>;
|
package/lib/utils.js
CHANGED
|
@@ -374,6 +374,8 @@ async function isUserAdmin(session, userId) {
|
|
|
374
374
|
}
|
|
375
375
|
async function sendResult_plain(ctx, session, config, result, logger) {
|
|
376
376
|
logger.debug('进入普通发送');
|
|
377
|
+
let downloadTime = 0; // 下载耗时计时
|
|
378
|
+
let sendTime = 0; // 发送耗时计时
|
|
377
379
|
const localDownloadDir = config.localDownloadDir;
|
|
378
380
|
const onebotReadDir = config.onebotReadDir;
|
|
379
381
|
let mediaCoverUrl = result.coverUrl;
|
|
@@ -386,6 +388,7 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
386
388
|
// --- 下载封面 ---
|
|
387
389
|
if (result.coverUrl) {
|
|
388
390
|
if (config.usingLocal) {
|
|
391
|
+
const t = Date.now(); // 计时开始
|
|
389
392
|
try {
|
|
390
393
|
mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
391
394
|
logger.debug(`封面已下载: ${mediaCoverUrl}`);
|
|
@@ -394,13 +397,15 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
394
397
|
logger.warn(`封面下载失败: ${result.coverUrl}`, e);
|
|
395
398
|
mediaCoverUrl = result.coverUrl;
|
|
396
399
|
}
|
|
400
|
+
downloadTime += Date.now() - t; // 累加耗时
|
|
397
401
|
}
|
|
398
402
|
else {
|
|
399
403
|
mediaCoverUrl = result.coverUrl;
|
|
400
404
|
}
|
|
401
405
|
}
|
|
402
406
|
// --- 下载 mainbody 中的图片 ---
|
|
403
|
-
if (result.mainbody) {
|
|
407
|
+
if (result.mainbody && config.usingLocal) {
|
|
408
|
+
const t = Date.now(); // 计时开始
|
|
404
409
|
const imgMatches = [...result.mainbody.matchAll(/<img\s[^>]*src\s*=\s*["']?([^"'>\s]+)["']?/gi)];
|
|
405
410
|
const urlMap = {};
|
|
406
411
|
await Promise.all(imgMatches.map(async (match) => {
|
|
@@ -419,6 +424,7 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
419
424
|
urlMap[remoteUrl] = remoteUrl;
|
|
420
425
|
}
|
|
421
426
|
}));
|
|
427
|
+
downloadTime += Date.now() - t; // 累加耗时
|
|
422
428
|
mediaMainbody = result.mainbody;
|
|
423
429
|
for (const [remote, local] of Object.entries(urlMap)) {
|
|
424
430
|
const escaped = remote.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -449,7 +455,9 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
449
455
|
continue;
|
|
450
456
|
let shouldSend = true;
|
|
451
457
|
if (config.Max_size !== undefined) {
|
|
458
|
+
const t = Date.now();
|
|
452
459
|
const sizeBytes = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
|
|
460
|
+
downloadTime += Date.now() - t;
|
|
453
461
|
const maxBytes = config.Max_size * 1024 * 1024;
|
|
454
462
|
if (sizeBytes !== null && sizeBytes > maxBytes) {
|
|
455
463
|
shouldSend = false;
|
|
@@ -457,13 +465,17 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
457
465
|
const maxMB = config.Max_size.toFixed(2);
|
|
458
466
|
sendPromises.push(session.send(`文件大小超限 (${sizeMB} MB > ${maxMB} MB)`));
|
|
459
467
|
logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
|
|
468
|
+
sendPromises.push(session.send(`文件大小超限...`));
|
|
460
469
|
}
|
|
461
470
|
}
|
|
462
471
|
if (shouldSend) {
|
|
463
472
|
try {
|
|
464
473
|
let localUrl = remoteUrl;
|
|
465
|
-
if (config.usingLocal)
|
|
474
|
+
if (config.usingLocal) {
|
|
475
|
+
const t = Date.now();
|
|
466
476
|
localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
477
|
+
downloadTime += Date.now() - t;
|
|
478
|
+
}
|
|
467
479
|
if (!localUrl)
|
|
468
480
|
continue;
|
|
469
481
|
let element = null;
|
|
@@ -492,10 +504,15 @@ async function sendResult_plain(ctx, session, config, result, logger) {
|
|
|
492
504
|
}
|
|
493
505
|
}
|
|
494
506
|
}
|
|
507
|
+
const tSend = Date.now(); // 发送计时开始
|
|
495
508
|
await Promise.all(sendPromises);
|
|
509
|
+
sendTime = Date.now() - tSend; // 计算发送耗时
|
|
510
|
+
return { downloadTime, sendTime }; // 返回统计
|
|
496
511
|
}
|
|
497
512
|
async function sendResult_forward(ctx, session, config, result, logger, mixed_sending = false) {
|
|
498
513
|
logger.debug(mixed_sending ? '进入混合发送' : '进入合并发送');
|
|
514
|
+
let downloadTime = 0;
|
|
515
|
+
let sendTime = 0;
|
|
499
516
|
const localDownloadDir = config.localDownloadDir;
|
|
500
517
|
const onebotReadDir = config.onebotReadDir;
|
|
501
518
|
let mediaCoverUrl = result.coverUrl;
|
|
@@ -508,6 +525,7 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
508
525
|
// --- 封面 ---
|
|
509
526
|
if (result.coverUrl) {
|
|
510
527
|
if (config.usingLocal) {
|
|
528
|
+
const t = Date.now();
|
|
511
529
|
try {
|
|
512
530
|
mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
513
531
|
}
|
|
@@ -515,6 +533,7 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
515
533
|
logger.warn('封面下载失败', e);
|
|
516
534
|
mediaCoverUrl = '';
|
|
517
535
|
}
|
|
536
|
+
downloadTime += Date.now() - t;
|
|
518
537
|
}
|
|
519
538
|
else {
|
|
520
539
|
mediaCoverUrl = result.coverUrl;
|
|
@@ -526,12 +545,14 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
526
545
|
const urlMap = {};
|
|
527
546
|
await Promise.all(imgUrls.map(async (url) => {
|
|
528
547
|
if (config.usingLocal) {
|
|
548
|
+
const t = Date.now();
|
|
529
549
|
try {
|
|
530
550
|
urlMap[url] = await downloadAndMapUrl(ctx, url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
531
551
|
}
|
|
532
552
|
catch (e) {
|
|
533
553
|
logger.warn(`正文图片下载失败: ${url}`, e);
|
|
534
554
|
}
|
|
555
|
+
downloadTime += Date.now() - t;
|
|
535
556
|
}
|
|
536
557
|
else {
|
|
537
558
|
urlMap[url] = url;
|
|
@@ -600,7 +621,9 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
600
621
|
continue;
|
|
601
622
|
let shouldInclude = true;
|
|
602
623
|
if (config.Max_size !== undefined) {
|
|
624
|
+
const t = Date.now();
|
|
603
625
|
const sizeBytes = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
|
|
626
|
+
downloadTime += Date.now() - t;
|
|
604
627
|
const maxBytes = config.Max_size * 1024 * 1024;
|
|
605
628
|
if (sizeBytes !== null && sizeBytes > maxBytes) {
|
|
606
629
|
shouldInclude = false;
|
|
@@ -624,8 +647,11 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
624
647
|
if (shouldInclude) {
|
|
625
648
|
try {
|
|
626
649
|
let localUrl = remoteUrl;
|
|
627
|
-
if (config.usingLocal)
|
|
650
|
+
if (config.usingLocal) {
|
|
651
|
+
const t = Date.now();
|
|
628
652
|
localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
653
|
+
downloadTime += Date.now() - t;
|
|
654
|
+
}
|
|
629
655
|
if (!localUrl)
|
|
630
656
|
continue;
|
|
631
657
|
if (!mixed_sending) {
|
|
@@ -686,8 +712,9 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
686
712
|
});
|
|
687
713
|
}
|
|
688
714
|
}
|
|
689
|
-
if (forwardNodes.length === 0 && extraSendPromises.length === 0)
|
|
690
|
-
return;
|
|
715
|
+
if (forwardNodes.length === 0 && extraSendPromises.length === 0) {
|
|
716
|
+
return { downloadTime, sendTime };
|
|
717
|
+
}
|
|
691
718
|
logger.debug(`解析结果: \n ${JSON.stringify(result, null, 2)}`);
|
|
692
719
|
if (!(session.onebot && session.onebot._request))
|
|
693
720
|
throw new Error("Onebot is not defined");
|
|
@@ -702,8 +729,14 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
|
|
|
702
729
|
source: result.title || ''
|
|
703
730
|
}));
|
|
704
731
|
}
|
|
732
|
+
// 混合模式额外消息
|
|
705
733
|
if (mixed_sending && extraSendPromises.length > 0) {
|
|
706
734
|
promises.push(...extraSendPromises);
|
|
707
735
|
}
|
|
708
|
-
|
|
736
|
+
if (promises.length > 0) {
|
|
737
|
+
const tSend = Date.now();
|
|
738
|
+
await Promise.all(promises);
|
|
739
|
+
sendTime = Date.now() - tSend;
|
|
740
|
+
}
|
|
741
|
+
return { downloadTime, sendTime };
|
|
709
742
|
}
|