koishi-plugin-video-parser-all 0.7.6 → 0.8.0

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 (3) hide show
  1. package/lib/index.js +114 -41
  2. package/package.json +1 -1
  3. package/readme.md +4 -2
package/lib/index.js CHANGED
@@ -42,7 +42,7 @@ exports.Config = koishi_1.Schema.intersect([
42
42
  }).description('内容长度限制'),
43
43
  koishi_1.Schema.object({
44
44
  timeout: koishi_1.Schema.number().min(0).default(180000).description('API请求超时时间(毫秒)'),
45
- videoSendTimeout: koishi_1.Schema.number().min(0).default(0).description('视频发送超时时间(毫秒,0为不限制)'),
45
+ videoSendTimeout: koishi_1.Schema.number().min(0).default(60000).description('视频发送超时时间(毫秒,0为不限制)'),
46
46
  userAgent: koishi_1.Schema.string().default('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36').description('请求UA标识'),
47
47
  }).description('网络与API设置'),
48
48
  koishi_1.Schema.object({
@@ -399,7 +399,20 @@ function parseData(rawResponse, maxDescLength) {
399
399
  const data = root.data || root;
400
400
  const stat = {};
401
401
  let totalImageCount = 0;
402
+ if (root.msg === 'live' && data.live) {
403
+ const liveData = data.live;
404
+ stat['标题'] = liveData.title || '';
405
+ stat['直播间地址'] = liveData.room_url || '';
406
+ stat['直播间ID'] = liveData.room_id || '';
407
+ stat['直播间状态'] = liveData.status === 1 ? '直播中' : (liveData.status === 0 ? '未开播' : '未知');
408
+ stat['在线人数'] = liveData.online || '';
409
+ stat['关注数'] = liveData.attention || '';
410
+ stat['发布时间'] = formatPublishTime(liveData.time);
411
+ stat['简介'] = liveData.desc || '';
412
+ }
402
413
  Object.entries(VARIABLE_MAPPING).forEach(([varName, keys]) => {
414
+ if (stat[varName] !== undefined)
415
+ return;
403
416
  let value = findValueInObject(data, keys) || findValueInObject(root, keys);
404
417
  if (varName === '图片数量' && value === undefined) {
405
418
  let imgCount = 0;
@@ -438,7 +451,7 @@ function parseData(rawResponse, maxDescLength) {
438
451
  else if ((data.images && data.images.length > 1) || (root.images && root.images.length > 1) ||
439
452
  (data.imgurl && data.imgurl.length > 1) || (root.imgurl && root.imgurl.length > 1))
440
453
  type = '图集';
441
- const title = data.title || '无标题';
454
+ const title = stat['标题'] || data.title || '无标题';
442
455
  let author = '';
443
456
  if (data.author && typeof data.author === 'object') {
444
457
  author = data.author.name || '';
@@ -446,16 +459,16 @@ function parseData(rawResponse, maxDescLength) {
446
459
  else {
447
460
  author = data.author || '';
448
461
  }
449
- author = author || '未知作者';
450
- const rawDesc = data.desc || data.content || '暂无简介';
462
+ author = author || stat['作者'] || '未知作者';
463
+ const rawDesc = data.desc || data.content || stat['简介'] || '暂无简介';
451
464
  const desc = rawDesc.slice(0, maxDescLength);
452
- const cover = data.cover || '';
465
+ const cover = data.cover || data.live?.cover || data.live?.keyframe || '';
453
466
  const images = Array.isArray(data.images) ? data.images : [];
454
- const video = data.url || data.video_backup || '';
467
+ const video = data.url || data.video_backup || (data.live?.url && Array.isArray(data.live.url) ? data.live.url[0] : '') || '';
455
468
  const durationValue = data.duration || 0;
456
469
  const duration = typeof durationValue === 'number' ? durationValue : parseInt(durationValue) || 0;
457
470
  const durationFormatted = formatDuration(durationValue);
458
- const pubTime = formatPublishTime(data.create_time || data.publish_time);
471
+ const pubTime = formatPublishTime(data.create_time || data.publish_time || data.live?.time);
459
472
  if (pubTime)
460
473
  stat['发布时间'] = pubTime;
461
474
  if (durationFormatted !== '00:00:00')
@@ -471,13 +484,6 @@ function parseData(rawResponse, maxDescLength) {
471
484
  const reposts_count = Number(stat['转发数']) || 0;
472
485
  const attitudes_count = Number(stat['点赞数']) || 0;
473
486
  const comments_count = Number(stat['评论数']) || 0;
474
- if (data.live) {
475
- stat['直播间地址'] = data.live.room_url || '';
476
- stat['直播间ID'] = data.live.room_id || '';
477
- stat['直播间状态'] = data.live.status === 1 ? '直播中' : (data.live.status === 0 ? '未开播' : '未知');
478
- stat['在线人数'] = data.live.online || '';
479
- stat['关注数'] = data.live.attention || '';
480
- }
481
487
  return {
482
488
  type: type,
483
489
  rawData: rawResponse,
@@ -627,6 +633,17 @@ function apply(ctx, config) {
627
633
  }
628
634
  try {
629
635
  const parseResult = parseData(resData, config.maxDescLength);
636
+ const isAllDefault = parseResult.title === '无标题' &&
637
+ parseResult.author === '未知作者' &&
638
+ parseResult.desc === '暂无简介';
639
+ if (isAllDefault) {
640
+ logger.warn(`解析结果均为默认值(可能暂不支持该链接): ${url}`);
641
+ return {
642
+ data: null,
643
+ success: false,
644
+ msg: '解析失败: 暂不支持解析该链接'
645
+ };
646
+ }
630
647
  logger.info(`解析成功: ${url}`);
631
648
  return {
632
649
  data: parseResult,
@@ -684,26 +701,32 @@ function apply(ctx, config) {
684
701
  msg: '处理成功'
685
702
  };
686
703
  }
687
- async function sendTimeout(session, content) {
704
+ async function sendWithTimeout(session, content) {
688
705
  if (config.videoSendTimeout <= 0) {
689
- return session.send(content).catch((err) => {
706
+ try {
707
+ return await session.send(content);
708
+ }
709
+ catch (err) {
690
710
  const errorMsg = getErrorMessage(err);
691
711
  logger.error(`发送消息失败: ${errorMsg}`);
692
712
  if (!config.ignoreSendError)
693
- return null;
713
+ throw err;
694
714
  return null;
715
+ }
716
+ }
717
+ try {
718
+ const timeoutPromise = new Promise((_, reject) => {
719
+ setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout);
695
720
  });
721
+ return await Promise.race([session.send(content), timeoutPromise]);
696
722
  }
697
- return Promise.race([
698
- session.send(content),
699
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.videoSendTimeout))
700
- ]).catch((err) => {
723
+ catch (err) {
701
724
  const errorMsg = getErrorMessage(err);
702
- logger.error(`发送消息超时: ${errorMsg}`);
725
+ logger.error(`发送消息超时或失败: ${errorMsg}`);
703
726
  if (!config.ignoreSendError)
704
- return null;
727
+ throw err;
705
728
  return null;
706
- });
729
+ }
707
730
  }
708
731
  async function flush(session, manualUrls) {
709
732
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
@@ -717,7 +740,7 @@ function apply(ctx, config) {
717
740
  const errors = [];
718
741
  for (const url of urls) {
719
742
  const result = await processSingleUrl(session, url);
720
- if (result.success) {
743
+ if (result.success && result.data) {
721
744
  items.push(result.data);
722
745
  }
723
746
  else {
@@ -727,11 +750,15 @@ function apply(ctx, config) {
727
750
  if (errors.length > 0) {
728
751
  const errorLines = errors.map(err => `【${err.url.slice(0, 50)}${err.url.length > 50 ? '...' : ''}】: ${err.msg}`);
729
752
  const errorMsg = `❌ 解析失败:\n${errorLines.join('\n')}`;
730
- await sendTimeout(session, errorMsg);
753
+ try {
754
+ await sendWithTimeout(session, errorMsg);
755
+ }
756
+ catch (e) {
757
+ logger.error(`发送错误消息失败: ${getErrorMessage(e)}`);
758
+ }
731
759
  await delay(500);
732
760
  }
733
761
  if (items.length === 0) {
734
- await sendTimeout(session, '⚠ 未解析到有效内容');
735
762
  return;
736
763
  }
737
764
  const enableForward = config.enableForward && session.platform === 'onebot';
@@ -750,7 +777,7 @@ function apply(ctx, config) {
750
777
  if (config.downloadVideoBeforeSend) {
751
778
  const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
752
779
  const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
753
- if (dl.success) {
780
+ if (dl.success && dl.filePath) {
754
781
  forwardMessages.push(buildForwardNode(session, koishi_1.h.file(dl.filePath), botName));
755
782
  }
756
783
  else {
@@ -762,6 +789,7 @@ function apply(ctx, config) {
762
789
  }
763
790
  }
764
791
  catch (e) {
792
+ logger.error(`处理视频发送失败: ${getErrorMessage(e)}`);
765
793
  forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
766
794
  }
767
795
  }
@@ -774,27 +802,60 @@ function apply(ctx, config) {
774
802
  }
775
803
  else {
776
804
  if (item.text) {
777
- await sendTimeout(session, item.text);
805
+ await sendWithTimeout(session, item.text);
778
806
  await delay(300);
779
807
  }
780
808
  if (item.cover && item.type !== '图集') {
781
- await sendTimeout(session, koishi_1.h.image(item.cover));
809
+ try {
810
+ await sendWithTimeout(session, koishi_1.h.image(item.cover));
811
+ }
812
+ catch (e) {
813
+ logger.error(`发送封面失败: ${getErrorMessage(e)}`);
814
+ }
782
815
  await delay(300);
783
816
  }
784
817
  if (item.video && config.showVideoFile) {
785
818
  try {
786
- await sendTimeout(session, koishi_1.h.video(item.video));
819
+ if (config.downloadVideoBeforeSend) {
820
+ const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
821
+ const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
822
+ if (dl.success && dl.filePath) {
823
+ await sendWithTimeout(session, koishi_1.h.file(dl.filePath));
824
+ }
825
+ else {
826
+ await sendWithTimeout(session, koishi_1.h.video(item.video));
827
+ }
828
+ }
829
+ else {
830
+ await sendWithTimeout(session, koishi_1.h.video(item.video));
831
+ }
787
832
  }
788
833
  catch (e) {
789
- await sendTimeout(session, koishi_1.h.video(item.video));
834
+ logger.error(`发送视频失败: ${getErrorMessage(e)}`);
835
+ try {
836
+ await sendWithTimeout(session, koishi_1.h.video(item.video));
837
+ }
838
+ catch (e2) {
839
+ logger.error(`发送视频链接也失败: ${getErrorMessage(e2)}`);
840
+ }
790
841
  }
791
842
  await delay(500);
792
843
  }
793
844
  if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
794
- await sendTimeout(session, `📸 图集内容(共${item.totalImageCount}张)`);
845
+ try {
846
+ await sendWithTimeout(session, `📸 图集内容(共${item.totalImageCount}张)`);
847
+ }
848
+ catch (e) {
849
+ logger.error(`发送图集提示失败: ${getErrorMessage(e)}`);
850
+ }
795
851
  await delay(300);
796
852
  for (const img of item.images) {
797
- await sendTimeout(session, koishi_1.h.image(img));
853
+ try {
854
+ await sendWithTimeout(session, koishi_1.h.image(img));
855
+ }
856
+ catch (e) {
857
+ logger.error(`发送图集图片失败: ${getErrorMessage(e)}`);
858
+ }
798
859
  await delay(200);
799
860
  }
800
861
  }
@@ -806,12 +867,18 @@ function apply(ctx, config) {
806
867
  }
807
868
  if (enableForward && forwardMessages.length) {
808
869
  try {
809
- await sendTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
870
+ await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
810
871
  }
811
872
  catch (e) {
873
+ logger.error(`合并转发失败,降级为逐条发送: ${getErrorMessage(e)}`);
812
874
  for (const node of forwardMessages) {
813
- await sendTimeout(session, node.data.content);
814
- await delay(300);
875
+ try {
876
+ await sendWithTimeout(session, node.data.content);
877
+ await delay(300);
878
+ }
879
+ catch (e2) {
880
+ logger.error(`降级发送失败: ${getErrorMessage(e2)}`);
881
+ }
815
882
  }
816
883
  }
817
884
  }
@@ -823,21 +890,27 @@ function apply(ctx, config) {
823
890
  const urls = extractUrl(content);
824
891
  if (!urls.length)
825
892
  return;
826
- if (config.showWaitingTip)
827
- await sendTimeout(session, config.waitingTipText);
893
+ if (config.showWaitingTip) {
894
+ try {
895
+ await sendWithTimeout(session, config.waitingTipText);
896
+ }
897
+ catch (e) {
898
+ logger.error(`发送等待提示失败: ${getErrorMessage(e)}`);
899
+ }
900
+ }
828
901
  await flush(session, urls);
829
902
  });
830
903
  ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
831
904
  const us = extractUrl(url);
832
905
  if (!us.length) {
833
- await sendTimeout(session, '无效的视频链接');
906
+ await sendWithTimeout(session, '无效的视频链接');
834
907
  return;
835
908
  }
836
909
  await flush(session, us);
837
910
  });
838
911
  ctx.command('clear-cache', '清空缓存').action(async ({ session }) => {
839
912
  clearAllCache();
840
- await sendTimeout(session, '✅ 缓存已清空');
913
+ await sendWithTimeout(session, '✅ 缓存已清空');
841
914
  });
842
915
  setInterval(() => {
843
916
  const now = Date.now();
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.7.6",
4
+ "version": "0.8.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -116,8 +116,8 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
116
116
  ## 支持的平台 (Supported Platforms)
117
117
  | 平台名称 | 关键词识别 | 解析能力 |
118
118
  |----------|------------|----------|
119
- | 哔哩哔哩 (B站) | bilibili、b23、B站 | 视频、番剧、直播、图集 |
120
- | 抖音 | douyin、v.douyin.com | 短视频、图集、直播 |
119
+ | 哔哩哔哩 (B站) | bilibili、b23、B站 | 视频、直播 |
120
+ | 抖音 | douyin、v.douyin.com | 短视频、图集 |
121
121
  | 快手 | kuaishou、v.kuaishou.com | 短视频、图集 |
122
122
  | 微博 | weibo、video.weibo.com | 视频、图集 |
123
123
  | 今日头条 | toutiao、ixigua.com | 短视频 |
@@ -131,6 +131,8 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
131
131
  |----------------------|-------------------------|
132
132
  | Minecraft-1314 | 插件完整开发 (Complete plugin development) |
133
133
  | JH-Ahua | BugPk-Api 支持 |
134
+ | 星之阁API | 星之阁API 支持 |
135
+ | shangxue | 灵感来源 |
134
136
  | (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
135
137
 
136
138
  ## 许可协议 (License)