koishi-plugin-share-links-analysis 0.5.0 → 0.5.1
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.d.ts +1 -0
- package/lib/core.js +2 -0
- package/lib/index.js +5 -2
- package/lib/types.d.ts +1 -0
- package/lib/utils.d.ts +1 -1
- package/lib/utils.js +87 -11
- package/package.json +1 -1
- package/lib/parsers/tmp.json +0 -46
package/lib/core.d.ts
CHANGED
package/lib/core.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.parsers_str = void 0;
|
|
37
38
|
exports.resolveLinks = resolveLinks;
|
|
38
39
|
exports.processLink = processLink;
|
|
39
40
|
exports.init = init;
|
|
@@ -42,6 +43,7 @@ const Xiaohongshu = __importStar(require("./parsers/xiaohongshu"));
|
|
|
42
43
|
const Twitter = __importStar(require("./parsers/twitter"));
|
|
43
44
|
// 定义所有支持的解析器
|
|
44
45
|
const parsers = [Bilibili, Xiaohongshu, Twitter];
|
|
46
|
+
exports.parsers_str = ['bilibili', 'xiaohongshu', 'twitter'];
|
|
45
47
|
/**
|
|
46
48
|
* 从文本中解析出所有支持的链接
|
|
47
49
|
* @param content 消息内容
|
package/lib/index.js
CHANGED
|
@@ -24,7 +24,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
24
24
|
koishi_1.Schema.const('1').description('低清晰度优先'),
|
|
25
25
|
koishi_1.Schema.const('2').description('高清晰度优先'),
|
|
26
26
|
]).role('radio').default('1').description("发送的视频清晰度优先策略"),
|
|
27
|
-
Max_size: koishi_1.Schema.number().default(
|
|
27
|
+
Max_size: koishi_1.Schema.number().default(20).description("允许发送的最大文件大小(Mb)"),
|
|
28
28
|
Max_size_tip: koishi_1.Schema.string().default('文件体积过大,策略已阻止发送').description("对大文件的文字提示内容"),
|
|
29
29
|
MinimumTimeInterval: koishi_1.Schema.number().default(600).description("若干秒内不再处理相同链接,防止刷屏").min(1),
|
|
30
30
|
waitTip_Switch: koishi_1.Schema.union([
|
|
@@ -53,8 +53,11 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
53
53
|
useNumeral: koishi_1.Schema.boolean().default(true).description("使用格式化数字 (如 10000 -> 1万)"),
|
|
54
54
|
showError: koishi_1.Schema.boolean().default(false).description("当链接不正确时提醒发送者"),
|
|
55
55
|
allow_sensitive: koishi_1.Schema.boolean().default(false).description("允许NSFW内容"),
|
|
56
|
-
proxy: koishi_1.Schema.string().description("代理设置"),
|
|
57
56
|
}).description("高级解析设置"),
|
|
57
|
+
koishi_1.Schema.object({
|
|
58
|
+
proxy: koishi_1.Schema.string().description("代理设置"),
|
|
59
|
+
proxy_settings: koishi_1.Schema.object(Object.fromEntries(core_1.parsers_str.map(parser => [parser, koishi_1.Schema.boolean().default(false)]))),
|
|
60
|
+
}).description("代理设置"),
|
|
58
61
|
koishi_1.Schema.object({
|
|
59
62
|
onebotReadDir: koishi_1.Schema.string().description('OneBot 实现 (如 NapCat) 所在的容器或环境提供的路径前缀。').default("/app/.config/QQ/NapCat/temp"),
|
|
60
63
|
localDownloadDir: koishi_1.Schema.string().description('与上述路径对应的、Koishi 所在的容器或主机可以访问的路径前缀。').default("/koishi/data/temp"),
|
package/lib/types.d.ts
CHANGED
package/lib/utils.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ import { Logger, Session } from "koishi";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function numeral(num: number, config: PluginConfig): string;
|
|
10
10
|
export declare function escapeHtml(str: string): string;
|
|
11
|
-
export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined): Promise<number | null>;
|
|
11
|
+
export declare function getFileSize(url: string, proxy: string | undefined, userAgent: string | undefined, logger: Logger): Promise<number | null>;
|
|
12
12
|
export declare function sendResult_plain(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger): Promise<void>;
|
|
13
13
|
export declare function sendResult_forward(session: Session, config: PluginConfig, result: ParsedInfo, logger: Logger, mixed_sending?: boolean): Promise<void>;
|
package/lib/utils.js
CHANGED
|
@@ -170,7 +170,22 @@ async function downloadAndMapUrl(url, proxy, userAgent, localDownloadDir, onebot
|
|
|
170
170
|
});
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
-
async function getFileSize(url, proxy, userAgent) {
|
|
173
|
+
async function getFileSize(url, proxy, userAgent, logger) {
|
|
174
|
+
try {
|
|
175
|
+
// 先尝试HEAD请求(标准方式)
|
|
176
|
+
const headSize = await tryHeadRequest(url, proxy, userAgent, logger);
|
|
177
|
+
if (headSize !== null) {
|
|
178
|
+
return headSize;
|
|
179
|
+
}
|
|
180
|
+
// HEAD失败,尝试GET请求获取部分内容
|
|
181
|
+
return await tryGetRequestForSize(url, proxy, userAgent, logger);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
logger.warn(`获取文件大小失败: ${url}`, e);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function tryHeadRequest(url, proxy, userAgent, logger) {
|
|
174
189
|
return new Promise((resolve) => {
|
|
175
190
|
const u = new url_1.URL(url);
|
|
176
191
|
const agent = getProxyAgent(proxy, url);
|
|
@@ -180,7 +195,8 @@ async function getFileSize(url, proxy, userAgent) {
|
|
|
180
195
|
method: 'HEAD',
|
|
181
196
|
timeout: 10000,
|
|
182
197
|
headers: {
|
|
183
|
-
'User-Agent': userAgent
|
|
198
|
+
'User-Agent': userAgent,
|
|
199
|
+
'Referer': 'https://www.bilibili.com/'
|
|
184
200
|
}
|
|
185
201
|
}, (res) => {
|
|
186
202
|
const len = res.headers['content-length'];
|
|
@@ -192,11 +208,61 @@ async function getFileSize(url, proxy, userAgent) {
|
|
|
192
208
|
}
|
|
193
209
|
req.destroy();
|
|
194
210
|
});
|
|
195
|
-
req.on('error', () => {
|
|
211
|
+
req.on('error', (err) => {
|
|
212
|
+
logger.warn(`HEAD请求失败: ${url}`, err);
|
|
213
|
+
req.destroy();
|
|
214
|
+
resolve(null);
|
|
215
|
+
});
|
|
216
|
+
req.on('timeout', () => {
|
|
217
|
+
logger.warn(`HEAD请求超时: ${url}`);
|
|
218
|
+
req.destroy();
|
|
219
|
+
resolve(null);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function tryGetRequestForSize(url, proxy, userAgent, logger) {
|
|
224
|
+
proxy = undefined;
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
const u = new url_1.URL(url);
|
|
227
|
+
const agent = getProxyAgent(proxy, url);
|
|
228
|
+
const getter = u.protocol === 'https:' ? require('https').get : require('http').get;
|
|
229
|
+
const req = getter(url, {
|
|
230
|
+
agent,
|
|
231
|
+
timeout: 15000,
|
|
232
|
+
headers: {
|
|
233
|
+
'User-Agent': userAgent,
|
|
234
|
+
'Range': 'bytes=0-1023' // 只请求前1KB
|
|
235
|
+
}
|
|
236
|
+
}, (res) => {
|
|
237
|
+
const contentRange = res.headers['content-range'];
|
|
238
|
+
if (contentRange) {
|
|
239
|
+
// 从 Content-Range 头获取总大小,例如: "bytes 0-1023/12345678"
|
|
240
|
+
const match = contentRange.match(/\/(\d+)$/);
|
|
241
|
+
if (match) {
|
|
242
|
+
resolve(parseInt(match[1], 10));
|
|
243
|
+
req.destroy();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const len = res.headers['content-length'];
|
|
248
|
+
if (len && /^\d+$/.test(len)) {
|
|
249
|
+
resolve(parseInt(len, 10));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
resolve(null);
|
|
253
|
+
}
|
|
254
|
+
// 读取少量数据后关闭连接
|
|
255
|
+
res.on('data', () => {
|
|
256
|
+
req.destroy();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
req.on('error', (err) => {
|
|
260
|
+
logger.warn(`GET请求获取大小失败: ${url}`, err);
|
|
196
261
|
req.destroy();
|
|
197
262
|
resolve(null);
|
|
198
263
|
});
|
|
199
264
|
req.on('timeout', () => {
|
|
265
|
+
logger.warn(`GET请求获取大小超时: ${url}`);
|
|
200
266
|
req.destroy();
|
|
201
267
|
resolve(null);
|
|
202
268
|
});
|
|
@@ -211,10 +277,15 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
211
277
|
let mediaCoverUrl = result.coverUrl;
|
|
212
278
|
let mediaVideoUrl = result.videoUrl || null;
|
|
213
279
|
let mediaMainbody = result.mainbody;
|
|
280
|
+
let proxy = undefined;
|
|
281
|
+
if (config.proxy_settings[result.platform]) {
|
|
282
|
+
proxy = config.proxy;
|
|
283
|
+
logger.info("正在使用代理");
|
|
284
|
+
}
|
|
214
285
|
// --- 下载封面 ---
|
|
215
286
|
if (result.coverUrl) {
|
|
216
287
|
try {
|
|
217
|
-
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl,
|
|
288
|
+
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
218
289
|
if (config.logLevel === 'full')
|
|
219
290
|
logger.info(`封面已下载: ${mediaCoverUrl}`);
|
|
220
291
|
}
|
|
@@ -226,7 +297,7 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
226
297
|
// --- 视频:先检查大小 ---
|
|
227
298
|
let videoExceedsLimit = false;
|
|
228
299
|
if (result.videoUrl) {
|
|
229
|
-
const sizeBytes = await getFileSize(result.videoUrl,
|
|
300
|
+
const sizeBytes = await getFileSize(result.videoUrl, proxy, config.userAgent, logger);
|
|
230
301
|
const maxBytes = config.Max_size !== undefined ? config.Max_size * 1024 * 1024 : undefined;
|
|
231
302
|
// 日志用 MB(保留 2 位小数)
|
|
232
303
|
const formatMB = (bytes) => (bytes / (1024 * 1024)).toFixed(2);
|
|
@@ -246,7 +317,7 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
246
317
|
else {
|
|
247
318
|
// 大小合规,执行下载
|
|
248
319
|
try {
|
|
249
|
-
mediaVideoUrl = await downloadAndMapUrl(result.videoUrl,
|
|
320
|
+
mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
250
321
|
if (config.logLevel === 'full') {
|
|
251
322
|
logger.info(`视频已下载 (${sizeMB} MB): ${mediaVideoUrl}`);
|
|
252
323
|
}
|
|
@@ -265,7 +336,7 @@ async function sendResult_plain(session, config, result, logger) {
|
|
|
265
336
|
await Promise.all(imgMatches.map(async (match) => {
|
|
266
337
|
const remoteUrl = match[1];
|
|
267
338
|
try {
|
|
268
|
-
const localUrl = await downloadAndMapUrl(remoteUrl,
|
|
339
|
+
const localUrl = await downloadAndMapUrl(remoteUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
269
340
|
urlMap[remoteUrl] = localUrl;
|
|
270
341
|
if (config.logLevel === 'full')
|
|
271
342
|
logger.info(`正文图片已下载: ${localUrl}`);
|
|
@@ -326,10 +397,15 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
326
397
|
let mediaCoverUrl = result.coverUrl;
|
|
327
398
|
let mediaVideoUrl = result.videoUrl || null;
|
|
328
399
|
let mediaMainbody = result.mainbody;
|
|
400
|
+
let proxy = undefined;
|
|
401
|
+
if (config.proxy_settings[result.platform]) {
|
|
402
|
+
proxy = config.proxy;
|
|
403
|
+
logger.info("正在使用代理");
|
|
404
|
+
}
|
|
329
405
|
// --- 封面 ---
|
|
330
406
|
if (result.coverUrl) {
|
|
331
407
|
try {
|
|
332
|
-
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl,
|
|
408
|
+
mediaCoverUrl = await downloadAndMapUrl(result.coverUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
333
409
|
}
|
|
334
410
|
catch (e) {
|
|
335
411
|
logger.warn('封面下载失败', e);
|
|
@@ -339,7 +415,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
339
415
|
// --- 视频大小检查 + 下载 ---
|
|
340
416
|
let videoExceedsLimit = false;
|
|
341
417
|
if (result.videoUrl) {
|
|
342
|
-
const sizeBytes = await getFileSize(result.videoUrl,
|
|
418
|
+
const sizeBytes = await getFileSize(result.videoUrl, proxy, config.userAgent, logger);
|
|
343
419
|
const maxBytes = config.Max_size !== undefined ? config.Max_size * 1024 * 1024 : undefined;
|
|
344
420
|
const formatMB = (bytes) => (bytes / (1024 * 1024)).toFixed(2);
|
|
345
421
|
if (sizeBytes === null) {
|
|
@@ -357,7 +433,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
357
433
|
}
|
|
358
434
|
else {
|
|
359
435
|
try {
|
|
360
|
-
mediaVideoUrl = await downloadAndMapUrl(result.videoUrl,
|
|
436
|
+
mediaVideoUrl = await downloadAndMapUrl(result.videoUrl, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
361
437
|
if (config.logLevel === 'full') {
|
|
362
438
|
logger.info(`视频已下载 (${sizeMB} MB): ${mediaVideoUrl}`);
|
|
363
439
|
}
|
|
@@ -375,7 +451,7 @@ async function sendResult_forward(session, config, result, logger, mixed_sending
|
|
|
375
451
|
const urlMap = {};
|
|
376
452
|
await Promise.all(imgUrls.map(async (url) => {
|
|
377
453
|
try {
|
|
378
|
-
urlMap[url] = await downloadAndMapUrl(url,
|
|
454
|
+
urlMap[url] = await downloadAndMapUrl(url, proxy, config.userAgent, localDownloadDir, onebotReadDir, logger);
|
|
379
455
|
}
|
|
380
456
|
catch (e) {
|
|
381
457
|
logger.warn(`正文图片下载失败: ${url}`, e);
|
package/package.json
CHANGED
package/lib/parsers/tmp.json
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
allSameType: true,
|
|
3
|
-
article: null,
|
|
4
|
-
combinedMediaUrl: null,
|
|
5
|
-
communityNote: null,
|
|
6
|
-
conversationID: '1988209837298377142',
|
|
7
|
-
date: 'Tue Nov 11 11:38:59 +0000 2025',
|
|
8
|
-
date_epoch: 1762861139,
|
|
9
|
-
fetched_on: 1763038701,
|
|
10
|
-
hasMedia: true,
|
|
11
|
-
hashtags: [],
|
|
12
|
-
lang: 'zxx',
|
|
13
|
-
likes: 80,
|
|
14
|
-
mediaURLs: [
|
|
15
|
-
'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg'
|
|
16
|
-
],
|
|
17
|
-
media_extended: [
|
|
18
|
-
{
|
|
19
|
-
altText: null,
|
|
20
|
-
id_str: '1988209827773116417',
|
|
21
|
-
size: {
|
|
22
|
-
height: 1840,
|
|
23
|
-
width: 1840
|
|
24
|
-
},
|
|
25
|
-
thumbnail_url: 'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg',
|
|
26
|
-
type: 'image',
|
|
27
|
-
url: 'https://pbs.twimg.com/media/G5eKTetbcAE9wtk.jpg'
|
|
28
|
-
}
|
|
29
|
-
],
|
|
30
|
-
pollData: null,
|
|
31
|
-
possibly_sensitive: false,
|
|
32
|
-
qrt: null,
|
|
33
|
-
qrtURL: null,
|
|
34
|
-
replies: 5,
|
|
35
|
-
replyingTo: null,
|
|
36
|
-
replyingToID: null,
|
|
37
|
-
retweet: null,
|
|
38
|
-
retweetURL: null,
|
|
39
|
-
retweets: 4,
|
|
40
|
-
text: 'https://t.co/b8AyvpjhAE',
|
|
41
|
-
tweetID: '1988209837298377142',
|
|
42
|
-
tweetURL: 'https://twitter.com/moyushuang_/status/1988209837298377142',
|
|
43
|
-
user_name: '魔芋凉粉',
|
|
44
|
-
user_profile_image_url: 'https://pbs.twimg.com/profile_images/1830265587676962816/KM46JNnI_normal.jpg',
|
|
45
|
-
user_screen_name: 'moyushuang_'
|
|
46
|
-
}
|