koishi-plugin-video-parser-all 0.9.7 → 0.9.9

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 CHANGED
@@ -11,6 +11,9 @@ export declare const Config: Schema<{
11
11
  showImageText?: boolean | null | undefined;
12
12
  showVideoFile?: boolean | null | undefined;
13
13
  maxDescLength?: number | null | undefined;
14
+ videoDownloadTimeout?: number | null | undefined;
15
+ tempDir?: string | null | undefined;
16
+ maxVideoSize?: number | null | undefined;
14
17
  } & {
15
18
  timeout?: number | null | undefined;
16
19
  videoSendTimeout?: number | null | undefined;
@@ -38,6 +41,9 @@ export declare const Config: Schema<{
38
41
  showImageText: boolean;
39
42
  showVideoFile: boolean;
40
43
  maxDescLength: number;
44
+ videoDownloadTimeout: number;
45
+ tempDir: string;
46
+ maxVideoSize: number;
41
47
  } & {
42
48
  timeout: number;
43
49
  videoSendTimeout: number;
package/lib/index.js CHANGED
@@ -7,7 +7,10 @@ exports.Config = exports.name = void 0;
7
7
  exports.apply = apply;
8
8
  const koishi_1 = require("koishi");
9
9
  const axios_1 = __importDefault(require("axios"));
10
- const fast_xml_parser_1 = require("fast-xml-parser");
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = require("fs");
13
+ const promises_2 = require("stream/promises");
11
14
  exports.name = 'video-parser-all';
12
15
  exports.Config = koishi_1.Schema.intersect([
13
16
  koishi_1.Schema.object({
@@ -23,6 +26,9 @@ exports.Config = koishi_1.Schema.intersect([
23
26
  showImageText: koishi_1.Schema.boolean().default(true).description('是否发送解析后的文字内容'),
24
27
  showVideoFile: koishi_1.Schema.boolean().default(true).description('是否发送视频文件(关闭则只发送视频链接)'),
25
28
  maxDescLength: koishi_1.Schema.number().default(200).description('简介内容最大长度(字符),超出自动截断'),
29
+ videoDownloadTimeout: koishi_1.Schema.number().default(120000).description('视频下载超时(毫秒)'),
30
+ tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
31
+ maxVideoSize: koishi_1.Schema.number().default(100 * 1024 * 1024).description('最大下载视频大小(字节),默认100MB'),
26
32
  }).description('内容显示设置'),
27
33
  koishi_1.Schema.object({
28
34
  timeout: koishi_1.Schema.number().min(0).default(180000).description('API 请求超时(毫秒)'),
@@ -54,85 +60,76 @@ function debugLog(level, ...args) {
54
60
  const message = `[${timestamp}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}`;
55
61
  logger.info(message);
56
62
  }
57
- const PLATFORM_KEYWORDS = {
58
- bilibili: ['bilibili', 'b23', 'www.bilibili.com', 'm.bilibili.com', 'b23.tv', 't.bilibili.com', 'bilibili.com/video', 'bilibili.com/opus', 'bilibili.com/bangumi'],
59
- kuaishou: ['kuaishou', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com'],
60
- weibo: ['weibo', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
61
- toutiao: ['toutiao', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video'],
62
- pipigx: ['pipigx', 'h5.pipigx.com', 'ippzone.com'],
63
- pipixia: ['pipixia', 'pipix', 'h5.pipix.com', 'ppxsign.byteimg.com', 'pipix.com'],
64
- douyin: ['douyin', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com', 'douyin.com/video', 'douyin.com/note', 'www.douyin.com'],
65
- zuiyou: ['zuiyou', 'xiaochuankeji.cn', 'izuiyou.com'],
66
- xiaohongshu: ['xiaohongshu', 'xhslink.com', 'www.xiaohongshu.com'],
67
- jianying: ['jianying', 'jimeng.jianying.com', 'lv.ulikecam.com'],
68
- acfun: ['acfun', 'acfun.cn', 'www.acfun.cn'],
69
- zhihu: ['zhihu', 'zhihu.com', 'www.zhihu.com'],
70
- weishi: ['weishi', 'weishi.qq.com'],
71
- huya: ['huya', 'huya.com', 'www.huya.com'],
72
- youtube: ['youtube', 'youtube.com', 'youtu.be', 'www.youtube.com'],
73
- tiktok: ['tiktok', 'tiktok.com', 'www.tiktok.com'],
74
- xigua: ['xigua', 'ixigua.com'],
75
- haokan: ['haokan', 'haokan.baidu.com'],
76
- li: ['video.li'],
77
- meipai: ['meipai', 'meipai.com'],
78
- quanmin: ['quanmin', 'quanmin.tv'],
79
- twitter: ['twitter', 'x.com'],
80
- instagram: ['instagram', 'instagram.com'],
81
- doubao: ['doubao', 'doubao.com'],
82
- jimeng: ['jimeng', 'jimeng.ai'],
83
- };
84
- function getErrorMessage(error) {
85
- if (error instanceof Error)
86
- return error.message;
87
- return String(error);
63
+ function linkTypeParser(content) {
64
+ content = content.replace(/\\\//g, '/');
65
+ const rules = [
66
+ { pattern: /bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://www.bilibili.com/video/${id}` },
67
+ { pattern: /b23\.tv(?:\\)?\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
68
+ { pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
69
+ { pattern: /bili2233\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
70
+ { pattern: /douyin\.com\/video\/(\d+)/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
71
+ { pattern: /v\.douyin\.com\/([0-9a-zA-Z]+)/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}` },
72
+ { pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
73
+ { pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
74
+ { pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
75
+ { pattern: /xhslink\.com\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
76
+ { pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
77
+ { pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
78
+ { pattern: /ixigua\.com\/(\d+)/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
79
+ { pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
80
+ { pattern: /youtu\.be\/([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
81
+ { pattern: /tiktok\.com\/@[\w.]+\/video\/(\d+)/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
82
+ { pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]+)/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
83
+ { pattern: /acfun\.cn\/v\/(ac\d+)/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
84
+ { pattern: /zhihu\.com\/video\/(\d+)/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
85
+ { pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]+)/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
86
+ { pattern: /huya\.com\/video\/([0-9a-zA-Z]+)/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
87
+ { pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]+)/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
88
+ { pattern: /meipai\.com\/media\/(\d+)/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
89
+ { pattern: /twitter\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
90
+ { pattern: /x\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
91
+ { pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]+)/gi, type: 'instagram', buildUrl: (id) => `https://www.instagram.com/p/${id}` },
92
+ { pattern: /doubao\.com\/video\/(\d+)/gi, type: 'doubao', buildUrl: (id) => `https://www.doubao.com/video/${id}` },
93
+ ];
94
+ const matches = [];
95
+ const seen = new Set();
96
+ for (const rule of rules) {
97
+ let match;
98
+ while ((match = rule.pattern.exec(content)) !== null) {
99
+ const id = match[1];
100
+ if (seen.has(id))
101
+ continue;
102
+ seen.add(id);
103
+ const url = rule.buildUrl(id);
104
+ matches.push({ type: rule.type, url, id });
105
+ }
106
+ }
107
+ return matches;
88
108
  }
89
- const xmlParser = new fast_xml_parser_1.XMLParser({
90
- ignoreAttributes: false,
91
- attributeNamePrefix: '@_',
92
- allowBooleanAttributes: true,
93
- trimValues: true,
94
- parseTagValue: false,
95
- isArray: (name) => name === 'item' || name === 'picture',
96
- });
97
- function extractUrlsFromXml(xml) {
98
- const urls = [];
99
- try {
100
- const parsed = xmlParser.parse(xml);
101
- const msg = parsed?.msg;
102
- if (!msg)
103
- return urls;
104
- if (msg.source && typeof msg.source === 'object') {
105
- const sourceUrl = msg.source['@_url'];
106
- if (sourceUrl && typeof sourceUrl === 'string')
107
- urls.push(sourceUrl);
109
+ function extractUrl(content) {
110
+ const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
111
+ return urlMatches.filter(url => {
112
+ try {
113
+ const hostname = new URL(url).hostname.toLowerCase();
114
+ if (hostname === 'multimedia.nt.qq.com.cn')
115
+ return false;
116
+ return true;
108
117
  }
109
- const items = Array.isArray(msg.item) ? msg.item : (msg.item ? [msg.item] : []);
110
- for (const item of items) {
111
- const pictures = Array.isArray(item.picture) ? item.picture : (item.picture ? [item.picture] : []);
112
- for (const pic of pictures) {
113
- if (pic['@_cover'] && typeof pic['@_cover'] === 'string')
114
- urls.push(pic['@_cover']);
115
- }
116
- if (item.title && typeof item.title === 'string') {
117
- const match = item.title.match(/https?:\/\/[^\s<>"']+/i);
118
- if (match)
119
- urls.push(match[0]);
120
- }
121
- if (item.summary && typeof item.summary === 'string') {
122
- const matches = item.summary.match(/https?:\/\/[^\s<>"']+/gi);
123
- if (matches)
124
- urls.push(...matches);
125
- }
118
+ catch {
119
+ return false;
126
120
  }
127
- }
128
- catch (err) {
129
- debugLog('WARN', `解析 XML 卡片失败: ${getErrorMessage(err)}`);
130
- }
131
- return urls;
121
+ });
132
122
  }
133
123
  function extractAllUrlsFromMessage(session) {
134
- const urls = [];
135
124
  const content = session.content?.trim() || '';
125
+ const urls = [];
126
+ const linkMatches = linkTypeParser(content);
127
+ if (linkMatches.length > 0) {
128
+ for (const match of linkMatches) {
129
+ urls.push(match.url);
130
+ }
131
+ return [...new Set(urls)];
132
+ }
136
133
  if (content) {
137
134
  const textUrls = extractUrl(content);
138
135
  urls.push(...textUrls);
@@ -140,8 +137,11 @@ function extractAllUrlsFromMessage(session) {
140
137
  if (session.elements) {
141
138
  for (const elem of session.elements) {
142
139
  if (elem.type === 'xml' && elem.data) {
143
- const xmlUrls = extractUrlsFromXml(elem.data);
144
- urls.push(...xmlUrls);
140
+ const urlRegex = /https?:\/\/[^\s<>"']+/gi;
141
+ let match;
142
+ while ((match = urlRegex.exec(elem.data)) !== null) {
143
+ urls.push(match[0]);
144
+ }
145
145
  }
146
146
  else if (elem.type === 'json' && elem.data) {
147
147
  try {
@@ -167,34 +167,6 @@ function extractAllUrlsFromMessage(session) {
167
167
  }
168
168
  return [...new Set(urls)];
169
169
  }
170
- function extractUrl(content) {
171
- const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
172
- return urlMatches.filter(url => {
173
- try {
174
- const hostname = new URL(url).hostname.toLowerCase();
175
- if (hostname === 'multimedia.nt.qq.com.cn')
176
- return false;
177
- return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => hostname.includes(keyword) || (!keyword.includes('.') && url.toLowerCase().includes(keyword))));
178
- }
179
- catch {
180
- const lower = url.toLowerCase();
181
- return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
182
- }
183
- });
184
- }
185
- function getPlatformType(url) {
186
- try {
187
- const hostname = new URL(url).hostname.toLowerCase();
188
- if (hostname === 'multimedia.nt.qq.com.cn')
189
- return null;
190
- for (const [platform, keywords] of Object.entries(PLATFORM_KEYWORDS)) {
191
- if (keywords.some(k => hostname.includes(k) || (!k.includes('.') && url.toLowerCase().includes(k))))
192
- return platform;
193
- }
194
- }
195
- catch { }
196
- return null;
197
- }
198
170
  function cleanUrl(url) {
199
171
  try {
200
172
  url = url.replace(/&amp;/g, '&');
@@ -219,7 +191,7 @@ async function resolveShortUrl(url) {
219
191
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
220
192
  'Referer': 'https://www.baidu.com/',
221
193
  },
222
- validateStatus: status => status >= 200 && status < 400,
194
+ validateStatus: (status) => status >= 200 && status < 400,
223
195
  });
224
196
  const finalUrl = res.request?.res?.responseUrl || url;
225
197
  return cleanUrl(finalUrl);
@@ -390,6 +362,44 @@ function buildForwardNode(session, content, botName) {
390
362
  }
391
363
  const urlCache = new Map();
392
364
  const CACHE_TTL = 10 * 60 * 1000;
365
+ async function downloadVideoFile(videoUrl, tempDir, timeout, maxSize) {
366
+ await promises_1.default.mkdir(tempDir, { recursive: true });
367
+ const fileName = `video_${Date.now()}_${Math.random().toString(36).substring(2, 8)}.mp4`;
368
+ const filePath = path_1.default.join(tempDir, fileName);
369
+ const writer = (0, fs_1.createWriteStream)(filePath);
370
+ const response = await (0, axios_1.default)({
371
+ method: 'GET',
372
+ url: videoUrl,
373
+ responseType: 'stream',
374
+ timeout: timeout,
375
+ headers: {
376
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
377
+ }
378
+ });
379
+ const contentLength = Number(response.headers['content-length'] || 0);
380
+ if (contentLength > maxSize) {
381
+ writer.destroy();
382
+ await promises_1.default.unlink(filePath).catch(() => { });
383
+ throw new Error(`视频文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${Math.round(maxSize / 1024 / 1024)}MB)`);
384
+ }
385
+ let downloadedSize = 0;
386
+ response.data.on('data', (chunk) => {
387
+ downloadedSize += chunk.length;
388
+ if (downloadedSize > maxSize) {
389
+ response.data.destroy();
390
+ writer.destroy();
391
+ promises_1.default.unlink(filePath).catch(() => { });
392
+ throw new Error(`视频文件过大,超过限制(${Math.round(maxSize / 1024 / 1024)}MB)`);
393
+ }
394
+ });
395
+ await (0, promises_2.pipeline)(response.data, writer);
396
+ return filePath;
397
+ }
398
+ function getErrorMessage(error) {
399
+ if (error instanceof Error)
400
+ return error.message;
401
+ return String(error);
402
+ }
393
403
  function apply(ctx, config) {
394
404
  debugEnabled = config.debug || false;
395
405
  debugLog('INFO', '插件初始化开始');
@@ -443,10 +453,6 @@ function apply(ctx, config) {
443
453
  }
444
454
  async function parseUrl(url) {
445
455
  const realUrl = await resolveShortUrl(url);
446
- const platform = getPlatformType(realUrl);
447
- if (!platform) {
448
- return { success: false, msg: texts.unsupportedPlatformText };
449
- }
450
456
  const candidates = [realUrl, url];
451
457
  for (const candidate of [...new Set(candidates)]) {
452
458
  try {
@@ -457,7 +463,7 @@ function apply(ctx, config) {
457
463
  debugLog('ERROR', `候选链接解析失败: ${candidate}`, getErrorMessage(error));
458
464
  }
459
465
  }
460
- return { success: false, msg: '解析失败' };
466
+ return { success: false, msg: texts.unsupportedPlatformText };
461
467
  }
462
468
  async function processSingleUrl(url) {
463
469
  const result = await parseUrl(url);
@@ -504,6 +510,29 @@ function apply(ctx, config) {
504
510
  }
505
511
  return null;
506
512
  }
513
+ async function sendVideoFile(session, videoUrl) {
514
+ if (!videoUrl)
515
+ throw new Error('视频链接为空');
516
+ try {
517
+ debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
518
+ return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
519
+ }
520
+ catch (err) {
521
+ debugLog('ERROR', `直接发送URL失败,开始下载视频: ${getErrorMessage(err)}`);
522
+ let tempFilePath = null;
523
+ try {
524
+ tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 100 * 1024 * 1024);
525
+ const localFile = `file://${path_1.default.resolve(tempFilePath)}`;
526
+ debugLog('INFO', `视频下载完成,发送本地文件: ${localFile}`);
527
+ return await sendWithTimeout(session, koishi_1.h.video(localFile));
528
+ }
529
+ finally {
530
+ if (tempFilePath) {
531
+ promises_1.default.unlink(tempFilePath).catch(e => debugLog('WARN', `删除临时文件失败: ${e}`));
532
+ }
533
+ }
534
+ }
535
+ }
507
536
  async function flush(session, urls) {
508
537
  const uniqueUrls = [...new Set(urls)];
509
538
  const items = [];
@@ -537,44 +566,75 @@ function apply(ctx, config) {
537
566
  return;
538
567
  const enableForward = config.enableForward && session.platform === 'onebot';
539
568
  const botName = config.botName || '视频解析机器人';
540
- const forwardMessages = [];
541
- for (const item of items) {
542
- const p = item.parsed;
543
- const text = item.text;
544
- if (text && config.showImageText) {
545
- if (enableForward)
569
+ if (enableForward) {
570
+ const forwardMessages = [];
571
+ for (const item of items) {
572
+ const p = item.parsed;
573
+ const text = item.text;
574
+ if (text && config.showImageText) {
546
575
  forwardMessages.push(buildForwardNode(session, text, botName));
547
- else {
548
- await sendWithTimeout(session, text);
549
- await delay(300);
550
576
  }
551
- }
552
- if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
553
- if (enableForward)
577
+ if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
554
578
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
555
- else {
556
- await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
557
- await delay(300);
579
+ }
580
+ if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
581
+ const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
582
+ for (const imgUrl of imageUrls) {
583
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
584
+ }
558
585
  }
559
586
  }
560
- if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
561
- const videoMsg = koishi_1.h.video(p.video);
562
- if (enableForward) {
563
- forwardMessages.push(buildForwardNode(session, videoMsg, botName));
587
+ if (forwardMessages.length) {
588
+ const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
589
+ try {
590
+ await sendWithTimeout(session, forwardMsg, config.retryTimes);
564
591
  }
565
- else {
566
- await sendWithTimeout(session, videoMsg).catch(() => { });
592
+ catch (err) {
593
+ debugLog('ERROR', '合并转发发送失败,降级为逐条发送:', err);
594
+ for (const node of forwardMessages) {
595
+ await sendWithTimeout(session, node.data.content).catch(() => { });
596
+ await delay(300);
597
+ }
598
+ }
599
+ }
600
+ for (const item of items) {
601
+ const p = item.parsed;
602
+ if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
603
+ try {
604
+ await sendVideoFile(session, p.video);
605
+ }
606
+ catch (err) {
607
+ debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
608
+ await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
609
+ }
567
610
  await delay(500);
568
611
  }
569
612
  }
570
- if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
571
- const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
572
- if (enableForward) {
573
- for (const imgUrl of imageUrls) {
574
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
613
+ }
614
+ else {
615
+ for (const item of items) {
616
+ const p = item.parsed;
617
+ const text = item.text;
618
+ if (text && config.showImageText) {
619
+ await sendWithTimeout(session, text);
620
+ await delay(300);
621
+ }
622
+ if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
623
+ await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
624
+ await delay(300);
625
+ }
626
+ if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
627
+ try {
628
+ await sendVideoFile(session, p.video);
629
+ }
630
+ catch (err) {
631
+ debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
632
+ await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
575
633
  }
634
+ await delay(500);
576
635
  }
577
- else {
636
+ if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
637
+ const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
578
638
  for (const imgUrl of imageUrls) {
579
639
  await sendWithTimeout(session, koishi_1.h.image(imgUrl)).catch(() => { });
580
640
  await delay(200);
@@ -582,15 +642,6 @@ function apply(ctx, config) {
582
642
  }
583
643
  }
584
644
  }
585
- if (enableForward && forwardMessages.length) {
586
- const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
587
- await sendWithTimeout(session, forwardMsg, config.retryTimes).catch(() => {
588
- debugLog('ERROR', '合并转发发送最终失败,降级为逐条发送');
589
- for (const node of forwardMessages) {
590
- sendWithTimeout(session, node.data.content).catch(() => { });
591
- }
592
- });
593
- }
594
645
  }
595
646
  ctx.on('message', async (session) => {
596
647
  if (!config.enable)
@@ -621,5 +672,17 @@ function apply(ctx, config) {
621
672
  urlCache.delete(key);
622
673
  }
623
674
  }, 60000);
675
+ process.on('exit', async () => {
676
+ try {
677
+ const tempDir = config.tempDir || './temp_videos';
678
+ const files = await promises_1.default.readdir(tempDir);
679
+ for (const file of files) {
680
+ if (file.startsWith('video_') && file.endsWith('.mp4')) {
681
+ await promises_1.default.unlink(path_1.default.join(tempDir, file)).catch(() => { });
682
+ }
683
+ }
684
+ }
685
+ catch { }
686
+ });
624
687
  debugLog('INFO', '插件初始化完成');
625
688
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
- "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台,新增XML卡片链接提取",
4
- "version": "0.9.7",
3
+ "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
4
+ "version": "0.9.9",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [