koishi-plugin-video-parser-all 1.0.6 → 1.0.8

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
@@ -25,6 +25,14 @@ 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
+ customApis?: ({
33
+ platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | null | undefined;
34
+ apiUrl?: string | null | undefined;
35
+ } & import("cosmokit").Dict)[] | null | undefined;
28
36
  } & {
29
37
  waitingTipText?: string | null | undefined;
30
38
  unsupportedPlatformText?: string | null | undefined;
@@ -56,6 +64,14 @@ export declare const Config: Schema<{
56
64
  retryInterval: number;
57
65
  } & {
58
66
  enableForward: boolean;
67
+ } & {
68
+ primaryApiUrl: string;
69
+ backupApiUrl: string;
70
+ useDedicatedApiFirst: boolean;
71
+ customApis: Schemastery.ObjectT<{
72
+ platform: Schema<"bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao", "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao">;
73
+ apiUrl: Schema<string, string>;
74
+ }>[];
59
75
  } & {
60
76
  waitingTipText: string;
61
77
  unsupportedPlatformText: string;
package/lib/index.js CHANGED
@@ -45,6 +45,33 @@ 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
+ customApis: koishi_1.Schema.array(koishi_1.Schema.object({
53
+ platform: koishi_1.Schema.union([
54
+ 'bilibili',
55
+ 'douyin',
56
+ 'kuaishou',
57
+ 'xiaohongshu',
58
+ 'weibo',
59
+ 'xigua',
60
+ 'youtube',
61
+ 'tiktok',
62
+ 'acfun',
63
+ 'zhihu',
64
+ 'weishi',
65
+ 'huya',
66
+ 'haokan',
67
+ 'meipai',
68
+ 'twitter',
69
+ 'instagram',
70
+ 'doubao',
71
+ ]).description('选择平台'),
72
+ apiUrl: koishi_1.Schema.string().description('API 地址'),
73
+ })).default([]).description('自定义平台专属 API,可覆盖默认专属 API'),
74
+ }).description('API 选择设置'),
48
75
  koishi_1.Schema.object({
49
76
  waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('解析等待提示'),
50
77
  unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持的平台提示'),
@@ -118,98 +145,9 @@ function linkTypeParser(content) {
118
145
  }
119
146
  return matches;
120
147
  }
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
148
  function extractAllUrlsFromMessage(session) {
149
149
  const content = session.content?.trim() || '';
150
- const urls = [];
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
- });
150
+ return linkTypeParser(content);
213
151
  }
214
152
  function cleanUrl(url) {
215
153
  try {
@@ -381,10 +319,6 @@ function parseApiResponse(raw, maxDescLen) {
381
319
  else if (extra.create_time) {
382
320
  publishTime = extra.create_time * 1000;
383
321
  }
384
- debugLog('DEBUG', '解析后的数据:', {
385
- type, title, author, video: video.substring(0, 100) + '...',
386
- images: images.length, live_photo: live_photo.length
387
- });
388
322
  return {
389
323
  type, title, desc, author, uid, avatar, cover,
390
324
  video, videos, images, live_photo, music,
@@ -534,48 +468,88 @@ function apply(ctx, config) {
534
468
  'Content-Type': 'application/x-www-form-urlencoded'
535
469
  }
536
470
  });
537
- async function fetchApi(url) {
471
+ const defaultDedicatedApis = {
472
+ bilibili: 'https://api.bugpk.com/api/bilibili',
473
+ douyin: 'https://api.bugpk.com/api/douyin',
474
+ doubao: 'https://api.bugpk.com/api/dbvideos',
475
+ kuaishou: 'https://api.bugpk.com/api/kuaishou',
476
+ xiaohongshu: 'https://api.bugpk.com/api/xhs',
477
+ jimeng: 'https://api.bugpk.com/api/jimengai',
478
+ toutiao: 'https://api.bugpk.com/api/toutiao',
479
+ weibo: 'https://api.bugpk.com/api/weibo',
480
+ huya: 'https://api.bugpk.com/api/huya',
481
+ pipigx: 'https://api.bugpk.com/api/pipigx',
482
+ pipixia: 'https://api.bugpk.com/api/pipixia',
483
+ zuiyou: 'https://api.bugpk.com/api/zuiyou',
484
+ };
485
+ const backupSupportedPlatforms = new Set(['douyin', 'xiaohongshu', 'instagram', 'jimeng']);
486
+ function getDedicatedApiUrl(type) {
487
+ if (config.customApis && Array.isArray(config.customApis)) {
488
+ const found = config.customApis.find((item) => item.platform === type);
489
+ if (found && found.apiUrl)
490
+ return found.apiUrl;
491
+ }
492
+ return defaultDedicatedApis[type] || null;
493
+ }
494
+ async function fetchApi(url, type) {
538
495
  const cacheKey = url;
539
496
  const cached = urlCache.get(cacheKey);
540
497
  if (cached && cached.expire > Date.now()) {
541
498
  debugLog('DEBUG', `使用缓存: ${url}`);
542
499
  return cached.data;
543
500
  }
544
- debugLog('INFO', `调用API解析: ${url}`);
501
+ const dedicatedApiUrl = getDedicatedApiUrl(type);
502
+ const primaryApi = config.primaryApiUrl || 'https://api.bugpk.com/api/short_videos';
503
+ const backupApi = config.backupApiUrl || 'https://api.bugpk.com/api/svparse';
504
+ const backupAllowed = backupSupportedPlatforms.has(type);
505
+ const apiList = [];
506
+ if (config.useDedicatedApiFirst) {
507
+ if (dedicatedApiUrl)
508
+ apiList.push({ url: dedicatedApiUrl, label: `专属API(${type})` });
509
+ apiList.push({ url: primaryApi, label: '默认主API' });
510
+ if (backupAllowed)
511
+ apiList.push({ url: backupApi, label: '备用主API' });
512
+ }
513
+ else {
514
+ apiList.push({ url: primaryApi, label: '默认主API' });
515
+ if (backupAllowed)
516
+ apiList.push({ url: backupApi, label: '备用主API' });
517
+ if (dedicatedApiUrl)
518
+ apiList.push({ url: dedicatedApiUrl, label: `专属API(${type})` });
519
+ }
545
520
  let lastError = null;
546
- for (let i = 0; i <= config.retryTimes; i++) {
547
- try {
548
- const res = await http.get('https://api.bugpk.com/api/short_videos', {
549
- params: { url },
550
- timeout: config.timeout
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
521
+ for (const api of apiList) {
522
+ for (let attempt = 0; attempt <= config.retryTimes; attempt++) {
523
+ try {
524
+ const res = await http.get(api.url, {
525
+ params: { url },
526
+ timeout: config.timeout
558
527
  });
559
- return parsed;
528
+ if (res.data && (res.data.code === 200 || res.data.code === 0)) {
529
+ const parsed = parseApiResponse(res.data, config.maxDescLength);
530
+ urlCache.set(cacheKey, { data: parsed, expire: Date.now() + 10 * 60 * 1000 });
531
+ return parsed;
532
+ }
533
+ throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
560
534
  }
561
- throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
562
- }
563
- catch (error) {
564
- lastError = error instanceof Error ? error : new Error(String(error));
565
- debugLog('ERROR', `第${i + 1}次请求失败: ${lastError.message}`);
566
- if (i < config.retryTimes) {
567
- await delay(config.retryInterval);
535
+ catch (error) {
536
+ lastError = error instanceof Error ? error : new Error(String(error));
537
+ debugLog('ERROR', `${api.label} 第${attempt + 1}次请求失败: ${lastError.message}`);
538
+ if (attempt < config.retryTimes) {
539
+ await delay(config.retryInterval);
540
+ }
568
541
  }
569
542
  }
543
+ debugLog('WARN', `${api.label} 所有重试均失败,切换下一个API`);
570
544
  }
571
- throw lastError || new Error('API请求全部失败');
545
+ throw lastError || new Error('所有API请求全部失败');
572
546
  }
573
- async function parseUrl(url) {
547
+ async function parseUrl(url, type) {
574
548
  const realUrl = await resolveShortUrl(url);
575
549
  const candidates = [realUrl, url];
576
550
  for (const candidate of [...new Set(candidates)]) {
577
551
  try {
578
- const info = await fetchApi(candidate);
552
+ const info = await fetchApi(candidate, type);
579
553
  if (info.video || info.images.length > 0) {
580
554
  return { success: true, data: info };
581
555
  }
@@ -587,8 +561,8 @@ function apply(ctx, config) {
587
561
  }
588
562
  return { success: false, msg: texts.unsupportedPlatformText };
589
563
  }
590
- async function processSingleUrl(url) {
591
- const result = await parseUrl(url);
564
+ async function processSingleUrl(url, type) {
565
+ const result = await parseUrl(url, type);
592
566
  if (!result.success) {
593
567
  return { success: false, msg: result.msg, url };
594
568
  }
@@ -641,72 +615,70 @@ function apply(ctx, config) {
641
615
  }
642
616
  async function sendVideoFile(session, videoUrl) {
643
617
  if (!videoUrl)
644
- throw new Error('视频链接为空');
618
+ return;
645
619
  if (!config.showVideoFile) {
646
620
  return await sendWithTimeout(session, `视频链接:${videoUrl}`);
647
621
  }
648
- const sendLinkAsFallback = async () => {
622
+ const sendLink = async () => {
649
623
  await sendWithTimeout(session, `视频链接:${videoUrl}`).catch(() => { });
650
624
  };
651
- const tryDownloadAndSend = async () => {
652
- let tempFilePath = null;
625
+ if (config.forceDownloadVideo) {
653
626
  try {
654
- tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
627
+ const tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
655
628
  const localFile = `file://${tempFilePath}`;
656
- debugLog('INFO', `发送本地视频文件: ${localFile}`);
657
- return await sendWithTimeout(session, koishi_1.h.video(localFile));
629
+ await sendWithTimeout(session, koishi_1.h.video(localFile));
630
+ return;
658
631
  }
659
- finally {
660
- if (tempFilePath) {
661
- promises_1.default.unlink(tempFilePath).catch(e => debugLog('WARN', `删除临时文件失败: ${e}`));
632
+ catch (e) {
633
+ debugLog('ERROR', '强制下载失败,尝试直接发送URL:', getErrorMessage(e));
634
+ try {
635
+ await sendWithTimeout(session, koishi_1.h.video(videoUrl));
636
+ return;
637
+ }
638
+ catch (urlErr) {
639
+ debugLog('ERROR', '发送URL也失败,降级发送链接:', getErrorMessage(urlErr));
640
+ await sendLink();
662
641
  }
663
642
  }
664
- };
665
- if (config.forceDownloadVideo) {
666
- try {
667
- return await tryDownloadAndSend();
668
- }
669
- catch (err) {
670
- debugLog('ERROR', `下载并发送视频失败: ${getErrorMessage(err)}`);
671
- await sendLinkAsFallback();
672
- }
643
+ return;
673
644
  }
674
- else {
645
+ try {
646
+ debugLog('INFO', '尝试直接发送视频URL');
647
+ await sendWithTimeout(session, koishi_1.h.video(videoUrl));
648
+ return;
649
+ }
650
+ catch (urlErr) {
651
+ debugLog('ERROR', '直接发送URL失败,尝试下载:', getErrorMessage(urlErr));
675
652
  try {
676
- debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
677
- return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
653
+ const tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 0);
654
+ const localFile = `file://${tempFilePath}`;
655
+ await sendWithTimeout(session, koishi_1.h.video(localFile));
656
+ return;
678
657
  }
679
- catch (err) {
680
- debugLog('ERROR', `直接发送URL失败,尝试下载: ${getErrorMessage(err)}`);
681
- try {
682
- return await tryDownloadAndSend();
683
- }
684
- catch (downloadErr) {
685
- debugLog('ERROR', `下载并发送视频也失败: ${getErrorMessage(downloadErr)}`);
686
- await sendLinkAsFallback();
687
- }
658
+ catch (downloadErr) {
659
+ debugLog('ERROR', '下载失败,降级发送链接:', getErrorMessage(downloadErr));
660
+ await sendLink();
688
661
  }
689
662
  }
690
663
  }
691
- async function flush(session, urls) {
692
- const uniqueUrls = [...new Set(urls)];
693
- debugLog('INFO', `开始解析 ${uniqueUrls.length} 个链接`);
664
+ async function flush(session, matches) {
665
+ debugLog('INFO', `开始解析 ${matches.length} 个链接`);
694
666
  const items = [];
695
667
  const errors = [];
696
- for (let i = 0; i < uniqueUrls.length; i++) {
697
- const url = uniqueUrls[i];
698
- debugLog('INFO', `正在解析第 ${i + 1}/${uniqueUrls.length} 个链接: ${url}`);
699
- const result = await processSingleUrl(url);
668
+ for (let i = 0; i < matches.length; i++) {
669
+ const match = matches[i];
670
+ debugLog('INFO', `正在解析第 ${i + 1}/${matches.length} 个链接: ${match.url} (平台: ${match.type})`);
671
+ const result = await processSingleUrl(match.url, match.type);
700
672
  if (result.success) {
701
673
  items.push(result.data);
702
674
  }
703
675
  else {
704
676
  const item = texts.parseErrorItemFormat
705
- .replace(/\$\{url\}/g, url.length > 50 ? url.slice(0, 50) + '...' : url)
677
+ .replace(/\$\{url\}/g, match.url.length > 50 ? match.url.slice(0, 50) + '...' : match.url)
706
678
  .replace(/\$\{msg\}/g, result.msg);
707
679
  errors.push(item);
708
680
  }
709
- if (i < uniqueUrls.length - 1) {
681
+ if (i < matches.length - 1) {
710
682
  await delay(500);
711
683
  }
712
684
  }
@@ -718,24 +690,6 @@ function apply(ctx, config) {
718
690
  debugLog('INFO', '没有成功解析的内容');
719
691
  return;
720
692
  }
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
693
  const enableForward = config.enableForward && session.platform === 'onebot';
740
694
  const botName = config.botName || '视频解析机器人';
741
695
  if (enableForward) {
@@ -755,7 +709,9 @@ function apply(ctx, config) {
755
709
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
756
710
  }
757
711
  }
758
- // 视频已单独发送,合并转发中不再添加
712
+ if (p.video) {
713
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.video(p.video), botName));
714
+ }
759
715
  }
760
716
  if (forwardMessages.length) {
761
717
  const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
@@ -773,7 +729,6 @@ function apply(ctx, config) {
773
729
  }
774
730
  }
775
731
  else {
776
- // 非合并转发,只发送文字、封面、图片(视频已在之前发过)
777
732
  for (const item of items) {
778
733
  const p = item.parsed;
779
734
  const text = item.text;
@@ -785,6 +740,20 @@ function apply(ctx, config) {
785
740
  await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
786
741
  await delay(300);
787
742
  }
743
+ if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
744
+ if (config.showVideoFile) {
745
+ try {
746
+ await sendVideoFile(session, p.video);
747
+ }
748
+ catch (e) {
749
+ debugLog('ERROR', `视频发送失败: ${getErrorMessage(e)}`);
750
+ }
751
+ }
752
+ else {
753
+ await sendWithTimeout(session, `视频链接:${p.video}`);
754
+ }
755
+ await delay(500);
756
+ }
788
757
  if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
789
758
  const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
790
759
  for (const imgUrl of imageUrls) {
@@ -805,10 +774,10 @@ function apply(ctx, config) {
805
774
  return;
806
775
  if (session.selfId === session.userId)
807
776
  return;
808
- const urls = extractAllUrlsFromMessage(session);
809
- if (!urls.length)
777
+ const matches = extractAllUrlsFromMessage(session);
778
+ if (!matches.length)
810
779
  return;
811
- debugLog('INFO', `检测到 ${urls.length} 个链接,开始处理`);
780
+ debugLog('INFO', `检测到 ${matches.length} 个链接,开始处理`);
812
781
  if (config.showWaitingTip) {
813
782
  try {
814
783
  await sendWithTimeout(session, texts.waitingTipText);
@@ -817,15 +786,15 @@ function apply(ctx, config) {
817
786
  debugLog('WARN', '发送等待提示失败:', e);
818
787
  }
819
788
  }
820
- await flush(session, urls);
789
+ await flush(session, matches);
821
790
  });
822
791
  ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
823
792
  if (!url) {
824
793
  await sendWithTimeout(session, texts.invalidLinkText);
825
794
  return;
826
795
  }
827
- const us = extractUrl(url);
828
- if (!us.length) {
796
+ const matches = linkTypeParser(url);
797
+ if (!matches.length) {
829
798
  await sendWithTimeout(session, texts.invalidLinkText);
830
799
  return;
831
800
  }
@@ -835,7 +804,7 @@ function apply(ctx, config) {
835
804
  }
836
805
  catch { }
837
806
  }
838
- await flush(session, us);
807
+ await flush(session, matches);
839
808
  });
840
809
  const tempCleanupInterval = setInterval(async () => {
841
810
  try {
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.6",
4
+ "version": "1.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -76,4 +76,4 @@
76
76
  "engines": {
77
77
  "node": ">=16.0.0"
78
78
  }
79
- }
79
+ }
package/readme.md CHANGED
@@ -3,30 +3,10 @@
3
3
  ## 项目介绍 (Project Introduction)
4
4
 
5
5
  ### 中文
6
- 这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20+主流平台的短视频/图集/实况链接。核心特性:
7
- - 🌐 统一API解析,覆盖20+热门平台,无需繁琐配置
8
- - 🤖 自动识别链接来源,即丢即用,并支持解析 XML/JSON 卡片消息中的链接(如 QQ/OneBot 平台的分享卡片)
9
- - 🎨 完全自定义的解析结果格式,支持多项变量替换,变量无值自动隐藏行
10
- - 🐛 内置Debug调试模式,可详细记录所有操作与API交互日志
11
- - 📤 支持OneBot平台消息合并转发,优化多图文展示体验
12
- - 💬 所有提示文案均可自定义,适配多语言场景
13
- - 🔁 消息发送支持自动重试,与API重试配置联动,增强稳定性
14
- - 🚀 内置LRU内存缓存,避免短时间内重复解析同一链接;串行解析防止API限流
15
- - ⚡ 智能视频发送策略:优先直接发送URL,失败自动降级为本地文件发送
16
- - 🛡️ 可选视频大小限制,防止超大文件占满服务器磁盘;自动清理所有临时文件
6
+ 这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20+主流平台的短视频/图集/实况链接。
17
7
 
18
8
  ### English
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:
20
- - 🌐 Unified API parsing, covering 20+ popular platforms without complex configuration
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)
22
- - 🎨 Fully customizable parsing result format with variable substitutions, empty variables hide the line automatically
23
- - 🐛 Built-in Debug mode, recording detailed operations and API interaction logs
24
- - 📤 Support OneBot message forwarding for better image/video display
25
- - 💬 All prompt texts are customizable for multilingual scenarios
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; 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
- - 🛡️ Optional video size limit to prevent oversized files from filling up server disk; automatic cleanup of all temporary files
9
+ 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.
30
10
 
31
11
  ## 项目仓库 (Repository)
32
12
  - GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
@@ -71,6 +51,14 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
71
51
  | `videoSendTimeout` | number | 60000 | 视频消息发送超时时间(毫秒,0 为不限制) |
72
52
  | `userAgent` | string | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36` | API 请求使用的 User-Agent |
73
53
 
54
+ ### API 选择与回退设置
55
+ | 配置项 | 类型 | 默认值 | 说明 |
56
+ |--------|------|--------|------|
57
+ | `primaryApiUrl` | string | `https://api.bugpk.com/api/short_videos` | 主 API 地址,解析时优先使用 |
58
+ | `backupApiUrl` | string | `https://api.bugpk.com/api/svparse` | 备用主 API 地址,仅支持抖音、小红书、Instagram、即梦平台解析 |
59
+ | `useDedicatedApiFirst` | boolean | false | 是否优先使用平台专属 API,失败后依次回退到主 API、备用主 API |
60
+ | `customApis` | array | [] | 自定义平台专属 API 列表,每项需选择平台并填写 API 地址,可覆盖内置的默认专属 API |
61
+
74
62
  ### 错误与重试设置
75
63
  | 配置项 | 类型 | 默认值 | 说明 |
76
64
  |--------|------|--------|------|
@@ -81,7 +69,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
81
69
  ### 发送方式设置
82
70
  | 配置项 | 类型 | 默认值 | 说明 |
83
71
  |--------|------|--------|------|
84
- | `enableForward` | boolean | false | 是否启用合并转发(仅 OneBot 平台),视频会单独发送 |
72
+ | `enableForward` | boolean | false | 是否启用合并转发(仅 OneBot 平台),启用后视频与图文将整合进同一条合并消息 |
85
73
 
86
74
  ### 界面文字设置
87
75
  | 配置项 | 类型 | 默认值 | 说明 |