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

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 +118 -34
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -22,7 +22,31 @@ 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(`标题:${'${标题}'}
26
+ 作者:${'${作者}'}
27
+ 简介:${'${简介}'}
28
+ 时长:${'${视频时长}'}
29
+ 点赞:${'${点赞数}'}
30
+ 投币:${'${投币数}'}
31
+ 收藏:${'${收藏数}'}
32
+ 转发:${'${转发数}'}
33
+ 播放:${'${播放数}'}
34
+ 评论:${'${评论数}'}
35
+ IP属地:${'${IP属地}'}
36
+ 发布时间:${'${发布时间}'}
37
+ 粉丝数:${'${粉丝数}'}
38
+ 在线人数:${'${在线人数}'}
39
+ 关注数:${'${关注数}'}
40
+ 文件大小:${'${文件大小}'}
41
+ 分辨率:${'${分辨率}'}
42
+ 音乐作者:${'${音乐作者}'}
43
+ 音乐标题:${'${音乐标题}'}
44
+ 直播间地址:${'${直播间地址}'}
45
+ 直播间ID:${'${直播间ID}'}
46
+ 直播间状态:${'${直播间状态}'}
47
+ 默认画质:${'${默认画质}'}
48
+ 图片数量:${'${图片数量}'}
49
+ 作者ID:${'${作者ID}'}`).description('统一消息格式'),
26
50
  }).description('统一消息格式'),
27
51
  koishi_1.Schema.object({
28
52
  showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
@@ -45,10 +69,10 @@ exports.Config = koishi_1.Schema.intersect([
45
69
  enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅OneBot平台)'),
46
70
  downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('发送前先下载视频'),
47
71
  maxVideoSize: koishi_1.Schema.number().min(0).default(0).description('最大视频大小限制(MB,0为不限制)'),
48
- downloadThreads: koishi_1.Schema.number().min(0).max(10).default(0).description('多线程下载线程数(0为不使用多线程,1-10为启用对应线程数)'),
72
+ downloadThreads: koishi_1.Schema.number().min(0).max(10).default(0).description('多线程下载线程数(0为不使用多线程)'),
49
73
  }).description('发送方式设置'),
50
74
  koishi_1.Schema.object({
51
- messageBufferDelay: koishi_1.Schema.number().min(0).default(0).description('消息缓冲延迟(毫秒,批量处理链接)'),
75
+ messageBufferDelay: koishi_1.Schema.number().min(0).default(0).description('消息缓冲延迟(毫秒)'),
52
76
  }).description('消息处理设置'),
53
77
  koishi_1.Schema.object({
54
78
  autoClearCacheInterval: koishi_1.Schema.number().min(0).default(0).description('自动清理缓存间隔(分钟,0为关闭)'),
@@ -84,7 +108,7 @@ exports.ErrorMessageMap = {
84
108
  [ErrorCode.PLATFORM_API_NOT_CONFIGURED]: '该平台暂未配置解析接口',
85
109
  [ErrorCode.REQUEST_TIMEOUT]: '请求超时',
86
110
  [ErrorCode.NETWORK_ERROR]: '网络请求失败',
87
- [ErrorCode.DUPLICATE_PARSE]: '请勿重复解析(相同链接解析间隔未到)',
111
+ [ErrorCode.DUPLICATE_PARSE]: '请勿重复解析',
88
112
  [ErrorCode.INVALID_URL]: '无效的链接格式',
89
113
  [ErrorCode.API_RETURN_ERROR]: 'API返回错误',
90
114
  [ErrorCode.API_DATA_PARSE_FAILED]: '数据解析异常',
@@ -125,17 +149,31 @@ const API_CONFIG = {
125
149
  zuiyou: 'https://api.bugpk.com/api/zuiyou'
126
150
  };
127
151
  const VARIABLE_MAPPING = {
128
- '标题': ['title', 'Title', 'TITLE', 'note_title', 'content_title'],
129
- '作者': ['author.name', 'author', 'name', 'Author', 'Name', 'owner.name', 'nickname', 'user_name'],
130
- '简介': ['desc', 'description', 'Desc', 'Description', 'content', 'Content', 'note_desc', 'text'],
131
- '视频时长': ['duration', 'Duration', 'time', 'Time', 'video_duration'],
132
- '点赞数': ['like', 'Like', 'attitudes_count', 'digg_count', 'praise', 'stat.like', 'liked_count'],
133
- '投币数': ['coin', 'Coin', 'bi', 'Bi', 'stat.coin'],
134
- '收藏数': ['collect', 'Collect', 'favorite', 'Favorite', 'star', 'Star', 'stat.collect', 'collected_count'],
135
- '转发数': ['share', 'Share', 'forward', 'Forward', 'repost', 'stat.share', 'reposts_count', 'shared_count'],
136
- '播放数': ['view', 'View', 'play_count', 'PlayCount', 'play', 'stat.view', 'play_times'],
137
- '评论数': ['comment', 'Comment', 'comments_count', 'comment_count', 'discuss', 'stat.comment'],
138
- '音乐名': ['music.title', 'music_name', 'audio_name', 'sound_name', 'muisic', 'music', 'bgm_name']
152
+ '标题': ['title', 'Title', 'TITLE', 'note_title', 'content_title', 'item.title', 'data.title', 'video.title', 'live.title'],
153
+ '作者': ['author.name', 'author', 'name', 'Author', 'Name', 'owner.name', 'nickname', 'user_name', 'data.author', 'item.author', 'user.name', 'live.author'],
154
+ '简介': ['desc', 'description', 'Desc', 'Description', 'content', 'Content', 'note_desc', 'text', 'data.desc', 'item.description', 'live.desc'],
155
+ '视频时长': ['duration', 'Duration', 'time', 'Time', 'video_duration', 'item.duration', 'stat.duration'],
156
+ '点赞数': ['like', 'Like', 'attitudes_count', 'digg_count', 'praise', 'stat.like', 'liked_count', 'data.like', 'data.attitudes_count', 'item.attitudes_count'],
157
+ '投币数': ['coin', 'Coin', 'bi', 'Bi', 'stat.coin', 'stast.coin'],
158
+ '收藏数': ['collect', 'Collect', 'favorite', 'Favorite', 'star', 'Star', 'stat.collect', 'collected_count', 'stast.favorite', 'data.favorite'],
159
+ '转发数': ['share', 'Share', 'forward', 'Forward', 'repost', 'stat.share', 'reposts_count', 'shared_count', 'stast.share', 'data.reposts_count'],
160
+ '播放数': ['view', 'View', 'play_count', 'PlayCount', 'play', 'stat.view', 'play_times', 'stast.view', 'data.play_count', 'item.play_count'],
161
+ '评论数': ['comment', 'Comment', 'comments_count', 'comment_count', 'discuss', 'stat.comment', 'stast.reply', 'data.comments_count', 'item.comments_count'],
162
+ 'IP属地': ['ip_info_str', 'data.ip_info_str', 'item.ip_info'],
163
+ '发布时间': ['date', 'time', 'publish_time', 'data.date', 'item.publish_time', 'live.time', 'stast.publish_time'],
164
+ '粉丝数': ['followers_count', 'data.followers_count', 'item.followers', 'author.fans'],
165
+ '在线人数': ['online', 'data.online', 'live.online', 'room.online'],
166
+ '关注数': ['attention', 'data.attention', 'live.attention', 'stast.attention'],
167
+ '文件大小': ['size', 'size_str', 'item.size', 'item.size_str', 'data.size'],
168
+ '分辨率': ['height', 'width', 'h_w', 'item.h_w', 'data.resolution', 'item.height', 'item.width'],
169
+ '音乐作者': ['music.author', 'item.music.author', 'data.music.author', 'music.artist'],
170
+ '音乐标题': ['music.title', 'item.music.title', 'data.music.title', 'music.name'],
171
+ '直播间地址': ['room_url', 'live.room_url', 'data.room_url', 'live.url'],
172
+ '直播间ID': ['room_id', 'live.room_id', 'data.room_id', 'live.room_id'],
173
+ '直播间状态': ['status', 'live.status', 'data.status', 'room.status'],
174
+ '默认画质': ['default_quality', 'data.default_quality', 'item.default_quality'],
175
+ '图片数量': ['count', 'data.count', 'item.count', 'images.length', 'data.images.length'],
176
+ '作者ID': ['userId', 'userID', 'author_id', 'data.userId', 'item.userID', 'author.mid', 'user.mid'],
139
177
  };
140
178
  function getErrorInfo(code, detail) {
141
179
  const baseMsg = exports.ErrorMessageMap[code] || exports.ErrorMessageMap[ErrorCode.UNKNOWN_ERROR];
@@ -325,7 +363,6 @@ async function resolveShortUrl(url) {
325
363
  headers: {
326
364
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
327
365
  'Referer': 'https://www.baidu.com/',
328
- 'Cookie': 'xhsTrackerId=xxx; xhs_sessionId=xxx'
329
366
  }
330
367
  });
331
368
  return cleanUrl(res.request.res?.responseUrl || url);
@@ -336,21 +373,25 @@ async function resolveShortUrl(url) {
336
373
  }
337
374
  function formatDuration(input) {
338
375
  if (!input || input === 0 || input === '0' || input === '00:00')
339
- return '00:00';
376
+ return '00:00:00';
340
377
  if (typeof input === 'string') {
341
- if (input.includes(':'))
342
- return input;
378
+ if (input.includes(':')) {
379
+ const parts = input.split(':');
380
+ if (parts.length === 2)
381
+ return `00:${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}`;
382
+ if (parts.length === 3)
383
+ return `${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}:${parts[2].padStart(2, '0')}`;
384
+ return '00:00:00';
385
+ }
343
386
  input = Number(input);
344
387
  }
345
388
  const seconds = Math.floor(Number(input));
346
- if (isNaN(seconds) || seconds <= 0)
347
- return '00:00';
389
+ if (isNaN(seconds) || seconds <= 0 || seconds > 315360000)
390
+ return '00:00:00';
348
391
  const hours = Math.floor(seconds / 3600);
349
392
  const minutes = Math.floor((seconds % 3600) / 60);
350
393
  const secs = Math.floor(seconds % 60);
351
- return hours > 0
352
- ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
353
- : `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
394
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
354
395
  }
355
396
  function getNestedValue(obj, path) {
356
397
  if (!obj || typeof obj !== 'object' || !path)
@@ -393,18 +434,38 @@ function parseData(rawResponse, maxDescLength) {
393
434
  const data = rootData.data || rootData.result || rootData || {};
394
435
  const stat = {};
395
436
  Object.entries(VARIABLE_MAPPING).forEach(([varName, keys]) => {
396
- const value = findValueInObject(data, keys) || findValueInObject(rootData, keys);
397
- stat[varName] = value ?? '';
437
+ let value = findValueInObject(data, keys) || findValueInObject(rootData, keys);
438
+ if (varName === '图片数量' && value === undefined) {
439
+ value = Array.isArray(data.images) ? data.images.length : (Array.isArray(rootData.images) ? rootData.images.length : undefined);
440
+ }
441
+ if (varName === '分辨率' && value === undefined) {
442
+ const h = findValueInObject(data, ['height', 'item.height']) || findValueInObject(rootData, ['height', 'item.height']);
443
+ const w = findValueInObject(data, ['width', 'item.width']) || findValueInObject(rootData, ['width', 'item.width']);
444
+ if (h && w)
445
+ value = `${w}x${h}`;
446
+ else if (data.h_w && Array.isArray(data.h_w) && data.h_w.length)
447
+ value = data.h_w.join(', ');
448
+ }
449
+ if (value !== undefined && value !== null && value !== '') {
450
+ stat[varName] = value;
451
+ }
398
452
  });
399
453
  let type = 'video';
400
454
  if (data.jx?.type)
401
455
  type = data.jx.type;
402
456
  else if (data.type)
403
457
  type = data.type;
404
- const title = stat['标题'] || '无标题';
405
- const author = stat['作者'] || '未知作者';
406
- const desc = (stat['简介'] || title).slice(0, maxDescLength);
407
- const cover = data.cover ?? data.imgurl ?? data.pic ?? data.thumbnail ?? data.cover_url ?? '';
458
+ else if (rootData.msg === 'cv')
459
+ type = 'cv';
460
+ else if (rootData.msg === 'live')
461
+ type = 'live';
462
+ else if ((data.images && data.images.length > 1) || (rootData.images && rootData.images.length > 1))
463
+ type = '图集';
464
+ const title = stat['标题'] || (data.note_title || data.title || data.content_title || '无标题');
465
+ const author = stat['作者'] || (data.author?.name || data.nickname || data.user_name || '未知作者');
466
+ const rawDesc = stat['简介'] || data.note_desc || data.content || data.text || data.description || '';
467
+ const desc = rawDesc.length > 0 ? rawDesc.slice(0, maxDescLength) : (title.length > 0 ? title : '暂无简介');
468
+ const cover = data.cover ?? data.imgurl ?? data.pic ?? data.thumbnail ?? data.cover_url ?? (Array.isArray(data.images) && data.images[0] ? data.images[0] : '');
408
469
  let images = [];
409
470
  const imgRaw = data.images ?? data.pics ?? data.pic_urls ?? data.image_list ?? [];
410
471
  if (Array.isArray(imgRaw))
@@ -451,14 +512,31 @@ function parseData(rawResponse, maxDescLength) {
451
512
  };
452
513
  }
453
514
  function generateFormattedText(parseData, config) {
454
- let format = config.unifiedMessageFormat || '标题:${标题}\n作者:${作者}\n简介:${简介}';
515
+ let format = config.unifiedMessageFormat || '';
516
+ if (!format) {
517
+ format = `标题:${'${标题}'}
518
+ 作者:${'${作者}'}
519
+ 简介:${'${简介}'}
520
+ 时长:${'${视频时长}'}
521
+ 点赞:${'${点赞数}'}
522
+ 投币:${'${投币数}'}
523
+ 收藏:${'${收藏数}'}
524
+ 转发:${'${转发数}'}
525
+ 播放:${'${播放数}'}
526
+ 评论:${'${评论数}'}`;
527
+ }
455
528
  let result = format;
456
529
  const varMatches = result.match(/\$\{([^}]+)\}/g) || [];
457
530
  varMatches.forEach((varMatch) => {
458
531
  const varName = varMatch.replace(/\$\{|\}/g, '');
459
532
  const value = parseData.stat[varName];
460
- const showValue = value ?? '';
461
- result = result.replace(varMatch, String(showValue));
533
+ if (value === undefined || value === null || value === '') {
534
+ const lines = result.split('\n');
535
+ result = lines.filter((line) => !line.includes(varMatch)).join('\n');
536
+ }
537
+ else {
538
+ result = result.replace(varMatch, String(value));
539
+ }
462
540
  });
463
541
  return result.trim() || `标题:${parseData.title}\n作者:${parseData.author}\n简介:${parseData.desc}`;
464
542
  }
@@ -556,7 +634,7 @@ function apply(ctx, config) {
556
634
  return { data: null, code, msg };
557
635
  }
558
636
  const isSuccess = resData.code === 0 || resData.code === 200 || resData.code === 1 ||
559
- (resData.msg && (resData.msg.includes('解析成功') || resData.msg.includes('success'))) ||
637
+ (resData.msg && (resData.msg.includes('解析成功') || resData.msg.includes('success') || resData.msg.includes('请求成功'))) ||
560
638
  !!resData.data || !!resData.result || !!resData.video || !!resData.images;
561
639
  if (!isSuccess) {
562
640
  const apiErrorMsg = resData.msg || resData.error || '解析失败';
@@ -783,5 +861,11 @@ function apply(ctx, config) {
783
861
  const now = Date.now();
784
862
  processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
785
863
  }, 3600000);
864
+ if (config.autoClearCacheInterval > 0) {
865
+ setInterval(() => {
866
+ clearAllCache();
867
+ logger.info('自动清理缓存完成');
868
+ }, config.autoClearCacheInterval * 60 * 1000);
869
+ }
786
870
  logger.info('✅ 视频解析插件已启动');
787
871
  }
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.2",
4
+ "version": "0.6.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [