koishi-plugin-video-parser-all 1.0.3 → 1.0.4

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.
Files changed (3) hide show
  1. package/lib/index.js +75 -92
  2. package/package.json +1 -1
  3. package/readme.md +5 -5
package/lib/index.js CHANGED
@@ -30,7 +30,7 @@ exports.Config = koishi_1.Schema.intersect([
30
30
  videoDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('视频下载超时(毫秒)'),
31
31
  tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
32
32
  maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制大小'),
33
- forceDownloadVideo: koishi_1.Schema.boolean().default(true).description('强制下载视频后发送(解决B站、小红书等平台URL无法直接发送的问题)'),
33
+ forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频后发送'),
34
34
  videoLoadWaitTime: koishi_1.Schema.number().min(0).step(1).default(180000).description('视频链接加载等待时间(毫秒),获取到视频链接后等待指定时间再发送,0为不等待'),
35
35
  }).description('内容显示设置'),
36
36
  koishi_1.Schema.object({
@@ -77,32 +77,32 @@ function linkTypeParser(content) {
77
77
  content = content.replace(/\\\//g, '/');
78
78
  const rules = [
79
79
  { pattern: /bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://www.bilibili.com/video/${id}` },
80
- { pattern: /b23\.tv\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
81
- { pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
82
- { pattern: /bili2233\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
83
- { pattern: /douyin\.com\/video\/(\d+)/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
84
- { pattern: /v\.douyin\.com\/([0-9a-zA-Z]+)/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}` },
85
- { pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
86
- { pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
87
- { pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
88
- { pattern: /xhslink\.com\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
89
- { pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
90
- { pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
91
- { pattern: /ixigua\.com\/(\d+)/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
92
- { pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
93
- { pattern: /youtu\.be\/([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
94
- { pattern: /tiktok\.com\/@[\w.]+\/video\/(\d+)/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
95
- { pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]+)/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
96
- { pattern: /acfun\.cn\/v\/(ac\d+)/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
97
- { pattern: /zhihu\.com\/video\/(\d+)/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
98
- { pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]+)/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
99
- { pattern: /huya\.com\/video\/([0-9a-zA-Z]+)/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
100
- { pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]+)/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
101
- { pattern: /meipai\.com\/media\/(\d+)/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
102
- { pattern: /twitter\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
103
- { pattern: /x\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
104
- { pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]+)/gi, type: 'instagram', buildUrl: (id) => `https://www.instagram.com/p/${id}` },
105
- { pattern: /doubao\.com\/video\/(\d+)/gi, type: 'doubao', buildUrl: (id) => `https://www.doubao.com/video/${id}` },
80
+ { pattern: /b23\.tv\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
81
+ { pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
82
+ { pattern: /bili2233\.cn\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
83
+ { pattern: /douyin\.com\/video\/(\d{10,})/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
84
+ { pattern: /v\.douyin\.com\/([0-9a-zA-Z]{8,})/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}/` },
85
+ { pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]{10,})/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
86
+ { pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]{8,})/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
87
+ { pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]{10,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
88
+ { pattern: /xhslink\.com\/([0-9a-zA-Z]{8,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
89
+ { pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
90
+ { pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
91
+ { pattern: /ixigua\.com\/(\d{10,})/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
92
+ { pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
93
+ { pattern: /youtu\.be\/([a-zA-Z0-9_-]{11})/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
94
+ { pattern: /tiktok\.com\/@[\w.]+\/video\/(\d{10,})/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
95
+ { pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]{8,})/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
96
+ { pattern: /acfun\.cn\/v\/(ac\d{10,})/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
97
+ { pattern: /zhihu\.com\/video\/(\d{10,})/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
98
+ { pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]{10,})/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
99
+ { pattern: /huya\.com\/video\/([0-9a-zA-Z]{10,})/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
100
+ { pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]{10,})/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
101
+ { pattern: /meipai\.com\/media\/(\d{10,})/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
102
+ { pattern: /twitter\.com\/\w+\/status\/(\d{10,})/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
103
+ { pattern: /x\.com\/\w+\/status\/(\d{10,})/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
104
+ { pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]{10,})/gi, type: 'instagram', buildUrl: (id) => `https://www.instagram.com/p/${id}` },
105
+ { pattern: /doubao\.com\/video\/(\d{10,})/gi, type: 'doubao', buildUrl: (id) => `https://www.doubao.com/video/${id}` },
106
106
  ];
107
107
  const matches = [];
108
108
  const seen = new Set();
@@ -133,6 +133,10 @@ function extractUrl(content) {
133
133
  hostname.includes('qlogo.cn')) {
134
134
  return false;
135
135
  }
136
+ if (hostname === 'v.douyin.com' && urlObj.pathname.length < 3)
137
+ return false;
138
+ if (hostname === 'www.douyin.com' && urlObj.pathname === '/')
139
+ return false;
136
140
  return true;
137
141
  }
138
142
  catch {
@@ -194,7 +198,19 @@ function extractAllUrlsFromMessage(session) {
194
198
  }
195
199
  }
196
200
  }
197
- return [...new Set(urls)];
201
+ return [...new Set(urls)].filter(url => {
202
+ try {
203
+ const urlObj = new URL(url);
204
+ if (urlObj.hostname === 'v.douyin.com' && urlObj.pathname.length < 3)
205
+ return false;
206
+ if (urlObj.hostname === 'www.douyin.com' && urlObj.pathname === '/')
207
+ return false;
208
+ return true;
209
+ }
210
+ catch {
211
+ return false;
212
+ }
213
+ });
198
214
  }
199
215
  function cleanUrl(url) {
200
216
  try {
@@ -459,7 +475,7 @@ async function downloadVideoFile(videoUrl, tempDir, timeout, maxSizeMB) {
459
475
  timeout: timeout,
460
476
  headers: {
461
477
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
462
- 'Referer': 'https://www.baidu.com/',
478
+ 'Referer': 'https://www.bilibili.com/',
463
479
  },
464
480
  validateStatus: (status) => status >= 200 && status < 300,
465
481
  });
@@ -516,30 +532,6 @@ function isSpecialPlatformVideo(url) {
516
532
  ];
517
533
  return specialHosts.some(host => url.includes(host));
518
534
  }
519
- async function getShortUrl(url) {
520
- if (!url)
521
- return '';
522
- try {
523
- const urlObj = new URL(url);
524
- if (urlObj.hostname.includes('bilibili.com')) {
525
- const bvMatch = url.match(/\/video\/(bv[0-9a-zA-Z]+)/i);
526
- if (bvMatch)
527
- return `https://b23.tv/${bvMatch[1]}`;
528
- }
529
- if (urlObj.hostname.includes('douyin.com')) {
530
- const idMatch = url.match(/\/video\/(\d+)/);
531
- if (idMatch)
532
- return `https://v.douyin.com/${idMatch[1].substring(0, 8)}/`;
533
- }
534
- if (urlObj.hostname.includes('xiaohongshu.com')) {
535
- const idMatch = url.match(/\/item\/([0-9a-zA-Z]+)/);
536
- if (idMatch)
537
- return `https://xhslink.com/${idMatch[1].substring(0, 8)}`;
538
- }
539
- }
540
- catch { }
541
- return url;
542
- }
543
535
  function apply(ctx, config) {
544
536
  debugEnabled = config.debug || false;
545
537
  debugLog('INFO', '插件初始化开始');
@@ -617,13 +609,11 @@ function apply(ctx, config) {
617
609
  return { success: false, msg: result.msg, url };
618
610
  }
619
611
  const text = generateFormattedText(result.data, config.unifiedMessageFormat);
620
- const shortUrl = await getShortUrl(result.data.video);
621
612
  return {
622
613
  success: true,
623
614
  data: {
624
615
  text,
625
- parsed: result.data,
626
- shortUrl
616
+ parsed: result.data
627
617
  }
628
618
  };
629
619
  }
@@ -665,30 +655,31 @@ function apply(ctx, config) {
665
655
  }
666
656
  return null;
667
657
  }
668
- async function sendVideoFile(session, videoUrl, shortUrl) {
658
+ async function sendVideoFile(session, videoUrl) {
669
659
  if (!videoUrl)
670
660
  throw new Error('视频链接为空');
671
661
  if (config.videoLoadWaitTime > 0) {
672
662
  await delay(config.videoLoadWaitTime);
673
663
  }
674
- if (shortUrl) {
675
- await sendWithTimeout(session, `视频链接:${shortUrl}`).catch(() => { });
676
- }
664
+ await sendWithTimeout(session, `视频链接:${videoUrl}`).catch(() => { });
677
665
  if (!config.showVideoFile) {
678
666
  return null;
679
667
  }
680
- const shouldForceDownload = config.forceDownloadVideo || isSpecialPlatformVideo(videoUrl);
681
- if (!shouldForceDownload) {
668
+ if (!config.forceDownloadVideo) {
682
669
  try {
683
670
  debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
684
- return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
671
+ const result = await sendWithTimeout(session, koishi_1.h.video(videoUrl));
672
+ if (result) {
673
+ debugLog('INFO', '直接发送视频URL成功');
674
+ return result;
675
+ }
685
676
  }
686
677
  catch (err) {
687
678
  debugLog('ERROR', `直接发送URL失败,开始下载视频: ${getErrorMessage(err)}`);
688
679
  }
689
680
  }
690
681
  else {
691
- debugLog('INFO', `检测到特殊平台视频,强制下载后发送: ${videoUrl.substring(0, 100)}...`);
682
+ debugLog('INFO', `强制下载视频后发送: ${videoUrl.substring(0, 100)}...`);
692
683
  }
693
684
  let tempFilePath = null;
694
685
  try {
@@ -708,25 +699,21 @@ function apply(ctx, config) {
708
699
  debugLog('INFO', `开始解析 ${uniqueUrls.length} 个链接`);
709
700
  const items = [];
710
701
  const errors = [];
711
- const concurrency = 3;
712
- const chunks = [];
713
- for (let i = 0; i < uniqueUrls.length; i += concurrency) {
714
- chunks.push(uniqueUrls.slice(i, i + concurrency));
715
- }
716
- for (const chunk of chunks) {
717
- const results = await Promise.all(chunk.map(url => processSingleUrl(url)));
718
- for (let idx = 0; idx < results.length; idx++) {
719
- const res = results[idx];
720
- if (res.success) {
721
- items.push(res.data);
722
- }
723
- else {
724
- const url = chunk[idx];
725
- const item = texts.parseErrorItemFormat
726
- .replace(/\$\{url\}/g, url.length > 50 ? url.slice(0, 50) + '...' : url)
727
- .replace(/\$\{msg\}/g, res.msg);
728
- errors.push(item);
729
- }
702
+ for (let i = 0; i < uniqueUrls.length; i++) {
703
+ const url = uniqueUrls[i];
704
+ debugLog('INFO', `正在解析第 ${i + 1}/${uniqueUrls.length} 个链接: ${url}`);
705
+ const result = await processSingleUrl(url);
706
+ if (result.success) {
707
+ items.push(result.data);
708
+ }
709
+ else {
710
+ const item = texts.parseErrorItemFormat
711
+ .replace(/\$\{url\}/g, url.length > 50 ? url.slice(0, 50) + '...' : url)
712
+ .replace(/\$\{msg\}/g, result.msg);
713
+ errors.push(item);
714
+ }
715
+ if (i < uniqueUrls.length - 1) {
716
+ await delay(500);
730
717
  }
731
718
  }
732
719
  if (errors.length) {
@@ -745,7 +732,6 @@ function apply(ctx, config) {
745
732
  for (const item of items) {
746
733
  const p = item.parsed;
747
734
  const text = item.text;
748
- const shortUrl = item.shortUrl;
749
735
  if (text && config.showImageText) {
750
736
  forwardMessages.push(buildForwardNode(session, text, botName));
751
737
  }
@@ -759,10 +745,8 @@ function apply(ctx, config) {
759
745
  }
760
746
  }
761
747
  if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
762
- videoItems.push({ parsed: p, shortUrl });
763
- if (shortUrl) {
764
- forwardMessages.push(buildForwardNode(session, `视频链接:${shortUrl}`, botName));
765
- }
748
+ forwardMessages.push(buildForwardNode(session, `视频链接:${p.video}`, botName));
749
+ videoItems.push(p);
766
750
  }
767
751
  }
768
752
  if (forwardMessages.length) {
@@ -779,9 +763,9 @@ function apply(ctx, config) {
779
763
  }
780
764
  }
781
765
  }
782
- for (const item of videoItems) {
766
+ for (const p of videoItems) {
783
767
  try {
784
- await sendVideoFile(session, item.parsed.video, item.shortUrl);
768
+ await sendVideoFile(session, p.video);
785
769
  }
786
770
  catch (err) {
787
771
  debugLog('ERROR', `视频发送失败: ${getErrorMessage(err)}`);
@@ -793,7 +777,6 @@ function apply(ctx, config) {
793
777
  for (const item of items) {
794
778
  const p = item.parsed;
795
779
  const text = item.text;
796
- const shortUrl = item.shortUrl;
797
780
  if (text && config.showImageText) {
798
781
  await sendWithTimeout(session, text);
799
782
  await delay(300);
@@ -804,7 +787,7 @@ function apply(ctx, config) {
804
787
  }
805
788
  if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
806
789
  try {
807
- await sendVideoFile(session, p.video, shortUrl);
790
+ await sendVideoFile(session, p.video);
808
791
  }
809
792
  catch (err) {
810
793
  debugLog('ERROR', `视频发送失败: ${getErrorMessage(err)}`);
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": "1.0.3",
4
+ "version": "1.0.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -11,8 +11,8 @@
11
11
  - 📤 支持OneBot平台消息合并转发,优化多图文展示体验
12
12
  - 💬 所有提示文案均可自定义,适配多语言场景
13
13
  - 🔁 消息发送支持自动重试,与API重试配置联动,增强稳定性
14
- - 🚀 内置LRU内存缓存,避免短时间内重复解析同一链接;并发控制,防止资源耗尽
15
- - ⚡ 智能视频发送策略:普通平台优先直接发送URL,特殊平台自动降级为本地文件发送
14
+ - 🚀 内置LRU内存缓存,避免短时间内重复解析同一链接;串行解析防止API限流
15
+ - ⚡ 智能视频发送策略:优先直接发送URL,失败自动降级为本地文件发送
16
16
  - 🛡️ 可选视频大小限制,防止超大文件占满服务器磁盘;自动清理所有临时文件
17
17
 
18
18
  ### English
@@ -24,8 +24,8 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
24
24
  - 📤 Support OneBot message forwarding for better image/video display
25
25
  - 💬 All prompt texts are customizable for multilingual scenarios
26
26
  - 🔁 Message sending supports automatic retries, linked with API retry configuration for improved stability
27
- - 🚀 Built-in LRU 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
27
+ - 🚀 Built-in LRU memory cache to avoid repeated parsing of the same URL; serial parsing to prevent API rate limiting
28
+ - ⚡ Smart video sending strategy: priority to send URL directly, auto downgrade to local file on failure
29
29
  - 🛡️ Optional video size limit to prevent oversized files from filling up server disk; automatic cleanup of all temporary files
30
30
 
31
31
  ## 项目仓库 (Repository)
@@ -62,7 +62,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
62
62
  | `videoDownloadTimeout` | number | 120000 | 视频下载超时(毫秒) |
63
63
  | `tempDir` | string | `./temp_videos` | 临时视频存储目录 |
64
64
  | `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0 为不限制大小 |
65
- | `forceDownloadVideo` | boolean | true | 强制下载视频后发送(解决B站、小红书等平台URL无法直接发送的问题) |
65
+ | `forceDownloadVideo` | boolean | false | 强制下载视频后发送 |
66
66
  | `videoLoadWaitTime` | number | 180000 | 视频链接加载等待时间(毫秒),获取到视频链接后等待指定时间再发送,0为不等待 |
67
67
 
68
68
  ### 网络与 API 设置