koishi-plugin-video-parser-all 0.3.0 → 0.3.1

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.d.ts +53 -25
  2. package/lib/index.js +135 -104
  3. package/package.json +1 -1
package/lib/index.d.ts CHANGED
@@ -1,26 +1,54 @@
1
- import { Context, Schema } from 'koishi';
1
+ import { Schema, Context } from 'koishi';
2
2
  export declare const name = "video-parser-all";
3
- export interface Config {
4
- enable: boolean;
5
- showWaitingTip: boolean;
6
- waitingTipText: string;
7
- sameLinkInterval: number;
8
- imageParseFormat: string;
9
- returnContent: {
10
- showImageText: boolean;
11
- showVideoUrl: boolean;
12
- showVideoFile: boolean;
13
- };
14
- maxDescLength: number;
15
- timeout: number;
16
- ignoreSendError: boolean;
17
- enableForward: boolean;
18
- downloadVideoBeforeSend: boolean;
19
- messageBufferDelay: number;
20
- retryTimes: number;
21
- retryInterval: number;
22
- videoSendTimeout: number;
23
- autoClearCacheInterval: number;
24
- }
25
- export declare const Config: Schema<Config>;
26
- export declare function apply(ctx: Context, config: Config): void;
3
+ export declare const Config: Schema<Schemastery.ObjectS<{
4
+ enable: Schema<boolean, boolean>;
5
+ showWaitingTip: Schema<boolean, boolean>;
6
+ waitingTipText: Schema<string, string>;
7
+ sameLinkInterval: Schema<number, number>;
8
+ imageParseFormat: Schema<string, string>;
9
+ returnContent: Schema<Schemastery.ObjectS<{
10
+ showImageText: Schema<boolean, boolean>;
11
+ showVideoUrl: Schema<boolean, boolean>;
12
+ showVideoFile: Schema<boolean, boolean>;
13
+ }>, Schemastery.ObjectT<{
14
+ showImageText: Schema<boolean, boolean>;
15
+ showVideoUrl: Schema<boolean, boolean>;
16
+ showVideoFile: Schema<boolean, boolean>;
17
+ }>>;
18
+ maxDescLength: Schema<number, number>;
19
+ timeout: Schema<number, number>;
20
+ ignoreSendError: Schema<boolean, boolean>;
21
+ enableForward: Schema<boolean, boolean>;
22
+ downloadVideoBeforeSend: Schema<boolean, boolean>;
23
+ messageBufferDelay: Schema<number, number>;
24
+ retryTimes: Schema<number, number>;
25
+ retryInterval: Schema<number, number>;
26
+ videoSendTimeout: Schema<number, number>;
27
+ autoClearCacheInterval: Schema<number, number>;
28
+ }>, Schemastery.ObjectT<{
29
+ enable: Schema<boolean, boolean>;
30
+ showWaitingTip: Schema<boolean, boolean>;
31
+ waitingTipText: Schema<string, string>;
32
+ sameLinkInterval: Schema<number, number>;
33
+ imageParseFormat: Schema<string, string>;
34
+ returnContent: Schema<Schemastery.ObjectS<{
35
+ showImageText: Schema<boolean, boolean>;
36
+ showVideoUrl: Schema<boolean, boolean>;
37
+ showVideoFile: Schema<boolean, boolean>;
38
+ }>, Schemastery.ObjectT<{
39
+ showImageText: Schema<boolean, boolean>;
40
+ showVideoUrl: Schema<boolean, boolean>;
41
+ showVideoFile: Schema<boolean, boolean>;
42
+ }>>;
43
+ maxDescLength: Schema<number, number>;
44
+ timeout: Schema<number, number>;
45
+ ignoreSendError: Schema<boolean, boolean>;
46
+ enableForward: Schema<boolean, boolean>;
47
+ downloadVideoBeforeSend: Schema<boolean, boolean>;
48
+ messageBufferDelay: Schema<number, number>;
49
+ retryTimes: Schema<number, number>;
50
+ retryInterval: Schema<number, number>;
51
+ videoSendTimeout: Schema<number, number>;
52
+ autoClearCacheInterval: Schema<number, number>;
53
+ }>>;
54
+ export declare function apply(ctx: Context, config: any): void;
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const promises_1 = require("stream/promises");
14
14
  const worker_threads_1 = require("worker_threads");
15
+ const currentFilePath = path_1.default.join(process.cwd(), 'src', 'index.ts');
15
16
  exports.name = 'video-parser-all';
16
17
  exports.Config = koishi_1.Schema.object({
17
18
  enable: koishi_1.Schema.boolean().default(true).description('【基础设置】启用插件'),
@@ -104,13 +105,9 @@ function extractUrl(content) {
104
105
  const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
105
106
  return urlMatches.filter(url => {
106
107
  const lower = url.toLowerCase();
107
- return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k)));
108
+ return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
108
109
  });
109
110
  }
110
- function hasPlatformKeyword(content) {
111
- const lower = content.toLowerCase();
112
- return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k)));
113
- }
114
111
  function getPlatformType(url) {
115
112
  const lower = url.toLowerCase();
116
113
  if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k)))
@@ -137,7 +134,8 @@ async function shortUrl(url) {
137
134
  if (res.data.code === 200)
138
135
  return res.data.short_url;
139
136
  }
140
- catch { }
137
+ catch (error) {
138
+ }
141
139
  return url;
142
140
  }
143
141
  async function downloadVideoWithThreads(url, filename) {
@@ -146,10 +144,20 @@ async function downloadVideoWithThreads(url, filename) {
146
144
  if (!fs_1.default.existsSync(dir))
147
145
  fs_1.default.mkdirSync(dir, { recursive: true });
148
146
  const filePath = path_1.default.join(dir, `${filename}.mp4`);
149
- const worker = new worker_threads_1.Worker(__filename, { workerData: { url, filePath } });
150
- worker.on('message', (result) => result.success ? resolve(result.filePath) : reject(new Error(result.error)));
147
+ const worker = new worker_threads_1.Worker(currentFilePath, { workerData: { url, filePath } });
148
+ worker.on('message', (result) => {
149
+ if (result.success && result.filePath) {
150
+ resolve(result.filePath);
151
+ }
152
+ else {
153
+ reject(new Error(result.error || '下载失败'));
154
+ }
155
+ });
151
156
  worker.on('error', reject);
152
- worker.on('exit', (code) => code !== 0 && reject(new Error('视频下载线程异常')));
157
+ worker.on('exit', (code) => {
158
+ if (code !== 0)
159
+ reject(new Error('视频下载线程异常'));
160
+ });
153
161
  });
154
162
  }
155
163
  function parseData(data, maxDescLength, platform) {
@@ -181,15 +189,12 @@ function parseData(data, maxDescLength, platform) {
181
189
  else if (Array.isArray(data.video_backup) && data.video_backup.length > 0) {
182
190
  video = data.video_backup[0]?.url || '';
183
191
  }
184
- if (video && (video.endsWith('.m4a') || video.endsWith('.mp3'))) {
192
+ if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
185
193
  video = '';
186
- }
187
194
  }
188
195
  else if (platform === 'bilibili') {
189
196
  if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
190
- const hdVideo = data.videos.find(v => v.title.includes('1080') ||
191
- (v.url && v.url.includes('192')) ||
192
- v.index === 1);
197
+ const hdVideo = data.videos.find((v) => v.title.includes('1080') || (v.url && v.url.includes('192')) || v.index === 1);
193
198
  video = hdVideo?.url || data.videos[0]?.url || '';
194
199
  }
195
200
  else if (data.url) {
@@ -197,12 +202,7 @@ function parseData(data, maxDescLength, platform) {
197
202
  }
198
203
  }
199
204
  else {
200
- if (data.url)
201
- video = data.url;
202
- else if (data.videos?.[0]?.url)
203
- video = data.videos[0].url;
204
- else if (data.video_backup?.[0]?.url)
205
- video = data.video_backup[0].url;
205
+ video = data.url || data.videos?.[0]?.url || data.video_backup?.[0]?.url || '';
206
206
  }
207
207
  if (video.endsWith('.m4a') || video.endsWith('.mp3'))
208
208
  video = '';
@@ -210,17 +210,21 @@ function parseData(data, maxDescLength, platform) {
210
210
  }
211
211
  function clearAllCache() {
212
212
  processed.clear();
213
- linkBuffer.forEach(b => clearTimeout(b.timer));
213
+ linkBuffer.forEach(buf => clearTimeout(buf.timer));
214
214
  linkBuffer.clear();
215
- const d = path_1.default.join(process.cwd(), 'temp_videos');
216
- if (fs_1.default.existsSync(d))
217
- fs_1.default.readdirSync(d).forEach(f => { try {
218
- fs_1.default.unlinkSync(path_1.default.join(d, f));
219
- }
220
- catch { } });
215
+ const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
216
+ if (fs_1.default.existsSync(tempDir)) {
217
+ fs_1.default.readdirSync(tempDir).forEach(file => {
218
+ try {
219
+ fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
220
+ }
221
+ catch (error) {
222
+ }
223
+ });
224
+ }
221
225
  return true;
222
226
  }
223
- const delay = (ms) => new Promise(r => setTimeout(r, ms));
227
+ const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
224
228
  function apply(ctx, config) {
225
229
  if (!worker_threads_1.isMainThread)
226
230
  return;
@@ -245,10 +249,9 @@ function apply(ctx, config) {
245
249
  break;
246
250
  }
247
251
  }
248
- catch (e) {
249
- if (retry === config.retryTimes) {
252
+ catch (error) {
253
+ if (retry === config.retryTimes)
250
254
  break;
251
- }
252
255
  await delay(config.retryInterval);
253
256
  }
254
257
  }
@@ -282,10 +285,9 @@ function apply(ctx, config) {
282
285
  break;
283
286
  }
284
287
  }
285
- catch (e) {
286
- if (retry === config.retryTimes) {
288
+ catch (error) {
289
+ if (retry === config.retryTimes)
287
290
  break;
288
- }
289
291
  await delay(config.retryInterval);
290
292
  }
291
293
  }
@@ -299,36 +301,54 @@ function apply(ctx, config) {
299
301
  return { data: null, msg: '请勿重复解析' };
300
302
  }
301
303
  processed.set(hash, now);
302
- const r = await parse(url);
303
- if (!r.data)
304
- return { data: null, msg: r.msg };
305
- const d = r.data;
304
+ const result = await parse(url);
305
+ if (!result.data)
306
+ return { data: null, msg: result.msg };
307
+ const parseData = result.data;
306
308
  let text = config.imageParseFormat
307
- .replace(/\${标题}/g, d.title)
308
- .replace(/\${UP主}/g, d.author)
309
- .replace(/\${简介}/g, d.desc)
309
+ .replace(/\${标题}/g, parseData.title)
310
+ .replace(/\${UP主}/g, parseData.author)
311
+ .replace(/\${简介}/g, parseData.desc)
310
312
  .replace(/\${tab}/g, '\t')
311
313
  .replace(/\${~~~}/g, '\n');
312
- return { data: { text, cover: d.cover, images: d.images, video: d.video, type: d.type }, msg: 'ok' };
314
+ return {
315
+ data: {
316
+ text,
317
+ cover: parseData.cover,
318
+ images: parseData.images,
319
+ video: parseData.video,
320
+ type: parseData.type
321
+ },
322
+ msg: 'ok'
323
+ };
313
324
  }
314
- async function sendTimeout(session, c) {
315
- if (config.videoSendTimeout <= 0)
316
- return session.send(c).catch(() => null);
317
- return Promise.race([session.send(c), new Promise((_, r) => setTimeout(() => r('timeout'), config.videoSendTimeout))]).catch(() => null);
325
+ async function sendTimeout(session, content) {
326
+ if (config.videoSendTimeout <= 0) {
327
+ return session.send(content).catch(() => null);
328
+ }
329
+ return Promise.race([
330
+ session.send(content),
331
+ new Promise((_, reject) => setTimeout(() => reject('timeout'), config.videoSendTimeout))
332
+ ]).catch(() => null);
318
333
  }
319
334
  async function flush(session, manualUrls) {
320
335
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
321
- const buf = linkBuffer.get(key);
322
- const urls = manualUrls || buf?.urls || [];
323
- if (buf) {
324
- clearTimeout(buf.timer);
336
+ const buffer = linkBuffer.get(key);
337
+ const urls = manualUrls || buffer?.urls || [];
338
+ if (buffer) {
339
+ clearTimeout(buffer.timer);
325
340
  linkBuffer.delete(key);
326
341
  }
327
342
  const items = [];
328
343
  const errs = [];
329
- for (const u of urls) {
330
- const one = await processSingleUrl(session, u);
331
- one.data ? items.push(one.data) : errs.push(`【${u.slice(0, 22)}...】:${one.msg}`);
344
+ for (const url of urls) {
345
+ const result = await processSingleUrl(session, url);
346
+ if (result.data) {
347
+ items.push(result.data);
348
+ }
349
+ else {
350
+ errs.push(`【${url.slice(0, 22)}...】:${result.msg}`);
351
+ }
332
352
  }
333
353
  const forwardMessages = [];
334
354
  const botName = '视频解析机器人';
@@ -358,41 +378,44 @@ function apply(ctx, config) {
358
378
  }
359
379
  return;
360
380
  }
361
- for (const it of items) {
381
+ for (const item of items) {
362
382
  if (config.enableForward && session.platform === 'onebot') {
363
383
  forwardMessages.push((0, koishi_1.h)('message', [
364
384
  (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
365
- it.text
385
+ item.text
366
386
  ]));
367
- if (it.cover) {
387
+ if (item.cover) {
368
388
  forwardMessages.push((0, koishi_1.h)('message', [
369
389
  (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
370
- koishi_1.h.image(it.cover)
390
+ koishi_1.h.image(item.cover)
371
391
  ]));
372
392
  }
373
- if (it.video && config.returnContent.showVideoFile) {
374
- let vid = koishi_1.h.video(it.video);
393
+ if (item.video && config.returnContent.showVideoFile) {
394
+ let videoElem = koishi_1.h.video(item.video);
375
395
  if (config.downloadVideoBeforeSend) {
376
396
  try {
377
- const name = crypto_1.default.createHash('md5').update(it.video).digest('hex');
378
- vid = koishi_1.h.file(await downloadVideoWithThreads(it.video, name));
397
+ const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
398
+ const filePath = await downloadVideoWithThreads(item.video, filename);
399
+ videoElem = koishi_1.h.file(filePath);
400
+ }
401
+ catch (error) {
402
+ videoElem = koishi_1.h.video(item.video);
379
403
  }
380
- catch { }
381
404
  }
382
405
  forwardMessages.push((0, koishi_1.h)('message', [
383
406
  (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
384
- vid
407
+ videoElem
385
408
  ]));
386
409
  }
387
- if (it.video && config.returnContent.showVideoUrl) {
388
- const s = await shortUrl(it.video);
410
+ if (item.video && config.returnContent.showVideoUrl) {
411
+ const shortLink = await shortUrl(item.video);
389
412
  forwardMessages.push((0, koishi_1.h)('message', [
390
413
  (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
391
- `🔗 无水印:${s}`
414
+ `🔗 无水印:${shortLink}`
392
415
  ]));
393
416
  }
394
- if (it.type === 'image' && it.images?.length) {
395
- it.images.forEach(imgUrl => {
417
+ if (item.type === 'image' && item.images?.length) {
418
+ item.images.forEach(imgUrl => {
396
419
  forwardMessages.push((0, koishi_1.h)('message', [
397
420
  (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
398
421
  koishi_1.h.image(imgUrl)
@@ -401,32 +424,35 @@ function apply(ctx, config) {
401
424
  }
402
425
  }
403
426
  else {
404
- await sendTimeout(session, it.text);
427
+ await sendTimeout(session, item.text);
405
428
  await delay(300);
406
- if (it.type === 'image' && it.images?.length) {
407
- const msg = (0, koishi_1.h)('message', ...it.images.map(u => koishi_1.h.image(u)));
408
- await sendTimeout(session, msg);
429
+ if (item.type === 'image' && item.images?.length) {
430
+ const imgMsg = (0, koishi_1.h)('message', ...item.images.map(url => koishi_1.h.image(url)));
431
+ await sendTimeout(session, imgMsg);
409
432
  }
410
433
  else {
411
- if (it.cover) {
412
- await sendTimeout(session, koishi_1.h.image(it.cover));
434
+ if (item.cover) {
435
+ await sendTimeout(session, koishi_1.h.image(item.cover));
413
436
  await delay(300);
414
437
  }
415
- if (it.video && config.returnContent.showVideoFile) {
416
- let vid = koishi_1.h.video(it.video);
438
+ if (item.video && config.returnContent.showVideoFile) {
439
+ let videoElem = koishi_1.h.video(item.video);
417
440
  if (config.downloadVideoBeforeSend) {
418
441
  try {
419
- const name = crypto_1.default.createHash('md5').update(it.video).digest('hex');
420
- vid = koishi_1.h.file(await downloadVideoWithThreads(it.video, name));
442
+ const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
443
+ const filePath = await downloadVideoWithThreads(item.video, filename);
444
+ videoElem = koishi_1.h.file(filePath);
445
+ }
446
+ catch (error) {
447
+ videoElem = koishi_1.h.video(item.video);
421
448
  }
422
- catch { }
423
449
  }
424
- await sendTimeout(session, vid);
450
+ await sendTimeout(session, videoElem);
425
451
  }
426
- if (it.video && config.returnContent.showVideoUrl) {
452
+ if (item.video && config.returnContent.showVideoUrl) {
427
453
  await delay(300);
428
- const s = await shortUrl(it.video);
429
- await sendTimeout(session, `🔗 无水印:${s}`);
454
+ const shortLink = await shortUrl(item.video);
455
+ await sendTimeout(session, `🔗 无水印:${shortLink}`);
430
456
  }
431
457
  }
432
458
  await delay(1000);
@@ -445,34 +471,34 @@ function apply(ctx, config) {
445
471
  return;
446
472
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
447
473
  if (linkBuffer.has(key)) {
448
- const b = linkBuffer.get(key);
449
- const newUrls = urls.filter(u => !b.urls.includes(u));
474
+ const buffer = linkBuffer.get(key);
475
+ const newUrls = urls.filter(url => !buffer.urls.includes(url));
450
476
  if (newUrls.length) {
451
- b.urls.push(...newUrls);
452
- clearTimeout(b.timer);
453
- b.timer = setTimeout(() => flush(session), config.messageBufferDelay * 1000);
477
+ buffer.urls.push(...newUrls);
478
+ clearTimeout(buffer.timer);
479
+ buffer.timer = setTimeout(() => flush(session, undefined), config.messageBufferDelay * 1000);
454
480
  }
455
481
  return;
456
482
  }
457
- let tipId;
483
+ let tipMsgId;
458
484
  if (config.showWaitingTip) {
459
- const m = await sendTimeout(session, config.waitingTipText);
460
- tipId = m?.messageId || m?.id || m;
485
+ const msg = await sendTimeout(session, config.waitingTipText);
486
+ tipMsgId = msg?.messageId || msg?.id || msg;
461
487
  }
462
488
  linkBuffer.set(key, {
463
489
  urls,
464
- timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
465
- tipMsgId: tipId
490
+ timer: setTimeout(() => flush(session, undefined), config.messageBufferDelay * 1000),
491
+ tipMsgId
466
492
  });
467
493
  });
468
494
  ctx.command('parse <url>', '手动解析视频链接')
469
495
  .action(async ({ session }, url) => {
470
496
  if (!url)
471
497
  return '请输入视频链接';
472
- const us = extractUrl(url);
473
- if (!us.length)
498
+ const urls = extractUrl(url);
499
+ if (!urls.length)
474
500
  return '不支持该链接';
475
- await flush(session, us);
501
+ await flush(session, urls);
476
502
  });
477
503
  ctx.command('clear-cache', '清空解析缓存与临时文件')
478
504
  .action(() => {
@@ -481,20 +507,25 @@ function apply(ctx, config) {
481
507
  });
482
508
  setInterval(() => {
483
509
  const now = Date.now();
484
- processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
510
+ processed.forEach((timestamp, hash) => {
511
+ if (now - timestamp > 86400000)
512
+ processed.delete(hash);
513
+ });
485
514
  }, 3600000);
486
515
  setInterval(() => {
487
- const d = path_1.default.join(process.cwd(), 'temp_videos');
488
- if (!fs_1.default.existsSync(d))
516
+ const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
517
+ if (!fs_1.default.existsSync(tempDir))
489
518
  return;
490
519
  const now = Date.now();
491
- fs_1.default.readdirSync(d).forEach(f => {
520
+ fs_1.default.readdirSync(tempDir).forEach(file => {
492
521
  try {
493
- const st = fs_1.default.statSync(path_1.default.join(d, f));
494
- if (now - st.mtimeMs > 3600000)
495
- fs_1.default.unlinkSync(path_1.default.join(d, f));
522
+ const stat = fs_1.default.statSync(path_1.default.join(tempDir, file));
523
+ if (now - stat.mtimeMs > 3600000) {
524
+ fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
525
+ }
526
+ }
527
+ catch (error) {
496
528
  }
497
- catch { }
498
529
  });
499
530
  }, 1800000);
500
531
  if (config.autoClearCacheInterval > 0) {
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.3.0",
4
+ "version": "0.3.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [