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.
- package/lib/index.js +75 -92
- package/package.json +1 -1
- 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(
|
|
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]
|
|
81
|
-
{ pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]
|
|
82
|
-
{ pattern: /bili2233\.cn\/([0-9a-zA-Z]
|
|
83
|
-
{ pattern: /douyin\.com\/video\/(\d
|
|
84
|
-
{ pattern: /v\.douyin\.com\/([0-9a-zA-Z]
|
|
85
|
-
{ pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]
|
|
86
|
-
{ pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]
|
|
87
|
-
{ pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]
|
|
88
|
-
{ pattern: /xhslink\.com\/([0-9a-zA-Z]
|
|
89
|
-
{ pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]
|
|
90
|
-
{ pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]
|
|
91
|
-
{ pattern: /ixigua\.com\/(\d
|
|
92
|
-
{ pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]
|
|
93
|
-
{ pattern: /youtu\.be\/([a-zA-Z0-9_-]
|
|
94
|
-
{ pattern: /tiktok\.com\/@[\w.]+\/video\/(\d
|
|
95
|
-
{ pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]
|
|
96
|
-
{ pattern: /acfun\.cn\/v\/(ac\d
|
|
97
|
-
{ pattern: /zhihu\.com\/video\/(\d
|
|
98
|
-
{ pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]
|
|
99
|
-
{ pattern: /huya\.com\/video\/([0-9a-zA-Z]
|
|
100
|
-
{ pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]
|
|
101
|
-
{ pattern: /meipai\.com\/media\/(\d
|
|
102
|
-
{ pattern: /twitter\.com\/\w+\/status\/(\d
|
|
103
|
-
{ pattern: /x\.com\/\w+\/status\/(\d
|
|
104
|
-
{ pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]
|
|
105
|
-
{ pattern: /doubao\.com\/video\/(\d
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
681
|
-
if (!shouldForceDownload) {
|
|
668
|
+
if (!config.forceDownloadVideo) {
|
|
682
669
|
try {
|
|
683
670
|
debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
|
|
684
|
-
|
|
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',
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
763
|
-
|
|
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
|
|
766
|
+
for (const p of videoItems) {
|
|
783
767
|
try {
|
|
784
|
-
await sendVideoFile(session,
|
|
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
|
|
790
|
+
await sendVideoFile(session, p.video);
|
|
808
791
|
}
|
|
809
792
|
catch (err) {
|
|
810
793
|
debugLog('ERROR', `视频发送失败: ${getErrorMessage(err)}`);
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
- 📤 支持OneBot平台消息合并转发,优化多图文展示体验
|
|
12
12
|
- 💬 所有提示文案均可自定义,适配多语言场景
|
|
13
13
|
- 🔁 消息发送支持自动重试,与API重试配置联动,增强稳定性
|
|
14
|
-
- 🚀 内置LRU
|
|
15
|
-
- ⚡
|
|
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;
|
|
28
|
-
- ⚡ Smart video sending strategy: priority to send URL directly
|
|
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 |
|
|
65
|
+
| `forceDownloadVideo` | boolean | false | 强制下载视频后发送 |
|
|
66
66
|
| `videoLoadWaitTime` | number | 180000 | 视频链接加载等待时间(毫秒),获取到视频链接后等待指定时间再发送,0为不等待 |
|
|
67
67
|
|
|
68
68
|
### 网络与 API 设置
|