koishi-plugin-video-parser-all 1.2.2 → 1.2.5
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 +76 -48
- package/package.json +1 -1
- package/readme.md +1 -0
package/lib/index.js
CHANGED
|
@@ -11,8 +11,35 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const fs_1 = require("fs");
|
|
13
13
|
const promises_2 = require("stream/promises");
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const crypto_1 = require("crypto");
|
|
15
|
+
class SimpleLRUCache {
|
|
16
|
+
constructor(max, ttlMs) {
|
|
17
|
+
this.max = max;
|
|
18
|
+
this.ttlMs = ttlMs;
|
|
19
|
+
this.map = new Map();
|
|
20
|
+
}
|
|
21
|
+
get(key) {
|
|
22
|
+
const entry = this.map.get(key);
|
|
23
|
+
if (!entry)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (Date.now() > entry.expireAt) {
|
|
26
|
+
this.map.delete(key);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return entry.value;
|
|
30
|
+
}
|
|
31
|
+
set(key, value) {
|
|
32
|
+
this.map.delete(key);
|
|
33
|
+
while (this.map.size >= this.max) {
|
|
34
|
+
const k = this.map.keys().next().value;
|
|
35
|
+
if (k === undefined)
|
|
36
|
+
break;
|
|
37
|
+
this.map.delete(k);
|
|
38
|
+
}
|
|
39
|
+
this.map.set(key, { value, expireAt: Date.now() + this.ttlMs });
|
|
40
|
+
}
|
|
41
|
+
clear() { this.map.clear(); }
|
|
42
|
+
}
|
|
16
43
|
exports.name = 'video-parser-all';
|
|
17
44
|
exports.Config = koishi_1.Schema.intersect([
|
|
18
45
|
koishi_1.Schema.object({
|
|
@@ -22,7 +49,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
22
49
|
debug: koishi_1.Schema.boolean().default(false).description('开启调试模式,在控制台输出详细日志'),
|
|
23
50
|
}).description('基础设置'),
|
|
24
51
|
koishi_1.Schema.object({
|
|
25
|
-
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default(
|
|
52
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('统一消息格式,可用变量:${标题} ${作者} ${简介} ${点赞数} ${收藏数} ${转发数} ${播放数} ${评论数} ${视频时长} ${发布时间} ${图片数量} ${作者ID} ${封面}'),
|
|
26
53
|
}).description('消息格式设置'),
|
|
27
54
|
koishi_1.Schema.object({
|
|
28
55
|
showImageText: koishi_1.Schema.boolean().default(true).description('是否发送解析后的文字内容'),
|
|
@@ -121,44 +148,40 @@ function debugLog(level, ...args) {
|
|
|
121
148
|
}).join(' ')}`;
|
|
122
149
|
logger.info(message);
|
|
123
150
|
}
|
|
124
|
-
const urlCache = new
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
151
|
+
const urlCache = new SimpleLRUCache(500, 10 * 60 * 1000);
|
|
152
|
+
const LINK_RULES = [
|
|
153
|
+
{ pattern: /https?:\/\/(?:www\.)?bilibili\.com\/video\/([ab]v[0-9a-zA-Z_-]+)/gi, type: 'bilibili' },
|
|
154
|
+
{ pattern: /https?:\/\/b23\.tv\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
155
|
+
{ pattern: /https?:\/\/bili\d+\.cn\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
156
|
+
{ pattern: /https?:\/\/(?:www\.)?douyin\.com\/video\/\d{10,}/gi, type: 'douyin' },
|
|
157
|
+
{ pattern: /https?:\/\/v\.douyin\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'douyin' },
|
|
158
|
+
{ pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/short-video\/[0-9a-zA-Z_-]{10,}/gi, type: 'kuaishou' },
|
|
159
|
+
{ pattern: /https?:\/\/v\.kuaishou\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'kuaishou' },
|
|
160
|
+
{ pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/discovery\/item\/[0-9a-zA-Z_-]{10,}/gi, type: 'xiaohongshu' },
|
|
161
|
+
{ pattern: /https?:\/\/xhslink\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'xiaohongshu' },
|
|
162
|
+
{ pattern: /https?:\/\/weibo\.com\/\d+\/[0-9a-zA-Z_-]{10,}/gi, type: 'weibo' },
|
|
163
|
+
{ pattern: /https?:\/\/video\.weibo\.com\/show\?fid=[0-9a-zA-Z_-]{10,}/gi, type: 'weibo' },
|
|
164
|
+
{ pattern: /https?:\/\/(?:www\.)?ixigua\.com\/\d{10,}/gi, type: 'xigua' },
|
|
165
|
+
{ pattern: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}/gi, type: 'youtube' },
|
|
166
|
+
{ pattern: /https?:\/\/youtu\.be\/[a-zA-Z0-9_-]{11}/gi, type: 'youtube' },
|
|
167
|
+
{ pattern: /https?:\/\/(?:www\.)?tiktok\.com\/@[\w.]+\/video\/\d{10,}/gi, type: 'tiktok' },
|
|
168
|
+
{ pattern: /https?:\/\/vm\.tiktok\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'tiktok' },
|
|
169
|
+
{ pattern: /https?:\/\/(?:www\.)?acfun\.cn\/v\/ac\d{10,}/gi, type: 'acfun' },
|
|
170
|
+
{ pattern: /https?:\/\/(?:www\.)?zhihu\.com\/video\/\d{10,}/gi, type: 'zhihu' },
|
|
171
|
+
{ pattern: /https?:\/\/weishi\.qq\.com\/weishi\/feed\/[0-9a-zA-Z_-]{10,}/gi, type: 'weishi' },
|
|
172
|
+
{ pattern: /https?:\/\/(?:www\.)?huya\.com\/video\/[0-9a-zA-Z_-]{10,}/gi, type: 'huya' },
|
|
173
|
+
{ pattern: /https?:\/\/haokan\.baidu\.com\/v\?vid=[0-9a-zA-Z_-]{10,}/gi, type: 'haokan' },
|
|
174
|
+
{ pattern: /https?:\/\/(?:www\.)?meipai\.com\/media\/\d{10,}/gi, type: 'meipai' },
|
|
175
|
+
{ pattern: /https?:\/\/twitter\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
176
|
+
{ pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
177
|
+
{ pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_-]{10,}/gi, type: 'instagram' },
|
|
178
|
+
{ pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}/gi, type: 'doubao' },
|
|
179
|
+
];
|
|
129
180
|
function linkTypeParser(content) {
|
|
130
181
|
content = content.replace(/\\\//g, '/');
|
|
131
|
-
const rules = [
|
|
132
|
-
{ pattern: /https?:\/\/(?:www\.)?bilibili\.com\/video\/([ab]v[0-9a-zA-Z_-]+)/gi, type: 'bilibili' },
|
|
133
|
-
{ pattern: /https?:\/\/b23\.tv\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
134
|
-
{ pattern: /https?:\/\/bili\d+\.cn\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
135
|
-
{ pattern: /https?:\/\/(?:www\.)?douyin\.com\/video\/\d{10,}/gi, type: 'douyin' },
|
|
136
|
-
{ pattern: /https?:\/\/v\.douyin\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'douyin' },
|
|
137
|
-
{ pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/short-video\/[0-9a-zA-Z_-]{10,}/gi, type: 'kuaishou' },
|
|
138
|
-
{ pattern: /https?:\/\/v\.kuaishou\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'kuaishou' },
|
|
139
|
-
{ pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/discovery\/item\/[0-9a-zA-Z_-]{10,}/gi, type: 'xiaohongshu' },
|
|
140
|
-
{ pattern: /https?:\/\/xhslink\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'xiaohongshu' },
|
|
141
|
-
{ pattern: /https?:\/\/weibo\.com\/\d+\/[0-9a-zA-Z_-]{10,}/gi, type: 'weibo' },
|
|
142
|
-
{ pattern: /https?:\/\/video\.weibo\.com\/show\?fid=[0-9a-zA-Z_-]{10,}/gi, type: 'weibo' },
|
|
143
|
-
{ pattern: /https?:\/\/(?:www\.)?ixigua\.com\/\d{10,}/gi, type: 'xigua' },
|
|
144
|
-
{ pattern: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}/gi, type: 'youtube' },
|
|
145
|
-
{ pattern: /https?:\/\/youtu\.be\/[a-zA-Z0-9_-]{11}/gi, type: 'youtube' },
|
|
146
|
-
{ pattern: /https?:\/\/(?:www\.)?tiktok\.com\/@[\w.]+\/video\/\d{10,}/gi, type: 'tiktok' },
|
|
147
|
-
{ pattern: /https?:\/\/vm\.tiktok\.com\/[0-9a-zA-Z_-]{8,}/gi, type: 'tiktok' },
|
|
148
|
-
{ pattern: /https?:\/\/(?:www\.)?acfun\.cn\/v\/ac\d{10,}/gi, type: 'acfun' },
|
|
149
|
-
{ pattern: /https?:\/\/(?:www\.)?zhihu\.com\/video\/\d{10,}/gi, type: 'zhihu' },
|
|
150
|
-
{ pattern: /https?:\/\/weishi\.qq\.com\/weishi\/feed\/[0-9a-zA-Z_-]{10,}/gi, type: 'weishi' },
|
|
151
|
-
{ pattern: /https?:\/\/(?:www\.)?huya\.com\/video\/[0-9a-zA-Z_-]{10,}/gi, type: 'huya' },
|
|
152
|
-
{ pattern: /https?:\/\/haokan\.baidu\.com\/v\?vid=[0-9a-zA-Z_-]{10,}/gi, type: 'haokan' },
|
|
153
|
-
{ pattern: /https?:\/\/(?:www\.)?meipai\.com\/media\/\d{10,}/gi, type: 'meipai' },
|
|
154
|
-
{ pattern: /https?:\/\/twitter\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
155
|
-
{ pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
156
|
-
{ pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_-]{10,}/gi, type: 'instagram' },
|
|
157
|
-
{ pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}/gi, type: 'doubao' },
|
|
158
|
-
];
|
|
159
182
|
const matches = [];
|
|
160
183
|
const seen = new Set();
|
|
161
|
-
for (const rule of
|
|
184
|
+
for (const rule of LINK_RULES) {
|
|
162
185
|
let match;
|
|
163
186
|
rule.pattern.lastIndex = 0;
|
|
164
187
|
while ((match = rule.pattern.exec(content)) !== null) {
|
|
@@ -372,6 +395,7 @@ function parseApiResponse(raw, maxDescLen) {
|
|
|
372
395
|
duration, publishTime
|
|
373
396
|
};
|
|
374
397
|
}
|
|
398
|
+
const formatVarRegex = /\$\{([^}]+)\}/g;
|
|
375
399
|
function generateFormattedText(p, format) {
|
|
376
400
|
const imageCount = p.images.length || p.live_photo.length;
|
|
377
401
|
const vars = {
|
|
@@ -390,14 +414,18 @@ function generateFormattedText(p, format) {
|
|
|
390
414
|
'封面': p.cover,
|
|
391
415
|
'视频链接': p.video,
|
|
392
416
|
};
|
|
417
|
+
const varReplacements = Object.entries(vars).map(([key, val]) => ({
|
|
418
|
+
regex: new RegExp(`\\$\\{${key}\\}`, 'g'),
|
|
419
|
+
value: val,
|
|
420
|
+
}));
|
|
393
421
|
const lines = format.split('\n');
|
|
394
422
|
const resultLines = [];
|
|
395
423
|
for (const line of lines) {
|
|
396
|
-
const varMatches = line.match(
|
|
424
|
+
const varMatches = line.match(formatVarRegex);
|
|
397
425
|
if (varMatches) {
|
|
398
426
|
let allEmpty = true;
|
|
399
427
|
for (const match of varMatches) {
|
|
400
|
-
const varName = match.
|
|
428
|
+
const varName = match.slice(2, -1);
|
|
401
429
|
const val = vars[varName];
|
|
402
430
|
if (val && val !== '0') {
|
|
403
431
|
allEmpty = false;
|
|
@@ -408,8 +436,8 @@ function generateFormattedText(p, format) {
|
|
|
408
436
|
continue;
|
|
409
437
|
}
|
|
410
438
|
let newLine = line;
|
|
411
|
-
for (const
|
|
412
|
-
newLine = newLine.replace(
|
|
439
|
+
for (const { regex, value } of varReplacements) {
|
|
440
|
+
newLine = newLine.replace(regex, value);
|
|
413
441
|
}
|
|
414
442
|
resultLines.push(newLine);
|
|
415
443
|
}
|
|
@@ -434,15 +462,14 @@ function buildForwardNode(session, content, botName) {
|
|
|
434
462
|
function getErrorMessage(error) {
|
|
435
463
|
if (error instanceof Error)
|
|
436
464
|
return error.message;
|
|
465
|
+
if (error && typeof error === 'object' && 'message' in error)
|
|
466
|
+
return String(error.message);
|
|
437
467
|
return String(error);
|
|
438
468
|
}
|
|
439
469
|
function apply(ctx, config) {
|
|
440
470
|
debugEnabled = config.debug || false;
|
|
441
471
|
debugLog('INFO', '插件初始化开始');
|
|
442
|
-
const dedupCache = new
|
|
443
|
-
max: 1000,
|
|
444
|
-
ttl: config.deduplicationInterval * 1000,
|
|
445
|
-
});
|
|
472
|
+
const dedupCache = new SimpleLRUCache(1000, config.deduplicationInterval * 1000);
|
|
446
473
|
const texts = {
|
|
447
474
|
waitingTipText: config.waitingTipText || '正在解析视频,请稍候...',
|
|
448
475
|
unsupportedPlatformText: config.unsupportedPlatformText || '不支持该平台链接',
|
|
@@ -506,7 +533,7 @@ function apply(ctx, config) {
|
|
|
506
533
|
throw new Error('视频链接为空');
|
|
507
534
|
const tempDir = config.tempDir || './temp_videos';
|
|
508
535
|
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
509
|
-
const fileName = `video_${Date.now()}_${
|
|
536
|
+
const fileName = `video_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.mp4`;
|
|
510
537
|
const filePath = path_1.default.resolve(tempDir, fileName);
|
|
511
538
|
debugLog('INFO', `开始下载视频: ${videoUrl.substring(0, 100)}...`);
|
|
512
539
|
debugLog('INFO', `临时文件路径: ${filePath}`);
|
|
@@ -522,6 +549,7 @@ function apply(ctx, config) {
|
|
|
522
549
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
523
550
|
'Referer': 'https://www.bilibili.com/',
|
|
524
551
|
},
|
|
552
|
+
maxRedirects: 5,
|
|
525
553
|
validateStatus: (status) => status >= 200 && status < 300,
|
|
526
554
|
});
|
|
527
555
|
}
|
|
@@ -530,7 +558,7 @@ function apply(ctx, config) {
|
|
|
530
558
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
531
559
|
throw new Error(`下载视频失败: ${getErrorMessage(e)}`);
|
|
532
560
|
}
|
|
533
|
-
const maxSizeBytes = (config.maxVideoSize
|
|
561
|
+
const maxSizeBytes = (config.maxVideoSize ?? 0) * 1024 * 1024;
|
|
534
562
|
const contentLength = Number(response.headers['content-length'] || 0);
|
|
535
563
|
if (maxSizeBytes > 0 && contentLength > maxSizeBytes) {
|
|
536
564
|
writer.destroy();
|
|
@@ -904,7 +932,7 @@ function apply(ctx, config) {
|
|
|
904
932
|
dedupCache.clear();
|
|
905
933
|
debugLog('INFO', '插件已卸载,资源已清理');
|
|
906
934
|
});
|
|
907
|
-
process.on('
|
|
935
|
+
process.on('beforeExit', async () => {
|
|
908
936
|
try {
|
|
909
937
|
const tempDir = config.tempDir || './temp_videos';
|
|
910
938
|
const files = await promises_1.default.readdir(tempDir);
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -141,6 +141,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
141
141
|
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
142
142
|
|----------------------|-------------------------|
|
|
143
143
|
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
144
|
+
| ShiraiKuroko003 | 修复消息格式设置问题并且PR-1.2.5版本已修复 |
|
|
144
145
|
| JH-Ahua | BugPk-Api 支持 |
|
|
145
146
|
| shangxue | 灵感来源 |
|
|
146
147
|
|