koishi-plugin-video-parser-all 1.0.6 → 1.0.7
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 +10 -0
- package/lib/index.js +137 -188
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export declare const Config: Schema<{
|
|
|
25
25
|
retryInterval?: number | null | undefined;
|
|
26
26
|
} & {
|
|
27
27
|
enableForward?: boolean | null | undefined;
|
|
28
|
+
} & {
|
|
29
|
+
primaryApiUrl?: string | null | undefined;
|
|
30
|
+
backupApiUrl?: string | null | undefined;
|
|
31
|
+
useDedicatedApiFirst?: boolean | null | undefined;
|
|
32
|
+
customApiUrls?: import("cosmokit").Dict<string, string> | null | undefined;
|
|
28
33
|
} & {
|
|
29
34
|
waitingTipText?: string | null | undefined;
|
|
30
35
|
unsupportedPlatformText?: string | null | undefined;
|
|
@@ -56,6 +61,11 @@ export declare const Config: Schema<{
|
|
|
56
61
|
retryInterval: number;
|
|
57
62
|
} & {
|
|
58
63
|
enableForward: boolean;
|
|
64
|
+
} & {
|
|
65
|
+
primaryApiUrl: string;
|
|
66
|
+
backupApiUrl: string;
|
|
67
|
+
useDedicatedApiFirst: boolean;
|
|
68
|
+
customApiUrls: import("cosmokit").Dict<string, string>;
|
|
59
69
|
} & {
|
|
60
70
|
waitingTipText: string;
|
|
61
71
|
unsupportedPlatformText: string;
|
package/lib/index.js
CHANGED
|
@@ -45,6 +45,12 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
45
45
|
koishi_1.Schema.object({
|
|
46
46
|
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot 平台)'),
|
|
47
47
|
}).description('发送方式设置'),
|
|
48
|
+
koishi_1.Schema.object({
|
|
49
|
+
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('主 API 地址'),
|
|
50
|
+
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').description('备用主 API 地址(仅支持抖音/小红书/ins/即梦)'),
|
|
51
|
+
useDedicatedApiFirst: koishi_1.Schema.boolean().default(false).description('优先使用平台专属 API,失败后回退到通用 API'),
|
|
52
|
+
customApiUrls: koishi_1.Schema.dict(koishi_1.Schema.string()).default({}).description('自定义专属 API 地址,key 为平台类型(如 bilibili,douyin,doubao),value 为完整 API 地址,留空则使用内置默认专属 API'),
|
|
53
|
+
}).description('API 选择设置'),
|
|
48
54
|
koishi_1.Schema.object({
|
|
49
55
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('解析等待提示'),
|
|
50
56
|
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持的平台提示'),
|
|
@@ -118,98 +124,9 @@ function linkTypeParser(content) {
|
|
|
118
124
|
}
|
|
119
125
|
return matches;
|
|
120
126
|
}
|
|
121
|
-
function extractUrl(content) {
|
|
122
|
-
if (!content)
|
|
123
|
-
return [];
|
|
124
|
-
const urlMatches = content.match(/https?:\/\/[^\s<>"'(){}[\]]+/gi) || [];
|
|
125
|
-
return urlMatches.filter(url => {
|
|
126
|
-
try {
|
|
127
|
-
const urlObj = new URL(url);
|
|
128
|
-
const hostname = urlObj.hostname.toLowerCase();
|
|
129
|
-
if (hostname.includes('multimedia.nt.qq.com.cn') ||
|
|
130
|
-
hostname.includes('grouptalk.qq.com') ||
|
|
131
|
-
hostname.includes('qpic.cn') ||
|
|
132
|
-
hostname.includes('qlogo.cn')) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
if (hostname === 'v.douyin.com' && urlObj.pathname.length < 3)
|
|
136
|
-
return false;
|
|
137
|
-
if (hostname === 'www.douyin.com' && urlObj.pathname === '/')
|
|
138
|
-
return false;
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
}).map(url => {
|
|
145
|
-
return url.replace(/[.,;:!?)]+$/, '');
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
127
|
function extractAllUrlsFromMessage(session) {
|
|
149
128
|
const content = session.content?.trim() || '';
|
|
150
|
-
|
|
151
|
-
const linkMatches = linkTypeParser(content);
|
|
152
|
-
if (linkMatches.length > 0) {
|
|
153
|
-
for (const match of linkMatches) {
|
|
154
|
-
urls.push(match.url);
|
|
155
|
-
}
|
|
156
|
-
return [...new Set(urls)];
|
|
157
|
-
}
|
|
158
|
-
if (content) {
|
|
159
|
-
const textUrls = extractUrl(content);
|
|
160
|
-
urls.push(...textUrls);
|
|
161
|
-
}
|
|
162
|
-
if (session.elements) {
|
|
163
|
-
for (const elem of session.elements) {
|
|
164
|
-
if (elem.type === 'xml' && elem.data) {
|
|
165
|
-
const urlRegex = /https?:\/\/[^\s<>"'(){}[\]]+/gi;
|
|
166
|
-
let match;
|
|
167
|
-
while ((match = urlRegex.exec(elem.data)) !== null) {
|
|
168
|
-
const cleanUrl = match[0].replace(/[.,;:!?)]+$/, '');
|
|
169
|
-
urls.push(cleanUrl);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (elem.type === 'json' && elem.data) {
|
|
173
|
-
try {
|
|
174
|
-
const json = JSON.parse(elem.data);
|
|
175
|
-
const extractFromObject = (obj) => {
|
|
176
|
-
if (!obj || typeof obj !== 'object')
|
|
177
|
-
return;
|
|
178
|
-
for (const val of Object.values(obj)) {
|
|
179
|
-
if (typeof val === 'string') {
|
|
180
|
-
const match = val.match(/https?:\/\/[^\s<>"'(){}[\]]+/gi);
|
|
181
|
-
if (match) {
|
|
182
|
-
match.forEach(url => {
|
|
183
|
-
const cleanUrl = url.replace(/[.,;:!?)]+$/, '');
|
|
184
|
-
urls.push(cleanUrl);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else if (typeof val === 'object')
|
|
189
|
-
extractFromObject(val);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
extractFromObject(json);
|
|
193
|
-
}
|
|
194
|
-
catch (e) {
|
|
195
|
-
debugLog('WARN', '解析JSON卡片失败:', e);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return [...new Set(urls)].filter(url => {
|
|
201
|
-
try {
|
|
202
|
-
const urlObj = new URL(url);
|
|
203
|
-
if (urlObj.hostname === 'v.douyin.com' && urlObj.pathname.length < 3)
|
|
204
|
-
return false;
|
|
205
|
-
if (urlObj.hostname === 'www.douyin.com' && urlObj.pathname === '/')
|
|
206
|
-
return false;
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
});
|
|
129
|
+
return linkTypeParser(content);
|
|
213
130
|
}
|
|
214
131
|
function cleanUrl(url) {
|
|
215
132
|
try {
|
|
@@ -381,10 +298,6 @@ function parseApiResponse(raw, maxDescLen) {
|
|
|
381
298
|
else if (extra.create_time) {
|
|
382
299
|
publishTime = extra.create_time * 1000;
|
|
383
300
|
}
|
|
384
|
-
debugLog('DEBUG', '解析后的数据:', {
|
|
385
|
-
type, title, author, video: video.substring(0, 100) + '...',
|
|
386
|
-
images: images.length, live_photo: live_photo.length
|
|
387
|
-
});
|
|
388
301
|
return {
|
|
389
302
|
type, title, desc, author, uid, avatar, cover,
|
|
390
303
|
video, videos, images, live_photo, music,
|
|
@@ -534,48 +447,89 @@ function apply(ctx, config) {
|
|
|
534
447
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
535
448
|
}
|
|
536
449
|
});
|
|
537
|
-
|
|
450
|
+
const defaultDedicatedApis = {
|
|
451
|
+
bilibili: 'https://api.bugpk.com/api/bilibili',
|
|
452
|
+
douyin: 'https://api.bugpk.com/api/douyin',
|
|
453
|
+
doubao: 'https://api.bugpk.com/api/dbvideos',
|
|
454
|
+
kuaishou: 'https://api.bugpk.com/api/kuaishou',
|
|
455
|
+
xiaohongshu: 'https://api.bugpk.com/api/xhs',
|
|
456
|
+
jimeng: 'https://api.bugpk.com/api/jimengai',
|
|
457
|
+
toutiao: 'https://api.bugpk.com/api/toutiao',
|
|
458
|
+
weibo: 'https://api.bugpk.com/api/weibo',
|
|
459
|
+
huya: 'https://api.bugpk.com/api/huya',
|
|
460
|
+
pipigx: 'https://api.bugpk.com/api/pipigx',
|
|
461
|
+
pipixia: 'https://api.bugpk.com/api/pipixia',
|
|
462
|
+
zuiyou: 'https://api.bugpk.com/api/zuiyou',
|
|
463
|
+
};
|
|
464
|
+
const backupSupportedPlatforms = new Set(['douyin', 'xiaohongshu', 'instagram', 'jimeng']);
|
|
465
|
+
function getDedicatedApiUrl(type) {
|
|
466
|
+
if (config.customApiUrls && config.customApiUrls[type]) {
|
|
467
|
+
return config.customApiUrls[type];
|
|
468
|
+
}
|
|
469
|
+
return defaultDedicatedApis[type] || null;
|
|
470
|
+
}
|
|
471
|
+
async function fetchApi(url, type) {
|
|
538
472
|
const cacheKey = url;
|
|
539
473
|
const cached = urlCache.get(cacheKey);
|
|
540
474
|
if (cached && cached.expire > Date.now()) {
|
|
541
475
|
debugLog('DEBUG', `使用缓存: ${url}`);
|
|
542
476
|
return cached.data;
|
|
543
477
|
}
|
|
544
|
-
|
|
478
|
+
const dedicatedApiUrl = getDedicatedApiUrl(type);
|
|
479
|
+
const primaryApi = config.primaryApiUrl || 'https://api.bugpk.com/api/short_videos';
|
|
480
|
+
const backupApi = config.backupApiUrl || 'https://api.bugpk.com/api/svparse';
|
|
481
|
+
const backupAllowed = backupSupportedPlatforms.has(type);
|
|
482
|
+
const apiList = [];
|
|
483
|
+
if (config.useDedicatedApiFirst) {
|
|
484
|
+
if (dedicatedApiUrl)
|
|
485
|
+
apiList.push({ url: dedicatedApiUrl, label: `专属API(${type})` });
|
|
486
|
+
apiList.push({ url: primaryApi, label: '默认主API' });
|
|
487
|
+
if (backupAllowed)
|
|
488
|
+
apiList.push({ url: backupApi, label: '备用主API' });
|
|
489
|
+
if (dedicatedApiUrl) {
|
|
490
|
+
// already tried dedicated first, don't repeat
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
apiList.push({ url: primaryApi, label: '默认主API' });
|
|
495
|
+
if (backupAllowed)
|
|
496
|
+
apiList.push({ url: backupApi, label: '备用主API' });
|
|
497
|
+
if (dedicatedApiUrl)
|
|
498
|
+
apiList.push({ url: dedicatedApiUrl, label: `专属API(${type})` });
|
|
499
|
+
}
|
|
545
500
|
let lastError = null;
|
|
546
|
-
for (
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
debugLog('DEBUG', `API响应状态: ${res.status}`);
|
|
553
|
-
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
554
|
-
const parsed = parseApiResponse(res.data, config.maxDescLength);
|
|
555
|
-
urlCache.set(cacheKey, {
|
|
556
|
-
data: parsed,
|
|
557
|
-
expire: Date.now() + 10 * 60 * 1000
|
|
501
|
+
for (const api of apiList) {
|
|
502
|
+
for (let attempt = 0; attempt <= config.retryTimes; attempt++) {
|
|
503
|
+
try {
|
|
504
|
+
const res = await http.get(api.url, {
|
|
505
|
+
params: { url },
|
|
506
|
+
timeout: config.timeout
|
|
558
507
|
});
|
|
559
|
-
|
|
508
|
+
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
509
|
+
const parsed = parseApiResponse(res.data, config.maxDescLength);
|
|
510
|
+
urlCache.set(cacheKey, { data: parsed, expire: Date.now() + 10 * 60 * 1000 });
|
|
511
|
+
return parsed;
|
|
512
|
+
}
|
|
513
|
+
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
560
514
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
await delay(config.retryInterval);
|
|
515
|
+
catch (error) {
|
|
516
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
517
|
+
debugLog('ERROR', `${api.label} 第${attempt + 1}次请求失败: ${lastError.message}`);
|
|
518
|
+
if (attempt < config.retryTimes) {
|
|
519
|
+
await delay(config.retryInterval);
|
|
520
|
+
}
|
|
568
521
|
}
|
|
569
522
|
}
|
|
523
|
+
debugLog('WARN', `${api.label} 所有重试均失败,切换下一个API`);
|
|
570
524
|
}
|
|
571
|
-
throw lastError || new Error('API请求全部失败');
|
|
525
|
+
throw lastError || new Error('所有API请求全部失败');
|
|
572
526
|
}
|
|
573
|
-
async function parseUrl(url) {
|
|
527
|
+
async function parseUrl(url, type) {
|
|
574
528
|
const realUrl = await resolveShortUrl(url);
|
|
575
529
|
const candidates = [realUrl, url];
|
|
576
530
|
for (const candidate of [...new Set(candidates)]) {
|
|
577
531
|
try {
|
|
578
|
-
const info = await fetchApi(candidate);
|
|
532
|
+
const info = await fetchApi(candidate, type);
|
|
579
533
|
if (info.video || info.images.length > 0) {
|
|
580
534
|
return { success: true, data: info };
|
|
581
535
|
}
|
|
@@ -587,8 +541,8 @@ function apply(ctx, config) {
|
|
|
587
541
|
}
|
|
588
542
|
return { success: false, msg: texts.unsupportedPlatformText };
|
|
589
543
|
}
|
|
590
|
-
async function processSingleUrl(url) {
|
|
591
|
-
const result = await parseUrl(url);
|
|
544
|
+
async function processSingleUrl(url, type) {
|
|
545
|
+
const result = await parseUrl(url, type);
|
|
592
546
|
if (!result.success) {
|
|
593
547
|
return { success: false, msg: result.msg, url };
|
|
594
548
|
}
|
|
@@ -641,72 +595,70 @@ function apply(ctx, config) {
|
|
|
641
595
|
}
|
|
642
596
|
async function sendVideoFile(session, videoUrl) {
|
|
643
597
|
if (!videoUrl)
|
|
644
|
-
|
|
598
|
+
return;
|
|
645
599
|
if (!config.showVideoFile) {
|
|
646
600
|
return await sendWithTimeout(session, `视频链接:${videoUrl}`);
|
|
647
601
|
}
|
|
648
|
-
const
|
|
602
|
+
const sendLink = async () => {
|
|
649
603
|
await sendWithTimeout(session, `视频链接:${videoUrl}`).catch(() => { });
|
|
650
604
|
};
|
|
651
|
-
|
|
652
|
-
let tempFilePath = null;
|
|
605
|
+
if (config.forceDownloadVideo) {
|
|
653
606
|
try {
|
|
654
|
-
tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
|
|
607
|
+
const tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
|
|
655
608
|
const localFile = `file://${tempFilePath}`;
|
|
656
|
-
|
|
657
|
-
return
|
|
609
|
+
await sendWithTimeout(session, koishi_1.h.video(localFile));
|
|
610
|
+
return;
|
|
658
611
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
612
|
+
catch (e) {
|
|
613
|
+
debugLog('ERROR', '强制下载失败,尝试直接发送URL:', getErrorMessage(e));
|
|
614
|
+
try {
|
|
615
|
+
await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
catch (urlErr) {
|
|
619
|
+
debugLog('ERROR', '发送URL也失败,降级发送链接:', getErrorMessage(urlErr));
|
|
620
|
+
await sendLink();
|
|
662
621
|
}
|
|
663
622
|
}
|
|
664
|
-
|
|
665
|
-
if (config.forceDownloadVideo) {
|
|
666
|
-
try {
|
|
667
|
-
return await tryDownloadAndSend();
|
|
668
|
-
}
|
|
669
|
-
catch (err) {
|
|
670
|
-
debugLog('ERROR', `下载并发送视频失败: ${getErrorMessage(err)}`);
|
|
671
|
-
await sendLinkAsFallback();
|
|
672
|
-
}
|
|
623
|
+
return;
|
|
673
624
|
}
|
|
674
|
-
|
|
625
|
+
try {
|
|
626
|
+
debugLog('INFO', '尝试直接发送视频URL');
|
|
627
|
+
await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
catch (urlErr) {
|
|
631
|
+
debugLog('ERROR', '直接发送URL失败,尝试下载:', getErrorMessage(urlErr));
|
|
675
632
|
try {
|
|
676
|
-
|
|
677
|
-
|
|
633
|
+
const tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
|
|
634
|
+
const localFile = `file://${tempFilePath}`;
|
|
635
|
+
await sendWithTimeout(session, koishi_1.h.video(localFile));
|
|
636
|
+
return;
|
|
678
637
|
}
|
|
679
|
-
catch (
|
|
680
|
-
debugLog('ERROR',
|
|
681
|
-
|
|
682
|
-
return await tryDownloadAndSend();
|
|
683
|
-
}
|
|
684
|
-
catch (downloadErr) {
|
|
685
|
-
debugLog('ERROR', `下载并发送视频也失败: ${getErrorMessage(downloadErr)}`);
|
|
686
|
-
await sendLinkAsFallback();
|
|
687
|
-
}
|
|
638
|
+
catch (downloadErr) {
|
|
639
|
+
debugLog('ERROR', '下载失败,降级发送链接:', getErrorMessage(downloadErr));
|
|
640
|
+
await sendLink();
|
|
688
641
|
}
|
|
689
642
|
}
|
|
690
643
|
}
|
|
691
|
-
async function flush(session,
|
|
692
|
-
|
|
693
|
-
debugLog('INFO', `开始解析 ${uniqueUrls.length} 个链接`);
|
|
644
|
+
async function flush(session, matches) {
|
|
645
|
+
debugLog('INFO', `开始解析 ${matches.length} 个链接`);
|
|
694
646
|
const items = [];
|
|
695
647
|
const errors = [];
|
|
696
|
-
for (let i = 0; i <
|
|
697
|
-
const
|
|
698
|
-
debugLog('INFO', `正在解析第 ${i + 1}/${
|
|
699
|
-
const result = await processSingleUrl(url);
|
|
648
|
+
for (let i = 0; i < matches.length; i++) {
|
|
649
|
+
const match = matches[i];
|
|
650
|
+
debugLog('INFO', `正在解析第 ${i + 1}/${matches.length} 个链接: ${match.url} (平台: ${match.type})`);
|
|
651
|
+
const result = await processSingleUrl(match.url, match.type);
|
|
700
652
|
if (result.success) {
|
|
701
653
|
items.push(result.data);
|
|
702
654
|
}
|
|
703
655
|
else {
|
|
704
656
|
const item = texts.parseErrorItemFormat
|
|
705
|
-
.replace(/\$\{url\}/g, url.length > 50 ? url.slice(0, 50) + '...' : url)
|
|
657
|
+
.replace(/\$\{url\}/g, match.url.length > 50 ? match.url.slice(0, 50) + '...' : match.url)
|
|
706
658
|
.replace(/\$\{msg\}/g, result.msg);
|
|
707
659
|
errors.push(item);
|
|
708
660
|
}
|
|
709
|
-
if (i <
|
|
661
|
+
if (i < matches.length - 1) {
|
|
710
662
|
await delay(500);
|
|
711
663
|
}
|
|
712
664
|
}
|
|
@@ -718,24 +670,6 @@ function apply(ctx, config) {
|
|
|
718
670
|
debugLog('INFO', '没有成功解析的内容');
|
|
719
671
|
return;
|
|
720
672
|
}
|
|
721
|
-
// 先发送所有视频(单独发送,在合并转发之前)
|
|
722
|
-
for (const item of items) {
|
|
723
|
-
const p = item.parsed;
|
|
724
|
-
if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
725
|
-
if (config.showVideoFile) {
|
|
726
|
-
try {
|
|
727
|
-
await sendVideoFile(session, p.video);
|
|
728
|
-
}
|
|
729
|
-
catch (e) {
|
|
730
|
-
debugLog('ERROR', `视频发送失败: ${getErrorMessage(e)}`);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
await sendWithTimeout(session, `视频链接:${p.video}`);
|
|
735
|
-
}
|
|
736
|
-
await delay(500);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
673
|
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
740
674
|
const botName = config.botName || '视频解析机器人';
|
|
741
675
|
if (enableForward) {
|
|
@@ -755,7 +689,9 @@ function apply(ctx, config) {
|
|
|
755
689
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
|
|
756
690
|
}
|
|
757
691
|
}
|
|
758
|
-
|
|
692
|
+
if (p.video) {
|
|
693
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(p.video), botName));
|
|
694
|
+
}
|
|
759
695
|
}
|
|
760
696
|
if (forwardMessages.length) {
|
|
761
697
|
const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
|
|
@@ -773,7 +709,6 @@ function apply(ctx, config) {
|
|
|
773
709
|
}
|
|
774
710
|
}
|
|
775
711
|
else {
|
|
776
|
-
// 非合并转发,只发送文字、封面、图片(视频已在之前发过)
|
|
777
712
|
for (const item of items) {
|
|
778
713
|
const p = item.parsed;
|
|
779
714
|
const text = item.text;
|
|
@@ -785,6 +720,20 @@ function apply(ctx, config) {
|
|
|
785
720
|
await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
|
|
786
721
|
await delay(300);
|
|
787
722
|
}
|
|
723
|
+
if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
724
|
+
if (config.showVideoFile) {
|
|
725
|
+
try {
|
|
726
|
+
await sendVideoFile(session, p.video);
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
debugLog('ERROR', `视频发送失败: ${getErrorMessage(e)}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
await sendWithTimeout(session, `视频链接:${p.video}`);
|
|
734
|
+
}
|
|
735
|
+
await delay(500);
|
|
736
|
+
}
|
|
788
737
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
789
738
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
790
739
|
for (const imgUrl of imageUrls) {
|
|
@@ -805,10 +754,10 @@ function apply(ctx, config) {
|
|
|
805
754
|
return;
|
|
806
755
|
if (session.selfId === session.userId)
|
|
807
756
|
return;
|
|
808
|
-
const
|
|
809
|
-
if (!
|
|
757
|
+
const matches = extractAllUrlsFromMessage(session);
|
|
758
|
+
if (!matches.length)
|
|
810
759
|
return;
|
|
811
|
-
debugLog('INFO', `检测到 ${
|
|
760
|
+
debugLog('INFO', `检测到 ${matches.length} 个链接,开始处理`);
|
|
812
761
|
if (config.showWaitingTip) {
|
|
813
762
|
try {
|
|
814
763
|
await sendWithTimeout(session, texts.waitingTipText);
|
|
@@ -817,15 +766,15 @@ function apply(ctx, config) {
|
|
|
817
766
|
debugLog('WARN', '发送等待提示失败:', e);
|
|
818
767
|
}
|
|
819
768
|
}
|
|
820
|
-
await flush(session,
|
|
769
|
+
await flush(session, matches);
|
|
821
770
|
});
|
|
822
771
|
ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
|
|
823
772
|
if (!url) {
|
|
824
773
|
await sendWithTimeout(session, texts.invalidLinkText);
|
|
825
774
|
return;
|
|
826
775
|
}
|
|
827
|
-
const
|
|
828
|
-
if (!
|
|
776
|
+
const matches = linkTypeParser(url);
|
|
777
|
+
if (!matches.length) {
|
|
829
778
|
await sendWithTimeout(session, texts.invalidLinkText);
|
|
830
779
|
return;
|
|
831
780
|
}
|
|
@@ -835,7 +784,7 @@ function apply(ctx, config) {
|
|
|
835
784
|
}
|
|
836
785
|
catch { }
|
|
837
786
|
}
|
|
838
|
-
await flush(session,
|
|
787
|
+
await flush(session, matches);
|
|
839
788
|
});
|
|
840
789
|
const tempCleanupInterval = setInterval(async () => {
|
|
841
790
|
try {
|
package/package.json
CHANGED