koishi-plugin-video-parser-all 0.5.1 → 0.5.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 +138 -121
  2. package/package.json +2 -2
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播放:${播放数}').description('统一消息格式(B站会显示投币,其他平台自动隐藏;无法获取的变量会自动隐藏)'),
25
+ unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('统一消息格式(B站会显示投币,其他平台自动隐藏;无法获取的变量会自动隐藏)'),
26
26
  }).description('统一消息格式'),
27
27
  koishi_1.Schema.object({
28
28
  showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
@@ -59,7 +59,7 @@ const linkBuffer = new Map();
59
59
  const logger = new koishi_1.Logger(exports.name);
60
60
  const PLATFORM_KEYWORDS = {
61
61
  bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com', '哔哩哔哩', 'bilibili.com/opus', 'bilibili.com/video', 'b23.tv', 't.bilibili.com', 'bilibili.com/bangumi'],
62
- kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app', 'kuaishou.com/short-video'],
62
+ kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app'],
63
63
  xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com', 'xiaohongshu.com/explore', 'xhslink.com/'],
64
64
  weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
65
65
  toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video', 'ixigua.com/i'],
@@ -69,15 +69,28 @@ const PLATFORM_KEYWORDS = {
69
69
  zuiyou: ['zuiyou', '最右', 'xiaochuankeji.cn', 'izuiyou.com', 'izuiyou.com/topic']
70
70
  };
71
71
  const API_CONFIG = {
72
- bilibili: 'https://api.xingzhige.com/API/b_parse/',
73
- douyin: 'https://api.bugpk.com/api/short_videos',
74
- kuaishou: 'https://api.bugpk.com/api/short_videos',
75
- xiaohongshu: 'https://api.bugpk.com/api/short_videos',
72
+ bilibili: 'https://api.bugpk.com/api/bilibili',
73
+ douyin: 'https://api.bugpk.com/api/dyjx',
74
+ kuaishou: 'https://api.bugpk.com/api/ksjx',
75
+ xiaohongshu: 'https://api.bugpk.com/api/xhsjx',
76
76
  weibo: 'https://api.bugpk.com/api/weibo',
77
- zuiyou: 'https://api.bugpk.com/api/short_videos',
78
- pipixia: 'https://api.bugpk.com/api/short_videos',
79
- pipigx: 'https://api.bugpk.com/api/short_videos',
80
- toutiao: 'https://api.bugpk.com/api/toutiao'
77
+ toutiao: 'https://api.bugpk.com/api/toutiao',
78
+ pipigx: 'https://api.bugpk.com/api/pipigx',
79
+ pipixia: 'https://api.bugpk.com/api/ppx',
80
+ zuiyou: 'https://api.bugpk.com/api/zuiyou'
81
+ };
82
+ const VARIABLE_MAPPING = {
83
+ '标题': ['title', 'Title', 'TITLE'],
84
+ '作者': ['author', 'name', 'Author', 'Name'],
85
+ '简介': ['desc', 'description', 'Desc', 'Description', 'content', 'Content'],
86
+ '视频时长': ['duration', 'Duration', 'time', 'Time'],
87
+ '点赞数': ['like', 'Like', 'attitudes_count', 'digg_count', 'praise'],
88
+ '投币数': ['coin', 'Coin', 'bi', 'Bi'],
89
+ '收藏数': ['collect', 'Collect', 'favorite', 'Favorite', 'star', 'Star'],
90
+ '转发数': ['share', 'Share', 'forward', 'Forward', 'repost'],
91
+ '播放数': ['view', 'View', 'play_count', 'PlayCount', 'play'],
92
+ '评论数': ['comment', 'Comment', 'comments_count', 'comment_count', 'discuss'],
93
+ '音乐名': ['music.title', 'music_name', 'audio_name', 'sound_name']
81
94
  };
82
95
  function getErrorMessage(error) {
83
96
  if (error instanceof Error)
@@ -203,43 +216,6 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
203
216
  throw error;
204
217
  }
205
218
  }
206
- function parseXingzhigeData(resData, platform) {
207
- const result = {
208
- title: '',
209
- author: '未知作者',
210
- desc: '',
211
- cover: '',
212
- video: '',
213
- images: [],
214
- stat: {
215
- like: 0,
216
- coin: 0,
217
- favorite: 0,
218
- share: 0,
219
- view: 0,
220
- duration: '00:00'
221
- },
222
- type: 'video'
223
- };
224
- if (platform === 'bilibili') {
225
- const d = resData.data || resData;
226
- result.title = d.video?.title || d.title || '';
227
- result.author = d.owner?.name || d.name || '未知UP主';
228
- result.desc = d.video?.desc || d.desc || '';
229
- result.cover = d.video?.fm || d.fm || '';
230
- result.video = d.video?.url || d.url || '';
231
- result.stat = {
232
- view: d.stat?.view || 0,
233
- favorite: d.stat?.favorite || 0,
234
- like: d.stat?.like || 0,
235
- coin: d.stat?.coin || 0,
236
- share: d.stat?.share || 0,
237
- duration: formatDuration(d.duration || 0)
238
- };
239
- result.duration = d.duration || 0;
240
- }
241
- return result;
242
- }
243
219
  function extractUrl(content) {
244
220
  let urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
245
221
  return urlMatches.filter(url => {
@@ -288,8 +264,16 @@ async function resolveShortUrl(url) {
288
264
  return url;
289
265
  }
290
266
  }
291
- function formatDuration(seconds) {
292
- if (!seconds)
267
+ function formatDuration(input) {
268
+ if (!input || input === 0 || input === '0' || input === '00:00')
269
+ return '00:00';
270
+ if (typeof input === 'string') {
271
+ if (input.includes(':'))
272
+ return input;
273
+ input = Number(input);
274
+ }
275
+ const seconds = Math.floor(Number(input));
276
+ if (isNaN(seconds) || seconds <= 0)
293
277
  return '00:00';
294
278
  const hours = Math.floor(seconds / 3600);
295
279
  const minutes = Math.floor((seconds % 3600) / 60);
@@ -298,39 +282,87 @@ function formatDuration(seconds) {
298
282
  ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
299
283
  : `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
300
284
  }
301
- function parseData(data, maxDescLength, platform) {
302
- let type = data.type || 'video';
303
- let title = data.title || data.desc || '无标题';
304
- let author = data.author || data.name || '未知作者';
305
- let desc = (data.desc || data.description || title).slice(0, maxDescLength);
306
- let cover = data.cover || data.imgurl || data.pic || data.fm || '';
307
- let images = data.images || [];
308
- let video = data.video || data.url || data.playUrl || '';
309
- let duration = data.duration || 0;
310
- let stat = data.stat || {
311
- like: 0,
312
- coin: 0,
313
- favorite: 0,
314
- share: 0,
315
- view: 0,
316
- duration: '00:00'
317
- };
318
- const durationFormatted = formatDuration(duration);
319
- if (platform === 'bilibili' && data.data) {
320
- title = data.data.title || title;
321
- author = data.data.owner?.name || '未知作者';
322
- desc = (data.data.desc || title).slice(0, maxDescLength);
323
- cover = data.data.pic || cover;
324
- duration = data.data.duration || 0;
325
- video = data.playUrl || video;
326
- stat = data.data.stat || stat;
285
+ function getNestedValue(obj, path) {
286
+ if (!obj || typeof obj !== 'object')
287
+ return undefined;
288
+ const keys = path.split('.');
289
+ let value = obj;
290
+ for (const key of keys) {
291
+ if (value === null || value === undefined)
292
+ return undefined;
293
+ value = value[key];
294
+ }
295
+ return value;
296
+ }
297
+ function findValueInObject(obj, keys) {
298
+ if (!obj || typeof obj !== 'object')
299
+ return undefined;
300
+ for (const key of keys) {
301
+ if (key.includes('.')) {
302
+ const value = getNestedValue(obj, key);
303
+ if (value !== undefined && value !== null && value !== '')
304
+ return value;
305
+ }
306
+ else {
307
+ if (obj[key] !== undefined && obj[key] !== null && obj[key] !== '')
308
+ return obj[key];
309
+ const lowerKey = key.toLowerCase();
310
+ for (const objKey of Object.keys(obj)) {
311
+ if (objKey.toLowerCase() === lowerKey) {
312
+ return obj[objKey];
313
+ }
314
+ }
315
+ }
316
+ }
317
+ return undefined;
318
+ }
319
+ function parseData(rawResponse, maxDescLength, platform) {
320
+ let data = rawResponse;
321
+ if (data.data) {
322
+ data = data.data;
327
323
  }
328
- if (images.length > 0 && !video)
324
+ const stat = {};
325
+ Object.entries(VARIABLE_MAPPING).forEach(([varName, keys]) => {
326
+ const value = findValueInObject(data, keys);
327
+ if (value !== undefined) {
328
+ stat[varName] = value;
329
+ }
330
+ });
331
+ let type = 'video';
332
+ const title = findValueInObject(data, ['title']) || '无标题';
333
+ const author = findValueInObject(data, ['author', 'name']) || '未知作者';
334
+ const desc = (findValueInObject(data, ['desc', 'description', 'content']) || title).toString().slice(0, maxDescLength);
335
+ const cover = findValueInObject(data, ['cover', 'imgurl', 'pic', 'thumbnail']) || '';
336
+ let images = findValueInObject(data, ['imgurl', 'images', 'pics']) || [];
337
+ if (!Array.isArray(images))
338
+ images = [images];
339
+ const videoUrls = [
340
+ findValueInObject(data, ['url']),
341
+ findValueInObject(data, ['download_url']),
342
+ findValueInObject(data, ['video_backup']),
343
+ findValueInObject(data, ['playUrl']),
344
+ findValueInObject(data, ['video_url'])
345
+ ];
346
+ let video = '';
347
+ for (const url of videoUrls) {
348
+ if (url && typeof url === 'string' && url.trim() !== '') {
349
+ video = url;
350
+ break;
351
+ }
352
+ }
353
+ const durationValue = findValueInObject(data, ['duration']);
354
+ const duration = typeof durationValue === 'number' ? durationValue : parseInt(durationValue) || 0;
355
+ const durationFormatted = formatDuration(durationValue || 0);
356
+ const dataType = findValueInObject(data, ['type']);
357
+ if (dataType === 'image' || (images.length > 0 && !video)) {
329
358
  type = 'image';
330
- if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
359
+ }
360
+ if (video && (video.endsWith('.m4a') || video.endsWith('.mp3'))) {
331
361
  video = '';
362
+ }
332
363
  return {
333
364
  type,
365
+ rawData: rawResponse,
334
366
  title,
335
367
  author,
336
368
  desc,
@@ -339,14 +371,7 @@ function parseData(data, maxDescLength, platform) {
339
371
  video,
340
372
  duration,
341
373
  durationFormatted,
342
- stat: {
343
- like: stat.like || 0,
344
- coin: stat.coin || 0,
345
- favorite: stat.favorite || 0,
346
- share: stat.share || 0,
347
- view: stat.view || 0,
348
- duration: durationFormatted
349
- }
374
+ stat
350
375
  };
351
376
  }
352
377
  function generateFormattedText(platform, parseData, config) {
@@ -354,28 +379,28 @@ function generateFormattedText(platform, parseData, config) {
354
379
  if (platform !== 'bilibili') {
355
380
  format = format.replace(/投币:\$\{投币数\}\n?/g, '');
356
381
  }
357
- const variables = {
358
- '标题': parseData.title || '',
359
- '作者': parseData.author || '',
360
- '简介': parseData.desc || '',
361
- '视频时长': parseData.stat.duration || '',
362
- '点赞数': parseData.stat.like > 0 ? parseData.stat.like : '',
363
- '投币数': parseData.stat.coin > 0 ? parseData.stat.coin : '',
364
- '收藏数': parseData.stat.favorite > 0 ? parseData.stat.favorite : '',
365
- '转发数': parseData.stat.share > 0 ? parseData.stat.share : '',
366
- '播放数': parseData.stat.view > 0 ? parseData.stat.view : ''
367
- };
368
382
  let result = format;
369
- Object.entries(variables).forEach(([key, value]) => {
370
- const regex = new RegExp(`${key}:\\$\\{${key}\\\}([\\n]?)`, 'g');
371
- if (!value) {
372
- result = result.replace(regex, '');
373
- }
374
- else {
375
- result = result.replace(`$\{${key}\}`, value);
383
+ const formatLines = result.split('\n');
384
+ const validLines = [];
385
+ formatLines.forEach((line) => {
386
+ let isValid = true;
387
+ let processedLine = line;
388
+ const varMatches = line.match(/\$\{([^}]+)\}/g) || [];
389
+ varMatches.forEach((varMatch) => {
390
+ const varName = varMatch.replace(/\$\{|\}/g, '');
391
+ const value = parseData.stat[varName];
392
+ if (value === undefined || value === null || value === '' || value === 0 || value === '00:00') {
393
+ isValid = false;
394
+ }
395
+ else {
396
+ processedLine = processedLine.replace(varMatch, String(value));
397
+ }
398
+ });
399
+ if (isValid && processedLine.trim() !== '') {
400
+ validLines.push(processedLine);
376
401
  }
377
402
  });
378
- result = result.replace(/\n+/g, '\n').trim();
403
+ result = validLines.join('\n').trim();
379
404
  return result;
380
405
  }
381
406
  function clearAllCache() {
@@ -431,24 +456,16 @@ function apply(ctx, config) {
431
456
  return { data: null, msg: '该平台暂未配置解析接口' };
432
457
  }
433
458
  try {
434
- const res = await http.get(apiUrl, { params: { url: realUrl } });
435
- let parseResult = null;
436
- if (platform === 'bilibili') {
437
- const xgData = parseXingzhigeData(res.data, platform);
438
- parseResult = parseData(xgData, config.maxDescLength, platform);
439
- }
440
- else {
441
- if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
442
- parseResult = parseData(res.data.data, config.maxDescLength, platform);
443
- }
444
- }
445
- if (parseResult) {
446
- return { data: parseResult, msg: `${platform}解析成功` };
447
- }
448
- else {
449
- logger.error(`解析返回数据无效: ${platform}, URL: ${url}`);
450
- return { data: null, msg: '解析失败,返回数据无效' };
459
+ const res = await http.get(apiUrl, {
460
+ params: { url: realUrl },
461
+ timeout: config.timeout
462
+ });
463
+ if (res.data.code !== 200 && res.data.code !== 0) {
464
+ logger.error(`API返回错误: ${platform}, URL: ${url}, 错误: ${res.data.msg || '未知错误'}`);
465
+ return { data: null, msg: res.data.msg || '解析失败' };
451
466
  }
467
+ const parseResult = parseData(res.data, config.maxDescLength, platform);
468
+ return { data: parseResult, msg: `${platform}解析成功` };
452
469
  }
453
470
  catch (error) {
454
471
  logger.error(`解析请求失败: ${platform}, URL: ${url}, 错误: ${getErrorMessage(error)}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
- "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/小红书/微博/今日头条/皮皮搞笑/皮皮虾/右视频链接解析",
4
- "version": "0.5.1",
3
+ "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/小红书/微博/今日头条/皮皮搞笑/皮皮虾/最右视频链接解析",
4
+ "version": "0.5.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [