koishi-plugin-video-parser-all 0.8.1 → 0.8.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.
- package/lib/index.d.ts +2 -0
- package/lib/index.js +199 -147
- package/package.json +1 -1
- package/readme.md +24 -19
package/lib/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare const Config: Schema<{
|
|
|
13
13
|
} & {
|
|
14
14
|
showImageText?: boolean | null | undefined;
|
|
15
15
|
showVideoFile?: boolean | null | undefined;
|
|
16
|
+
sendLivePhotoVideos?: boolean | null | undefined;
|
|
16
17
|
} & {
|
|
17
18
|
maxDescLength?: number | null | undefined;
|
|
18
19
|
} & {
|
|
@@ -45,6 +46,7 @@ export declare const Config: Schema<{
|
|
|
45
46
|
} & {
|
|
46
47
|
showImageText: boolean;
|
|
47
48
|
showVideoFile: boolean;
|
|
49
|
+
sendLivePhotoVideos: boolean;
|
|
48
50
|
} & {
|
|
49
51
|
maxDescLength: number;
|
|
50
52
|
} & {
|
package/lib/index.js
CHANGED
|
@@ -24,14 +24,12 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
24
24
|
debugFile: koishi_1.Schema.boolean().default(false).description('调试日志写入文件'),
|
|
25
25
|
}).description('基础设置'),
|
|
26
26
|
koishi_1.Schema.object({
|
|
27
|
-
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default(
|
|
28
|
-
作者:${'${作者}'}
|
|
29
|
-
点赞:${'${点赞数}'}
|
|
30
|
-
链接:${'${视频链接}'}`).description('统一消息格式'),
|
|
27
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default(`标题:\${标题}\n作者:\${作者}\n点赞:\${点赞数}\n视频链接:\${视频链接}`).description('统一消息格式,可用变量:${标题} ${作者} ${简介} ${视频时长} ${点赞数} ${收藏数} ${转发数} ${播放数} ${评论数} ${发布时间} ${图片数量} ${作者ID} ${视频链接} ${封面} ${音乐作者} ${音乐标题}'),
|
|
31
28
|
}).description('统一消息格式'),
|
|
32
29
|
koishi_1.Schema.object({
|
|
33
30
|
showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
|
|
34
31
|
showVideoFile: koishi_1.Schema.boolean().default(true).description('发送视频文件(关闭则只发链接)'),
|
|
32
|
+
sendLivePhotoVideos: koishi_1.Schema.boolean().default(true).description('发送实况图片对应的视频'),
|
|
35
33
|
}).description('内容显示设置'),
|
|
36
34
|
koishi_1.Schema.object({
|
|
37
35
|
maxDescLength: koishi_1.Schema.number().default(200).description('简介内容最大长度(字符)'),
|
|
@@ -111,7 +109,6 @@ const PLATFORM_KEYWORDS = {
|
|
|
111
109
|
doubao: ['doubao', 'doubao.com'],
|
|
112
110
|
jimeng: ['jimeng', 'jimeng.ai'],
|
|
113
111
|
};
|
|
114
|
-
const PLATFORM_TYPES = Object.keys(PLATFORM_KEYWORDS);
|
|
115
112
|
function getErrorMessage(error) {
|
|
116
113
|
if (error instanceof Error)
|
|
117
114
|
return error.message;
|
|
@@ -262,94 +259,132 @@ async function resolveShortUrl(url) {
|
|
|
262
259
|
return cleanUrl(url);
|
|
263
260
|
}
|
|
264
261
|
}
|
|
265
|
-
function formatDuration(
|
|
266
|
-
if (!
|
|
262
|
+
function formatDuration(seconds) {
|
|
263
|
+
if (!seconds || seconds <= 0)
|
|
267
264
|
return '00:00:00';
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return `00:${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}`;
|
|
273
|
-
if (parts.length === 3)
|
|
274
|
-
return `${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}:${parts[2].padStart(2, '0')}`;
|
|
275
|
-
return '00:00:00';
|
|
276
|
-
}
|
|
277
|
-
input = Number(input);
|
|
278
|
-
}
|
|
279
|
-
const seconds = Math.floor(Number(input));
|
|
280
|
-
if (isNaN(seconds) || seconds <= 0 || seconds > 315360000)
|
|
281
|
-
return '00:00:00';
|
|
282
|
-
const hours = Math.floor(seconds / 3600);
|
|
283
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
284
|
-
const secs = Math.floor(seconds % 60);
|
|
285
|
-
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
265
|
+
const h = Math.floor(seconds / 3600);
|
|
266
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
267
|
+
const s = Math.floor(seconds % 60);
|
|
268
|
+
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
286
269
|
}
|
|
287
|
-
function formatPublishTime(
|
|
288
|
-
if (!
|
|
270
|
+
function formatPublishTime(ms) {
|
|
271
|
+
if (!ms)
|
|
289
272
|
return '';
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
273
|
+
const d = new Date(ms);
|
|
274
|
+
const y = d.getFullYear(), mo = (d.getMonth() + 1).toString().padStart(2, '0'), day = d.getDate().toString().padStart(2, '0'), H = d.getHours().toString().padStart(2, '0'), i = d.getMinutes().toString().padStart(2, '0');
|
|
275
|
+
return `${y}年${mo}月${day}日 ${H}:${i}`;
|
|
276
|
+
}
|
|
277
|
+
function pickBestQuality(videoBackup) {
|
|
278
|
+
if (!Array.isArray(videoBackup))
|
|
279
|
+
return [];
|
|
280
|
+
return videoBackup
|
|
281
|
+
.map(v => ({ quality: v.quality || v.label, url: v.url, bit_rate: v.bit_rate || 0 }))
|
|
282
|
+
.sort((a, b) => (b.bit_rate || 0) - (a.bit_rate || 0));
|
|
283
|
+
}
|
|
284
|
+
function parseApiResponse(raw, maxDescLen) {
|
|
285
|
+
const data = raw?.data || {};
|
|
286
|
+
const extra = data.extra || {};
|
|
287
|
+
let type = data.type || '';
|
|
288
|
+
if (!type) {
|
|
289
|
+
if (data.images?.length > 0 && !data.url)
|
|
290
|
+
type = 'image';
|
|
291
|
+
else if (data.live_photo?.length > 0)
|
|
292
|
+
type = 'live_photo';
|
|
293
|
+
else if (raw.msg === 'live' || data.live)
|
|
294
|
+
type = 'live';
|
|
295
|
+
else
|
|
296
|
+
type = 'video';
|
|
298
297
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (y > 2000)
|
|
306
|
-
parts.push(`${y}年`);
|
|
307
|
-
if (m)
|
|
308
|
-
parts.push(`${m}月`);
|
|
309
|
-
if (d_)
|
|
310
|
-
parts.push(`${d_}日`);
|
|
311
|
-
if (H && i)
|
|
312
|
-
parts.push(`${H}:${i}`);
|
|
313
|
-
return parts.join(' ').trim();
|
|
298
|
+
const authorObj = data.author;
|
|
299
|
+
let author = '', uid = '', avatar = '';
|
|
300
|
+
if (typeof authorObj === 'object' && authorObj) {
|
|
301
|
+
author = authorObj.name || authorObj.author || '';
|
|
302
|
+
uid = String(authorObj.id || data.uid || '');
|
|
303
|
+
avatar = authorObj.avatar || data.avatar || '';
|
|
314
304
|
}
|
|
315
|
-
|
|
316
|
-
|
|
305
|
+
else {
|
|
306
|
+
author = data.author || data.auther || '';
|
|
307
|
+
uid = String(data.uid || '');
|
|
308
|
+
avatar = data.avatar || '';
|
|
309
|
+
}
|
|
310
|
+
const title = data.title || '';
|
|
311
|
+
const desc = (data.desc || data.description || '').slice(0, maxDescLen);
|
|
312
|
+
const cover = data.cover || '';
|
|
313
|
+
let video = '';
|
|
314
|
+
let videos = [];
|
|
315
|
+
if (data.video_backup?.length) {
|
|
316
|
+
const bestQ = pickBestQuality(data.video_backup);
|
|
317
|
+
videos = bestQ;
|
|
318
|
+
video = bestQ[0]?.url || data.url || '';
|
|
319
|
+
}
|
|
320
|
+
else if (data.videos?.length) {
|
|
321
|
+
video = data.videos[0]?.url || '';
|
|
322
|
+
videos = data.videos.map((v) => ({ quality: v.accept?.[0] || 'unknown', url: v.url }));
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
video = data.url || '';
|
|
326
|
+
}
|
|
327
|
+
const images = Array.isArray(data.images) ? data.images : [];
|
|
328
|
+
const live_photo = Array.isArray(data.live_photo) ? data.live_photo : [];
|
|
329
|
+
const music = {
|
|
330
|
+
title: data.music?.title || data.music?.name || '',
|
|
331
|
+
author: data.music?.author || data.music?.artist || '',
|
|
332
|
+
cover: data.music?.cover || '',
|
|
333
|
+
url: data.music?.url || ''
|
|
334
|
+
};
|
|
335
|
+
const stats = extra.statistics || {};
|
|
336
|
+
const like = Number(data.like || stats.digg_count || 0);
|
|
337
|
+
const comment = Number(stats.comment_count || 0);
|
|
338
|
+
const collect = Number(stats.collect_count || 0);
|
|
339
|
+
const share = Number(stats.share_count || 0);
|
|
340
|
+
const play = Number(stats.play_count || 0);
|
|
341
|
+
let duration = 0;
|
|
342
|
+
if (data.duration) {
|
|
343
|
+
duration = typeof data.duration === 'string' ? parseInt(data.duration) : data.duration;
|
|
344
|
+
if (duration > 1000000)
|
|
345
|
+
duration = Math.floor(duration / 1000);
|
|
346
|
+
}
|
|
347
|
+
else if (extra.duration_ms) {
|
|
348
|
+
duration = Math.floor(extra.duration_ms / 1000);
|
|
349
|
+
}
|
|
350
|
+
let publishTime = 0;
|
|
351
|
+
if (data.time) {
|
|
352
|
+
publishTime = typeof data.time === 'number' ? data.time : parseInt(data.time);
|
|
353
|
+
if (publishTime < 1000000000000)
|
|
354
|
+
publishTime *= 1000;
|
|
355
|
+
}
|
|
356
|
+
else if (extra.create_time) {
|
|
357
|
+
publishTime = extra.create_time * 1000;
|
|
317
358
|
}
|
|
318
|
-
}
|
|
319
|
-
function parseApiResponse(rawResponse) {
|
|
320
|
-
const data = rawResponse?.data || {};
|
|
321
359
|
return {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
time: Number(data.time) || 0,
|
|
327
|
-
title: data.title || '',
|
|
328
|
-
cover: data.cover || '',
|
|
329
|
-
url: data.url || '',
|
|
330
|
-
music: data.music ? {
|
|
331
|
-
author: data.music.author || '',
|
|
332
|
-
avatar: data.music.avatar || '',
|
|
333
|
-
title: data.music.title || ''
|
|
334
|
-
} : undefined
|
|
360
|
+
type, title, desc, author, uid, avatar, cover,
|
|
361
|
+
video, videos, images, live_photo, music,
|
|
362
|
+
like, comment, collect, share, play,
|
|
363
|
+
duration, publishTime
|
|
335
364
|
};
|
|
336
365
|
}
|
|
337
|
-
function generateFormattedText(
|
|
338
|
-
const format = config.unifiedMessageFormat || `标题:${'${标题}'}\n作者:${'${作者}'}\n点赞:${'${点赞数}'}\n链接:${'${视频链接}'}`;
|
|
366
|
+
function generateFormattedText(p, format) {
|
|
339
367
|
const vars = {
|
|
340
|
-
'标题':
|
|
341
|
-
'作者':
|
|
342
|
-
'
|
|
343
|
-
'
|
|
344
|
-
'
|
|
345
|
-
'
|
|
346
|
-
'
|
|
347
|
-
'
|
|
348
|
-
'
|
|
368
|
+
'标题': p.title,
|
|
369
|
+
'作者': p.author,
|
|
370
|
+
'简介': p.desc,
|
|
371
|
+
'视频时长': p.duration > 0 ? formatDuration(p.duration) : '',
|
|
372
|
+
'点赞数': String(p.like),
|
|
373
|
+
'收藏数': String(p.collect),
|
|
374
|
+
'转发数': String(p.share),
|
|
375
|
+
'播放数': String(p.play),
|
|
376
|
+
'评论数': String(p.comment),
|
|
377
|
+
'发布时间': p.publishTime ? formatPublishTime(p.publishTime) : '',
|
|
378
|
+
'图片数量': String(p.images.length || p.live_photo.length),
|
|
379
|
+
'作者ID': p.uid,
|
|
380
|
+
'视频链接': p.video,
|
|
381
|
+
'封面': p.cover,
|
|
382
|
+
'音乐作者': p.music.author || '',
|
|
383
|
+
'音乐标题': p.music.title || '',
|
|
349
384
|
};
|
|
350
385
|
let result = format;
|
|
351
386
|
for (const [key, value] of Object.entries(vars)) {
|
|
352
|
-
result = result.replace(new RegExp(
|
|
387
|
+
result = result.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
|
|
353
388
|
}
|
|
354
389
|
return result.replace(/^\s*\n/gm, '').trim();
|
|
355
390
|
}
|
|
@@ -391,7 +426,7 @@ function apply(ctx, config) {
|
|
|
391
426
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
392
427
|
}
|
|
393
428
|
});
|
|
394
|
-
async function
|
|
429
|
+
async function fetchApi(url) {
|
|
395
430
|
for (let i = 0; i <= config.retryTimes; i++) {
|
|
396
431
|
try {
|
|
397
432
|
const res = await http.get('https://api.bugpk.com/api/short_videos', {
|
|
@@ -400,7 +435,7 @@ function apply(ctx, config) {
|
|
|
400
435
|
});
|
|
401
436
|
debugLog('INFO', `API响应: code=${res.data?.code}, msg=${res.data?.msg}`);
|
|
402
437
|
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
403
|
-
return parseApiResponse(res.data);
|
|
438
|
+
return parseApiResponse(res.data, config.maxDescLength);
|
|
404
439
|
}
|
|
405
440
|
throw new Error(res.data?.msg || '解析失败');
|
|
406
441
|
}
|
|
@@ -420,16 +455,16 @@ function apply(ctx, config) {
|
|
|
420
455
|
const platform = getPlatformType(realUrl);
|
|
421
456
|
if (!platform) {
|
|
422
457
|
debugLog('WARN', `不支持的平台: ${realUrl}`);
|
|
423
|
-
return {
|
|
458
|
+
return { success: false, msg: '不支持该平台链接' };
|
|
424
459
|
}
|
|
425
460
|
try {
|
|
426
|
-
const info = await
|
|
461
|
+
const info = await fetchApi(realUrl);
|
|
427
462
|
debugLog('INFO', `解析成功: ${info.title}`);
|
|
428
|
-
return {
|
|
463
|
+
return { success: true, data: info };
|
|
429
464
|
}
|
|
430
465
|
catch (error) {
|
|
431
466
|
debugLog('ERROR', `解析失败: ${getErrorMessage(error)}`);
|
|
432
|
-
return {
|
|
467
|
+
return { success: false, msg: getErrorMessage(error) };
|
|
433
468
|
}
|
|
434
469
|
}
|
|
435
470
|
async function processSingleUrl(session, url) {
|
|
@@ -442,22 +477,13 @@ function apply(ctx, config) {
|
|
|
442
477
|
}
|
|
443
478
|
processed.set(hash, now);
|
|
444
479
|
const result = await parseUrl(url);
|
|
445
|
-
if (!result.
|
|
480
|
+
if (!result.success) {
|
|
446
481
|
return { success: false, msg: result.msg };
|
|
447
|
-
|
|
482
|
+
}
|
|
483
|
+
const text = generateFormattedText(result.data, config.unifiedMessageFormat);
|
|
448
484
|
return {
|
|
449
485
|
success: true,
|
|
450
|
-
data: {
|
|
451
|
-
text,
|
|
452
|
-
cover: result.info.cover,
|
|
453
|
-
video: result.info.url,
|
|
454
|
-
music: result.info.music,
|
|
455
|
-
author: result.info.author,
|
|
456
|
-
uid: result.info.uid,
|
|
457
|
-
avatar: result.info.avatar,
|
|
458
|
-
like: result.info.like,
|
|
459
|
-
title: result.info.title
|
|
460
|
-
}
|
|
486
|
+
data: { text, parsed: result.data }
|
|
461
487
|
};
|
|
462
488
|
}
|
|
463
489
|
async function sendWithTimeout(session, content) {
|
|
@@ -494,73 +520,99 @@ function apply(ctx, config) {
|
|
|
494
520
|
const items = [];
|
|
495
521
|
const errors = [];
|
|
496
522
|
for (const url of urls) {
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
499
|
-
items.push(
|
|
500
|
-
|
|
501
|
-
|
|
523
|
+
const res = await processSingleUrl(session, url);
|
|
524
|
+
if (res.success) {
|
|
525
|
+
items.push(res.data);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
errors.push(`【${url.slice(0, 50)}...】: ${res.msg}`);
|
|
529
|
+
}
|
|
502
530
|
}
|
|
503
|
-
if (errors.length
|
|
504
|
-
|
|
505
|
-
await sendWithTimeout(session, errorMsg).catch(() => { });
|
|
531
|
+
if (errors.length) {
|
|
532
|
+
await sendWithTimeout(session, `❌ 解析失败:\n${errors.join('\n')}`).catch(() => { });
|
|
506
533
|
await delay(500);
|
|
507
534
|
}
|
|
508
|
-
if (items.length
|
|
535
|
+
if (!items.length)
|
|
509
536
|
return;
|
|
510
537
|
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
511
538
|
const botName = config.botName || '视频解析机器人';
|
|
512
539
|
const forwardMessages = [];
|
|
513
540
|
for (const item of items) {
|
|
514
|
-
|
|
541
|
+
const p = item.parsed;
|
|
542
|
+
const text = item.text;
|
|
543
|
+
if (text) {
|
|
544
|
+
if (enableForward)
|
|
545
|
+
forwardMessages.push(buildForwardNode(session, text, botName));
|
|
546
|
+
else {
|
|
547
|
+
await sendWithTimeout(session, text);
|
|
548
|
+
await delay(300);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (p.cover && p.type !== 'live_photo') {
|
|
552
|
+
if (enableForward)
|
|
553
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
554
|
+
else {
|
|
555
|
+
await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
|
|
556
|
+
await delay(300);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (p.video && config.showVideoFile && (p.type === 'video' || p.type === 'live')) {
|
|
560
|
+
const sendVideo = async () => {
|
|
561
|
+
if (config.downloadVideoBeforeSend) {
|
|
562
|
+
const fname = crypto_1.default.createHash('md5').update(p.video).digest('hex');
|
|
563
|
+
const dl = await downloadVideo(p.video, fname, config.userAgent, config.maxVideoSize, config.downloadThreads);
|
|
564
|
+
if (dl.success)
|
|
565
|
+
return koishi_1.h.file(dl.filePath);
|
|
566
|
+
}
|
|
567
|
+
return koishi_1.h.video(p.video);
|
|
568
|
+
};
|
|
515
569
|
if (enableForward) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
|
|
570
|
+
const vMsg = await sendVideo();
|
|
571
|
+
forwardMessages.push(buildForwardNode(session, vMsg, botName));
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
try {
|
|
575
|
+
const vMsg = await sendVideo();
|
|
576
|
+
await sendWithTimeout(session, vMsg);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
try {
|
|
580
|
+
await sendWithTimeout(session, koishi_1.h.video(p.video));
|
|
528
581
|
}
|
|
529
|
-
|
|
530
|
-
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
|
|
582
|
+
catch { }
|
|
531
583
|
}
|
|
584
|
+
await delay(500);
|
|
532
585
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
586
|
+
}
|
|
587
|
+
if (p.type === 'image' || p.type === 'live_photo') {
|
|
588
|
+
const mediaList = [];
|
|
589
|
+
if (p.type === 'live_photo' && p.live_photo?.length) {
|
|
590
|
+
for (const lp of p.live_photo) {
|
|
591
|
+
if (lp.image)
|
|
592
|
+
mediaList.push({ type: 'image', url: lp.image });
|
|
593
|
+
if (lp.video && config.sendLivePhotoVideos)
|
|
594
|
+
mediaList.push({ type: 'video', url: lp.video });
|
|
537
595
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
596
|
+
}
|
|
597
|
+
else if (p.images?.length) {
|
|
598
|
+
p.images.forEach(url => mediaList.push({ type: 'image', url }));
|
|
599
|
+
}
|
|
600
|
+
if (enableForward) {
|
|
601
|
+
for (const m of mediaList) {
|
|
602
|
+
const msg = m.type === 'image' ? koishi_1.h.image(m.url) : koishi_1.h.video(m.url);
|
|
603
|
+
forwardMessages.push(buildForwardNode(session, msg, botName));
|
|
541
604
|
}
|
|
542
|
-
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
for (const m of mediaList) {
|
|
543
608
|
try {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const dl = await downloadVideo(item.video, fname, config.userAgent, config.maxVideoSize, config.downloadThreads);
|
|
547
|
-
await sendWithTimeout(session, dl.success ? koishi_1.h.file(dl.filePath) : koishi_1.h.video(item.video));
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
await sendWithTimeout(session, koishi_1.h.video(item.video));
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
catch {
|
|
554
|
-
try {
|
|
555
|
-
await sendWithTimeout(session, koishi_1.h.video(item.video));
|
|
556
|
-
}
|
|
557
|
-
catch { }
|
|
609
|
+
await sendWithTimeout(session, m.type === 'image' ? koishi_1.h.image(m.url) : koishi_1.h.video(m.url));
|
|
610
|
+
await delay(200);
|
|
558
611
|
}
|
|
559
|
-
|
|
612
|
+
catch { }
|
|
560
613
|
}
|
|
561
614
|
}
|
|
562
615
|
}
|
|
563
|
-
catch (e) { }
|
|
564
616
|
}
|
|
565
617
|
if (enableForward && forwardMessages.length) {
|
|
566
618
|
try {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -3,22 +3,24 @@
|
|
|
3
3
|
## 项目介绍 (Project Introduction)
|
|
4
4
|
|
|
5
5
|
### 中文
|
|
6
|
-
这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20
|
|
6
|
+
这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20+主流平台的短视频/图集/实况链接。核心特性:
|
|
7
7
|
- 🌐 统一API解析,覆盖20+热门平台,无需繁琐配置
|
|
8
8
|
- 🤖 自动识别链接来源,即丢即用
|
|
9
|
-
- 🎨
|
|
9
|
+
- 🎨 完全自定义的解析结果格式,支持多项变量替换
|
|
10
10
|
- 🐛 内置Debug调试模式,可详细记录所有操作与API交互日志
|
|
11
11
|
- ⚡ 防重复解析、API重试、本地视频下载、多线程加速等实用功能
|
|
12
12
|
- 📤 支持OneBot平台消息合并转发,优化多图文展示体验
|
|
13
|
+
- 🧹 内置手动/自动双模式缓存清理,自动删除临时视频与冗余缓存
|
|
13
14
|
|
|
14
15
|
### English
|
|
15
|
-
This is a **multi-platform video/image parsing plugin** developed for the Koishi bot framework, using a unified API interface to automatically recognize and parse short video/image links from 20+ mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, YouTube, TikTok, Jianying, AcFun, Zhihu, Huya and more. Core features:
|
|
16
|
+
This is a **multi-platform video/image parsing plugin** developed for the Koishi bot framework, using a unified API interface to automatically recognize and parse short video/image/live photo links from 20+ mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, YouTube, TikTok, Jianying, AcFun, Zhihu, Huya and more. Core features:
|
|
16
17
|
- 🌐 Unified API parsing, covering 20+ popular platforms without complex configuration
|
|
17
18
|
- 🤖 Auto-detection of link sources, just drop & go
|
|
18
|
-
- 🎨 Fully customizable parsing result format with
|
|
19
|
+
- 🎨 Fully customizable parsing result format with variable substitutions
|
|
19
20
|
- 🐛 Built-in Debug mode, recording detailed operations and API interaction logs
|
|
20
21
|
- ⚡ Duplicate parsing prevention, API retry, local video download, multithread acceleration
|
|
21
22
|
- 📤 Support OneBot message forwarding for better image/video display
|
|
23
|
+
- 🧹 Manual & automatic cache cleaning, auto delete temporary videos and redundant cache
|
|
22
24
|
|
|
23
25
|
## 项目仓库 (Repository)
|
|
24
26
|
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
|
|
@@ -54,6 +56,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
54
56
|
|--------|------|--------|------|
|
|
55
57
|
| `showImageText` | boolean | true | 是否显示解析后的图文内容 |
|
|
56
58
|
| `showVideoFile` | boolean | true | 是否发送视频文件(关闭则只发送视频链接) |
|
|
59
|
+
| `sendLivePhotoVideos` | boolean | true | 是否发送实况图片附带的短视频(仅 live_photo 类型) |
|
|
57
60
|
|
|
58
61
|
### 内容长度限制
|
|
59
62
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
@@ -92,38 +95,40 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
92
95
|
|--------|------|--------|------|
|
|
93
96
|
| `autoClearCacheInterval` | number | 0 | 自动清理缓存间隔(分钟,0为关闭自动清理) |
|
|
94
97
|
|
|
98
|
+
## 缓存机制说明 (Cache Mechanism)
|
|
99
|
+
- 临时视频默认存放目录:项目根目录 `temp_videos`
|
|
100
|
+
- 支持定时自动清空缓存、手动执行 `clear-cache` 一键清理
|
|
101
|
+
- 自动清理过期解析记录、残留分段下载文件,避免磁盘占用
|
|
102
|
+
|
|
95
103
|
## 支持的变量 (Supported Variables)
|
|
96
104
|
在 `unifiedMessageFormat` 中可使用以下变量进行自定义格式化:
|
|
97
105
|
|
|
98
106
|
| 变量名 | 说明 | 适用平台 |
|
|
99
107
|
|--------|------|----------|
|
|
100
108
|
| `${标题}` | 视频/图集标题 | 所有平台 |
|
|
101
|
-
| `${作者}` |
|
|
102
|
-
| `${简介}` | 内容简介/描述 |
|
|
103
|
-
| `${视频时长}` |
|
|
109
|
+
| `${作者}` | 作者/发布者名称 | 所有平台 |
|
|
110
|
+
| `${简介}` | 内容简介/描述 | 所有平台 |
|
|
111
|
+
| `${视频时长}` | 视频时长(时:分:秒) | 视频/实况 |
|
|
104
112
|
| `${点赞数}` | 点赞数量 | 所有平台 |
|
|
105
|
-
| `${投币数}` | 投币数量 | 部分平台 (B站) |
|
|
106
113
|
| `${收藏数}` | 收藏数量 | 所有平台 |
|
|
107
114
|
| `${转发数}` | 转发/分享数量 | 所有平台 |
|
|
108
115
|
| `${播放数}` | 播放量 | 部分平台 |
|
|
109
116
|
| `${评论数}` | 评论数量 | 所有平台 |
|
|
110
|
-
| `${IP属地}` | 作者IP属地 | 部分平台 |
|
|
111
117
|
| `${发布时间}` | 发布时间(格式化) | 所有平台 |
|
|
112
|
-
| `${
|
|
113
|
-
| `${在线人数}` | 直播间在线人数 | 部分平台 |
|
|
114
|
-
| `${关注数}` | 关注数 | 部分平台 |
|
|
115
|
-
| `${文件大小}` | 文件大小(MB) | 部分平台 |
|
|
116
|
-
| `${直播间地址}` | 直播间链接 | 部分平台 |
|
|
117
|
-
| `${直播间ID}` | 直播间ID | 部分平台 |
|
|
118
|
-
| `${直播间状态}` | 直播间状态(直播中/未开播) | 部分平台 |
|
|
119
|
-
| `${图片数量}` | 图集图片数量 | 部分平台 |
|
|
118
|
+
| `${图片数量}` | 图集/实况图片数量 | 图集/实况 |
|
|
120
119
|
| `${作者ID}` | 作者唯一标识ID | 部分平台 |
|
|
120
|
+
| `${视频链接}` | 视频直链地址 | 视频/实况 |
|
|
121
|
+
| `${封面}` | 封面图片地址 | 所有平台 |
|
|
122
|
+
| `${音乐作者}` | 背景音乐作者 | 部分平台 |
|
|
123
|
+
| `${音乐标题}` | 背景音乐标题 | 部分平台 |
|
|
124
|
+
|
|
125
|
+
> 注:部分变量可能因平台API返回数据不同而显示为空,请根据实际情况自定义格式。
|
|
121
126
|
|
|
122
127
|
## 支持的平台 (Supported Platforms)
|
|
123
128
|
| 平台名称 | 关键词识别 | 解析能力 |
|
|
124
129
|
|----------|------------|----------|
|
|
125
|
-
| 哔哩哔哩 (B站) | bilibili, b23.tv, bilibili.com |
|
|
126
|
-
| 抖音 | douyin, v.douyin.com |
|
|
130
|
+
| 哔哩哔哩 (B站) | bilibili, b23.tv, bilibili.com | 视频(不含番剧/直播/图文) |
|
|
131
|
+
| 抖音 | douyin, v.douyin.com | 短视频、图集、实况 |
|
|
127
132
|
| 快手 | kuaishou, v.kuaishou.com | 短视频、图集 |
|
|
128
133
|
| 小红书 | xiaohongshu, xhslink.com | 图文、视频 |
|
|
129
134
|
| 微博 | weibo, video.weibo.com | 视频、图集 |
|