koishi-plugin-video-parser-all 0.6.0 → 0.6.2

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 +58 -236
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -22,7 +22,7 @@ exports.Config = koishi_1.Schema.intersect([
22
22
  sameLinkInterval: koishi_1.Schema.number().min(0).default(180).description('相同链接重复解析间隔(秒)'),
23
23
  }).description('基础设置'),
24
24
  koishi_1.Schema.object({
25
- unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('统一消息格式(无法获取的变量会自动隐藏)\n变量介绍:\n${标题} - 内容标题\n${作者} - 作者名称\n${简介} - 内容简介\n${视频时长} - 视频时长\n${点赞数} - 点赞数量\n${投币数} - 投币数量(仅B站)\n${收藏数} - 收藏数量\n${转发数} - 转发/分享数量\n${播放数} - 播放数量\n${评论数} - 评论数量\n${音乐名} - 背景音乐名称'),
25
+ unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('统一消息格式(无法获取的变量会自动隐藏)\n变量介绍:\n${标题} - 内容标题\n${作者} - 作者名称\n${简介} - 内容简介\n${视频时长} - 视频时长\n${点赞数} - 点赞数量\n${投币数} - 投币数(仅B站)\n收藏数 - 收藏数量\n${转发数} - 转发/分享数量\n${播放数} - 播放数量\n${评论数} - 评论数量\n${音乐名} - 背景音乐名称'),
26
26
  }).description('统一消息格式'),
27
27
  koishi_1.Schema.object({
28
28
  showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
@@ -394,17 +394,16 @@ function parseData(rawResponse, maxDescLength) {
394
394
  const stat = {};
395
395
  Object.entries(VARIABLE_MAPPING).forEach(([varName, keys]) => {
396
396
  const value = findValueInObject(data, keys) || findValueInObject(rootData, keys);
397
- stat[varName] = value;
397
+ stat[varName] = value ?? '';
398
398
  });
399
399
  let type = 'video';
400
400
  if (data.jx?.type)
401
401
  type = data.jx.type;
402
402
  else if (data.type)
403
403
  type = data.type;
404
- const title = data.title ?? data.note_title ?? data.content_title ?? stat['标题'] ?? '无标题';
405
- const author = data.author?.name ?? data.nickname ?? data.user_name ?? stat['作者'] ?? '未知作者';
406
- let desc = data.desc ?? data.content ?? data.note_desc ?? stat['简介'] ?? title;
407
- desc = desc.toString().slice(0, maxDescLength);
404
+ const title = stat['标题'] || '无标题';
405
+ const author = stat['作者'] || '未知作者';
406
+ const desc = (stat['简介'] || title).slice(0, maxDescLength);
408
407
  const cover = data.cover ?? data.imgurl ?? data.pic ?? data.thumbnail ?? data.cover_url ?? '';
409
408
  let images = [];
410
409
  const imgRaw = data.images ?? data.pics ?? data.pic_urls ?? data.image_list ?? [];
@@ -412,21 +411,20 @@ function parseData(rawResponse, maxDescLength) {
412
411
  images = imgRaw.filter(i => i && typeof i === 'string');
413
412
  else if (imgRaw)
414
413
  images = [String(imgRaw)];
415
- let video = '';
416
- video = data.video?.url ?? data.item?.url ?? data.url ?? data.download_url ?? data.playUrl ?? data.video_url ?? '';
417
- const durationValue = data.duration ?? stat['视频时长'] ?? 0;
414
+ let video = data.video?.url ?? data.item?.url ?? data.url ?? data.download_url ?? data.playUrl ?? data.video_url ?? '';
415
+ const durationValue = stat['视频时长'] || 0;
418
416
  const duration = typeof durationValue === 'number' ? durationValue : parseInt(durationValue) || 0;
419
417
  const durationFormatted = formatDuration(durationValue);
420
418
  const live_photo = data.live_photo ?? [];
421
- const music = data.music?.title ?? data.music ?? stat['音乐名'] ?? '';
419
+ const music = stat['音乐名'] || '';
422
420
  const h_w = data.item?.h_w ?? [];
423
421
  const quality_urls = data.quality_urls ?? {};
424
422
  const default_quality = data.default_quality ?? '';
425
423
  const download_url = data.download_url ?? video;
426
424
  const play_count = stat['播放数'] ?? '';
427
- const reposts_count = stat['转发数'] ?? 0;
428
- const attitudes_count = stat['点赞数'] ?? 0;
429
- const comments_count = stat['评论数'] ?? 0;
425
+ const reposts_count = Number(stat['转发数']) || 0;
426
+ const attitudes_count = Number(stat['点赞数']) || 0;
427
+ const comments_count = Number(stat['评论数']) || 0;
430
428
  return {
431
429
  type: type,
432
430
  rawData: rawResponse,
@@ -453,9 +451,7 @@ function parseData(rawResponse, maxDescLength) {
453
451
  };
454
452
  }
455
453
  function generateFormattedText(parseData, config) {
456
- let format = config.unifiedMessageFormat;
457
- if (!format)
458
- format = '标题:${标题}\n作者:${作者}\n简介:${简介}';
454
+ let format = config.unifiedMessageFormat || '标题:${标题}\n作者:${作者}\n简介:${简介}';
459
455
  let result = format;
460
456
  const varMatches = result.match(/\$\{([^}]+)\}/g) || [];
461
457
  varMatches.forEach((varMatch) => {
@@ -571,13 +567,6 @@ function apply(ctx, config) {
571
567
  }
572
568
  try {
573
569
  const parseResult = parseData(resData, config.maxDescLength);
574
- const hasValidContent = true;
575
- if (!hasValidContent) {
576
- const code = ErrorCode.NO_VIDEO_FOUND;
577
- const msg = getErrorInfo(code, '链接有效但未获取到有效视频/图片内容,可能是私密/需要登录/已删除内容');
578
- logger.warn(`[${code}] 解析成功但无有效内容: ${url}`);
579
- return { data: null, code, msg };
580
- }
581
570
  logger.info(`[${ErrorCode.SUCCESS}] 解析成功: ${url}`);
582
571
  return {
583
572
  data: parseResult,
@@ -680,20 +669,17 @@ function apply(ctx, config) {
680
669
  items.push(result.data);
681
670
  }
682
671
  else {
683
- errors.push({ url, code: result.code, msg: result.msg });
684
- logger.error(`[${result.code}] 解析失败: ${url}, 原因: ${result.msg}`);
672
+ errors.push({ url, msg: result.msg });
685
673
  }
686
674
  }
687
675
  if (errors.length > 0) {
688
676
  const errorLines = errors.map(err => `【${err.url.slice(0, 50)}${err.url.length > 50 ? '...' : ''}】: ${err.msg}`);
689
- const errorMsg = `❌ 解析失败列表(共${errors.length}个链接):\n${errorLines.join('\n')}`;
690
- logger.error(`解析失败数量: ${errors.length}, 错误码列表: ${errors.map(e => e.code).join(', ')}`);
677
+ const errorMsg = `❌ 解析失败:\n${errorLines.join('\n')}`;
691
678
  await sendTimeout(session, errorMsg);
692
679
  await delay(500);
693
680
  }
694
681
  if (items.length === 0) {
695
- const failMsg = getErrorInfo(ErrorCode.UNKNOWN_ERROR, '所有链接均解析失败,请检查链接是否有效/是否需要登录,或稍后重试');
696
- await sendTimeout(session, `⚠ ${failMsg}`);
682
+ await sendTimeout(session, '⚠ 未解析到有效内容');
697
683
  return;
698
684
  }
699
685
  const enableForward = config.enableForward && session.platform === 'onebot';
@@ -704,72 +690,31 @@ function apply(ctx, config) {
704
690
  if (enableForward) {
705
691
  if (item.text)
706
692
  forwardMessages.push(buildForwardNode(session, item.text, botName));
707
- if (item.cover && forwardMessages.length < 100)
693
+ if (item.cover)
708
694
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.cover), botName));
709
- if (item.type === '图集' && item.images?.length) {
710
- for (let i = 0; i < item.images.length && forwardMessages.length < 100; i++) {
711
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
712
- }
713
- }
714
- if (item.type === 'image' && item.images?.length) {
715
- for (let i = 0; i < item.images.length && forwardMessages.length < 100; i++) {
716
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
695
+ if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
696
+ for (const img of item.images) {
697
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.image(img), botName));
717
698
  }
718
699
  }
719
- if (item.type === 'live' || item.type === 'live_photo') {
720
- if (item.live_photo?.length) {
721
- for (let i = 0; i < item.live_photo.length && forwardMessages.length < 100; i++) {
722
- const liveItem = item.live_photo[i];
723
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(liveItem.image), botName));
724
- if (liveItem.video) {
725
- try {
726
- const videoElem = koishi_1.h.video(liveItem.video);
727
- forwardMessages.push(buildForwardNode(session, videoElem, botName));
728
- }
729
- catch (e) {
730
- forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`视频链接: ${liveItem.video}`), botName));
731
- }
732
- }
733
- }
734
- }
735
- else if (item.images?.length) {
736
- for (let i = 0; i < item.images.length && forwardMessages.length < 100; i++) {
737
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
738
- }
739
- }
740
- }
741
- if (item.video && config.showVideoFile && forwardMessages.length < 100) {
742
- let videoElem;
700
+ if (item.video && config.showVideoFile) {
743
701
  try {
744
702
  if (config.downloadVideoBeforeSend) {
745
703
  const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
746
- const downloadResult = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
747
- if (downloadResult.code !== ErrorCode.SUCCESS) {
748
- const errorMsg = getErrorInfo(downloadResult.code);
749
- forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`${errorMsg}\n链接:${item.video}`), botName));
704
+ const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
705
+ if (dl.code === ErrorCode.SUCCESS) {
706
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.file(dl.filePath), botName));
750
707
  }
751
708
  else {
752
- videoElem = koishi_1.h.file(downloadResult.filePath);
753
- forwardMessages.push(buildForwardNode(session, videoElem, botName));
709
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`视频:${item.video}`), botName));
754
710
  }
755
711
  }
756
712
  else {
757
- const fileSize = await getFileSize(item.video, config.userAgent);
758
- if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
759
- const code = ErrorCode.VIDEO_SIZE_EXCEEDED;
760
- const errorMsg = getErrorInfo(code, `${fileSize}MB超过限制${config.maxVideoSize}MB`);
761
- videoElem = koishi_1.h.text(`${errorMsg},仅发送链接:${item.video}`);
762
- }
763
- else {
764
- videoElem = koishi_1.h.video(item.video);
765
- }
713
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
766
714
  }
767
715
  }
768
- catch (error) {
769
- const errorMsg = getErrorMessage(error);
770
- const code = ErrorCode.VIDEO_DOWNLOAD_FAILED;
771
- logger.error(`[${code}] 视频处理失败: ${errorMsg}`);
772
- forwardMessages.push(buildForwardNode(session, koishi_1.h.text(getErrorInfo(code, errorMsg) + `\n链接:${item.video}`), botName));
716
+ catch (e) {
717
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`视频:${item.video}`), botName));
773
718
  }
774
719
  }
775
720
  }
@@ -778,98 +723,37 @@ function apply(ctx, config) {
778
723
  await sendTimeout(session, item.text);
779
724
  await delay(300);
780
725
  }
781
- if (item.type === '图集' && item.images?.length) {
782
- const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
783
- await sendTimeout(session, imgMsg);
726
+ if (item.cover) {
727
+ await sendTimeout(session, koishi_1.h.image(item.cover));
728
+ await delay(300);
784
729
  }
785
- else if (item.type === 'live' || item.type === 'live_photo') {
786
- if (item.live_photo?.length) {
787
- for (const liveItem of item.live_photo) {
788
- await sendTimeout(session, koishi_1.h.image(liveItem.image));
789
- await delay(200);
790
- if (liveItem.video) {
791
- try {
792
- await sendTimeout(session, koishi_1.h.video(liveItem.video));
793
- }
794
- catch (e) {
795
- await sendTimeout(session, koishi_1.h.text(`Live Photo 视频链接: ${liveItem.video}`));
796
- }
797
- await delay(300);
798
- }
799
- }
730
+ if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
731
+ for (const img of item.images) {
732
+ await sendTimeout(session, koishi_1.h.image(img));
733
+ await delay(200);
800
734
  }
801
- else if (item.images?.length) {
802
- const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
803
- await sendTimeout(session, imgMsg);
804
- }
805
- }
806
- else if (item.type === 'image' && item.images?.length) {
807
- const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
808
- await sendTimeout(session, imgMsg);
809
735
  }
810
- else {
811
- if (item.cover) {
812
- await sendTimeout(session, koishi_1.h.image(item.cover));
813
- await delay(300);
736
+ if (item.video && config.showVideoFile) {
737
+ try {
738
+ await sendTimeout(session, koishi_1.h.video(item.video));
814
739
  }
815
- if (item.video && config.showVideoFile) {
816
- try {
817
- let videoElem;
818
- if (config.downloadVideoBeforeSend) {
819
- const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
820
- const downloadResult = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
821
- if (downloadResult.code !== ErrorCode.SUCCESS) {
822
- await sendTimeout(session, koishi_1.h.text(`${getErrorInfo(downloadResult.code)}\n链接:${item.video}`));
823
- continue;
824
- }
825
- else {
826
- videoElem = koishi_1.h.file(downloadResult.filePath);
827
- }
828
- }
829
- else {
830
- const fileSize = await getFileSize(item.video, config.userAgent);
831
- if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
832
- const code = ErrorCode.VIDEO_SIZE_EXCEEDED;
833
- const errorMsg = getErrorInfo(code, `${fileSize}MB超过限制${config.maxVideoSize}MB`);
834
- videoElem = koishi_1.h.text(`${errorMsg},仅发送链接:${item.video}`);
835
- }
836
- else {
837
- videoElem = koishi_1.h.video(item.video);
838
- }
839
- }
840
- await sendTimeout(session, videoElem);
841
- }
842
- catch (error) {
843
- const errorMsg = getErrorMessage(error);
844
- const code = ErrorCode.VIDEO_DOWNLOAD_FAILED;
845
- logger.error(`[${code}] 视频处理失败: ${errorMsg}`);
846
- await sendTimeout(session, koishi_1.h.text(getErrorInfo(code, errorMsg) + `\n链接:${item.video}`));
847
- }
740
+ catch (e) {
741
+ await sendTimeout(session, `视频:${item.video}`);
848
742
  }
743
+ await delay(500);
849
744
  }
850
- await delay(1000);
851
745
  }
852
746
  }
853
- catch (error) {
854
- const errorMsg = getErrorMessage(error);
855
- const code = ErrorCode.UNKNOWN_ERROR;
856
- logger.error(`[${code}] 处理内容失败: ${errorMsg}`);
857
- await sendTimeout(session, koishi_1.h.text(getErrorInfo(code, `处理${item.type}内容失败: ${errorMsg}`)));
858
- }
747
+ catch (e) { }
859
748
  }
860
749
  if (enableForward && forwardMessages.length) {
861
750
  try {
862
- const safeForwardMessages = forwardMessages.slice(0, 100);
863
- const forwardMsg = (0, koishi_1.h)('message', { forward: true }, safeForwardMessages);
864
- await sendTimeout(session, forwardMsg);
751
+ await sendTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
865
752
  }
866
- catch (error) {
867
- const errorMsg = getErrorMessage(error);
868
- const code = ErrorCode.FORWARD_MESSAGE_FAILED;
869
- logger.error(`[${code}] 合并转发失败: ${errorMsg}`);
753
+ catch (e) {
870
754
  for (const node of forwardMessages) {
871
755
  await sendTimeout(session, node.data.content);
872
- await delay(500);
756
+ await delay(300);
873
757
  }
874
758
  }
875
759
  }
@@ -878,88 +762,26 @@ function apply(ctx, config) {
878
762
  if (!config.enable)
879
763
  return;
880
764
  const content = session.content?.trim() || '';
881
- let urls = extractUrl(content);
882
- if (urls.length === 0 && hasPlatformKeyword(content)) {
883
- const allLinks = content.match(/https?:\/\/[^\s\"\'\>\]]+/gi) || [];
884
- urls = allLinks.filter((u) => getPlatformType(u));
885
- }
886
- if (urls.length === 0)
765
+ const urls = extractUrl(content);
766
+ if (!urls.length)
887
767
  return;
888
- const key = `${session.platform}:${session.userId}:${session.channelId}`;
889
- if (linkBuffer.has(key)) {
890
- const buffer = linkBuffer.get(key);
891
- const newUrls = urls.filter(url => !buffer.urls.includes(url));
892
- if (newUrls.length) {
893
- buffer.urls.push(...newUrls);
894
- clearTimeout(buffer.timer);
895
- buffer.timer = setTimeout(() => flush(session), config.messageBufferDelay * 1000);
896
- }
897
- return;
898
- }
899
- let tipMsgId;
900
- if (config.showWaitingTip) {
901
- const msg = await sendTimeout(session, config.waitingTipText);
902
- tipMsgId = msg?.messageId || msg?.id || msg;
903
- }
904
- linkBuffer.set(key, {
905
- urls,
906
- timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
907
- tipMsgId
908
- });
909
- });
910
- ctx.command('parse <url>', '手动解析视频链接')
911
- .action(async ({ session }, url) => {
912
- if (!url) {
913
- const code = ErrorCode.INVALID_URL;
914
- return getErrorInfo(code, '请输入视频链接');
915
- }
916
- let urls = extractUrl(url);
917
- if (urls.length === 0 && hasPlatformKeyword(url)) {
918
- const allLinks = url.match(/https?:\/\/[^\s\"\'\>\]]+/gi) || [];
919
- urls = allLinks.filter((u) => getPlatformType(u));
920
- }
921
- if (urls.length === 0) {
922
- const code = ErrorCode.UNSUPPORTED_PLATFORM;
923
- return getErrorInfo(code, '不支持该链接');
924
- }
768
+ if (config.showWaitingTip)
769
+ await sendTimeout(session, config.waitingTipText);
925
770
  await flush(session, urls);
926
771
  });
927
- ctx.command('clear-cache', '清空解析缓存与临时文件')
928
- .action(() => {
772
+ ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
773
+ const us = extractUrl(url);
774
+ if (!us.length)
775
+ return getErrorInfo(ErrorCode.INVALID_URL);
776
+ await flush(session, us);
777
+ });
778
+ ctx.command('clear-cache', '清空缓存').action(() => {
929
779
  clearAllCache();
930
- return getErrorInfo(ErrorCode.SUCCESS, '解析缓存已清空');
780
+ return '✅ 缓存已清空';
931
781
  });
932
782
  setInterval(() => {
933
783
  const now = Date.now();
934
- processed.forEach((timestamp, hash) => {
935
- if (now - timestamp > 86400000)
936
- processed.delete(hash);
937
- });
784
+ processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
938
785
  }, 3600000);
939
- setInterval(() => {
940
- const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
941
- if (!fs_1.default.existsSync(tempDir))
942
- return;
943
- const now = Date.now();
944
- fs_1.default.readdirSync(tempDir).forEach(file => {
945
- try {
946
- const stat = fs_1.default.statSync(path_1.default.join(tempDir, file));
947
- if (now - stat.mtimeMs > 3600000) {
948
- fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
949
- logger.info(`清理过期临时文件: ${file}`);
950
- }
951
- }
952
- catch (error) {
953
- const errorMsg = getErrorMessage(error);
954
- logger.error(`[${ErrorCode.UNKNOWN_ERROR}] 清理临时文件失败: ${file}, ${errorMsg}`);
955
- }
956
- });
957
- }, 1800000);
958
- if (config.autoClearCacheInterval > 0) {
959
- setInterval(() => {
960
- clearAllCache();
961
- logger.info(getErrorInfo(ErrorCode.SUCCESS, '自动清理缓存完成'));
962
- }, config.autoClearCacheInterval * 60000);
963
- }
964
- logger.info(getErrorInfo(ErrorCode.SUCCESS, '视频解析插件已加载'));
786
+ logger.info('✅ 视频解析插件已启动');
965
787
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
3
  "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/小红书/微博/今日头条/皮皮搞笑/皮皮虾/最右视频链接解析",
4
- "version": "0.6.0",
4
+ "version": "0.6.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [