koishi-plugin-share-links-analysis 0.8.1 → 0.8.3

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 CHANGED
@@ -112,7 +112,27 @@ 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
+ }
116
136
  function apply(ctx, config) {
117
137
  // 数据库模型定义
118
138
  ctx.model.extend('sla_cookie_cache', {
@@ -237,7 +257,7 @@ function apply(ctx, config) {
237
257
  });
238
258
  // 清除缓存指令
239
259
  cmd.subcommand('.clean', '清除所有缓存和文件', { authority: 3 })
240
- .action(async ({ session }) => {
260
+ .action(async () => {
241
261
  await ctx.database.remove('sla_parse_cache', {});
242
262
  const allFiles = await ctx.database.get('sla_file_cache', {});
243
263
  for (const file of allFiles) {
@@ -246,7 +266,8 @@ function apply(ctx, config) {
246
266
  await fs.promises.unlink(file.path);
247
267
  }
248
268
  }
249
- catch { }
269
+ catch {
270
+ }
250
271
  }
251
272
  await ctx.database.remove('sla_file_cache', {});
252
273
  return '缓存及对应文件已清理。';
@@ -297,87 +318,142 @@ function apply(ctx, config) {
297
318
  if (config.waitTip_Switch) {
298
319
  await session.send(config.waitTip_Switch);
299
320
  }
321
+ // === 性能统计变量 ===
322
+ const startTotal = Date.now();
323
+ const timeStats = { downloadTime: 0, sendTime: 0 };
324
+ let parseTime = 0;
325
+ let isCache = false;
326
+ let status = "success";
327
+ let errorMsg = "";
328
+ let errorStack = "";
300
329
  // === 缓存与并发控制逻辑 ===
301
330
  let result = null;
302
331
  const cacheKey = `${link.platform}:${link.id}`;
303
- // 1. 查持久化缓存 (DB)
304
- if (config.enableCache) {
305
- const cached = await ctx.database.get('sla_parse_cache', cacheKey);
306
- // 检查是否存在且未过期
307
- if (cached.length > 0) {
308
- const entry = cached[0];
309
- const isExpired = config.cacheExpiration > 0 && (Date.now() - entry.created_at > config.cacheExpiration * 60 * 60 * 1000);
310
- if (!isExpired) {
311
- logger.debug(`使用缓存解析结果: ${cacheKey}`);
312
- result = entry.data;
313
- }
314
- else {
315
- // 过期删除
316
- await ctx.database.remove('sla_parse_cache', { key: cacheKey });
332
+ try {
333
+ // 1. 查持久化缓存 (DB)
334
+ if (config.enableCache) {
335
+ const cached = await ctx.database.get('sla_parse_cache', cacheKey);
336
+ // 检查是否存在且未过期
337
+ if (cached.length > 0) {
338
+ const entry = cached[0];
339
+ const isExpired = config.cacheExpiration > 0 && (Date.now() - entry.created_at > config.cacheExpiration * 60 * 60 * 1000);
340
+ if (!isExpired) {
341
+ logger.debug(`使用缓存解析结果: ${cacheKey}`);
342
+ result = entry.data;
343
+ isCache = true;
344
+ }
345
+ else {
346
+ // 过期删除
347
+ await ctx.database.remove('sla_parse_cache', { key: cacheKey });
348
+ }
317
349
  }
318
350
  }
319
- }
320
- // 2. 查内存任务队列
321
- if (!result) {
322
- if (pendingChecks.has(cacheKey)) {
323
- logger.debug(`检测到正在进行的解析任务,正在等待合并结果: ${cacheKey}`);
324
- // 如果有相同的任务正在进行,直接等待它的结果
325
- result = await pendingChecks.get(cacheKey) || null;
326
- }
327
- else {
328
- // 如果没有,创建一个新的 Promise 任务
329
- const task = (async () => {
351
+ // 2. 查内存任务队列
352
+ if (!result) {
353
+ if (pendingChecks.has(cacheKey)) {
354
+ logger.debug(`检测到正在进行的解析任务,正在等待合并结果: ${cacheKey}`);
355
+ // 如果有相同的任务正在进行,直接等待它的结果
330
356
  try {
331
- const res = await (0, core_1.processLink)(ctx, config, link, session);
332
- // 解析成功且开启缓存,则写入 DB
333
- if (res && config.enableCache) {
334
- await ctx.database.upsert('sla_parse_cache', [{
335
- key: cacheKey,
336
- data: res,
337
- created_at: Date.now()
338
- }]);
339
- }
340
- return res;
357
+ result = await pendingChecks.get(cacheKey) || null;
341
358
  }
342
359
  catch (e) {
343
- logger.warn(`解析任务出错: ${e}`);
344
- return null;
360
+ // 如果等待的任务失败了,这里也会捕获到
361
+ throw e;
345
362
  }
346
- })();
347
- // 将任务存入 Map
348
- pendingChecks.set(cacheKey, task);
349
- try {
350
- result = await task;
351
363
  }
352
- finally {
353
- // 无论成功失败,任务结束后从 Map 中移除
354
- pendingChecks.delete(cacheKey);
364
+ else {
365
+ // 如果没有,创建一个新的 Promise 任务
366
+ const task = (async () => {
367
+ const t = Date.now();
368
+ try {
369
+ const res = await (0, core_1.processLink)(ctx, config, link, session);
370
+ // 解析成功且开启缓存,则写入 DB
371
+ if (res && config.enableCache) {
372
+ await ctx.database.upsert('sla_parse_cache', [{
373
+ key: cacheKey,
374
+ data: res,
375
+ created_at: Date.now()
376
+ }]);
377
+ }
378
+ return res;
379
+ }
380
+ catch (e) {
381
+ logger.warn(`解析任务出错: ${e}`);
382
+ throw e;
383
+ }
384
+ finally {
385
+ // 执行上报
386
+ // 无论成功失败,都在 finally 中上报数据
387
+ parseTime = Date.now() - t;
388
+ }
389
+ })();
390
+ // 将任务存入 Map
391
+ pendingChecks.set(cacheKey, task);
392
+ try {
393
+ result = await task;
394
+ }
395
+ finally {
396
+ // 无论成功失败,任务结束后从 Map 中移除
397
+ pendingChecks.delete(cacheKey);
398
+ }
355
399
  }
356
400
  }
401
+ // === 逻辑结束 ===
402
+ if (result) {
403
+ lastProcessedUrls[channelId][link.url] = Date.now();
404
+ await (0, utils_1.sendResult)(ctx, session, config, result, logger, timeStats);
405
+ }
406
+ else {
407
+ status = "failed";
408
+ errorMsg = "parser_returned_null";
409
+ // 即使没有抛错,如果返回 null,也可以视为一种“软失败”,记录一下
410
+ }
411
+ linkCount++;
357
412
  }
358
- // === 逻辑结束 ===
359
- if (result) {
360
- lastProcessedUrls[channelId][link.url] = Date.now();
361
- await sendResult(ctx, session, config, result, logger);
413
+ catch (e) {
414
+ status = "error";
415
+ errorMsg = e.message || String(e);
416
+ errorStack = e.stack || String(e);
417
+ logger.warn(`处理异常: ${e}`);
418
+ }
419
+ finally {
420
+ // 4. 上报数据
421
+ const totalTime = Date.now() - startTotal;
422
+ // 获取内存使用情况 (MB)
423
+ const memoryUsage = process.memoryUsage();
424
+ const rssMB = (memoryUsage.rss / 1024 / 1024).toFixed(2);
425
+ // 截取堆栈前 1000 个字符,防止数据包过大(根据您的后端数据库限制调整)
426
+ const truncatedStack = errorStack.length > 1000 ? errorStack.substring(0, 1000) + "..." : errorStack;
427
+ reportMetric(ctx, config, {
428
+ type: "link_process", // 业务类型
429
+ // Tags
430
+ platform: link.platform,
431
+ status: status,
432
+ is_cache: isCache.toString(),
433
+ using_local: config.usingLocal.toString(),
434
+ user_id: session.userId || "unknown",
435
+ // [新增] 上下文信息,帮助定位是哪个群/频道
436
+ guild_id: session.guildId || "private",
437
+ channel_id: session.channelId || "unknown",
438
+ // [新增] 核心复现数据:具体的 URL
439
+ // 注意:如果您的后端是时序数据库(InfluxDB等),URL作为Tag可能会导致基数爆炸(High Cardinality)。
440
+ // 但根据您的描述是存入 tags 列用于筛选,且为内网服务,通常问题不大。
441
+ target_url: link.url,
442
+ // [新增] 错误信息
443
+ error_msg: errorMsg,
444
+ // 建议将堆栈放入 tags (如果数据库支持长文本) 或者单独的 log 字段
445
+ // 这里放入 tags 供查阅
446
+ error_stack: status === 'error' ? truncatedStack : "",
447
+ // === Metrics (数值/指标) ===
448
+ time_total_ms: totalTime,
449
+ time_parse_ms: parseTime,
450
+ time_download_ms: timeStats.downloadTime,
451
+ time_send_ms: timeStats.sendTime,
452
+ // 系统负载指标
453
+ memory_rss_mb: parseFloat(rssMB), // 当前进程内存占用
454
+ concurrent_tasks: pendingChecks.size, // 当前正在进行的并发解析数
455
+ });
362
456
  }
363
- linkCount++;
364
457
  }
365
458
  });
366
459
  }
367
- async function sendResult(ctx, session, config, result, logger) {
368
- if (!session.channel) {
369
- await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
370
- return;
371
- }
372
- switch (config.useForward) {
373
- case "plain":
374
- await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
375
- return;
376
- case 'forward':
377
- await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, false);
378
- return;
379
- case "mixed":
380
- await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, true);
381
- return;
382
- }
383
- }
@@ -8,11 +8,11 @@ const utils_1 = require("../utils");
8
8
  exports.name = "bilibili";
9
9
  const linkRules = [
10
10
  {
11
- pattern: /(?:https?:\/\/)?(?:www\.bilibili\.com\/video\/)(([ab]v[0-9a-zA-Z]+))/gi,
11
+ pattern: /(?:https?:\/\/)?www\.bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi,
12
12
  type: "video",
13
13
  },
14
14
  {
15
- pattern: /(?:https?:\/\/)?(?:b23\.tv\/([0-9a-zA-Z]+))/gi,
15
+ pattern: /(?:https?:\/\/)?b23\.tv\/([0-9a-zA-Z]+)/gi,
16
16
  type: "short",
17
17
  },
18
18
  ];
@@ -151,7 +151,7 @@ async function process(ctx, config, link, session) {
151
151
  const mainContent = container.querySelector('.image-text__content')?.textContent?.trim() || '';
152
152
  contentBlocks.push({ type: 'text', content: mainContent });
153
153
  // 图片(轮播图)
154
- container.querySelectorAll('.header-image__item-image img').forEach((img, index) => {
154
+ container.querySelectorAll('.header-image__item-image img').forEach((img) => {
155
155
  const src = img.src.replace(/\?.*$/, '');
156
156
  contentBlocks.push({ type: 'image', content: src });
157
157
  });
@@ -11,15 +11,15 @@ const utils_1 = require("../utils");
11
11
  exports.name = "xiaohongshu";
12
12
  const linkRules = [
13
13
  {
14
- pattern: /(?:https?:\/\/)?(?:www\.xiaohongshu\.com\/discovery\/item\/)([\w?=&\-.%]+)/gi,
14
+ pattern: /(?:https?:\/\/)?www\.xiaohongshu\.com\/discovery\/item\/([\w?=&\-.%]+)/gi,
15
15
  type: "discovery",
16
16
  },
17
17
  {
18
- pattern: /(?:https?:\/\/)?(?:www\.xiaohongshu\.com\/explore\/)([\w?=&\-.%]+)/gi,
18
+ pattern: /(?:https?:\/\/)?www\.xiaohongshu\.com\/explore\/([\w?=&\-.%]+)/gi,
19
19
  type: "explore",
20
20
  },
21
21
  {
22
- pattern: /(?:https?:\/\/)?(?:xhslink\.com\/(?:\w\/)?)([0-9a-zA-Z]+)/gi,
22
+ pattern: /(?:https?:\/\/)?xhslink\.com\/(?:\w\/)?([0-9a-zA-Z]+)/gi,
23
23
  type: "short",
24
24
  },
25
25
  ];
package/lib/types.d.ts CHANGED
@@ -43,6 +43,8 @@ export interface PluginConfig {
43
43
  localDownloadDir: string;
44
44
  userAgent: string;
45
45
  debug: boolean;
46
+ reportEnabled: boolean;
47
+ reportUrl: string;
46
48
  }
47
49
  export interface BilibiliVideoInfo {
48
50
  data: {
package/lib/types.js CHANGED
@@ -1,4 +1,3 @@
1
1
  "use strict";
2
2
  // src/types.ts
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- let session_global;
package/lib/utils.d.ts CHANGED
@@ -19,5 +19,15 @@ 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<void>;
23
- export declare function sendResult_forward(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<void>;
22
+ export declare function sendResult(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, statsRef: {
23
+ downloadTime: number;
24
+ sendTime: number;
25
+ }): Promise<void>;
26
+ export declare function sendResult_plain(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, statsRef: {
27
+ downloadTime: number;
28
+ sendTime: number;
29
+ }): Promise<void>;
30
+ export declare function sendResult_forward(ctx: Context, session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending: boolean | undefined, statsRef: {
31
+ downloadTime: number;
32
+ sendTime: number;
33
+ }): Promise<void>;
package/lib/utils.js CHANGED
@@ -43,6 +43,7 @@ exports.getProxyAgent = getProxyAgent;
43
43
  exports.getFileSize = getFileSize;
44
44
  exports.getEffectiveSettings = getEffectiveSettings;
45
45
  exports.isUserAdmin = isUserAdmin;
46
+ exports.sendResult = sendResult;
46
47
  exports.sendResult_plain = sendResult_plain;
47
48
  exports.sendResult_forward = sendResult_forward;
48
49
  const koishi_1 = require("koishi");
@@ -261,7 +262,6 @@ async function tryHeadRequest(url, proxy, userAgent, logger) {
261
262
  timeout: 10000,
262
263
  headers: {
263
264
  'User-Agent': userAgent,
264
- 'Referer': 'https://www.bilibili.com/'
265
265
  }
266
266
  }, (res) => {
267
267
  const len = res.headers['content-length'];
@@ -286,7 +286,6 @@ async function tryHeadRequest(url, proxy, userAgent, logger) {
286
286
  });
287
287
  }
288
288
  async function tryGetRequestForSize(url, proxy, userAgent, logger) {
289
- proxy = undefined;
290
289
  return new Promise((resolve) => {
291
290
  const u = new url_1.URL(url);
292
291
  const agent = getProxyAgent(proxy, url);
@@ -372,7 +371,26 @@ async function isUserAdmin(session, userId) {
372
371
  return true;
373
372
  }
374
373
  }
375
- async function sendResult_plain(ctx, session, config, result, logger) {
374
+ async function sendResult(ctx, session, config, result, logger, statsRef // 新增参数
375
+ ) {
376
+ if (!session.channel) {
377
+ await sendResult_plain(ctx, session, config, result, logger, statsRef);
378
+ return;
379
+ }
380
+ switch (config.useForward) {
381
+ case "plain":
382
+ await sendResult_plain(ctx, session, config, result, logger, statsRef);
383
+ break;
384
+ case 'forward':
385
+ await sendResult_forward(ctx, session, config, result, logger, false, statsRef);
386
+ break;
387
+ case "mixed":
388
+ await sendResult_forward(ctx, session, config, result, logger, true, statsRef);
389
+ break;
390
+ }
391
+ }
392
+ // 2. 修改 sendResult_plain
393
+ async function sendResult_plain(ctx, session, config, result, logger, statsRef) {
376
394
  logger.debug('进入普通发送');
377
395
  const localDownloadDir = config.localDownloadDir;
378
396
  const onebotReadDir = config.onebotReadDir;
@@ -386,6 +404,7 @@ async function sendResult_plain(ctx, session, config, result, logger) {
386
404
  // --- 下载封面 ---
387
405
  if (result.coverUrl) {
388
406
  if (config.usingLocal) {
407
+ const t = Date.now(); // 计时开始
389
408
  try {
390
409
  mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
391
410
  logger.debug(`封面已下载: ${mediaCoverUrl}`);
@@ -394,13 +413,15 @@ async function sendResult_plain(ctx, session, config, result, logger) {
394
413
  logger.warn(`封面下载失败: ${result.coverUrl}`, e);
395
414
  mediaCoverUrl = result.coverUrl;
396
415
  }
416
+ statsRef.downloadTime += Date.now() - t; // 累加耗时
397
417
  }
398
418
  else {
399
419
  mediaCoverUrl = result.coverUrl;
400
420
  }
401
421
  }
402
422
  // --- 下载 mainbody 中的图片 ---
403
- if (result.mainbody) {
423
+ if (result.mainbody && config.usingLocal) {
424
+ const t = Date.now(); // 计时开始
404
425
  const imgMatches = [...result.mainbody.matchAll(/<img\s[^>]*src\s*=\s*["']?([^"'>\s]+)["']?/gi)];
405
426
  const urlMap = {};
406
427
  await Promise.all(imgMatches.map(async (match) => {
@@ -419,6 +440,7 @@ async function sendResult_plain(ctx, session, config, result, logger) {
419
440
  urlMap[remoteUrl] = remoteUrl;
420
441
  }
421
442
  }));
443
+ statsRef.downloadTime += Date.now() - t; // 累加耗时
422
444
  mediaMainbody = result.mainbody;
423
445
  for (const [remote, local] of Object.entries(urlMap)) {
424
446
  const escaped = remote.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -449,7 +471,9 @@ async function sendResult_plain(ctx, session, config, result, logger) {
449
471
  continue;
450
472
  let shouldSend = true;
451
473
  if (config.Max_size !== undefined) {
474
+ const t = Date.now();
452
475
  const sizeBytes = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
476
+ statsRef.downloadTime += Date.now() - t;
453
477
  const maxBytes = config.Max_size * 1024 * 1024;
454
478
  if (sizeBytes !== null && sizeBytes > maxBytes) {
455
479
  shouldSend = false;
@@ -457,13 +481,17 @@ async function sendResult_plain(ctx, session, config, result, logger) {
457
481
  const maxMB = config.Max_size.toFixed(2);
458
482
  sendPromises.push(session.send(`文件大小超限 (${sizeMB} MB > ${maxMB} MB)`));
459
483
  logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
484
+ sendPromises.push(session.send(`文件大小超限...`));
460
485
  }
461
486
  }
462
487
  if (shouldSend) {
463
488
  try {
464
489
  let localUrl = remoteUrl;
465
- if (config.usingLocal)
490
+ if (config.usingLocal) {
491
+ const t = Date.now();
466
492
  localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
493
+ statsRef.downloadTime += Date.now() - t;
494
+ }
467
495
  if (!localUrl)
468
496
  continue;
469
497
  let element = null;
@@ -492,9 +520,12 @@ async function sendResult_plain(ctx, session, config, result, logger) {
492
520
  }
493
521
  }
494
522
  }
523
+ const tSend = Date.now(); // 发送计时开始
495
524
  await Promise.all(sendPromises);
525
+ statsRef.sendTime = Date.now() - tSend; // 计算发送耗时
496
526
  }
497
- async function sendResult_forward(ctx, session, config, result, logger, mixed_sending = false) {
527
+ async function sendResult_forward(ctx, session, config, result, logger, mixed_sending = false, statsRef // 接收引用
528
+ ) {
498
529
  logger.debug(mixed_sending ? '进入混合发送' : '进入合并发送');
499
530
  const localDownloadDir = config.localDownloadDir;
500
531
  const onebotReadDir = config.onebotReadDir;
@@ -508,6 +539,7 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
508
539
  // --- 封面 ---
509
540
  if (result.coverUrl) {
510
541
  if (config.usingLocal) {
542
+ const t = Date.now();
511
543
  try {
512
544
  mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
513
545
  }
@@ -515,6 +547,7 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
515
547
  logger.warn('封面下载失败', e);
516
548
  mediaCoverUrl = '';
517
549
  }
550
+ statsRef.downloadTime += Date.now() - t;
518
551
  }
519
552
  else {
520
553
  mediaCoverUrl = result.coverUrl;
@@ -526,12 +559,14 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
526
559
  const urlMap = {};
527
560
  await Promise.all(imgUrls.map(async (url) => {
528
561
  if (config.usingLocal) {
562
+ const t = Date.now();
529
563
  try {
530
564
  urlMap[url] = await downloadAndMapUrl(ctx, url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
531
565
  }
532
566
  catch (e) {
533
567
  logger.warn(`正文图片下载失败: ${url}`, e);
534
568
  }
569
+ statsRef.downloadTime += Date.now() - t;
535
570
  }
536
571
  else {
537
572
  urlMap[url] = url;
@@ -600,7 +635,9 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
600
635
  continue;
601
636
  let shouldInclude = true;
602
637
  if (config.Max_size !== undefined) {
638
+ const t = Date.now();
603
639
  const sizeBytes = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
640
+ statsRef.downloadTime += Date.now() - t;
604
641
  const maxBytes = config.Max_size * 1024 * 1024;
605
642
  if (sizeBytes !== null && sizeBytes > maxBytes) {
606
643
  shouldInclude = false;
@@ -624,8 +661,11 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
624
661
  if (shouldInclude) {
625
662
  try {
626
663
  let localUrl = remoteUrl;
627
- if (config.usingLocal)
664
+ if (config.usingLocal) {
665
+ const t = Date.now();
628
666
  localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
667
+ statsRef.downloadTime += Date.now() - t;
668
+ }
629
669
  if (!localUrl)
630
670
  continue;
631
671
  if (!mixed_sending) {
@@ -702,8 +742,13 @@ async function sendResult_forward(ctx, session, config, result, logger, mixed_se
702
742
  source: result.title || ''
703
743
  }));
704
744
  }
745
+ // 混合模式额外消息
705
746
  if (mixed_sending && extraSendPromises.length > 0) {
706
747
  promises.push(...extraSendPromises);
707
748
  }
708
- await Promise.all(promises);
749
+ if (promises.length > 0) {
750
+ const tSend = Date.now();
751
+ await Promise.all(promises);
752
+ statsRef.sendTime = Date.now() - tSend;
753
+ }
709
754
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "koishi-plugin-share-links-analysis",
3
3
  "description": "自用插件",
4
4
  "license": "MIT",
5
- "version": "0.8.1",
5
+ "version": "0.8.3",
6
6
  "main": "lib/index.js",
7
7
  "typings": "lib/index.d.ts",
8
8
  "files": [