koishi-plugin-video-parser-all 0.5.0 → 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.
package/lib/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export declare const Config: Schema<{
24
24
  } & {
25
25
  enableForward?: boolean | null | undefined;
26
26
  downloadVideoBeforeSend?: boolean | null | undefined;
27
+ maxVideoSize?: number | null | undefined;
28
+ downloadThreads?: number | null | undefined;
27
29
  } & {
28
30
  messageBufferDelay?: number | null | undefined;
29
31
  } & {
@@ -52,6 +54,8 @@ export declare const Config: Schema<{
52
54
  } & {
53
55
  enableForward: boolean;
54
56
  downloadVideoBeforeSend: boolean;
57
+ maxVideoSize: number;
58
+ downloadThreads: number;
55
59
  } & {
56
60
  messageBufferDelay: number;
57
61
  } & {
package/lib/index.js CHANGED
@@ -11,6 +11,7 @@ const crypto_1 = __importDefault(require("crypto"));
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const promises_1 = require("stream/promises");
14
+ const worker_threads_1 = require("worker_threads");
14
15
  exports.name = 'video-parser-all';
15
16
  exports.Config = koishi_1.Schema.intersect([
16
17
  koishi_1.Schema.object({
@@ -21,7 +22,7 @@ exports.Config = koishi_1.Schema.intersect([
21
22
  sameLinkInterval: koishi_1.Schema.number().min(0).default(180).description('相同链接重复解析间隔(秒)'),
22
23
  }).description('基础设置'),
23
24
  koishi_1.Schema.object({
24
- unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n视频大小:${视频大小}MB').description('统一消息格式(B站会显示投币,其他平台自动隐藏)'),
25
+ unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('统一消息格式(B站会显示投币,其他平台自动隐藏;无法获取的变量会自动隐藏)'),
25
26
  }).description('统一消息格式'),
26
27
  koishi_1.Schema.object({
27
28
  showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
@@ -43,20 +44,22 @@ exports.Config = koishi_1.Schema.intersect([
43
44
  koishi_1.Schema.object({
44
45
  enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅OneBot平台)'),
45
46
  downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('发送前先下载视频(避免链接失效)'),
46
- }).description('发送方式设置'),
47
+ 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为启用对应线程数)'),
49
+ }).description('发送方式设置(说明:视频大小超过限制将只发送链接;多线程下载可提升速度但可能增加服务器负载)'),
47
50
  koishi_1.Schema.object({
48
51
  messageBufferDelay: koishi_1.Schema.number().min(0).default(0).description('消息缓冲延迟(毫秒,批量处理链接)'),
49
52
  }).description('消息处理设置'),
50
53
  koishi_1.Schema.object({
51
54
  autoClearCacheInterval: koishi_1.Schema.number().min(0).default(0).description('自动清理缓存间隔(分钟,0为关闭)'),
52
- }).description('缓存清理设置'),
55
+ }).description('缓存清理设置(说明:开启自动清理可定期删除过期的临时视频文件和解析缓存)'),
53
56
  ]);
54
57
  const processed = new Map();
55
58
  const linkBuffer = new Map();
56
59
  const logger = new koishi_1.Logger(exports.name);
57
60
  const PLATFORM_KEYWORDS = {
58
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'],
59
- 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'],
60
63
  xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com', 'xiaohongshu.com/explore', 'xhslink.com/'],
61
64
  weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
62
65
  toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video', 'ixigua.com/i'],
@@ -66,22 +69,87 @@ const PLATFORM_KEYWORDS = {
66
69
  zuiyou: ['zuiyou', '最右', 'xiaochuankeji.cn', 'izuiyou.com', 'izuiyou.com/topic']
67
70
  };
68
71
  const API_CONFIG = {
69
- bilibili: 'https://api.xingzhige.com/API/b_parse/',
70
- douyin: 'https://api.xingzhige.com/API/douyin/',
71
- kuaishou: 'https://api.xingzhige.com/API/kuaishou/',
72
- 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',
73
76
  weibo: 'https://api.bugpk.com/api/weibo',
74
- zuiyou: 'https://api.bugpk.com/api/short_videos',
75
- pipixia: 'https://api.bugpk.com/api/short_videos',
76
- pipigx: 'https://api.bugpk.com/api/short_videos',
77
- 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']
78
94
  };
79
95
  function getErrorMessage(error) {
80
96
  if (error instanceof Error)
81
97
  return error.message;
82
98
  return String(error);
83
99
  }
84
- async function downloadVideo(url, filename, userAgent) {
100
+ async function getFileSize(url, userAgent) {
101
+ try {
102
+ const response = await axios_1.default.head(url, {
103
+ timeout: 10000,
104
+ headers: {
105
+ 'User-Agent': userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
106
+ }
107
+ });
108
+ const contentLength = response.headers['content-length'];
109
+ if (contentLength) {
110
+ return Math.round(Number(contentLength) / 1024 / 1024 * 100) / 100;
111
+ }
112
+ }
113
+ catch (error) { }
114
+ return 0;
115
+ }
116
+ async function downloadVideoThread(workerData) {
117
+ return new Promise((resolve, reject) => {
118
+ const worker = new worker_threads_1.Worker(__filename, { workerData });
119
+ worker.on('message', resolve);
120
+ worker.on('error', reject);
121
+ worker.on('exit', (code) => {
122
+ if (code !== 0)
123
+ reject(new Error(`Worker stopped with exit code ${code}`));
124
+ });
125
+ });
126
+ }
127
+ if (!worker_threads_1.isMainThread) {
128
+ const { url, start, end, filename, userAgent } = worker_threads_1.workerData;
129
+ const filePath = path_1.default.join(process.cwd(), 'temp_videos', `${filename}_${start}_${end}.part`);
130
+ (0, axios_1.default)({
131
+ url,
132
+ method: 'GET',
133
+ responseType: 'stream',
134
+ timeout: 60000,
135
+ headers: {
136
+ 'User-Agent': userAgent,
137
+ 'Range': `bytes=${start}-${end}`
138
+ }
139
+ }).then(response => {
140
+ const writeStream = fs_1.default.createWriteStream(filePath);
141
+ response.data.pipe(writeStream);
142
+ writeStream.on('finish', () => {
143
+ worker_threads_1.parentPort?.postMessage({ success: true, filePath, start, end });
144
+ });
145
+ writeStream.on('error', (error) => {
146
+ worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
147
+ });
148
+ }).catch(error => {
149
+ worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
150
+ });
151
+ }
152
+ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
85
153
  const dir = path_1.default.join(process.cwd(), 'temp_videos');
86
154
  if (!fs_1.default.existsSync(dir))
87
155
  fs_1.default.mkdirSync(dir, { recursive: true });
@@ -90,99 +158,64 @@ async function downloadVideo(url, filename, userAgent) {
90
158
  if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
91
159
  throw new Error('不支持音频');
92
160
  }
93
- const response = await (0, axios_1.default)({
94
- url,
95
- method: 'GET',
96
- responseType: 'stream',
97
- timeout: 60000,
98
- headers: {
99
- 'User-Agent': userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
100
- }
101
- });
161
+ const fileSize = await getFileSize(url, userAgent);
162
+ if (maxSize > 0 && fileSize > maxSize) {
163
+ throw new Error(`视频大小${fileSize}MB超过限制${maxSize}MB`);
164
+ }
165
+ if (threads <= 0 || fileSize === 0) {
166
+ const response = await (0, axios_1.default)({
167
+ url,
168
+ method: 'GET',
169
+ responseType: 'stream',
170
+ timeout: 60000,
171
+ headers: {
172
+ 'User-Agent': userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
173
+ }
174
+ });
175
+ const writeStream = fs_1.default.createWriteStream(filePath);
176
+ await (0, promises_1.pipeline)(response.data, writeStream);
177
+ return filePath;
178
+ }
179
+ const totalSize = fileSize * 1024 * 1024;
180
+ const chunkSize = Math.ceil(totalSize / threads);
181
+ const promises = [];
182
+ for (let i = 0; i < threads; i++) {
183
+ const start = i * chunkSize;
184
+ const end = i === threads - 1 ? totalSize - 1 : start + chunkSize - 1;
185
+ promises.push(downloadVideoThread({
186
+ url,
187
+ start,
188
+ end,
189
+ filename,
190
+ userAgent
191
+ }));
192
+ }
193
+ const results = await Promise.all(promises);
102
194
  const writeStream = fs_1.default.createWriteStream(filePath);
103
- await (0, promises_1.pipeline)(response.data, writeStream);
195
+ for (const result of results) {
196
+ if (!result.success)
197
+ throw new Error(result.error);
198
+ const readStream = fs_1.default.createReadStream(result.filePath);
199
+ await (0, promises_1.pipeline)(readStream, writeStream, { end: false });
200
+ fs_1.default.unlinkSync(result.filePath);
201
+ }
202
+ writeStream.end();
104
203
  return filePath;
105
204
  }
106
205
  catch (error) {
107
206
  if (fs_1.default.existsSync(filePath)) {
108
207
  fs_1.default.unlinkSync(filePath);
109
208
  }
209
+ const partFiles = fs_1.default.readdirSync(dir).filter(file => file.startsWith(`${filename}_`) && file.endsWith('.part'));
210
+ partFiles.forEach(file => {
211
+ try {
212
+ fs_1.default.unlinkSync(path_1.default.join(dir, file));
213
+ }
214
+ catch (e) { }
215
+ });
110
216
  throw error;
111
217
  }
112
218
  }
113
- function parseXingzhigeData(resData, platform) {
114
- const result = {
115
- title: '',
116
- author: '未知作者',
117
- desc: '',
118
- cover: '',
119
- video: '',
120
- images: [],
121
- stat: {
122
- like: 0,
123
- coin: 0,
124
- favorite: 0,
125
- share: 0,
126
- view: 0,
127
- size: 0,
128
- duration: '00:00'
129
- },
130
- type: 'video'
131
- };
132
- if (platform === 'kuaishou' && resData.jx && resData.jx.length > 0) {
133
- const item = resData.jx[0];
134
- result.title = item.title || '';
135
- result.cover = item.cover || '';
136
- result.video = item.url || item.video || '';
137
- result.images = item.images || [];
138
- result.stat = {
139
- like: resData.stat?.like || 0,
140
- coin: 0,
141
- favorite: 0,
142
- share: resData.stat?.share || 0,
143
- view: resData.stat?.view || 0,
144
- size: 0,
145
- duration: '00:00'
146
- };
147
- result.type = result.images.length > 0 ? 'image' : 'video';
148
- }
149
- else if (platform === 'douyin' && resData.jx && resData.jx.length > 0) {
150
- const item = resData.jx[0];
151
- result.title = item.title || '';
152
- result.cover = item.cover || '';
153
- result.video = item.url || '';
154
- result.images = item.images || [];
155
- result.stat = {
156
- like: resData.stat?.like || 0,
157
- coin: 0,
158
- favorite: resData.stat?.collect || 0,
159
- share: resData.stat?.share || 0,
160
- view: 0,
161
- size: 0,
162
- duration: '00:00'
163
- };
164
- result.type = result.images.length > 0 ? 'image' : 'video';
165
- }
166
- else if (platform === 'bilibili') {
167
- const d = resData.data || resData;
168
- result.title = d.video?.title || d.title || '';
169
- result.author = d.owner?.name || d.name || '未知UP主';
170
- result.desc = d.video?.desc || d.desc || '';
171
- result.cover = d.video?.fm || d.fm || '';
172
- result.video = d.video?.url || d.url || '';
173
- result.stat = {
174
- view: d.stat?.view || 0,
175
- favorite: d.stat?.favorite || 0,
176
- like: d.stat?.like || 0,
177
- coin: d.stat?.coin || 0,
178
- share: d.stat?.share || 0,
179
- size: 0,
180
- duration: formatDuration(d.duration || 0)
181
- };
182
- result.duration = d.duration || 0;
183
- }
184
- return result;
185
- }
186
219
  function extractUrl(content) {
187
220
  let urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
188
221
  return urlMatches.filter(url => {
@@ -231,8 +264,16 @@ async function resolveShortUrl(url) {
231
264
  return url;
232
265
  }
233
266
  }
234
- function formatDuration(seconds) {
235
- 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)
236
277
  return '00:00';
237
278
  const hours = Math.floor(seconds / 3600);
238
279
  const minutes = Math.floor((seconds % 3600) / 60);
@@ -241,40 +282,87 @@ function formatDuration(seconds) {
241
282
  ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
242
283
  : `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
243
284
  }
244
- function parseData(data, maxDescLength, platform) {
245
- let type = data.type || 'video';
246
- let title = data.title || data.desc || '无标题';
247
- let author = data.author || data.name || '未知作者';
248
- let desc = (data.desc || data.description || title).slice(0, maxDescLength);
249
- let cover = data.cover || data.imgurl || data.pic || data.fm || '';
250
- let images = data.images || [];
251
- let video = data.video || data.url || data.playUrl || '';
252
- let duration = data.duration || 0;
253
- let stat = data.stat || {
254
- like: 0,
255
- coin: 0,
256
- favorite: 0,
257
- share: 0,
258
- view: 0,
259
- size: 0,
260
- duration: '00:00'
261
- };
262
- const durationFormatted = formatDuration(duration);
263
- if (platform === 'bilibili' && data.data) {
264
- title = data.data.title || title;
265
- author = data.data.owner?.name || '未知作者';
266
- desc = (data.data.desc || title).slice(0, maxDescLength);
267
- cover = data.data.pic || cover;
268
- duration = data.data.duration || 0;
269
- video = data.playUrl || video;
270
- 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];
271
294
  }
272
- if (images.length > 0 && !video)
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;
323
+ }
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)) {
273
358
  type = 'image';
274
- if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
359
+ }
360
+ if (video && (video.endsWith('.m4a') || video.endsWith('.mp3'))) {
275
361
  video = '';
362
+ }
276
363
  return {
277
364
  type,
365
+ rawData: rawResponse,
278
366
  title,
279
367
  author,
280
368
  desc,
@@ -283,15 +371,7 @@ function parseData(data, maxDescLength, platform) {
283
371
  video,
284
372
  duration,
285
373
  durationFormatted,
286
- stat: {
287
- like: stat.like || 0,
288
- coin: stat.coin || 0,
289
- favorite: stat.favorite || 0,
290
- share: stat.share || 0,
291
- view: stat.view || 0,
292
- size: stat.size || 0,
293
- duration: durationFormatted
294
- }
374
+ stat
295
375
  };
296
376
  }
297
377
  function generateFormattedText(platform, parseData, config) {
@@ -299,17 +379,29 @@ function generateFormattedText(platform, parseData, config) {
299
379
  if (platform !== 'bilibili') {
300
380
  format = format.replace(/投币:\$\{投币数\}\n?/g, '');
301
381
  }
302
- return format
303
- .replace(/\${标题}/g, parseData.title)
304
- .replace(/\${作者}/g, parseData.author)
305
- .replace(/\${简介}/g, parseData.desc)
306
- .replace(/\${视频时长}/g, parseData.stat.duration)
307
- .replace(/\${点赞数}/g, parseData.stat.like)
308
- .replace(/\${投币数}/g, parseData.stat.coin)
309
- .replace(/\${收藏数}/g, parseData.stat.favorite)
310
- .replace(/\${转发数}/g, parseData.stat.share)
311
- .replace(/\${播放数}/g, parseData.stat.view)
312
- .replace(/\${视频大小}/g, parseData.stat.size);
382
+ let result = format;
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);
401
+ }
402
+ });
403
+ result = validLines.join('\n').trim();
404
+ return result;
313
405
  }
314
406
  function clearAllCache() {
315
407
  processed.clear();
@@ -364,24 +456,16 @@ function apply(ctx, config) {
364
456
  return { data: null, msg: '该平台暂未配置解析接口' };
365
457
  }
366
458
  try {
367
- const res = await http.get(apiUrl, { params: { url: realUrl } });
368
- let parseResult = null;
369
- if (['bilibili', 'douyin', 'kuaishou'].includes(platform)) {
370
- const xgData = parseXingzhigeData(res.data, platform);
371
- parseResult = parseData(xgData, config.maxDescLength, platform);
372
- }
373
- else {
374
- if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
375
- parseResult = parseData(res.data.data, config.maxDescLength, platform);
376
- }
377
- }
378
- if (parseResult) {
379
- return { data: parseResult, msg: `${platform}解析成功` };
380
- }
381
- else {
382
- logger.error(`解析返回数据无效: ${platform}, URL: ${url}`);
383
- 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 || '解析失败' };
384
466
  }
467
+ const parseResult = parseData(res.data, config.maxDescLength, platform);
468
+ return { data: parseResult, msg: `${platform}解析成功` };
385
469
  }
386
470
  catch (error) {
387
471
  logger.error(`解析请求失败: ${platform}, URL: ${url}, 错误: ${getErrorMessage(error)}`);
@@ -483,21 +567,27 @@ function apply(ctx, config) {
483
567
  }
484
568
  if (item.video && config.showVideoFile && forwardMessages.length < 100) {
485
569
  let videoElem;
486
- if (config.downloadVideoBeforeSend) {
487
- try {
570
+ try {
571
+ if (config.downloadVideoBeforeSend) {
488
572
  const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
489
- const filePath = await downloadVideo(item.video, filename, config.userAgent);
573
+ const filePath = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
490
574
  videoElem = koishi_1.h.file(filePath);
491
575
  }
492
- catch (error) {
493
- logger.error(`视频下载失败: ${getErrorMessage(error)}`);
494
- videoElem = koishi_1.h.video(item.video);
576
+ else {
577
+ const fileSize = await getFileSize(item.video, config.userAgent);
578
+ if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
579
+ videoElem = koishi_1.h.text(`视频大小${fileSize}MB超过限制${config.maxVideoSize}MB,仅发送链接:${item.video}`);
580
+ }
581
+ else {
582
+ videoElem = koishi_1.h.video(item.video);
583
+ }
495
584
  }
585
+ forwardMessages.push(buildForwardNode(session, videoElem, botName));
496
586
  }
497
- else {
498
- videoElem = koishi_1.h.video(item.video);
587
+ catch (error) {
588
+ logger.error(`视频处理失败: ${getErrorMessage(error)}`);
589
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`视频处理失败:${getErrorMessage(error)}\n链接:${item.video}`), botName));
499
590
  }
500
- forwardMessages.push(buildForwardNode(session, videoElem, botName));
501
591
  }
502
592
  }
503
593
  else {
@@ -515,22 +605,28 @@ function apply(ctx, config) {
515
605
  await delay(300);
516
606
  }
517
607
  if (item.video && config.showVideoFile) {
518
- let videoElem;
519
- if (config.downloadVideoBeforeSend) {
520
- try {
608
+ try {
609
+ let videoElem;
610
+ if (config.downloadVideoBeforeSend) {
521
611
  const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
522
- const filePath = await downloadVideo(item.video, filename, config.userAgent);
612
+ const filePath = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
523
613
  videoElem = koishi_1.h.file(filePath);
524
614
  }
525
- catch (error) {
526
- logger.error(`视频下载失败: ${getErrorMessage(error)}`);
527
- videoElem = koishi_1.h.video(item.video);
615
+ else {
616
+ const fileSize = await getFileSize(item.video, config.userAgent);
617
+ if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
618
+ videoElem = koishi_1.h.text(`视频大小${fileSize}MB超过限制${config.maxVideoSize}MB,仅发送链接:${item.video}`);
619
+ }
620
+ else {
621
+ videoElem = koishi_1.h.video(item.video);
622
+ }
528
623
  }
624
+ await sendTimeout(session, videoElem);
529
625
  }
530
- else {
531
- videoElem = koishi_1.h.video(item.video);
626
+ catch (error) {
627
+ logger.error(`视频处理失败: ${getErrorMessage(error)}`);
628
+ await sendTimeout(session, koishi_1.h.text(`视频处理失败:${getErrorMessage(error)}\n链接:${item.video}`));
532
629
  }
533
- await sendTimeout(session, videoElem);
534
630
  }
535
631
  }
536
632
  await delay(1000);
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.0",
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": [