koishi-plugin-video-parser-all 1.5.6 → 1.5.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.
Files changed (2) hide show
  1. package/lib/index.js +147 -131
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -283,68 +283,76 @@ let debugEnabled = false;
283
283
  function debugLog(level, ...args) {
284
284
  if (!debugEnabled)
285
285
  return;
286
- logger.info(`[${new Date().toISOString()}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}`);
286
+ const safe = args.map(a => {
287
+ try {
288
+ return typeof a === 'object' ? JSON.stringify(a) : String(a);
289
+ }
290
+ catch {
291
+ return '[unserializable]';
292
+ }
293
+ });
294
+ logger.info(`[${new Date().toISOString()}] [${level}] ${safe.join(' ')}`);
287
295
  }
288
296
  const BUILTIN_LINK_RULES = [
289
- { pattern: /https?:\/\/(?:www\.)?bilibili\.com\/video\/([ab]v[0-9a-zA-Z_-]+)[^\s]*/gi, type: 'bilibili' },
290
- { pattern: /https?:\/\/b23\.tv\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'bilibili' },
291
- { pattern: /https?:\/\/bili\d+\.cn\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'bilibili' },
292
- { pattern: /https?:\/\/b23\.wtf\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'bilibili' },
293
- { pattern: /https?:\/\/bili2233\.cn\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'bilibili' },
294
- { pattern: /https?:\/\/(?:www\.)?douyin\.com\/video\/\d{10,}[^\s]*/gi, type: 'douyin' },
295
- { pattern: /https?:\/\/v\.douyin\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'douyin' },
296
- { pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/short-video\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'kuaishou' },
297
- { pattern: /https?:\/\/v\.kuaishou\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'kuaishou' },
298
- { pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/f\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'kuaishou' },
299
- { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/discovery\/item\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'xiaohongshu' },
300
- { pattern: /https?:\/\/xhslink\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'xiaohongshu' },
301
- { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/explore\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'xiaohongshu' },
302
- { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/board\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'xiaohongshu' },
303
- { pattern: /https?:\/\/weibo\.com\/\d+\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'weibo' },
304
- { pattern: /https?:\/\/video\.weibo\.com\/show\?fid=[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'weibo' },
305
- { pattern: /https?:\/\/t\.cn\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'weibo' },
306
- { pattern: /https?:\/\/m\.weibo\.cn\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'weibo' },
307
- { pattern: /https?:\/\/(?:www\.)?ixigua\.com\/\d{10,}[^\s]*/gi, type: 'xigua' },
308
- { pattern: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}[^\s]*/gi, type: 'youtube' },
309
- { pattern: /https?:\/\/youtu\.be\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'youtube' },
310
- { pattern: /https?:\/\/(?:www\.)?youtube\.com\/shorts\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'youtube' },
311
- { pattern: /https?:\/\/(?:www\.)?tiktok\.com\/@[\w.]+\/video\/\d{10,}[^\s]*/gi, type: 'tiktok' },
312
- { pattern: /https?:\/\/vm\.tiktok\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'tiktok' },
313
- { pattern: /https?:\/\/vt\.tiktok\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'tiktok' },
314
- { pattern: /https?:\/\/(?:www\.)?acfun\.cn\/v\/ac\d{10,}[^\s]*/gi, type: 'acfun' },
315
- { pattern: /https?:\/\/(?:www\.)?zhihu\.com\/video\/\d{10,}[^\s]*/gi, type: 'zhihu' },
316
- { pattern: /https?:\/\/(?:www\.|m\.)?zhihu\.com\/question\/\d+\/answer\/\d+[^\s]*/gi, type: 'zhihu' },
317
- { pattern: /https?:\/\/zhuanlan\.zhihu\.com\/p\/\d+[^\s]*/gi, type: 'zhihu' },
318
- { pattern: /https?:\/\/(?:www\.|m\.)?zhihu\.com\/zvideo\/\d+[^\s]*/gi, type: 'zhihu' },
319
- { pattern: /https?:\/\/weishi\.qq\.com\/weishi\/feed\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'weishi' },
320
- { pattern: /https?:\/\/(?:www\.)?huya\.com\/video\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'huya' },
321
- { pattern: /https?:\/\/haokan\.baidu\.com\/v\?vid=[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'haokan' },
322
- { pattern: /https?:\/\/(?:www\.)?meipai\.com\/media\/\d{10,}[^\s]*/gi, type: 'meipai' },
323
- { pattern: /https?:\/\/twitter\.com\/\w+\/status\/\d{10,}[^\s]*/gi, type: 'twitter' },
324
- { pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}[^\s]*/gi, type: 'twitter' },
325
- { pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'instagram' },
326
- { pattern: /https?:\/\/(?:www\.)?instagram\.com\/reel\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'instagram' },
327
- { pattern: /https?:\/\/(?:www\.)?instagram\.com\/share\/(?:reel|p)\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'instagram' },
328
- { pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}[^\s]*/gi, type: 'doubao' },
329
- { pattern: /https?:\/\/(?:www\.)?doubao\.com\/video-sharing\?[^\s]*/gi, type: 'doubao' },
330
- { pattern: /https?:\/\/(?:www\.)?doubao\.com\/thread\/[^\s]+/gi, type: 'doubao_image' },
331
- { pattern: /https?:\/\/(?:www\.)?jimeng\.jianying\.com\/[^\s]*/gi, type: 'jimeng' },
332
- { pattern: /https?:\/\/(?:www\.)?jimeng\.cn\/[^\s]*/gi, type: 'jimeng' },
333
- { pattern: /https?:\/\/(?:www\.)?dreamina\.jianying\.com\/[^\s]*/gi, type: 'jimeng' },
334
- { pattern: /https?:\/\/(?:www\.)?dreamina\.capcut\.com\/[^\s]*/gi, type: 'jimeng' },
335
- { pattern: /https?:\/\/(?:www\.)?oasis\.weibo\.com\/v\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'oasis' },
336
- { pattern: /https?:\/\/channels\.weixin\.qq\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'wechat_channel' },
337
- { pattern: /https?:\/\/weixin\.qq\.com\/sph\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'wechat_channel' },
338
- { pattern: /https?:\/\/(?:www\.)?pearvideo\.com\/video_\d+[^\s]*/gi, type: 'lishi' },
339
- { pattern: /https?:\/\/video\.li\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'lishi' },
340
- { pattern: /https?:\/\/(?:www\.)?quanmin\.tv\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'quanmin' },
341
- { pattern: /https?:\/\/(?:www\.)?quanmintv\.cn\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'quanmin' },
342
- { pattern: /https?:\/\/h5\.pipigx\.com\/pp\/post\/\d+[^\s]*/gi, type: 'pipigx' },
343
- { pattern: /https?:\/\/(?:www\.)?ippzone\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'pipigx' },
344
- { pattern: /https?:\/\/(?:h5|www)\.pipix\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'pipixia' },
345
- { pattern: /https?:\/\/(?:www\.)?pipixia\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'pipixia' },
346
- { pattern: /https?:\/\/share\.xiaochuankeji\.cn\/hybrid\/share\/post\?pid=\d+[^\s]*/gi, type: 'zuiyou' },
347
- { pattern: /https?:\/\/(?:h5|www)\.izuiyou\.com\/[0-9a-zA-Z_\/-]+[^\s]*/gi, type: 'zuiyou' },
297
+ { pattern: /https?:\/\/(?:www\.)?bilibili\.com\/video\/([ab]v[0-9a-zA-Z_-]+)(?:\?[^\s'"“”‘’]*)?/gi, type: 'bilibili' },
298
+ { pattern: /https?:\/\/b23\.tv\/[0-9a-zA-Z_\/-]+/gi, type: 'bilibili' },
299
+ { pattern: /https?:\/\/bili\d+\.cn\/[0-9a-zA-Z_\/-]+/gi, type: 'bilibili' },
300
+ { pattern: /https?:\/\/b23\.wtf\/[0-9a-zA-Z_\/-]+/gi, type: 'bilibili' },
301
+ { pattern: /https?:\/\/bili2233\.cn\/[0-9a-zA-Z_\/-]+/gi, type: 'bilibili' },
302
+ { pattern: /https?:\/\/(?:www\.)?douyin\.com\/video\/\d{10,}/gi, type: 'douyin' },
303
+ { pattern: /https?:\/\/v\.douyin\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'douyin' },
304
+ { pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/short-video\/[0-9a-zA-Z_\/-]+/gi, type: 'kuaishou' },
305
+ { pattern: /https?:\/\/v\.kuaishou\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'kuaishou' },
306
+ { pattern: /https?:\/\/(?:www\.)?kuaishou\.com\/f\/[0-9a-zA-Z_\/-]+/gi, type: 'kuaishou' },
307
+ { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/discovery\/item\/[0-9a-zA-Z_\/-]+/gi, type: 'xiaohongshu' },
308
+ { pattern: /https?:\/\/xhslink\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'xiaohongshu' },
309
+ { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/explore\/[0-9a-zA-Z_\/-]+/gi, type: 'xiaohongshu' },
310
+ { pattern: /https?:\/\/(?:www\.)?xiaohongshu\.com\/board\/[0-9a-zA-Z_\/-]+/gi, type: 'xiaohongshu' },
311
+ { pattern: /https?:\/\/weibo\.com\/\d+\/[0-9a-zA-Z_\/-]+/gi, type: 'weibo' },
312
+ { pattern: /https?:\/\/video\.weibo\.com\/show\?fid=[0-9a-zA-Z_\/-]+/gi, type: 'weibo' },
313
+ { pattern: /https?:\/\/t\.cn\/[0-9a-zA-Z_\/-]+/gi, type: 'weibo' },
314
+ { pattern: /https?:\/\/m\.weibo\.cn\/[0-9a-zA-Z_\/-]+/gi, type: 'weibo' },
315
+ { pattern: /https?:\/\/(?:www\.)?ixigua\.com\/\d{10,}/gi, type: 'xigua' },
316
+ { pattern: /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}/gi, type: 'youtube' },
317
+ { pattern: /https?:\/\/youtu\.be\/[0-9a-zA-Z_\/-]+/gi, type: 'youtube' },
318
+ { pattern: /https?:\/\/(?:www\.)?youtube\.com\/shorts\/[0-9a-zA-Z_\/-]+/gi, type: 'youtube' },
319
+ { pattern: /https?:\/\/(?:www\.)?tiktok\.com\/@[\w.]+\/video\/\d{10,}/gi, type: 'tiktok' },
320
+ { pattern: /https?:\/\/vm\.tiktok\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'tiktok' },
321
+ { pattern: /https?:\/\/vt\.tiktok\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'tiktok' },
322
+ { pattern: /https?:\/\/(?:www\.)?acfun\.cn\/v\/ac\d{10,}/gi, type: 'acfun' },
323
+ { pattern: /https?:\/\/(?:www\.)?zhihu\.com\/video\/\d{10,}/gi, type: 'zhihu' },
324
+ { pattern: /https?:\/\/(?:www\.|m\.)?zhihu\.com\/question\/\d+\/answer\/\d+/gi, type: 'zhihu' },
325
+ { pattern: /https?:\/\/zhuanlan\.zhihu\.com\/p\/\d+/gi, type: 'zhihu' },
326
+ { pattern: /https?:\/\/(?:www\.|m\.)?zhihu\.com\/zvideo\/\d+/gi, type: 'zhihu' },
327
+ { pattern: /https?:\/\/weishi\.qq\.com\/weishi\/feed\/[0-9a-zA-Z_\/-]+/gi, type: 'weishi' },
328
+ { pattern: /https?:\/\/(?:www\.)?huya\.com\/video\/[0-9a-zA-Z_\/-]+/gi, type: 'huya' },
329
+ { pattern: /https?:\/\/haokan\.baidu\.com\/v\?vid=[0-9a-zA-Z_\/-]+/gi, type: 'haokan' },
330
+ { pattern: /https?:\/\/(?:www\.)?meipai\.com\/media\/\d{10,}/gi, type: 'meipai' },
331
+ { pattern: /https?:\/\/twitter\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
332
+ { pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
333
+ { pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_\/-]+/gi, type: 'instagram' },
334
+ { pattern: /https?:\/\/(?:www\.)?instagram\.com\/reel\/[0-9a-zA-Z_\/-]+/gi, type: 'instagram' },
335
+ { pattern: /https?:\/\/(?:www\.)?instagram\.com\/share\/(?:reel|p)\/[0-9a-zA-Z_\/-]+/gi, type: 'instagram' },
336
+ { pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}/gi, type: 'doubao' },
337
+ { pattern: /https?:\/\/(?:www\.)?doubao\.com\/video-sharing\?[^\s'"“”‘’]*/gi, type: 'doubao' },
338
+ { pattern: /https?:\/\/(?:www\.)?doubao\.com\/thread\/[^\s'"“”‘’]+/gi, type: 'doubao_image' },
339
+ { pattern: /https?:\/\/(?:www\.)?jimeng\.jianying\.com\/[^\s'"“”‘’]*/gi, type: 'jimeng' },
340
+ { pattern: /https?:\/\/(?:www\.)?jimeng\.cn\/[^\s'"“”‘’]*/gi, type: 'jimeng' },
341
+ { pattern: /https?:\/\/(?:www\.)?dreamina\.jianying\.com\/[^\s'"“”‘’]*/gi, type: 'jimeng' },
342
+ { pattern: /https?:\/\/(?:www\.)?dreamina\.capcut\.com\/[^\s'"“”‘’]*/gi, type: 'jimeng' },
343
+ { pattern: /https?:\/\/(?:www\.)?oasis\.weibo\.com\/v\/[0-9a-zA-Z_\/-]+/gi, type: 'oasis' },
344
+ { pattern: /https?:\/\/channels\.weixin\.qq\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'wechat_channel' },
345
+ { pattern: /https?:\/\/weixin\.qq\.com\/sph\/[0-9a-zA-Z_\/-]+/gi, type: 'wechat_channel' },
346
+ { pattern: /https?:\/\/(?:www\.)?pearvideo\.com\/video_\d+/gi, type: 'lishi' },
347
+ { pattern: /https?:\/\/video\.li\/[0-9a-zA-Z_\/-]+/gi, type: 'lishi' },
348
+ { pattern: /https?:\/\/(?:www\.)?quanmin\.tv\/[0-9a-zA-Z_\/-]+/gi, type: 'quanmin' },
349
+ { pattern: /https?:\/\/(?:www\.)?quanmintv\.cn\/[0-9a-zA-Z_\/-]+/gi, type: 'quanmin' },
350
+ { pattern: /https?:\/\/h5\.pipigx\.com\/pp\/post\/\d+/gi, type: 'pipigx' },
351
+ { pattern: /https?:\/\/(?:www\.)?ippzone\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'pipigx' },
352
+ { pattern: /https?:\/\/(?:h5|www)\.pipix\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'pipixia' },
353
+ { pattern: /https?:\/\/(?:www\.)?pipixia\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'pipixia' },
354
+ { pattern: /https?:\/\/share\.xiaochuankeji\.cn\/hybrid\/share\/post\?pid=\d+/gi, type: 'zuiyou' },
355
+ { pattern: /https?:\/\/(?:h5|www)\.izuiyou\.com\/[0-9a-zA-Z_\/-]+/gi, type: 'zuiyou' },
348
356
  ];
349
357
  function buildCustomLinkRules(customPlatforms) {
350
358
  if (!Array.isArray(customPlatforms) || customPlatforms.length === 0)
@@ -356,7 +364,7 @@ function buildCustomLinkRules(customPlatforms) {
356
364
  if (keywords.length === 0)
357
365
  return null;
358
366
  const escaped = keywords.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
359
- const pattern = new RegExp('https?://[^/\\s]*(' + escaped.join('|') + ')[^\\s]*', 'gi');
367
+ const pattern = new RegExp(`https?://[^/\\s"'“”‘’]*(${escaped.join('|')})[^\\s"'“”‘’]*`, 'gi');
360
368
  return { pattern, type: `custom_${p.name}` };
361
369
  })
362
370
  .filter(Boolean);
@@ -370,7 +378,10 @@ function linkTypeParser(content, customRules) {
370
378
  let match;
371
379
  rule.pattern.lastIndex = 0;
372
380
  while ((match = rule.pattern.exec(content)) !== null) {
373
- const url = match[0];
381
+ let url = match[0];
382
+ url = cleanUrl(url);
383
+ if (!url)
384
+ continue;
374
385
  if (seen.has(url))
375
386
  continue;
376
387
  seen.add(url);
@@ -385,8 +396,8 @@ function cleanUrl(url) {
385
396
  .replace(/&lt;/g, '<')
386
397
  .replace(/&gt;/g, '>')
387
398
  .replace(/\\\//g, '/');
388
- url = url.replace(/^[\s"'<]+/, '');
389
- url = url.replace(/[\s"'<>\{\}\[\]`,;]+$/, '');
399
+ url = url.replace(/^[\s"'<“”‘’]+/, '');
400
+ url = url.replace(/[\s"'<>\{\}\[\]`,;,。!?:;“”‘’…—~]+$/, '');
390
401
  if (!/^https?:\/\//i.test(url)) {
391
402
  if (/^\/\//.test(url))
392
403
  url = 'https:' + url;
@@ -630,7 +641,7 @@ function parseApiResponse(raw, maxDescLen, fieldMapping) {
630
641
  return { type, title, desc, author, uid, avatar, cover, video, videos, images, live_photo, music, like, comment, collect, share, play, duration, publishTime, author_followers, author_signature, admire };
631
642
  }
632
643
  const formatVarRegex = /\$\{([^}]+)\}/g;
633
- function generateFormattedText(p, format) {
644
+ function generateFormattedText(p, format, index, total) {
634
645
  const imageCount = p.images.length || p.live_photo.length;
635
646
  const vars = {
636
647
  '标题': p.title,
@@ -648,10 +659,6 @@ function generateFormattedText(p, format) {
648
659
  '音乐标题': p.music.title || '',
649
660
  '音乐作者': p.music.author || '',
650
661
  };
651
- const varReplacements = Object.entries(vars).map(([key, val]) => ({
652
- regex: new RegExp(`\\$\\{${key}\\}`, 'g'),
653
- value: val,
654
- }));
655
662
  const lines = format.split('\n');
656
663
  const resultLines = [];
657
664
  for (const line of lines) {
@@ -670,12 +677,16 @@ function generateFormattedText(p, format) {
670
677
  continue;
671
678
  }
672
679
  let newLine = line;
673
- for (const { regex, value } of varReplacements) {
674
- newLine = newLine.replace(regex, value);
680
+ for (const [key, val] of Object.entries(vars)) {
681
+ newLine = newLine.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), val);
675
682
  }
676
683
  resultLines.push(newLine);
677
684
  }
678
- return resultLines.join('\n').trim();
685
+ let text = resultLines.join('\n').trim();
686
+ if (index !== undefined && total !== undefined && total > 1) {
687
+ text = `【${index}/${total}】\n${text}`;
688
+ }
689
+ return text;
679
690
  }
680
691
  const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
681
692
  function buildForwardNode(session, content, botName) {
@@ -719,14 +730,17 @@ function apply(ctx, config) {
719
730
  const imgSig = p.images?.length ? p.images.slice(0, 3).join('|') : (p.live_photo?.slice(0, 3).map(lp => lp.image).join('|') || '');
720
731
  return [p.type, p.title, p.author, p.uid, p.video, imgSig].map(v => String(v ?? '')).join('::');
721
732
  }
722
- const texts = {
723
- waitingTipText: config.waitingTipText || '正在解析视频,请稍候...',
724
- unsupportedPlatformText: config.unsupportedPlatformText || '不支持该平台链接',
725
- invalidLinkText: config.invalidLinkText || '无效的视频链接',
726
- parseErrorPrefix: config.parseErrorPrefix || '❌ 解析失败:',
727
- parseErrorItemFormat: config.parseErrorItemFormat || '【${url}】: ${msg}',
728
- deduplicationTipText: config.deduplicationTipText || '链接 ${url} 在最近 ${interval} 秒内已解析过,已跳过。',
729
- };
733
+ function getText(key) {
734
+ const defaults = {
735
+ waitingTipText: '正在解析视频,请稍候...',
736
+ unsupportedPlatformText: '不支持该平台链接',
737
+ invalidLinkText: '无效的视频链接',
738
+ parseErrorPrefix: ' 解析失败:',
739
+ parseErrorItemFormat: '【${url}】: ${msg}',
740
+ deduplicationTipText: '链接 ${url} 在最近 ${interval} 秒内已解析过,已跳过。',
741
+ };
742
+ return config[key] || defaults[key] || '';
743
+ }
730
744
  const proxyConfig = config.proxy || {};
731
745
  const customPlatforms = (config.customPlatforms || []).map((p) => ({
732
746
  name: p.name,
@@ -803,6 +817,31 @@ function apply(ctx, config) {
803
817
  return { [customHeaderName]: apiKey };
804
818
  return {};
805
819
  }
820
+ async function sendWithTimeout(session, content, customRetries) {
821
+ const maxRetries = customRetries ?? config.retryTimes ?? 3;
822
+ const retryDelay = config.retryInterval || 1000;
823
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
824
+ try {
825
+ let sendPromise = session.send(content);
826
+ if (config.videoSendTimeout > 0) {
827
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout));
828
+ return await Promise.race([sendPromise, timeoutPromise]);
829
+ }
830
+ else {
831
+ return await sendPromise;
832
+ }
833
+ }
834
+ catch (err) {
835
+ const errMsg = getErrorMessage(err);
836
+ debugLog('ERROR', `发送失败尝试 ${attempt + 1}: ${errMsg}`);
837
+ if (attempt < maxRetries)
838
+ await delay(retryDelay);
839
+ else if (!config.ignoreSendError)
840
+ throw err;
841
+ }
842
+ }
843
+ return null;
844
+ }
806
845
  async function sendMedia(session, url, type, showFile) {
807
846
  if (!url)
808
847
  return;
@@ -835,7 +874,7 @@ function apply(ctx, config) {
835
874
  if (lastTime && (Date.now() - lastTime < config.deduplicationInterval * 1000)) {
836
875
  debugLog('INFO', `跳过重复链接: ${match.url}`);
837
876
  const shortUrl = match.url.length > 80 ? match.url.slice(0, 80) + '...' : match.url;
838
- const tip = texts.deduplicationTipText.replace(/\$\{url\}/g, shortUrl).replace(/\$\{interval\}/g, String(config.deduplicationInterval));
877
+ const tip = getText('deduplicationTipText').replace(/\$\{url\}/g, shortUrl).replace(/\$\{interval\}/g, String(config.deduplicationInterval));
839
878
  await sendWithTimeout(session, tip).catch(() => { });
840
879
  return;
841
880
  }
@@ -859,7 +898,7 @@ function apply(ctx, config) {
859
898
  }
860
899
  else {
861
900
  const displayUrl = match.url.length > 80 ? match.url.slice(0, 80) + '...' : match.url;
862
- const item = texts.parseErrorItemFormat.replace(/\$\{url\}/g, displayUrl).replace(/\$\{msg\}/g, result.msg);
901
+ const item = getText('parseErrorItemFormat').replace(/\$\{url\}/g, displayUrl).replace(/\$\{msg\}/g, result.msg);
863
902
  errors.push(item);
864
903
  }
865
904
  }
@@ -869,16 +908,19 @@ function apply(ctx, config) {
869
908
  });
870
909
  await Promise.all(promises);
871
910
  if (errors.length)
872
- await sendWithTimeout(session, `${texts.parseErrorPrefix}\n${errors.join('\n')}`);
911
+ await sendWithTimeout(session, `${getText('parseErrorPrefix')}\n${errors.join('\n')}`);
873
912
  if (!items.length)
874
913
  return;
914
+ const totalItems = items.length;
875
915
  const enableForward = config.enableForward && (session.platform === 'onebot' || session.platform === 'satori');
876
916
  const botName = config.botName || '视频解析机器人';
877
917
  if (enableForward) {
878
918
  const forwardMessages = [];
879
- for (const item of items) {
919
+ for (let i = 0; i < items.length; i++) {
920
+ const item = items[i];
880
921
  const p = item.parsed;
881
- let text = item.text;
922
+ const textWithIndex = (totalItems > 1) ? `【${i + 1}/${totalItems}】\n${item.text}` : item.text;
923
+ let text = textWithIndex;
882
924
  if (config.showAuthorAvatar && p.avatar && config.showAuthorAvatarText) {
883
925
  text = text ? text + '\n' + (config.authorAvatarText || '作者头像:') : (config.authorAvatarText || '作者头像:');
884
926
  }
@@ -900,8 +942,6 @@ function apply(ctx, config) {
900
942
  if (p.type === 'live_photo' && p.live_photo?.length) {
901
943
  for (const lp of p.live_photo) {
902
944
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(lp.image), botName));
903
- if (lp.video)
904
- forwardMessages.push(buildForwardNode(session, koishi_1.h.video(lp.video), botName));
905
945
  }
906
946
  }
907
947
  else if (p.type === 'image' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
@@ -917,13 +957,15 @@ function apply(ctx, config) {
917
957
  forwardMessages.push(buildForwardNode(session, koishi_1.h.audio(p.music.url), botName));
918
958
  }
919
959
  }
920
- if (forwardMessages.length) {
960
+ const MAX_NODES = 50;
961
+ for (let i = 0; i < forwardMessages.length; i += MAX_NODES) {
962
+ const batch = forwardMessages.slice(i, i + MAX_NODES);
921
963
  try {
922
- await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)), config.retryTimes);
964
+ await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, batch), config.retryTimes);
923
965
  }
924
966
  catch (err) {
925
967
  debugLog('ERROR', '合并转发失败,降级逐条发送:', err);
926
- for (const node of forwardMessages) {
968
+ for (const node of batch) {
927
969
  await sendWithTimeout(session, node.data.content).catch(() => { });
928
970
  await delay(300);
929
971
  }
@@ -931,9 +973,11 @@ function apply(ctx, config) {
931
973
  }
932
974
  }
933
975
  else {
934
- for (const item of items) {
976
+ for (let i = 0; i < items.length; i++) {
977
+ const item = items[i];
935
978
  const p = item.parsed;
936
- let text = item.text;
979
+ const textWithIndex = (totalItems > 1) ? `【${i + 1}/${totalItems}】\n${item.text}` : item.text;
980
+ let text = textWithIndex;
937
981
  if (config.showAuthorAvatar && p.avatar && config.showAuthorAvatarText) {
938
982
  text = text ? text + '\n' + (config.authorAvatarText || '作者头像:') : (config.authorAvatarText || '作者头像:');
939
983
  }
@@ -958,16 +1002,14 @@ function apply(ctx, config) {
958
1002
  if (p.type === 'live_photo' && p.live_photo?.length) {
959
1003
  for (const lp of p.live_photo) {
960
1004
  await sendMedia(session, lp.image, 'image', config.showImageFileNew).catch(() => { });
961
- if (lp.video)
962
- await sendMedia(session, lp.video, 'video', config.showVideoFile).catch(() => { });
963
1005
  await delay(500);
964
1006
  }
965
1007
  }
966
1008
  else if (p.type === 'image' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
967
1009
  const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
968
- for (let i = 0; i < imageUrls.length; i++) {
969
- logger.info(`[发送] 图片 ${i + 1}/${imageUrls.length}`);
970
- await sendMedia(session, imageUrls[i], 'image', config.showImageFileNew).catch(() => { });
1010
+ for (let j = 0; j < imageUrls.length; j++) {
1011
+ logger.info(`[发送] 图片 ${j + 1}/${imageUrls.length}`);
1012
+ await sendMedia(session, imageUrls[j], 'image', config.showImageFileNew).catch(() => { });
971
1013
  await delay(1000);
972
1014
  }
973
1015
  }
@@ -1072,18 +1114,17 @@ function apply(ctx, config) {
1072
1114
  throw lastError || new Error('所有API请求全部失败');
1073
1115
  }
1074
1116
  async function parseUrl(url, type, fieldMapping, platformConf) {
1075
- const cleanedUrl = cleanUrl(url);
1076
1117
  try {
1077
- const info = await fetchApi(cleanedUrl, type, fieldMapping, platformConf);
1118
+ const info = await fetchApi(url, type, fieldMapping, platformConf);
1078
1119
  if (info.video || info.images.length > 0 || info.live_photo.length > 0)
1079
1120
  return { success: true, data: info };
1080
- debugLog('WARN', `解析成功但无内容: ${cleanedUrl}`);
1121
+ debugLog('WARN', `解析成功但无内容: ${url}`);
1122
+ return { success: false, msg: '解析接口返回空内容' };
1081
1123
  }
1082
1124
  catch (error) {
1083
- debugLog('ERROR', `解析失败: ${cleanedUrl}`, getErrorMessage(error));
1125
+ debugLog('ERROR', `解析失败: ${url}`, getErrorMessage(error));
1084
1126
  return { success: false, msg: getErrorMessage(error) };
1085
1127
  }
1086
- return { success: false, msg: texts.unsupportedPlatformText };
1087
1128
  }
1088
1129
  async function processSingleUrl(url, type, fieldMapping, platformConf) {
1089
1130
  const result = await parseUrl(url, type, fieldMapping, platformConf);
@@ -1092,31 +1133,6 @@ function apply(ctx, config) {
1092
1133
  const text = generateFormattedText(result.data, config.unifiedMessageFormat);
1093
1134
  return { success: true, data: { text, parsed: result.data } };
1094
1135
  }
1095
- async function sendWithTimeout(session, content, customRetries) {
1096
- const maxRetries = customRetries ?? config.retryTimes ?? 3;
1097
- const retryDelay = config.retryInterval || 1000;
1098
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1099
- try {
1100
- let sendPromise = session.send(content);
1101
- if (config.videoSendTimeout > 0) {
1102
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout));
1103
- return await Promise.race([sendPromise, timeoutPromise]);
1104
- }
1105
- else {
1106
- return await sendPromise;
1107
- }
1108
- }
1109
- catch (err) {
1110
- const errMsg = getErrorMessage(err);
1111
- debugLog('ERROR', `发送失败尝试 ${attempt + 1}: ${errMsg}`);
1112
- if (attempt < maxRetries)
1113
- await delay(retryDelay);
1114
- else if (!config.ignoreSendError)
1115
- throw err;
1116
- }
1117
- }
1118
- return null;
1119
- }
1120
1136
  const customRules = buildCustomLinkRules(config.customPlatforms || []);
1121
1137
  const axiosConfig = {
1122
1138
  timeout: config.timeout,
@@ -1155,7 +1171,7 @@ function apply(ctx, config) {
1155
1171
  debugLog('INFO', `检测到 ${matches.length} 个链接`);
1156
1172
  if (config.showWaitingTip) {
1157
1173
  try {
1158
- await sendWithTimeout(session, texts.waitingTipText);
1174
+ await sendWithTimeout(session, koishi_1.h.quote(session.messageId) + getText('waitingTipText'));
1159
1175
  }
1160
1176
  catch (e) {
1161
1177
  debugLog('WARN', '等待提示发送失败:', e);
@@ -1165,17 +1181,17 @@ function apply(ctx, config) {
1165
1181
  });
1166
1182
  ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
1167
1183
  if (!url) {
1168
- await sendWithTimeout(session, texts.invalidLinkText);
1184
+ await sendWithTimeout(session, getText('invalidLinkText'));
1169
1185
  return;
1170
1186
  }
1171
1187
  const matches = linkTypeParser(url, customRules);
1172
1188
  if (!matches.length) {
1173
- await sendWithTimeout(session, texts.invalidLinkText);
1189
+ await sendWithTimeout(session, getText('invalidLinkText'));
1174
1190
  return;
1175
1191
  }
1176
1192
  if (config.showWaitingTip) {
1177
1193
  try {
1178
- await sendWithTimeout(session, texts.waitingTipText);
1194
+ await sendWithTimeout(session, koishi_1.h.quote(session?.messageId) + getText('waitingTipText'));
1179
1195
  }
1180
1196
  catch { }
1181
1197
  }
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.5.6",
4
+ "version": "1.5.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": ["lib"],