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 +145 -69
- package/lib/parsers/bilibili.js +2 -2
- package/lib/parsers/xiaoheihe.js +1 -1
- package/lib/parsers/xiaohongshu.js +3 -3
- package/lib/types.d.ts +2 -0
- package/lib/types.js +0 -1
- package/lib/utils.d.ts +12 -2
- package/lib/utils.js +53 -8
- package/package.json +1 -1
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 (
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
360
|
+
// 如果等待的任务失败了,这里也会捕获到
|
|
361
|
+
throw e;
|
|
345
362
|
}
|
|
346
|
-
})();
|
|
347
|
-
// 将任务存入 Map
|
|
348
|
-
pendingChecks.set(cacheKey, task);
|
|
349
|
-
try {
|
|
350
|
-
result = await task;
|
|
351
363
|
}
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
}
|
package/lib/parsers/bilibili.js
CHANGED
|
@@ -8,11 +8,11 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
exports.name = "bilibili";
|
|
9
9
|
const linkRules = [
|
|
10
10
|
{
|
|
11
|
-
pattern: /(?:https?:\/\/)?
|
|
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?:\/\/)?
|
|
15
|
+
pattern: /(?:https?:\/\/)?b23\.tv\/([0-9a-zA-Z]+)/gi,
|
|
16
16
|
type: "short",
|
|
17
17
|
},
|
|
18
18
|
];
|
package/lib/parsers/xiaoheihe.js
CHANGED
|
@@ -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
|
|
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?:\/\/)?
|
|
14
|
+
pattern: /(?:https?:\/\/)?www\.xiaohongshu\.com\/discovery\/item\/([\w?=&\-.%]+)/gi,
|
|
15
15
|
type: "discovery",
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
pattern: /(?:https?:\/\/)?
|
|
18
|
+
pattern: /(?:https?:\/\/)?www\.xiaohongshu\.com\/explore\/([\w?=&\-.%]+)/gi,
|
|
19
19
|
type: "explore",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
pattern: /(?:https?:\/\/)?
|
|
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
package/lib/types.js
CHANGED
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
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
749
|
+
if (promises.length > 0) {
|
|
750
|
+
const tSend = Date.now();
|
|
751
|
+
await Promise.all(promises);
|
|
752
|
+
statsRef.sendTime = Date.now() - tSend;
|
|
753
|
+
}
|
|
709
754
|
}
|