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 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(100 * 1024 * 1024).description('最大下载视频大小(字节),默认100MB'),
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, maxSize) {
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 > maxSize) {
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),超过限制(${Math.round(maxSize / 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 > maxSize) {
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(`视频文件过大,超过限制(${Math.round(maxSize / 1024 / 1024)}MB)`);
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
- try {
517
- debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
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
- tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 100 * 1024 * 1024);
525
- const localFile = `file://${path_1.default.resolve(tempFilePath)}`;
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
- finally {
530
- if (tempFilePath) {
531
- promises_1.default.unlink(tempFilePath).catch(e => debugLog('WARN', `删除临时文件失败: ${e}`));
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 item of items) {
601
- const p = item.parsed;
602
- if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
603
- try {
604
- await sendVideoFile(session, p.video);
605
- }
606
- catch (err) {
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
3
  "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
4
- "version": "0.9.9",
4
+ "version": "1.0.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
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 | Chrome 124 UA | API 请求使用的 User-Agent |
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 | 【${url}】: ${msg} | 每条解析失败的展示格式,可用 ${url}(链接)和 ${msg}(错误信息) |
93
+ | `parseErrorItemFormat` | string | `【${url}】: ${msg}` | 每条解析失败的展示格式,可用 ${url}(链接)和 ${msg}(错误信息) |
86
94
 
87
95
  ## 支持的变量 (Supported Variables)
88
96
  在 `unifiedMessageFormat` 中可使用以下变量进行自定义格式化,某行所有变量均为空(或为"0")时该行不显示: