koishi-plugin-video-parser-all 0.9.9 → 1.0.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/index.d.ts +2 -0
- package/lib/index.js +56 -34
- package/package.json +1 -1
- package/readme.md +13 -5
package/lib/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const Config: Schema<{
|
|
|
14
14
|
videoDownloadTimeout?: number | null | undefined;
|
|
15
15
|
tempDir?: string | null | undefined;
|
|
16
16
|
maxVideoSize?: number | null | undefined;
|
|
17
|
+
forceDownloadVideo?: boolean | null | undefined;
|
|
17
18
|
} & {
|
|
18
19
|
timeout?: number | null | undefined;
|
|
19
20
|
videoSendTimeout?: number | null | undefined;
|
|
@@ -44,6 +45,7 @@ export declare const Config: Schema<{
|
|
|
44
45
|
videoDownloadTimeout: number;
|
|
45
46
|
tempDir: string;
|
|
46
47
|
maxVideoSize: number;
|
|
48
|
+
forceDownloadVideo: boolean;
|
|
47
49
|
} & {
|
|
48
50
|
timeout: number;
|
|
49
51
|
videoSendTimeout: number;
|
package/lib/index.js
CHANGED
|
@@ -28,7 +28,8 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
28
28
|
maxDescLength: koishi_1.Schema.number().default(200).description('简介内容最大长度(字符),超出自动截断'),
|
|
29
29
|
videoDownloadTimeout: koishi_1.Schema.number().default(120000).description('视频下载超时(毫秒)'),
|
|
30
30
|
tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
|
|
31
|
-
maxVideoSize: koishi_1.Schema.number().default(
|
|
31
|
+
maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制大小'),
|
|
32
|
+
forceDownloadVideo: koishi_1.Schema.boolean().default(true).description('强制下载视频后发送(解决部分平台URL无法直接发送的问题)'),
|
|
32
33
|
}).description('内容显示设置'),
|
|
33
34
|
koishi_1.Schema.object({
|
|
34
35
|
timeout: koishi_1.Schema.number().min(0).default(180000).description('API 请求超时(毫秒)'),
|
|
@@ -41,7 +42,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
41
42
|
retryInterval: koishi_1.Schema.number().min(0).default(1000).description('重试间隔(毫秒,同时用于消息发送重试)'),
|
|
42
43
|
}).description('错误与重试设置'),
|
|
43
44
|
koishi_1.Schema.object({
|
|
44
|
-
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot
|
|
45
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot 平台),视频会单独发送'),
|
|
45
46
|
}).description('发送方式设置'),
|
|
46
47
|
koishi_1.Schema.object({
|
|
47
48
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('解析等待提示'),
|
|
@@ -208,7 +209,7 @@ function formatDuration(seconds) {
|
|
|
208
209
|
const s = Math.floor(seconds % 60);
|
|
209
210
|
if (h > 0)
|
|
210
211
|
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
211
|
-
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
212
|
+
return `${m}:${s.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
212
213
|
}
|
|
213
214
|
function formatPublishTime(ms) {
|
|
214
215
|
if (!ms)
|
|
@@ -362,7 +363,7 @@ function buildForwardNode(session, content, botName) {
|
|
|
362
363
|
}
|
|
363
364
|
const urlCache = new Map();
|
|
364
365
|
const CACHE_TTL = 10 * 60 * 1000;
|
|
365
|
-
async function downloadVideoFile(videoUrl, tempDir, timeout,
|
|
366
|
+
async function downloadVideoFile(videoUrl, tempDir, timeout, maxSizeMB) {
|
|
366
367
|
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
367
368
|
const fileName = `video_${Date.now()}_${Math.random().toString(36).substring(2, 8)}.mp4`;
|
|
368
369
|
const filePath = path_1.default.join(tempDir, fileName);
|
|
@@ -376,20 +377,21 @@ async function downloadVideoFile(videoUrl, tempDir, timeout, maxSize) {
|
|
|
376
377
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
377
378
|
}
|
|
378
379
|
});
|
|
380
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
379
381
|
const contentLength = Number(response.headers['content-length'] || 0);
|
|
380
|
-
if (contentLength >
|
|
382
|
+
if (maxSizeMB > 0 && contentLength > maxSizeBytes) {
|
|
381
383
|
writer.destroy();
|
|
382
384
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
383
|
-
throw new Error(`视频文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${
|
|
385
|
+
throw new Error(`视频文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${maxSizeMB}MB)`);
|
|
384
386
|
}
|
|
385
387
|
let downloadedSize = 0;
|
|
386
388
|
response.data.on('data', (chunk) => {
|
|
387
389
|
downloadedSize += chunk.length;
|
|
388
|
-
if (downloadedSize >
|
|
390
|
+
if (maxSizeMB > 0 && downloadedSize > maxSizeBytes) {
|
|
389
391
|
response.data.destroy();
|
|
390
392
|
writer.destroy();
|
|
391
393
|
promises_1.default.unlink(filePath).catch(() => { });
|
|
392
|
-
throw new Error(`视频文件过大,超过限制(${
|
|
394
|
+
throw new Error(`视频文件过大,超过限制(${maxSizeMB}MB)`);
|
|
393
395
|
}
|
|
394
396
|
});
|
|
395
397
|
await (0, promises_2.pipeline)(response.data, writer);
|
|
@@ -400,6 +402,19 @@ function getErrorMessage(error) {
|
|
|
400
402
|
return error.message;
|
|
401
403
|
return String(error);
|
|
402
404
|
}
|
|
405
|
+
function isSpecialPlatformVideo(url) {
|
|
406
|
+
const specialHosts = [
|
|
407
|
+
'bilibili.com',
|
|
408
|
+
'akamaized.net',
|
|
409
|
+
'hdslb.com',
|
|
410
|
+
'xiaohongshu.com',
|
|
411
|
+
'xhslink.com',
|
|
412
|
+
'zhihu.com',
|
|
413
|
+
'weibo.com',
|
|
414
|
+
'sinaimg.cn'
|
|
415
|
+
];
|
|
416
|
+
return specialHosts.some(host => url.includes(host));
|
|
417
|
+
}
|
|
403
418
|
function apply(ctx, config) {
|
|
404
419
|
debugEnabled = config.debug || false;
|
|
405
420
|
debugLog('INFO', '插件初始化开始');
|
|
@@ -513,23 +528,29 @@ function apply(ctx, config) {
|
|
|
513
528
|
async function sendVideoFile(session, videoUrl) {
|
|
514
529
|
if (!videoUrl)
|
|
515
530
|
throw new Error('视频链接为空');
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
519
|
-
}
|
|
520
|
-
catch (err) {
|
|
521
|
-
debugLog('ERROR', `直接发送URL失败,开始下载视频: ${getErrorMessage(err)}`);
|
|
522
|
-
let tempFilePath = null;
|
|
531
|
+
const shouldForceDownload = config.forceDownloadVideo || isSpecialPlatformVideo(videoUrl);
|
|
532
|
+
if (!shouldForceDownload) {
|
|
523
533
|
try {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
debugLog('INFO', `视频下载完成,发送本地文件: ${localFile}`);
|
|
527
|
-
return await sendWithTimeout(session, koishi_1.h.video(localFile));
|
|
534
|
+
debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
|
|
535
|
+
return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
528
536
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
537
|
+
catch (err) {
|
|
538
|
+
debugLog('ERROR', `直接发送URL失败,开始下载视频: ${getErrorMessage(err)}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
debugLog('INFO', `检测到特殊平台视频,强制下载后发送: ${videoUrl.substring(0, 100)}...`);
|
|
543
|
+
}
|
|
544
|
+
let tempFilePath = null;
|
|
545
|
+
try {
|
|
546
|
+
tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
|
|
547
|
+
const localFile = `file://${path_1.default.resolve(tempFilePath)}`;
|
|
548
|
+
debugLog('INFO', `视频下载完成,发送本地文件: ${localFile}`);
|
|
549
|
+
return await sendWithTimeout(session, koishi_1.h.video(localFile));
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
if (tempFilePath) {
|
|
553
|
+
promises_1.default.unlink(tempFilePath).catch(e => debugLog('WARN', `删除临时文件失败: ${e}`));
|
|
533
554
|
}
|
|
534
555
|
}
|
|
535
556
|
}
|
|
@@ -566,6 +587,7 @@ function apply(ctx, config) {
|
|
|
566
587
|
return;
|
|
567
588
|
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
568
589
|
const botName = config.botName || '视频解析机器人';
|
|
590
|
+
const videoItems = [];
|
|
569
591
|
if (enableForward) {
|
|
570
592
|
const forwardMessages = [];
|
|
571
593
|
for (const item of items) {
|
|
@@ -583,6 +605,9 @@ function apply(ctx, config) {
|
|
|
583
605
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
|
|
584
606
|
}
|
|
585
607
|
}
|
|
608
|
+
if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
609
|
+
videoItems.push(p);
|
|
610
|
+
}
|
|
586
611
|
}
|
|
587
612
|
if (forwardMessages.length) {
|
|
588
613
|
const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
|
|
@@ -597,18 +622,15 @@ function apply(ctx, config) {
|
|
|
597
622
|
}
|
|
598
623
|
}
|
|
599
624
|
}
|
|
600
|
-
for (const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
|
|
608
|
-
await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
|
|
609
|
-
}
|
|
610
|
-
await delay(500);
|
|
625
|
+
for (const p of videoItems) {
|
|
626
|
+
try {
|
|
627
|
+
await sendVideoFile(session, p.video);
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
|
|
631
|
+
await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
|
|
611
632
|
}
|
|
633
|
+
await delay(500);
|
|
612
634
|
}
|
|
613
635
|
}
|
|
614
636
|
else {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -5,24 +5,28 @@
|
|
|
5
5
|
### 中文
|
|
6
6
|
这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20+主流平台的短视频/图集/实况链接。核心特性:
|
|
7
7
|
- 🌐 统一API解析,覆盖20+热门平台,无需繁琐配置
|
|
8
|
-
- 🤖 自动识别链接来源,即丢即用,并支持解析 XML 卡片消息中的链接(如 QQ/OneBot 平台的分享卡片)
|
|
8
|
+
- 🤖 自动识别链接来源,即丢即用,并支持解析 XML/JSON 卡片消息中的链接(如 QQ/OneBot 平台的分享卡片)
|
|
9
9
|
- 🎨 完全自定义的解析结果格式,支持多项变量替换,变量无值自动隐藏行
|
|
10
10
|
- 🐛 内置Debug调试模式,可详细记录所有操作与API交互日志
|
|
11
11
|
- 📤 支持OneBot平台消息合并转发,优化多图文展示体验
|
|
12
12
|
- 💬 所有提示文案均可自定义,适配多语言场景
|
|
13
13
|
- 🔁 消息发送支持自动重试,与API重试配置联动,增强稳定性
|
|
14
14
|
- 🚀 内置内存缓存,避免短时间内重复解析同一链接;并发控制,防止资源耗尽
|
|
15
|
+
- ⚡ 智能视频发送策略:普通平台优先直接发送URL,特殊平台自动降级为本地文件发送
|
|
16
|
+
- 🛡️ 可选视频大小限制,防止超大文件占满服务器磁盘;自动清理所有临时文件
|
|
15
17
|
|
|
16
18
|
### English
|
|
17
19
|
This is a **multi-platform video/image parsing plugin** developed for the Koishi bot framework, using a unified API interface to automatically recognize and parse short video/image/live photo links from 20+ mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, YouTube, TikTok, Jianying, AcFun, Zhihu, Huya and more. Core features:
|
|
18
20
|
- 🌐 Unified API parsing, covering 20+ popular platforms without complex configuration
|
|
19
|
-
- 🤖 Auto-detection of link sources, drop & go, and support for extracting links from XML card messages (e.g., share cards on QQ/OneBot)
|
|
21
|
+
- 🤖 Auto-detection of link sources, drop & go, and support for extracting links from XML/JSON card messages (e.g., share cards on QQ/OneBot)
|
|
20
22
|
- 🎨 Fully customizable parsing result format with variable substitutions, empty variables hide the line automatically
|
|
21
23
|
- 🐛 Built-in Debug mode, recording detailed operations and API interaction logs
|
|
22
24
|
- 📤 Support OneBot message forwarding for better image/video display
|
|
23
25
|
- 💬 All prompt texts are customizable for multilingual scenarios
|
|
24
26
|
- 🔁 Message sending supports automatic retries, linked with API retry configuration for improved stability
|
|
25
27
|
- 🚀 Built-in memory cache to avoid repeated parsing of the same URL; concurrency control to prevent resource exhaustion
|
|
28
|
+
- ⚡ Smart video sending strategy: priority to send URL directly for common platforms, auto downgrade to local file for special platforms
|
|
29
|
+
- 🛡️ Optional video size limit to prevent oversized files from filling up server disk; automatic cleanup of all temporary files
|
|
26
30
|
|
|
27
31
|
## 项目仓库 (Repository)
|
|
28
32
|
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
|
|
@@ -55,13 +59,17 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
55
59
|
| `showImageText` | boolean | true | 是否发送解析后的文字内容 |
|
|
56
60
|
| `showVideoFile` | boolean | true | 是否发送视频文件(关闭则只发送视频链接) |
|
|
57
61
|
| `maxDescLength` | number | 200 | 简介内容最大长度(字符),超出自动截断 |
|
|
62
|
+
| `videoDownloadTimeout` | number | 120000 | 视频下载超时(毫秒) |
|
|
63
|
+
| `tempDir` | string | `./temp_videos` | 临时视频存储目录 |
|
|
64
|
+
| `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0 为不限制大小 |
|
|
65
|
+
| `forceDownloadVideo` | boolean | true | 强制下载视频后发送(解决B站、小红书等平台URL无法直接发送的问题) |
|
|
58
66
|
|
|
59
67
|
### 网络与 API 设置
|
|
60
68
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
61
69
|
|--------|------|--------|------|
|
|
62
70
|
| `timeout` | number | 180000 | API 请求超时时间(毫秒) |
|
|
63
71
|
| `videoSendTimeout` | number | 60000 | 视频消息发送超时时间(毫秒,0 为不限制) |
|
|
64
|
-
| `userAgent` | string |
|
|
72
|
+
| `userAgent` | string | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36` | API 请求使用的 User-Agent |
|
|
65
73
|
|
|
66
74
|
### 错误与重试设置
|
|
67
75
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
@@ -73,7 +81,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
73
81
|
### 发送方式设置
|
|
74
82
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
75
83
|
|--------|------|--------|------|
|
|
76
|
-
| `enableForward` | boolean | false | 是否启用合并转发(仅 OneBot
|
|
84
|
+
| `enableForward` | boolean | false | 是否启用合并转发(仅 OneBot 平台),视频会单独发送 |
|
|
77
85
|
|
|
78
86
|
### 界面文字设置
|
|
79
87
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
@@ -82,7 +90,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
82
90
|
| `unsupportedPlatformText` | string | 不支持该平台链接 | 不支持的平台提示 |
|
|
83
91
|
| `invalidLinkText` | string | 无效的视频链接 | 无效链接提示(parse 指令) |
|
|
84
92
|
| `parseErrorPrefix` | string | ❌ 解析失败: | 解析失败消息前缀 |
|
|
85
|
-
| `parseErrorItemFormat` | string |
|
|
93
|
+
| `parseErrorItemFormat` | string | `【${url}】: ${msg}` | 每条解析失败的展示格式,可用 ${url}(链接)和 ${msg}(错误信息) |
|
|
86
94
|
|
|
87
95
|
## 支持的变量 (Supported Variables)
|
|
88
96
|
在 `unifiedMessageFormat` 中可使用以下变量进行自定义格式化,某行所有变量均为空(或为"0")时该行不显示:
|