koishi-plugin-share-links-analysis 0.7.1 → 0.7.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/core.js +1 -2
- package/lib/index.js +151 -26
- package/lib/parsers/bilibili.js +5 -11
- package/lib/parsers/twitter.js +1 -1
- package/lib/parsers/xiaoheihe.js +6 -2
- package/lib/parsers/xiaohongshu.js +11 -22
- package/lib/types.d.ts +32 -3
- package/lib/utils.d.ts +8 -4
- package/lib/utils.js +97 -67
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -69,8 +69,7 @@ function resolveLinks(content) {
|
|
|
69
69
|
async function processLink(ctx, config, link, session) {
|
|
70
70
|
for (const parser of parsers) {
|
|
71
71
|
if (parser.name == link.platform) {
|
|
72
|
-
|
|
73
|
-
ctx.logger('share-links-analysis').info(`解析平台:${parser.name},链接:${link.url}`);
|
|
72
|
+
ctx.logger('share-links-analysis').debug(`解析平台:${parser.name},链接:${link.url}`);
|
|
74
73
|
return await parser.process(ctx, config, link, session);
|
|
75
74
|
}
|
|
76
75
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/index.ts
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
37
|
exports.Config = exports.usage = exports.inject = exports.name = void 0;
|
|
5
38
|
exports.apply = apply;
|
|
6
39
|
const koishi_1 = require("koishi");
|
|
7
40
|
const core_1 = require("./core");
|
|
8
41
|
const utils_1 = require("./utils");
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
9
43
|
exports.name = 'share-links-analysis';
|
|
10
44
|
exports.inject = {
|
|
11
45
|
required: ['BiliBiliVideo', 'database', 'puppeteer'],
|
|
@@ -39,6 +73,11 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
39
73
|
sendFiles: koishi_1.Schema.boolean().default(true).description("是否发送文件(视频等)"),
|
|
40
74
|
sendLinks: koishi_1.Schema.boolean().default(false).description("是否附加直链(仅对合并发送有效)"),
|
|
41
75
|
}).description("基础设置"),
|
|
76
|
+
koishi_1.Schema.object({
|
|
77
|
+
enableCache: koishi_1.Schema.boolean().default(true).description("开启缓存(包括解析结果缓存和资源文件缓存)"),
|
|
78
|
+
cacheExpiration: koishi_1.Schema.number().default(24).description("缓存过期时间(小时)。设置为 0 则不过期。"),
|
|
79
|
+
autoCleanInterval: koishi_1.Schema.number().default(1).description("自动清理过期缓存的检查间隔(小时)。"),
|
|
80
|
+
}).description("缓存设置"),
|
|
42
81
|
koishi_1.Schema.object({
|
|
43
82
|
format: koishi_1.Schema.string().role('textarea').default(`{title}
|
|
44
83
|
{cover}
|
|
@@ -68,22 +107,17 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
68
107
|
}).description('跨环境路径映射设置'),
|
|
69
108
|
koishi_1.Schema.object({
|
|
70
109
|
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"),
|
|
71
|
-
|
|
72
|
-
koishi_1.Schema.const('none').description('不记录'),
|
|
73
|
-
koishi_1.Schema.const('link_only').description('仅记录视频直链'),
|
|
74
|
-
koishi_1.Schema.const('full').description('记录完整调试信息'),
|
|
75
|
-
]).role('radio').default('none').description("选择后台日志记录等级"),
|
|
110
|
+
debug: koishi_1.Schema.boolean().default(false).description("开启调试模式 (输出详细日志)"),
|
|
76
111
|
}).description("调试设置"),
|
|
77
112
|
]);
|
|
78
113
|
function apply(ctx, config) {
|
|
79
|
-
//
|
|
114
|
+
// 数据库模型定义 (无需 ts-ignore)
|
|
80
115
|
ctx.model.extend('sla_cookie_cache', {
|
|
81
116
|
platform: 'string', // 平台名称,如 'xiaohongshu'
|
|
82
117
|
cookie: 'text', // 存储的 cookie 字符串
|
|
83
118
|
}, {
|
|
84
119
|
primary: 'platform' // 使用平台名称作为主键
|
|
85
120
|
});
|
|
86
|
-
// @ts-ignore
|
|
87
121
|
ctx.model.extend('sla_group_settings', {
|
|
88
122
|
guildId: 'string',
|
|
89
123
|
custom_parsers: 'json',
|
|
@@ -91,6 +125,59 @@ function apply(ctx, config) {
|
|
|
91
125
|
}, {
|
|
92
126
|
primary: 'guildId',
|
|
93
127
|
});
|
|
128
|
+
// 解析结果缓存
|
|
129
|
+
ctx.model.extend('sla_parse_cache', {
|
|
130
|
+
key: 'string', // platform + ':' + id
|
|
131
|
+
data: 'json',
|
|
132
|
+
created_at: 'double',
|
|
133
|
+
}, { primary: 'key' });
|
|
134
|
+
// 资源文件缓存 (hash)
|
|
135
|
+
ctx.model.extend('sla_file_cache', {
|
|
136
|
+
hash: 'string', // URL MD5
|
|
137
|
+
path: 'string', // 本地绝对路径
|
|
138
|
+
url: 'string',
|
|
139
|
+
created_at: 'double',
|
|
140
|
+
}, { primary: 'hash' });
|
|
141
|
+
const logger = ctx.logger('share-links-analysis');
|
|
142
|
+
// 根据配置设置日志等级
|
|
143
|
+
if (config.debug) {
|
|
144
|
+
logger.level = 3; // Debug Level
|
|
145
|
+
}
|
|
146
|
+
// 清理缓存函数
|
|
147
|
+
const cleanExpiredCache = async () => {
|
|
148
|
+
if (!config.enableCache || config.cacheExpiration <= 0)
|
|
149
|
+
return;
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
const threshold = now - config.cacheExpiration * 60 * 60 * 1000;
|
|
152
|
+
// 清理解析缓存
|
|
153
|
+
await ctx.database.remove('sla_parse_cache', {
|
|
154
|
+
created_at: { $lt: threshold }
|
|
155
|
+
});
|
|
156
|
+
// 清理文件缓存
|
|
157
|
+
const expiredFiles = await ctx.database.get('sla_file_cache', {
|
|
158
|
+
created_at: { $lt: threshold }
|
|
159
|
+
});
|
|
160
|
+
for (const file of expiredFiles) {
|
|
161
|
+
try {
|
|
162
|
+
if (fs.existsSync(file.path)) {
|
|
163
|
+
await fs.promises.unlink(file.path);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
logger.warn(`删除过期文件失败 ${file.path}: ${e}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await ctx.database.remove('sla_file_cache', {
|
|
171
|
+
created_at: { $lt: threshold }
|
|
172
|
+
});
|
|
173
|
+
if (expiredFiles.length > 0) {
|
|
174
|
+
logger.info(`已自动清理 ${expiredFiles.length} 个过期文件。`);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
// 设置定时清理
|
|
178
|
+
if (config.enableCache && config.autoCleanInterval > 0) {
|
|
179
|
+
ctx.setInterval(cleanExpiredCache, config.autoCleanInterval * 60 * 60 * 1000);
|
|
180
|
+
}
|
|
94
181
|
// 注册指令
|
|
95
182
|
const cmd = ctx.command('share', '分享解析插件配置', { authority: 1 })
|
|
96
183
|
.action(async ({ session }) => {
|
|
@@ -115,12 +202,9 @@ function apply(ctx, config) {
|
|
|
115
202
|
if (!value)
|
|
116
203
|
return '请输入正确的模式';
|
|
117
204
|
const mode = value.trim().toLowerCase() === 'true';
|
|
118
|
-
// @ts-ignore
|
|
119
205
|
const data = await ctx.database.get('sla_group_settings', session.guildId);
|
|
120
|
-
// @ts-ignore
|
|
121
206
|
const final_parsers = { ...data[0]?.custom_parsers, ...{ [parser]: mode } };
|
|
122
207
|
const record = { guildId: session.guildId, custom_parsers: final_parsers };
|
|
123
|
-
// @ts-ignore
|
|
124
208
|
await ctx.database.upsert('sla_group_settings', [record]);
|
|
125
209
|
}
|
|
126
210
|
await session.execute('share');
|
|
@@ -134,7 +218,6 @@ function apply(ctx, config) {
|
|
|
134
218
|
if (value) {
|
|
135
219
|
const mode = value.trim().toLowerCase() === 'true';
|
|
136
220
|
const record = { guildId: session.guildId, nsfw_enabled: mode };
|
|
137
|
-
// @ts-ignore
|
|
138
221
|
await ctx.database.upsert('sla_group_settings', [record]);
|
|
139
222
|
}
|
|
140
223
|
await session.execute('share');
|
|
@@ -145,11 +228,25 @@ function apply(ctx, config) {
|
|
|
145
228
|
return '该指令只能在群组中使用。';
|
|
146
229
|
if (!await (0, utils_1.isUserAdmin)(session, session.userId))
|
|
147
230
|
return '权限不足';
|
|
148
|
-
// @ts-ignore
|
|
149
231
|
await ctx.database.remove('sla_group_settings', { guildId: session.guildId });
|
|
150
232
|
return '已重置为全局默认设置。';
|
|
151
233
|
});
|
|
152
|
-
|
|
234
|
+
// 清除缓存指令
|
|
235
|
+
cmd.subcommand('.clean', '清除所有缓存和文件', { authority: 3 })
|
|
236
|
+
.action(async ({ session }) => {
|
|
237
|
+
await ctx.database.remove('sla_parse_cache', {});
|
|
238
|
+
const allFiles = await ctx.database.get('sla_file_cache', {});
|
|
239
|
+
for (const file of allFiles) {
|
|
240
|
+
try {
|
|
241
|
+
if (fs.existsSync(file.path)) {
|
|
242
|
+
await fs.promises.unlink(file.path);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch { }
|
|
246
|
+
}
|
|
247
|
+
await ctx.database.remove('sla_file_cache', {});
|
|
248
|
+
return '缓存及对应文件已清理。';
|
|
249
|
+
});
|
|
153
250
|
const lastProcessedUrls = {};
|
|
154
251
|
ctx.on('ready', async () => {
|
|
155
252
|
logger.info('插件已启动,执行插件初始化');
|
|
@@ -168,8 +265,7 @@ function apply(ctx, config) {
|
|
|
168
265
|
if (session.guildId) {
|
|
169
266
|
const settings = await (0, utils_1.getEffectiveSettings)(ctx, session.guildId, config);
|
|
170
267
|
if (!settings.parsers[link.platform]) {
|
|
171
|
-
|
|
172
|
-
ctx.logger('share-links-analysis').info(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
268
|
+
logger.debug(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
173
269
|
if (config.showError)
|
|
174
270
|
await session.send(`根据策略,该链接已被阻止解析:平台:${link.platform}`);
|
|
175
271
|
continue;
|
|
@@ -177,8 +273,7 @@ function apply(ctx, config) {
|
|
|
177
273
|
}
|
|
178
274
|
else {
|
|
179
275
|
if (!config.default_parsers[link.platform]) {
|
|
180
|
-
|
|
181
|
-
ctx.logger('share-links-analysis').info(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
276
|
+
logger.debug(`根据策略,该链接已被阻止解析:平台:${link.platform},链接:${link.url}`);
|
|
182
277
|
if (config.showError)
|
|
183
278
|
await session.send(`根据策略,该链接已被阻止解析:平台:${link.platform}`);
|
|
184
279
|
continue;
|
|
@@ -192,36 +287,66 @@ function apply(ctx, config) {
|
|
|
192
287
|
if (!lastProcessedUrls[channelId])
|
|
193
288
|
lastProcessedUrls[channelId] = {};
|
|
194
289
|
if (now - (lastProcessedUrls[channelId][link.url] || 0) < config.Min_Interval * 1000) {
|
|
195
|
-
|
|
196
|
-
logger.info(`链接 ${link.url} 在冷却时间内,跳过处理。`);
|
|
290
|
+
logger.debug(`链接 ${link.url} 在冷却时间内,跳过处理。`);
|
|
197
291
|
continue;
|
|
198
292
|
}
|
|
199
293
|
if (config.waitTip_Switch) {
|
|
200
294
|
await session.send(config.waitTip_Switch);
|
|
201
295
|
}
|
|
202
|
-
|
|
296
|
+
// === 缓存逻辑 ===
|
|
297
|
+
let result = null;
|
|
298
|
+
const cacheKey = `${link.platform}:${link.id}`;
|
|
299
|
+
if (config.enableCache) {
|
|
300
|
+
const cached = await ctx.database.get('sla_parse_cache', cacheKey);
|
|
301
|
+
// 检查是否存在且未过期
|
|
302
|
+
if (cached.length > 0) {
|
|
303
|
+
const entry = cached[0];
|
|
304
|
+
const isExpired = config.cacheExpiration > 0 && (Date.now() - entry.created_at > config.cacheExpiration * 60 * 60 * 1000);
|
|
305
|
+
if (!isExpired) {
|
|
306
|
+
logger.debug(`使用缓存解析结果: ${cacheKey}`);
|
|
307
|
+
result = entry.data;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// 过期删除
|
|
311
|
+
await ctx.database.remove('sla_parse_cache', { key: cacheKey });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// 缓存未命中,执行解析
|
|
316
|
+
if (!result) {
|
|
317
|
+
result = await (0, core_1.processLink)(ctx, config, link, session);
|
|
318
|
+
// 写入缓存
|
|
319
|
+
if (result && config.enableCache) {
|
|
320
|
+
await ctx.database.upsert('sla_parse_cache', [{
|
|
321
|
+
key: cacheKey,
|
|
322
|
+
data: result,
|
|
323
|
+
created_at: Date.now()
|
|
324
|
+
}]);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// === 缓存逻辑结束 ===
|
|
203
328
|
if (result) {
|
|
204
329
|
lastProcessedUrls[channelId][link.url] = now;
|
|
205
|
-
await sendResult(session, config, result, logger);
|
|
330
|
+
await sendResult(ctx, session, config, result, logger);
|
|
206
331
|
}
|
|
207
332
|
linkCount++;
|
|
208
333
|
}
|
|
209
334
|
});
|
|
210
335
|
}
|
|
211
|
-
async function sendResult(session, config, result, logger) {
|
|
336
|
+
async function sendResult(ctx, session, config, result, logger) {
|
|
212
337
|
if (!session.channel) {
|
|
213
|
-
await (0, utils_1.sendResult_plain)(session, config, result, logger);
|
|
338
|
+
await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
214
339
|
return;
|
|
215
340
|
}
|
|
216
341
|
switch (config.useForward) {
|
|
217
342
|
case "plain":
|
|
218
|
-
await (0, utils_1.sendResult_plain)(session, config, result, logger);
|
|
343
|
+
await (0, utils_1.sendResult_plain)(ctx, session, config, result, logger);
|
|
219
344
|
return;
|
|
220
345
|
case 'forward':
|
|
221
|
-
await (0, utils_1.sendResult_forward)(session, config, result, logger, false);
|
|
346
|
+
await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, false);
|
|
222
347
|
return;
|
|
223
348
|
case "mixed":
|
|
224
|
-
await (0, utils_1.sendResult_forward)(session, config, result, logger, true);
|
|
349
|
+
await (0, utils_1.sendResult_forward)(ctx, session, config, result, logger, true);
|
|
225
350
|
return;
|
|
226
351
|
}
|
|
227
352
|
}
|
package/lib/parsers/bilibili.js
CHANGED
|
@@ -92,9 +92,7 @@ async function process(ctx, config, link, session) {
|
|
|
92
92
|
if (locationHeader) {
|
|
93
93
|
finalUrl = locationHeader;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
logger.debug(`解析短链接时发生网络错误或未找到跳转地址: ${e.message}`);
|
|
97
|
-
}
|
|
95
|
+
logger.debug(`解析短链接时发生网络错误或未找到跳转地址: ${e.message}`);
|
|
98
96
|
}
|
|
99
97
|
if (finalUrl && (finalUrl.includes('b23.tv') || finalUrl.includes('bilibili.com/'))) {
|
|
100
98
|
const urlObj = new URL(finalUrl);
|
|
@@ -122,8 +120,7 @@ async function process(ctx, config, link, session) {
|
|
|
122
120
|
}
|
|
123
121
|
}
|
|
124
122
|
if (finalUrl) {
|
|
125
|
-
|
|
126
|
-
logger.info(`短链接解析成功,指向: ${finalUrl}`);
|
|
123
|
+
logger.debug(`短链接解析成功,指向: ${finalUrl}`);
|
|
127
124
|
const matchedLinks = match(finalUrl);
|
|
128
125
|
if (matchedLinks.length > 0 && matchedLinks[0].type === 'video') {
|
|
129
126
|
videoId = matchedLinks[0].id;
|
|
@@ -143,8 +140,7 @@ async function process(ctx, config, link, session) {
|
|
|
143
140
|
logger.warn(`无法从链接 ${link.url} 中解析出有效的B站视频ID。`);
|
|
144
141
|
return null;
|
|
145
142
|
}
|
|
146
|
-
|
|
147
|
-
logger.info(`获取视频信息,ID: ${videoId}`);
|
|
143
|
+
logger.debug(`获取视频信息,ID: ${videoId}`);
|
|
148
144
|
const idType = videoId.startsWith('BV') ? 'bvid' : 'aid';
|
|
149
145
|
const infoUrl = `https://api.bilibili.com/x/web-interface/view?${idType}=${videoId}`;
|
|
150
146
|
try {
|
|
@@ -158,14 +154,12 @@ async function process(ctx, config, link, session) {
|
|
|
158
154
|
const data = info.data;
|
|
159
155
|
// --- 获取视频直链 ---
|
|
160
156
|
let videoUrl = null;
|
|
161
|
-
|
|
162
|
-
logger.info(`尝试获取视频流,bvid: ${data.bvid}`);
|
|
157
|
+
logger.debug(`尝试获取视频流,bvid: ${data.bvid}`);
|
|
163
158
|
try {
|
|
164
159
|
const videoStream = await ctx.BiliBiliVideo.getBilibiliVideoStream(data.aid, data.bvid, data.pages[0].cid, config.Video_ClarityPriority === '1' ? 32 : 80, 'html5', 1);
|
|
165
160
|
if (videoStream?.data?.durl?.[0]?.url) {
|
|
166
161
|
videoUrl = videoStream.data.durl[0].url;
|
|
167
|
-
|
|
168
|
-
logger.info(`成功获取视频流,bvid: ${data.bvid}`);
|
|
162
|
+
logger.debug(`成功获取视频流,bvid: ${data.bvid}`);
|
|
169
163
|
}
|
|
170
164
|
}
|
|
171
165
|
catch (e) {
|
package/lib/parsers/twitter.js
CHANGED
package/lib/parsers/xiaoheihe.js
CHANGED
|
@@ -9,7 +9,7 @@ const utils_1 = require("../utils");
|
|
|
9
9
|
exports.name = "xiaoheihe";
|
|
10
10
|
const linkRules = [
|
|
11
11
|
{
|
|
12
|
-
pattern: /https?:\/\/api.xiaoheihe\.cn\/v3\/bbs\/app\/api\/web\/share\?
|
|
12
|
+
pattern: /https?:\/\/api.xiaoheihe\.cn\/v3\/bbs\/app\/api\/web\/share\?[\w=&]+/gi,
|
|
13
13
|
type: "bbs_api",
|
|
14
14
|
},
|
|
15
15
|
{
|
|
@@ -23,7 +23,11 @@ function match(content) {
|
|
|
23
23
|
const match = content.match(rule.pattern);
|
|
24
24
|
if (match) {
|
|
25
25
|
for (const fullUrl of match) {
|
|
26
|
-
|
|
26
|
+
let id;
|
|
27
|
+
if (rule.type == "bbs")
|
|
28
|
+
id = fullUrl.match(/\w+$/)?.[0];
|
|
29
|
+
else if (rule.type == "bbs_api")
|
|
30
|
+
id = fullUrl.match(/link_id=\w+/gi)?.[0].slice(8);
|
|
27
31
|
if (id) {
|
|
28
32
|
results.push({
|
|
29
33
|
platform: exports.name,
|
|
@@ -113,8 +113,7 @@ async function init(ctx, config) {
|
|
|
113
113
|
const filteredCookies = finalCookies.filter((c) => c.name !== 'acw_tc');
|
|
114
114
|
// 使用过滤后的 cookie 数组来生成字符串
|
|
115
115
|
const cookieString = filteredCookies.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
116
|
-
|
|
117
|
-
await ctx.database.upsert('sla_cookie_cache', [{ platform: platformId, cookie: cookieString }]);
|
|
116
|
+
await ctx.database.upsert('sla_cookie_cache', [{ platform: exports.name, cookie: cookieString }]);
|
|
118
117
|
logger.info('成功执行两步刷新策略并缓存了小红书 Cookie!');
|
|
119
118
|
return true;
|
|
120
119
|
}
|
|
@@ -145,22 +144,20 @@ async function process(ctx, config, link, session) {
|
|
|
145
144
|
const decodedUrl = link.url.replace(/&/g, '&');
|
|
146
145
|
const originalUrl = new URL(decodedUrl);
|
|
147
146
|
token = originalUrl.searchParams.get('xsec_token');
|
|
148
|
-
if (token
|
|
149
|
-
logger.
|
|
147
|
+
if (token) {
|
|
148
|
+
logger.debug(`成功从分享链接中提取 xsec_token。`);
|
|
150
149
|
}
|
|
151
|
-
else
|
|
150
|
+
else {
|
|
152
151
|
logger.debug(`分享链接中未找到 xsec_token: ${link.url}`);
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
154
|
catch (e) {
|
|
156
|
-
|
|
157
|
-
logger.debug(`解析分享链接URL失败: ${link.url}`);
|
|
155
|
+
logger.debug(`解析分享链接URL失败: ${link.url}`);
|
|
158
156
|
}
|
|
159
157
|
let finalUrl = link.url;
|
|
160
158
|
// 步骤二:如果是短链接,获取其跳转后的基础地址
|
|
161
159
|
if (link.url.includes('xhslink.com')) {
|
|
162
|
-
|
|
163
|
-
logger.info(`小红书短链接解析:尝试获取 ${link.url} 的最终地址`);
|
|
160
|
+
logger.debug(`小红书短链接解析:尝试获取 ${link.url} 的最终地址`);
|
|
164
161
|
try {
|
|
165
162
|
await ctx.http(link.url, {
|
|
166
163
|
method: 'GET',
|
|
@@ -172,8 +169,7 @@ async function process(ctx, config, link, session) {
|
|
|
172
169
|
const location = e.response?.headers?.location;
|
|
173
170
|
if (location) {
|
|
174
171
|
finalUrl = location;
|
|
175
|
-
|
|
176
|
-
logger.info(`短链接解析成功,跳转地址: ${finalUrl}`);
|
|
172
|
+
logger.debug(`短链接解析成功,跳转地址: ${finalUrl}`);
|
|
177
173
|
}
|
|
178
174
|
else {
|
|
179
175
|
logger.error(`解析短链接时发生网络错误: ${e.message}`);
|
|
@@ -198,12 +194,9 @@ async function process(ctx, config, link, session) {
|
|
|
198
194
|
logger.error(`构建最终请求URL失败: ${finalUrl}`);
|
|
199
195
|
return null;
|
|
200
196
|
}
|
|
201
|
-
|
|
202
|
-
logger.info(`正在抓取小红书页面: ${urlToFetch}`);
|
|
197
|
+
logger.debug(`正在抓取小红书页面: ${urlToFetch}`);
|
|
203
198
|
try {
|
|
204
|
-
|
|
205
|
-
const dbCache = await ctx.database.get('sla_cookie_cache', platformId);
|
|
206
|
-
// @ts-ignore
|
|
199
|
+
const dbCache = await ctx.database.get('sla_cookie_cache', exports.name);
|
|
207
200
|
let currentCookie = (dbCache && dbCache.length > 0) ? dbCache[0].cookie : '';
|
|
208
201
|
const requestHeaders = {
|
|
209
202
|
'User-Agent': config.userAgent,
|
|
@@ -236,14 +229,10 @@ async function process(ctx, config, link, session) {
|
|
|
236
229
|
let coverUrl = undefined;
|
|
237
230
|
const images = [];
|
|
238
231
|
if (noteData.type === 'video' && noteData.video) {
|
|
239
|
-
|
|
240
|
-
logger.info(`[XHS Video Debug] 发现视频笔记,视频数据对象: \n${JSON.stringify(noteData.video, null, 2)}`);
|
|
241
|
-
}
|
|
232
|
+
logger.debug(`[XHS Video Debug] 发现视频笔记,视频数据对象: \n${JSON.stringify(noteData.video, null, 2)}`);
|
|
242
233
|
if (noteData.video.media?.stream?.h264?.[0]?.masterUrl) {
|
|
243
234
|
videoUrl = noteData.video.media.stream.h264[0].masterUrl;
|
|
244
|
-
|
|
245
|
-
logger.info(`[XHS Video Debug] 已提取视频链接: ${videoUrl}`);
|
|
246
|
-
}
|
|
235
|
+
logger.debug(`[XHS Video Debug] 已提取视频链接: ${videoUrl}`);
|
|
247
236
|
}
|
|
248
237
|
else {
|
|
249
238
|
logger.warn('[XHS Video Debug] 未能从预期路径 `note.video.media.stream.h264[0].masterUrl` 找到视频链接。');
|
package/lib/types.d.ts
CHANGED
|
@@ -27,18 +27,21 @@ export interface PluginConfig {
|
|
|
27
27
|
usingLocal: boolean;
|
|
28
28
|
sendFiles: boolean;
|
|
29
29
|
sendLinks: boolean;
|
|
30
|
+
enableCache: boolean;
|
|
31
|
+
cacheExpiration: number;
|
|
32
|
+
autoCleanInterval: number;
|
|
30
33
|
format: string;
|
|
31
34
|
parseLimit: number;
|
|
32
35
|
useNumeral: boolean;
|
|
33
36
|
showError: boolean;
|
|
34
37
|
proxy: string;
|
|
35
|
-
proxy_settings:
|
|
36
|
-
default_parsers:
|
|
38
|
+
proxy_settings: Record<string, boolean>;
|
|
39
|
+
default_parsers: Record<string, boolean>;
|
|
37
40
|
allow_sensitive: boolean;
|
|
38
41
|
onebotReadDir: string;
|
|
39
42
|
localDownloadDir: string;
|
|
40
43
|
userAgent: string;
|
|
41
|
-
|
|
44
|
+
debug: boolean;
|
|
42
45
|
}
|
|
43
46
|
export interface BilibiliVideoInfo {
|
|
44
47
|
data: {
|
|
@@ -79,6 +82,32 @@ declare module 'koishi' {
|
|
|
79
82
|
BiliBiliVideo: any;
|
|
80
83
|
puppeteer?: any;
|
|
81
84
|
}
|
|
85
|
+
interface Tables {
|
|
86
|
+
sla_parse_cache: SlaParseCache;
|
|
87
|
+
sla_file_cache: SlaFileCache;
|
|
88
|
+
sla_cookie_cache: SlaCookieCache;
|
|
89
|
+
sla_group_settings: SlaGroupSettings;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export interface SlaParseCache {
|
|
93
|
+
key: string;
|
|
94
|
+
data: ParsedInfo;
|
|
95
|
+
created_at: number;
|
|
96
|
+
}
|
|
97
|
+
export interface SlaFileCache {
|
|
98
|
+
hash: string;
|
|
99
|
+
path: string;
|
|
100
|
+
url: string;
|
|
101
|
+
created_at: number;
|
|
102
|
+
}
|
|
103
|
+
export interface SlaCookieCache {
|
|
104
|
+
platform: string;
|
|
105
|
+
cookie: string;
|
|
106
|
+
}
|
|
107
|
+
export interface SlaGroupSettings {
|
|
108
|
+
guildId: string;
|
|
109
|
+
custom_parsers: Record<string, boolean>;
|
|
110
|
+
nsfw_enabled: boolean;
|
|
82
111
|
}
|
|
83
112
|
export interface XhsImageInfo {
|
|
84
113
|
imageScene: string;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ParsedInfo, PluginConfig } from './types';
|
|
2
2
|
import { Context, Logger, Session } from "koishi";
|
|
3
|
+
import { Agent as HttpAgent } from 'http';
|
|
4
|
+
import { Agent as HttpsAgent } from 'https';
|
|
5
|
+
import 'koishi-plugin-adapter-onebot';
|
|
3
6
|
/**
|
|
4
7
|
* 将数字格式化为易读的字符串(如 万、亿)
|
|
5
8
|
* @param num 数字
|
|
@@ -9,11 +12,12 @@ import { Context, Logger, Session } from "koishi";
|
|
|
9
12
|
export declare function numeral(num: number, config: PluginConfig): string;
|
|
10
13
|
export declare function escapeHtml(str: string): string;
|
|
11
14
|
export declare function unescapeHtml(str: string): string;
|
|
15
|
+
export declare function getProxyAgent(proxy: string | undefined, url: string): HttpAgent | HttpsAgent | undefined;
|
|
12
16
|
export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined, logger: Logger): Promise<number | null>;
|
|
13
17
|
export declare function getEffectiveSettings(ctx: Context, guildId: string | undefined, config: PluginConfig): Promise<{
|
|
14
|
-
parsers:
|
|
15
|
-
nsfw:
|
|
18
|
+
parsers: Record<string, boolean>;
|
|
19
|
+
nsfw: boolean;
|
|
16
20
|
}>;
|
|
17
21
|
export declare function isUserAdmin(session: Session, userId: string): Promise<boolean>;
|
|
18
|
-
export declare function sendResult_plain(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<void>;
|
|
19
|
-
export declare function sendResult_forward(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<void>;
|
|
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>;
|
package/lib/utils.js
CHANGED
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.numeral = numeral;
|
|
40
40
|
exports.escapeHtml = escapeHtml;
|
|
41
41
|
exports.unescapeHtml = unescapeHtml;
|
|
42
|
+
exports.getProxyAgent = getProxyAgent;
|
|
42
43
|
exports.getFileSize = getFileSize;
|
|
43
44
|
exports.getEffectiveSettings = getEffectiveSettings;
|
|
44
45
|
exports.isUserAdmin = isUserAdmin;
|
|
@@ -53,6 +54,8 @@ const url_1 = require("url");
|
|
|
53
54
|
const http_proxy_agent_1 = require("http-proxy-agent");
|
|
54
55
|
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
55
56
|
const fs = __importStar(require("node:fs"));
|
|
57
|
+
const crypto_1 = require("crypto");
|
|
58
|
+
require("koishi-plugin-adapter-onebot");
|
|
56
59
|
/**
|
|
57
60
|
* 将数字格式化为易读的字符串(如 万、亿)
|
|
58
61
|
* @param num 数字
|
|
@@ -122,14 +125,50 @@ function parseHtmlToSegments(html) {
|
|
|
122
125
|
}
|
|
123
126
|
return segments;
|
|
124
127
|
}
|
|
125
|
-
async function downloadAndMapUrl(url, proxy, userAgent, localDownloadDir, onebotReadDir, logger) {
|
|
128
|
+
async function downloadAndMapUrl(ctx, url, proxy, userAgent, localDownloadDir, onebotReadDir, logger, enableCache) {
|
|
126
129
|
await fs.promises.mkdir(localDownloadDir, { recursive: true });
|
|
130
|
+
// 1. 计算 Hash
|
|
131
|
+
const hash = (0, crypto_1.createHash)('md5').update(url).digest('hex');
|
|
127
132
|
const u = new url_1.URL(url);
|
|
128
133
|
const ext = path_1.default.extname(u.pathname).split('?')[0] || '.bin';
|
|
129
|
-
|
|
134
|
+
// 如果开启缓存,先查库
|
|
135
|
+
if (enableCache) {
|
|
136
|
+
try {
|
|
137
|
+
const cached = await ctx.database.get('sla_file_cache', hash);
|
|
138
|
+
if (cached.length > 0) {
|
|
139
|
+
const cachedPath = cached[0].path;
|
|
140
|
+
if (fs.existsSync(cachedPath)) {
|
|
141
|
+
const filename = path_1.default.basename(cachedPath);
|
|
142
|
+
const onebotPath = path_1.default.posix.join(onebotReadDir, filename);
|
|
143
|
+
logger.debug(`缓存命中: ${url} -> ${cachedPath}`);
|
|
144
|
+
return `file://${onebotPath}`;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// 数据库有记录但文件不存在,删除记录
|
|
148
|
+
await ctx.database.remove('sla_file_cache', { hash });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
logger.warn(`读取文件缓存失败,将重新下载: ${e}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// 2. 生成文件名 (使用 Hash 以实现去重)
|
|
157
|
+
const safeFilename = `${hash}${ext}`;
|
|
130
158
|
const actualPath = path_1.default.join(localDownloadDir, safeFilename);
|
|
131
159
|
const onebotPath = path_1.default.posix.join(onebotReadDir, safeFilename);
|
|
132
160
|
const fileUrl = `file://${onebotPath}`;
|
|
161
|
+
// 3. 检查本地文件是否存在 (双重保险,或者应对未清理的情况)
|
|
162
|
+
if (enableCache && fs.existsSync(actualPath)) {
|
|
163
|
+
// 补写数据库
|
|
164
|
+
await ctx.database.upsert('sla_file_cache', [{
|
|
165
|
+
hash,
|
|
166
|
+
path: actualPath,
|
|
167
|
+
url,
|
|
168
|
+
created_at: Date.now()
|
|
169
|
+
}]);
|
|
170
|
+
return fileUrl;
|
|
171
|
+
}
|
|
133
172
|
return new Promise((resolve, reject) => {
|
|
134
173
|
const agent = getProxyAgent(proxy, url);
|
|
135
174
|
const headers = {
|
|
@@ -144,7 +183,7 @@ async function downloadAndMapUrl(url, proxy, userAgent, localDownloadDir, onebot
|
|
|
144
183
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
145
184
|
req.destroy();
|
|
146
185
|
logger.debug(`重定向: ${url} -> ${res.headers.location}`);
|
|
147
|
-
downloadAndMapUrl(res.headers.location, proxy, userAgent, localDownloadDir, onebotReadDir, logger)
|
|
186
|
+
downloadAndMapUrl(ctx, res.headers.location, proxy, userAgent, localDownloadDir, onebotReadDir, logger, enableCache)
|
|
148
187
|
.then(resolve)
|
|
149
188
|
.catch(reject);
|
|
150
189
|
return;
|
|
@@ -163,8 +202,22 @@ async function downloadAndMapUrl(url, proxy, userAgent, localDownloadDir, onebot
|
|
|
163
202
|
}
|
|
164
203
|
const pipelineAsync = (0, util_1.promisify)(stream_1.pipeline);
|
|
165
204
|
pipelineAsync(res, (0, fs_1.createWriteStream)(actualPath))
|
|
166
|
-
.then(() => {
|
|
205
|
+
.then(async () => {
|
|
167
206
|
logger.debug(`下载成功: ${url} -> ${fileUrl}`);
|
|
207
|
+
// 下载成功,写入数据库缓存
|
|
208
|
+
if (enableCache) {
|
|
209
|
+
try {
|
|
210
|
+
await ctx.database.upsert('sla_file_cache', [{
|
|
211
|
+
hash,
|
|
212
|
+
path: actualPath,
|
|
213
|
+
url,
|
|
214
|
+
created_at: Date.now()
|
|
215
|
+
}]);
|
|
216
|
+
}
|
|
217
|
+
catch (dbErr) {
|
|
218
|
+
logger.warn(`写入文件缓存数据库失败: ${dbErr}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
168
221
|
resolve(fileUrl);
|
|
169
222
|
})
|
|
170
223
|
.catch((err) => {
|
|
@@ -287,13 +340,10 @@ async function getEffectiveSettings(ctx, guildId, config) {
|
|
|
287
340
|
nsfw: config.allow_sensitive
|
|
288
341
|
};
|
|
289
342
|
}
|
|
290
|
-
// @ts-ignore
|
|
291
343
|
const data = await ctx.database.get('sla_group_settings', guildId);
|
|
292
344
|
const record = data[0];
|
|
293
345
|
// 合并:自定义设置覆盖默认
|
|
294
|
-
// @ts-ignore
|
|
295
346
|
const effectiveParsers = { ...config.default_parsers, ...record?.custom_parsers ? record.custom_parsers : {} };
|
|
296
|
-
// @ts-ignore
|
|
297
347
|
const nsfw_enabled = record?.nsfw_enabled ? record.nsfw_enabled : config.allow_sensitive;
|
|
298
348
|
return {
|
|
299
349
|
parsers: effectiveParsers,
|
|
@@ -303,7 +353,7 @@ async function getEffectiveSettings(ctx, guildId, config) {
|
|
|
303
353
|
async function isUserAdmin(session, userId) {
|
|
304
354
|
if (!session.guildId)
|
|
305
355
|
return false;
|
|
306
|
-
//
|
|
356
|
+
// 使用 (session.user as any) 来规避类型检查,同时保留可选链以防 user 为空
|
|
307
357
|
if (session.user?.authority >= 3)
|
|
308
358
|
return true;
|
|
309
359
|
try {
|
|
@@ -322,10 +372,8 @@ async function isUserAdmin(session, userId) {
|
|
|
322
372
|
return true;
|
|
323
373
|
}
|
|
324
374
|
}
|
|
325
|
-
async function sendResult_plain(session, config, result, logger) {
|
|
326
|
-
|
|
327
|
-
logger.info('进入普通发送');
|
|
328
|
-
}
|
|
375
|
+
async function sendResult_plain(ctx, session, config, result, logger) {
|
|
376
|
+
logger.debug('进入普通发送');
|
|
329
377
|
const localDownloadDir = config.localDownloadDir;
|
|
330
378
|
const onebotReadDir = config.onebotReadDir;
|
|
331
379
|
let mediaCoverUrl = result.coverUrl;
|
|
@@ -333,15 +381,14 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
333
381
|
let proxy = undefined;
|
|
334
382
|
if (config.proxy_settings[result.platform]) {
|
|
335
383
|
proxy = config.proxy;
|
|
336
|
-
logger.
|
|
384
|
+
logger.debug("正在使用代理");
|
|
337
385
|
}
|
|
338
386
|
// --- 下载封面 ---
|
|
339
387
|
if (result.coverUrl) {
|
|
340
388
|
if (config.usingLocal) {
|
|
341
389
|
try {
|
|
342
|
-
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
343
|
-
|
|
344
|
-
logger.info(`封面已下载: ${mediaCoverUrl}`);
|
|
390
|
+
mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
391
|
+
logger.debug(`封面已下载: ${mediaCoverUrl}`);
|
|
345
392
|
}
|
|
346
393
|
catch (e) {
|
|
347
394
|
logger.warn(`封面下载失败: ${result.coverUrl}`, e);
|
|
@@ -360,10 +407,9 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
360
407
|
const remoteUrl = match[1];
|
|
361
408
|
if (config.usingLocal) {
|
|
362
409
|
try {
|
|
363
|
-
const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
410
|
+
const localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
364
411
|
urlMap[remoteUrl] = localUrl;
|
|
365
|
-
|
|
366
|
-
logger.info(`正文图片已下载: ${localUrl}`);
|
|
412
|
+
logger.debug(`正文图片已下载: ${localUrl}`);
|
|
367
413
|
}
|
|
368
414
|
catch (e) {
|
|
369
415
|
logger.warn(`正文图片下载失败: ${remoteUrl}`, e);
|
|
@@ -389,9 +435,7 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
389
435
|
message = message.replace(/{stats}/g, escapeHtml(result.stats || ''));
|
|
390
436
|
// 清理空行
|
|
391
437
|
const cleanMessage = message.split('\n').filter(line => line.trim() !== '' || line.includes('<')).join('\n');
|
|
392
|
-
|
|
393
|
-
logger.info(`解析结果: \n ${JSON.stringify(result, null, 2)}`);
|
|
394
|
-
}
|
|
438
|
+
logger.debug(`解析结果: \n ${JSON.stringify(result, null, 2)}`);
|
|
395
439
|
const sendPromises = [];
|
|
396
440
|
// 发送主消息
|
|
397
441
|
if (cleanMessage) {
|
|
@@ -409,19 +453,17 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
409
453
|
const maxBytes = config.Max_size * 1024 * 1024;
|
|
410
454
|
if (sizeBytes !== null && sizeBytes > maxBytes) {
|
|
411
455
|
shouldSend = false;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
|
|
417
|
-
}
|
|
456
|
+
const sizeMB = (sizeBytes / (1024 * 1024)).toFixed(2);
|
|
457
|
+
const maxMB = config.Max_size.toFixed(2);
|
|
458
|
+
sendPromises.push(session.send(`文件大小超限 (${sizeMB} MB > ${maxMB} MB)`));
|
|
459
|
+
logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
|
|
418
460
|
}
|
|
419
461
|
}
|
|
420
462
|
if (shouldSend) {
|
|
421
463
|
try {
|
|
422
464
|
let localUrl = remoteUrl;
|
|
423
465
|
if (config.usingLocal)
|
|
424
|
-
localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
466
|
+
localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
425
467
|
if (!localUrl)
|
|
426
468
|
continue;
|
|
427
469
|
let element = null;
|
|
@@ -438,14 +480,10 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
438
480
|
}
|
|
439
481
|
if (element) {
|
|
440
482
|
sendPromises.push(session.send(element));
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const size = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
|
|
446
|
-
const sizeMB = size ? (size / (1024 * 1024)).toFixed(2) : 'unknown';
|
|
447
|
-
logger.info(`${type} 已发送 (${sizeMB} MB): ${localUrl}`);
|
|
448
|
-
}
|
|
483
|
+
logger.debug(`${type} 直链 (${result.platform}): ${remoteUrl}`);
|
|
484
|
+
const size = await getFileSize(remoteUrl, proxy, config.userAgent, logger);
|
|
485
|
+
const sizeMB = size ? (size / (1024 * 1024)).toFixed(2) : 'unknown';
|
|
486
|
+
logger.debug(`${type} 已发送 (${sizeMB} MB): ${localUrl}`);
|
|
449
487
|
}
|
|
450
488
|
}
|
|
451
489
|
catch (e) {
|
|
@@ -456,10 +494,8 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
456
494
|
}
|
|
457
495
|
await Promise.all(sendPromises);
|
|
458
496
|
}
|
|
459
|
-
async function sendResult_forward(session, config, result, logger, mixed_sending = false) {
|
|
460
|
-
|
|
461
|
-
logger.info(mixed_sending ? '进入混合发送' : '进入合并发送');
|
|
462
|
-
}
|
|
497
|
+
async function sendResult_forward(ctx, session, config, result, logger, mixed_sending = false) {
|
|
498
|
+
logger.debug(mixed_sending ? '进入混合发送' : '进入合并发送');
|
|
463
499
|
const localDownloadDir = config.localDownloadDir;
|
|
464
500
|
const onebotReadDir = config.onebotReadDir;
|
|
465
501
|
let mediaCoverUrl = result.coverUrl;
|
|
@@ -473,7 +509,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
473
509
|
if (result.coverUrl) {
|
|
474
510
|
if (config.usingLocal) {
|
|
475
511
|
try {
|
|
476
|
-
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
512
|
+
mediaCoverUrl = await downloadAndMapUrl(ctx, result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
477
513
|
}
|
|
478
514
|
catch (e) {
|
|
479
515
|
logger.warn('封面下载失败', e);
|
|
@@ -491,7 +527,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
491
527
|
await Promise.all(imgUrls.map(async (url) => {
|
|
492
528
|
if (config.usingLocal) {
|
|
493
529
|
try {
|
|
494
|
-
urlMap[url] = await downloadAndMapUrl(url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
530
|
+
urlMap[url] = await downloadAndMapUrl(ctx, url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
495
531
|
}
|
|
496
532
|
catch (e) {
|
|
497
533
|
logger.warn(`正文图片下载失败: ${url}`, e);
|
|
@@ -507,7 +543,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
507
543
|
mediaMainbody = mediaMainbody.replace(new RegExp(escaped, 'g'), local);
|
|
508
544
|
}
|
|
509
545
|
}
|
|
510
|
-
// ===
|
|
546
|
+
// === 主消息 ===
|
|
511
547
|
let message = config.format;
|
|
512
548
|
message = message.replace(/{title}/g, result.title || '');
|
|
513
549
|
message = message.replace(/{authorName}/g, result.authorName || '');
|
|
@@ -555,7 +591,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
555
591
|
}
|
|
556
592
|
});
|
|
557
593
|
}
|
|
558
|
-
// --- 处理 files
|
|
594
|
+
// --- 处理 files ---
|
|
559
595
|
const extraSendPromises = [];
|
|
560
596
|
if (config.sendFiles && Array.isArray(result.files)) {
|
|
561
597
|
for (const file of result.files) {
|
|
@@ -568,30 +604,28 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
568
604
|
const maxBytes = config.Max_size * 1024 * 1024;
|
|
569
605
|
if (sizeBytes !== null && sizeBytes > maxBytes) {
|
|
570
606
|
shouldInclude = false;
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
text: `文件大小超限 (${sizeMB} MB > ${maxMB} MB)`
|
|
582
|
-
}
|
|
607
|
+
const sizeMB = (sizeBytes / (1024 * 1024)).toFixed(2);
|
|
608
|
+
const maxMB = config.Max_size.toFixed(2);
|
|
609
|
+
forwardNodes.push({
|
|
610
|
+
type: 'node',
|
|
611
|
+
data: {
|
|
612
|
+
user_id: session.selfId,
|
|
613
|
+
nickname: '分享助手',
|
|
614
|
+
content: {
|
|
615
|
+
type: 'text', data: {
|
|
616
|
+
text: `文件大小超限 (${sizeMB} MB > ${maxMB} MB)`
|
|
583
617
|
}
|
|
584
618
|
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
logger.info(`文件大小超限 (${sizeMB} MB > ${maxMB} MB),跳过: ${remoteUrl}`);
|
|
588
622
|
}
|
|
589
623
|
}
|
|
590
624
|
if (shouldInclude) {
|
|
591
625
|
try {
|
|
592
626
|
let localUrl = remoteUrl;
|
|
593
627
|
if (config.usingLocal)
|
|
594
|
-
localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
628
|
+
localUrl = await downloadAndMapUrl(ctx, remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger, config.enableCache);
|
|
595
629
|
if (!localUrl)
|
|
596
630
|
continue;
|
|
597
631
|
if (!mixed_sending) {
|
|
@@ -631,9 +665,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
631
665
|
extraSendPromises.push(session.send(element));
|
|
632
666
|
}
|
|
633
667
|
}
|
|
634
|
-
|
|
635
|
-
logger.info(`${type} 直链 (${result.platform}): ${remoteUrl}`);
|
|
636
|
-
}
|
|
668
|
+
logger.debug(`${type} 直链 (${result.platform}): ${remoteUrl}`);
|
|
637
669
|
}
|
|
638
670
|
catch (e) {
|
|
639
671
|
logger.warn(`${type} 下载失败: ${remoteUrl}`, e);
|
|
@@ -656,9 +688,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
656
688
|
}
|
|
657
689
|
if (forwardNodes.length === 0 && extraSendPromises.length === 0)
|
|
658
690
|
return;
|
|
659
|
-
|
|
660
|
-
logger.info(`解析结果: \n ${JSON.stringify(result, null, 2)}`);
|
|
661
|
-
}
|
|
691
|
+
logger.debug(`解析结果: \n ${JSON.stringify(result, null, 2)}`);
|
|
662
692
|
if (!(session.onebot && session.onebot._request))
|
|
663
693
|
throw new Error("Onebot is not defined");
|
|
664
694
|
const promises = [];
|