koishi-plugin-video-parser-all 0.8.0 → 0.8.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.
package/lib/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export declare const Config: Schema<{
6
6
  showWaitingTip?: boolean | null | undefined;
7
7
  waitingTipText?: string | null | undefined;
8
8
  sameLinkInterval?: number | null | undefined;
9
+ debug?: boolean | null | undefined;
10
+ debugFile?: boolean | null | undefined;
9
11
  } & import("cosmokit").Dict & {
10
12
  unifiedMessageFormat?: string | null | undefined;
11
13
  } & {
@@ -36,6 +38,8 @@ export declare const Config: Schema<{
36
38
  showWaitingTip: boolean;
37
39
  waitingTipText: string;
38
40
  sameLinkInterval: number;
41
+ debug: boolean;
42
+ debugFile: boolean;
39
43
  } & import("cosmokit").Dict & {
40
44
  unifiedMessageFormat: string;
41
45
  } & {
package/lib/index.js CHANGED
@@ -20,18 +20,14 @@ exports.Config = koishi_1.Schema.intersect([
20
20
  showWaitingTip: koishi_1.Schema.boolean().default(true).description('解析时显示等待提示'),
21
21
  waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示文本内容'),
22
22
  sameLinkInterval: koishi_1.Schema.number().min(0).default(180).description('相同链接重复解析间隔(秒)'),
23
+ debug: koishi_1.Schema.boolean().default(false).description('开启调试模式'),
24
+ debugFile: koishi_1.Schema.boolean().default(false).description('调试日志写入文件'),
23
25
  }).description('基础设置'),
24
26
  koishi_1.Schema.object({
25
27
  unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default(`标题:${'${标题}'}
26
28
  作者:${'${作者}'}
27
- 简介:${'${简介}'}
28
- 时长:${'${视频时长}'}
29
29
  点赞:${'${点赞数}'}
30
- 投币:${'${投币数}'}
31
- 收藏:${'${收藏数}'}
32
- 转发:${'${转发数}'}
33
- 播放:${'${播放数}'}
34
- 评论:${'${评论数}'}`).description('统一消息格式'),
30
+ 链接:${'${视频链接}'}`).description('统一消息格式'),
35
31
  }).description('统一消息格式'),
36
32
  koishi_1.Schema.object({
37
33
  showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
@@ -66,49 +62,56 @@ exports.Config = koishi_1.Schema.intersect([
66
62
  const processed = new Map();
67
63
  const linkBuffer = new Map();
68
64
  const logger = new koishi_1.Logger(exports.name);
65
+ let debugEnabled = false;
66
+ let debugFileEnabled = false;
67
+ let debugStream = null;
68
+ function debugLog(level, ...args) {
69
+ if (!debugEnabled)
70
+ return;
71
+ const timestamp = new Date().toISOString();
72
+ const message = `[${timestamp}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}`;
73
+ logger.info(message);
74
+ if (debugFileEnabled && debugStream) {
75
+ debugStream.write(message + '\n');
76
+ }
77
+ }
78
+ function initDebug(enabled, fileEnabled) {
79
+ debugEnabled = enabled;
80
+ debugFileEnabled = fileEnabled;
81
+ if (fileEnabled && enabled) {
82
+ const logPath = path_1.default.join(process.cwd(), 'debug.log');
83
+ debugStream = fs_1.default.createWriteStream(logPath, { flags: 'a' });
84
+ debugStream.write(`\n=== Debug session started at ${new Date().toISOString()} ===\n`);
85
+ }
86
+ }
69
87
  const PLATFORM_KEYWORDS = {
70
- bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com', '哔哩哔哩', 'bilibili.com/opus', 'bilibili.com/video', 'b23.tv', 't.bilibili.com', 'bilibili.com/bangumi'],
71
- kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app'],
72
- weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
73
- toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video', 'ixigua.com/i'],
74
- pipigx: ['pipigx', '皮皮搞笑', 'h5.pipigx.com', 'ippzone.com', 'pipigx.com/share'],
75
- pipixia: ['pipixia', '皮皮虾', 'h5.pipix.com', 'ppxsign.byteimg.com', 'pipix.com/s', 'pipix.com/home'],
76
- douyin: ['douyin', '抖音', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com', 'douyin.com/video', 'douyin.com/note', 'www.douyin.com', 'tiktok.com'],
77
- zuiyou: ['zuiyou', '最右', 'xiaochuankeji.cn', 'izuiyou.com', 'izuiyou.com/topic']
78
- };
79
- const API_CONFIG = {
80
- bilibili: 'https://api.xingzhige.com/API/b_parse',
81
- douyin: 'https://api.xingzhige.com/API/douyin/',
82
- kuaishou: 'https://api.bugpk.com/api/ksjx',
83
- weibo: 'https://api.bugpk.com/api/weibo',
84
- toutiao: 'https://api.bugpk.com/api/toutiao',
85
- pipigx: 'https://api.bugpk.com/api/pipigx',
86
- pipixia: 'https://api.bugpk.com/api/pipixia',
87
- zuiyou: 'https://api.bugpk.com/api/zuiyou'
88
- };
89
- const VARIABLE_MAPPING = {
90
- '标题': ['title', 'note_title', 'content_title', 'item.title', 'data.title', 'video.title', 'live.title', 'data.item.title', 'data.live.title', 'data.note.title', 'data.video_info.title', 'data.aweme_info.title', 'data.video_data.title', 'data.post_title', 'data.article_title', 'data.video_title', 'data.work_title', 'data.media_title', 'data.caption'],
91
- '作者': ['author', 'author.name', 'name', 'nickname', 'user_name', 'owner.name', 'data.author', 'item.author', 'user.name', 'live.author', 'data.user.name', 'data.author.name', 'data.note.author', 'data.user', 'data.user_info', 'data.owner_info', 'data.creator', 'data.username', 'data.author_name', 'data.user_nickname', 'data.publisher', 'data.uploader'],
92
- '简介': ['desc', 'description', 'content', 'note_desc', 'text', 'data.desc', 'item.description', 'live.desc', 'data.item.description', 'data.note.desc', 'data.caption', 'data.detail', 'data.intro', 'data.introduce', 'data.summary', 'data.video_desc', 'data.post_content', 'data.article_content'],
93
- '视频时长': ['duration', 'time', 'video_duration', 'item.duration', 'stat.duration', 'data.item.duration', 'data.video_duration', 'data.video_time', 'data.play_time', 'data.length', 'data.video_length'],
94
- '点赞数': ['like', 'attitudes_count', 'digg_count', 'praise', 'stat.like', 'liked_count', 'data.like', 'data.attitudes_count', 'item.attitudes_count', 'data.item.attitudes_count', 'data.like_count', 'data.likes', 'data.praise_count', 'data.digg', 'data.favorited_count', 'data.love_count', 'data.up_count', 'data.zan'],
95
- '投币数': ['coin', 'bi', 'stat.coin', 'stast.coin', 'data.coin', 'data.coin_num', 'data.coin_count'],
96
- '收藏数': ['collect', 'favorite', 'star', 'stat.collect', 'collected_count', 'stast.favorite', 'data.favorite', 'data.collect_count', 'data.collection_count', 'data.star_count', 'data.bookmark_count', 'data.fav_count', 'data.save_count'],
97
- '转发数': ['share', 'forward', 'repost', 'stat.share', 'reposts_count', 'shared_count', 'stast.share', 'data.reposts_count', 'data.item.reposts_count', 'data.share_count', 'data.forward_count', 'data.repost_count'],
98
- '播放数': ['view', 'play_count', 'play', 'stat.view', 'play_times', 'stast.view', 'data.play_count', 'item.play_count', 'data.item.play_count', 'data.view_count', 'data.views', 'data.play_num', 'data.watch_count', 'data.video_play_count'],
99
- '评论数': ['comment', 'comments_count', 'comment_count', 'discuss', 'stat.comment', 'stast.reply', 'data.comments_count', 'item.comments_count', 'data.item.comments_count', 'stat.reply', 'data.comment_num', 'data.reply_count', 'data.review_count'],
100
- 'IP属地': ['ip', 'ip_info', 'ip_location', 'ip_info_str', 'data.ip_info_str', 'item.ip', 'item.ip_info', 'data.item.ip_info_str', 'data.user_ip', 'data.location', 'data.region', 'data.area', 'data.addr'],
101
- '发布时间': ['date', 'time', 'publish_time', 'create_time', 'ctime', 'pubdate', 'data.date', 'item.publish_time', 'live.time', 'stast.publish_time', 'stat.time', 'data.time.publish_time', 'data.live.time', 'stat.ctime', 'data.upload_time', 'data.post_time', 'data.create_date', 'data.pub_time', 'data.timestamp'],
102
- '粉丝数': ['fans', 'fans_count', 'follower', 'followers', 'follower_count', 'followers_count', 'data.followers_count', 'item.followers', 'author.fans', 'data.item.followers_count', 'data.user_fans', 'data.fan_num'],
103
- '在线人数': ['online', 'online_count', 'data.online', 'live.online', 'room.online', 'data.live.online', 'data.watching', 'data.viewer_count', 'data.audience_count'],
104
- '关注数': ['follow', 'follow_count', 'attention', 'data.attention', 'live.attention', 'stast.attention', 'data.live.attention', 'data.following_count', 'data.follow_num'],
105
- '文件大小': ['size', 'size_str', 'item.size', 'item.size_str', 'data.size', 'data.item.size_str', 'data.file_size', 'data.video_size', 'data.vid_size'],
106
- '直播间地址': ['room_url', 'live.room_url', 'data.room_url', 'live.url', 'data.live.room_url', 'data.room_link', 'data.live_url', 'data.stream_url'],
107
- '直播间ID': ['room_id', 'live.room_id', 'data.room_id', 'live.room_id', 'data.live.room_id', 'data.live_id', 'data.stream_id'],
108
- '直播间状态': ['status', 'live_status', 'live.status', 'data.status', 'room.status', 'data.live.status', 'data.stream_status', 'data.room_status'],
109
- '图片数量': ['count', 'img_count', 'image_count', 'pic_count', 'data.count', 'item.count', 'images.length', 'data.images.length', 'data.item.count', 'data.pic_num', 'data.img_num'],
110
- '作者ID': ['uid', 'userid', 'user_id', 'userId', 'userID', 'author_id', 'data.userId', 'item.userID', 'author.mid', 'user.mid', 'data.item.userID', 'data.author_id', 'data.user.mid', 'author.id', 'short_id', 'data.author.id', 'data.uid', 'data.mid', 'data.open_id', 'data.account_id']
88
+ bilibili: ['bilibili', 'b23', 'www.bilibili.com', 'm.bilibili.com', 'b23.tv', 't.bilibili.com', 'bilibili.com/video', 'bilibili.com/opus', 'bilibili.com/bangumi'],
89
+ kuaishou: ['kuaishou', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com'],
90
+ weibo: ['weibo', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
91
+ toutiao: ['toutiao', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video'],
92
+ pipigx: ['pipigx', 'h5.pipigx.com', 'ippzone.com'],
93
+ pipixia: ['pipixia', 'pipix', 'h5.pipix.com', 'ppxsign.byteimg.com', 'pipix.com'],
94
+ douyin: ['douyin', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com', 'douyin.com/video', 'douyin.com/note', 'www.douyin.com'],
95
+ zuiyou: ['zuiyou', 'xiaochuankeji.cn', 'izuiyou.com'],
96
+ xiaohongshu: ['xiaohongshu', 'xhslink.com', 'www.xiaohongshu.com'],
97
+ jianying: ['jianying', 'jimeng.jianying.com', 'lv.ulikecam.com'],
98
+ acfun: ['acfun', 'acfun.cn', 'www.acfun.cn'],
99
+ zhihu: ['zhihu', 'zhihu.com', 'www.zhihu.com'],
100
+ weishi: ['weishi', 'weishi.qq.com'],
101
+ huya: ['huya', 'huya.com', 'www.huya.com'],
102
+ youtube: ['youtube', 'youtube.com', 'youtu.be', 'www.youtube.com'],
103
+ tiktok: ['tiktok', 'tiktok.com', 'www.tiktok.com'],
104
+ xigua: ['xigua', 'ixigua.com'],
105
+ haokan: ['haokan', 'haokan.baidu.com'],
106
+ li: ['li', 'video.li'],
107
+ meipai: ['meipai', 'meipai.com'],
108
+ quanmin: ['quanmin', 'quanmin.tv'],
109
+ twitter: ['twitter', 'x.com'],
110
+ instagram: ['instagram', 'instagram.com'],
111
+ doubao: ['doubao', 'doubao.com'],
112
+ jimeng: ['jimeng', 'jimeng.ai'],
111
113
  };
114
+ const PLATFORM_TYPES = Object.keys(PLATFORM_KEYWORDS);
112
115
  function getErrorMessage(error) {
113
116
  if (error instanceof Error)
114
117
  return error.message;
@@ -118,14 +121,11 @@ async function getFileSize(url, userAgent) {
118
121
  try {
119
122
  const response = await axios_1.default.head(url, {
120
123
  timeout: 10000,
121
- headers: {
122
- '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'
123
- }
124
+ headers: { 'User-Agent': userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }
124
125
  });
125
126
  const contentLength = response.headers['content-length'];
126
- if (contentLength) {
127
+ if (contentLength)
127
128
  return Math.round(Number(contentLength) / 1024 / 1024 * 100) / 100;
128
- }
129
129
  }
130
130
  catch (error) { }
131
131
  return 0;
@@ -156,15 +156,9 @@ if (!worker_threads_1.isMainThread) {
156
156
  }).then(response => {
157
157
  const writeStream = fs_1.default.createWriteStream(filePath);
158
158
  response.data.pipe(writeStream);
159
- writeStream.on('finish', () => {
160
- worker_threads_1.parentPort?.postMessage({ success: true, filePath, start, end });
161
- });
162
- writeStream.on('error', (error) => {
163
- worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
164
- });
165
- }).catch(error => {
166
- worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
167
- });
159
+ writeStream.on('finish', () => worker_threads_1.parentPort?.postMessage({ success: true, filePath, start, end }));
160
+ writeStream.on('error', (error) => worker_threads_1.parentPort?.postMessage({ success: false, error: error.message }));
161
+ }).catch((error) => worker_threads_1.parentPort?.postMessage({ success: false, error: error.message }));
168
162
  }
169
163
  async function downloadVideo(url, filename, userAgent, maxSize, threads) {
170
164
  const dir = path_1.default.join(process.cwd(), 'temp_videos');
@@ -172,22 +166,18 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
172
166
  fs_1.default.mkdirSync(dir, { recursive: true });
173
167
  const filePath = path_1.default.join(dir, `${filename}.mp4`);
174
168
  try {
175
- if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
169
+ if (url.endsWith('.m4a') || url.endsWith('.mp3'))
176
170
  return { filePath: '', success: false };
177
- }
178
171
  const fileSize = await getFileSize(url, userAgent);
179
- if (maxSize > 0 && fileSize > maxSize) {
172
+ if (maxSize > 0 && fileSize > maxSize)
180
173
  return { filePath: '', success: false };
181
- }
182
174
  if (threads <= 0 || fileSize === 0) {
183
175
  const response = await (0, axios_1.default)({
184
176
  url,
185
177
  method: 'GET',
186
178
  responseType: 'stream',
187
179
  timeout: 60000,
188
- headers: {
189
- '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'
190
- }
180
+ headers: { 'User-Agent': userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }
191
181
  });
192
182
  const writeStream = fs_1.default.createWriteStream(filePath);
193
183
  await (0, promises_1.pipeline)(response.data, writeStream);
@@ -199,13 +189,7 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
199
189
  for (let i = 0; i < threads; i++) {
200
190
  const start = i * chunkSize;
201
191
  const end = i === threads - 1 ? totalSize - 1 : start + chunkSize - 1;
202
- promises.push(downloadVideoThread({
203
- url,
204
- start,
205
- end,
206
- filename,
207
- userAgent
208
- }));
192
+ promises.push(downloadVideoThread({ url, start, end, filename, userAgent }));
209
193
  }
210
194
  const results = await Promise.all(promises);
211
195
  const writeStream = fs_1.default.createWriteStream(filePath);
@@ -220,49 +204,29 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
220
204
  return { filePath, success: true };
221
205
  }
222
206
  catch (error) {
223
- if (fs_1.default.existsSync(filePath)) {
207
+ if (fs_1.default.existsSync(filePath))
224
208
  fs_1.default.unlinkSync(filePath);
225
- }
226
209
  const partFiles = fs_1.default.readdirSync(dir).filter(file => file.startsWith(`${filename}_`) && file.endsWith('.part'));
227
- partFiles.forEach(file => {
228
- try {
229
- fs_1.default.unlinkSync(path_1.default.join(dir, file));
230
- }
231
- catch (e) { }
232
- });
233
- logger.error(`视频下载失败: ${getErrorMessage(error)}`);
210
+ partFiles.forEach(file => { try {
211
+ fs_1.default.unlinkSync(path_1.default.join(dir, file));
212
+ }
213
+ catch (e) { } });
234
214
  return { filePath: '', success: false };
235
215
  }
236
216
  }
237
217
  function extractUrl(content) {
238
- let urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
218
+ const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
239
219
  return urlMatches.filter(url => {
240
220
  const lower = url.toLowerCase();
241
221
  return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
242
222
  });
243
223
  }
244
- function hasPlatformKeyword(content) {
245
- const lower = content.toLowerCase();
246
- return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
247
- }
248
224
  function getPlatformType(url) {
249
225
  const lower = url.toLowerCase();
250
- if (PLATFORM_KEYWORDS.bilibili.some(k => lower.includes(k)))
251
- return 'bilibili';
252
- if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k)))
253
- return 'kuaishou';
254
- if (PLATFORM_KEYWORDS.weibo.some(k => lower.includes(k)))
255
- return 'weibo';
256
- if (PLATFORM_KEYWORDS.toutiao.some(k => lower.includes(k)))
257
- return 'toutiao';
258
- if (PLATFORM_KEYWORDS.pipigx.some(k => lower.includes(k)))
259
- return 'pipigx';
260
- if (PLATFORM_KEYWORDS.pipixia.some(k => lower.includes(k)))
261
- return 'pipixia';
262
- if (PLATFORM_KEYWORDS.douyin.some(k => lower.includes(k)))
263
- return 'douyin';
264
- if (PLATFORM_KEYWORDS.zuiyou.some(k => lower.includes(k)))
265
- return 'zuiyou';
226
+ for (const [platform, keywords] of Object.entries(PLATFORM_KEYWORDS)) {
227
+ if (keywords.some(k => lower.includes(k)))
228
+ return platform;
229
+ }
266
230
  return null;
267
231
  }
268
232
  function cleanUrl(url) {
@@ -286,7 +250,7 @@ async function resolveShortUrl(url) {
286
250
  timeout: 10000,
287
251
  maxRedirects: 10,
288
252
  headers: {
289
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
253
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
290
254
  'Referer': 'https://www.baidu.com/',
291
255
  },
292
256
  validateStatus: status => true
@@ -326,23 +290,17 @@ function formatPublishTime(value) {
326
290
  const str = String(value).trim();
327
291
  if (value === 'ctime')
328
292
  return '';
329
- if (/^\d{10}$/.test(str)) {
293
+ if (/^\d{10}$/.test(str))
330
294
  value = Number(str) * 1000;
331
- }
332
295
  if (/^\d{10,}$/.test(str) && Number(str) > 1e12) {
333
- if (Number(str) > 1e15) {
296
+ if (Number(str) > 1e15)
334
297
  value = Number(str) / 1000;
335
- }
336
298
  }
337
299
  try {
338
300
  const d = new Date(/^\d+$/.test(str) ? Number(str) : str);
339
301
  if (isNaN(d.getTime()))
340
302
  return str;
341
- const y = d.getFullYear();
342
- const m = (d.getMonth() + 1).toString().padStart(2, '0');
343
- const d_ = d.getDate().toString().padStart(2, '0');
344
- const H = d.getHours().toString().padStart(2, '0');
345
- const i = d.getMinutes().toString().padStart(2, '0');
303
+ const y = d.getFullYear(), m = (d.getMonth() + 1).toString().padStart(2, '0'), d_ = d.getDate().toString().padStart(2, '0'), H = d.getHours().toString().padStart(2, '0'), i = d.getMinutes().toString().padStart(2, '0');
346
304
  const parts = [];
347
305
  if (y > 2000)
348
306
  parts.push(`${y}年`);
@@ -358,187 +316,42 @@ function formatPublishTime(value) {
358
316
  return str;
359
317
  }
360
318
  }
361
- function getNestedValue(obj, path) {
362
- if (!obj || typeof obj !== 'object' || !path)
363
- return undefined;
364
- const keys = path.split('.');
365
- let value = obj;
366
- for (const key of keys) {
367
- if (value === null || value === undefined)
368
- return undefined;
369
- value = value[key];
370
- }
371
- return value;
372
- }
373
- function findValueInObject(obj, keys) {
374
- if (!obj || typeof obj !== 'object' || !keys || keys.length === 0)
375
- return undefined;
376
- for (const key of keys) {
377
- if (key.includes('.')) {
378
- const value = getNestedValue(obj, key);
379
- if (value !== undefined && value !== null && value !== '' && value !== 0)
380
- return value;
381
- }
382
- else {
383
- if (obj[key] !== undefined && obj[key] !== null && obj[key] !== '' && obj[key] !== 0)
384
- return obj[key];
385
- const lowerKey = key.toLowerCase();
386
- for (const objKey of Object.keys(obj)) {
387
- if (objKey.toLowerCase() === lowerKey) {
388
- const val = obj[objKey];
389
- if (val !== undefined && val !== null && val !== '' && val !== 0)
390
- return val;
391
- }
392
- }
393
- }
394
- }
395
- return undefined;
396
- }
397
- function parseData(rawResponse, maxDescLength) {
398
- const root = rawResponse || {};
399
- const data = root.data || root;
400
- const stat = {};
401
- let totalImageCount = 0;
402
- if (root.msg === 'live' && data.live) {
403
- const liveData = data.live;
404
- stat['标题'] = liveData.title || '';
405
- stat['直播间地址'] = liveData.room_url || '';
406
- stat['直播间ID'] = liveData.room_id || '';
407
- stat['直播间状态'] = liveData.status === 1 ? '直播中' : (liveData.status === 0 ? '未开播' : '未知');
408
- stat['在线人数'] = liveData.online || '';
409
- stat['关注数'] = liveData.attention || '';
410
- stat['发布时间'] = formatPublishTime(liveData.time);
411
- stat['简介'] = liveData.desc || '';
412
- }
413
- Object.entries(VARIABLE_MAPPING).forEach(([varName, keys]) => {
414
- if (stat[varName] !== undefined)
415
- return;
416
- let value = findValueInObject(data, keys) || findValueInObject(root, keys);
417
- if (varName === '图片数量' && value === undefined) {
418
- let imgCount = 0;
419
- const imgSources = [
420
- data.images, data.pics, data.pic_urls, data.image_list, data.imgurl,
421
- root.images, root.pics, root.pic_urls, root.image_list, root.imgurl,
422
- data.item?.images
423
- ];
424
- for (const source of imgSources) {
425
- if (Array.isArray(source) && source.length > 0) {
426
- imgCount = source.filter(i => i && typeof i === 'string').length;
427
- break;
428
- }
429
- }
430
- totalImageCount = imgCount;
431
- const cover = data.cover || data.video?.fm || data.imgurl || data.pic || data.thumbnail || data.cover_url ||
432
- data.item?.cover || root.cover || data.live?.cover || data.live?.keyframe || '';
433
- if (cover && imgCount > 0) {
434
- imgCount = imgSources.find(source => Array.isArray(source))?.filter(i => i && typeof i === 'string' && i !== cover).length || 0;
435
- }
436
- value = totalImageCount;
437
- }
438
- if (value !== undefined && value !== null && value !== '' && value !== 0) {
439
- stat[varName] = value;
440
- }
441
- });
442
- let type = 'video';
443
- if (data.jx?.type)
444
- type = data.jx.type;
445
- else if (data.type)
446
- type = data.type;
447
- else if (root.msg === 'cv')
448
- type = 'cv';
449
- else if (root.msg === 'live')
450
- type = 'live';
451
- else if ((data.images && data.images.length > 1) || (root.images && root.images.length > 1) ||
452
- (data.imgurl && data.imgurl.length > 1) || (root.imgurl && root.imgurl.length > 1))
453
- type = '图集';
454
- const title = stat['标题'] || data.title || '无标题';
455
- let author = '';
456
- if (data.author && typeof data.author === 'object') {
457
- author = data.author.name || '';
458
- }
459
- else {
460
- author = data.author || '';
461
- }
462
- author = author || stat['作者'] || '未知作者';
463
- const rawDesc = data.desc || data.content || stat['简介'] || '暂无简介';
464
- const desc = rawDesc.slice(0, maxDescLength);
465
- const cover = data.cover || data.live?.cover || data.live?.keyframe || '';
466
- const images = Array.isArray(data.images) ? data.images : [];
467
- const video = data.url || data.video_backup || (data.live?.url && Array.isArray(data.live.url) ? data.live.url[0] : '') || '';
468
- const durationValue = data.duration || 0;
469
- const duration = typeof durationValue === 'number' ? durationValue : parseInt(durationValue) || 0;
470
- const durationFormatted = formatDuration(durationValue);
471
- const pubTime = formatPublishTime(data.create_time || data.publish_time || data.live?.time);
472
- if (pubTime)
473
- stat['发布时间'] = pubTime;
474
- if (durationFormatted !== '00:00:00')
475
- stat['视频时长'] = durationFormatted;
476
- if (stat['图片数量'] === 0)
477
- delete stat['图片数量'];
478
- const live_photo = data.live_photo || [];
479
- const h_w = data.item?.h_w || [];
480
- const quality_urls = data.quality_urls || {};
481
- const default_quality = data.default_quality || '';
482
- const download_url = video;
483
- const play_count = stat['播放数'] || '';
484
- const reposts_count = Number(stat['转发数']) || 0;
485
- const attitudes_count = Number(stat['点赞数']) || 0;
486
- const comments_count = Number(stat['评论数']) || 0;
319
+ function parseApiResponse(rawResponse) {
320
+ const data = rawResponse?.data || {};
487
321
  return {
488
- type: type,
489
- rawData: rawResponse,
490
- title: String(title),
491
- author: String(author),
492
- desc: String(desc),
493
- cover: String(cover),
494
- images,
495
- video: String(video),
496
- duration,
497
- durationFormatted,
498
- stat,
499
- totalImageCount,
500
- live_photo,
501
- h_w,
502
- jx: data.jx || null,
503
- quality_urls,
504
- default_quality,
505
- download_url,
506
- play_count,
507
- reposts_count,
508
- attitudes_count,
509
- comments_count
322
+ author: data.author || '',
323
+ uid: data.uid || '',
324
+ avatar: data.avatar || '',
325
+ like: Number(data.like) || 0,
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
510
335
  };
511
336
  }
512
- function generateFormattedText(parseData, config) {
513
- let format = config.unifiedMessageFormat || '';
514
- if (!format) {
515
- format = `标题:${'${标题}'}
516
- 作者:${'${作者}'}
517
- 简介:${'${简介}'}
518
- 时长:${'${视频时长}'}
519
- 点赞:${'${点赞数}'}
520
- 投币:${'${投币数}'}
521
- 收藏:${'${收藏数}'}
522
- 转发:${'${转发数}'}
523
- 播放:${'${播放数}'}
524
- 评论:${'${评论数}'}`;
525
- }
337
+ function generateFormattedText(info, config) {
338
+ const format = config.unifiedMessageFormat || `标题:${'${标题}'}\n作者:${'${作者}'}\n点赞:${'${点赞数}'}\n链接:${'${视频链接}'}`;
339
+ const vars = {
340
+ '标题': info.title,
341
+ '作者': info.author,
342
+ '点赞数': String(info.like),
343
+ '作者ID': info.uid,
344
+ '视频链接': info.url,
345
+ '发布时间': info.time ? formatPublishTime(info.time) : '',
346
+ '封面': info.cover,
347
+ '音乐作者': info.music?.author || '',
348
+ '音乐封面': info.music?.avatar || '',
349
+ };
526
350
  let result = format;
527
- const varMatches = result.match(/\$\{([^}]+)\}/g) || [];
528
- varMatches.forEach((varMatch) => {
529
- const varName = varMatch.replace(/\$\{|\}/g, '');
530
- const value = parseData.stat[varName];
531
- if (value === undefined || value === null || value === '') {
532
- const lines = result.split('\n');
533
- result = lines.filter((line) => !line.includes(varMatch)).join('\n');
534
- }
535
- else {
536
- result = result.replace(varMatch, String(value));
537
- }
538
- });
539
- return result.trim() || `标题:${parseData.title}
540
- 作者:${parseData.author}
541
- 简介:${parseData.desc}`;
351
+ for (const [key, value] of Object.entries(vars)) {
352
+ result = result.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), value);
353
+ }
354
+ return result.replace(/^\s*\n/gm, '').trim();
542
355
  }
543
356
  function clearAllCache() {
544
357
  processed.clear();
@@ -558,147 +371,93 @@ function clearAllCache() {
558
371
  const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
559
372
  function buildForwardNode(session, content, botName) {
560
373
  let messageContent;
561
- if (Array.isArray(content)) {
374
+ if (Array.isArray(content))
562
375
  messageContent = content;
563
- }
564
- else if (content && typeof content === 'object' && content.type) {
376
+ else if (content && typeof content === 'object' && content.type)
565
377
  messageContent = [content];
566
- }
567
- else {
378
+ else
568
379
  messageContent = [koishi_1.h.text(String(content))];
569
- }
570
- return (0, koishi_1.h)('node', {
571
- user: {
572
- nickname: botName.substring(0, 15),
573
- user_id: session.selfId
574
- }
575
- }, messageContent);
380
+ return (0, koishi_1.h)('node', { user: { nickname: botName.substring(0, 15), user_id: session.selfId } }, messageContent);
576
381
  }
577
382
  function apply(ctx, config) {
383
+ initDebug(config.debug, config.debugFile);
384
+ debugLog('INFO', '插件初始化开始');
578
385
  clearAllCache();
579
386
  const http = axios_1.default.create({
580
387
  timeout: config.timeout,
581
388
  headers: {
582
- 'User-Agent': config.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
389
+ 'User-Agent': config.userAgent,
583
390
  'Referer': 'https://www.baidu.com/',
584
391
  'Content-Type': 'application/x-www-form-urlencoded'
585
392
  }
586
393
  });
587
- async function parseWithRetry(url, platform, retryTimes) {
588
- let lastError = null;
589
- for (let i = 0; i <= retryTimes; i++) {
394
+ async function parseViaApi(url) {
395
+ for (let i = 0; i <= config.retryTimes; i++) {
590
396
  try {
591
- const params = { url };
592
- const res = await http.get(API_CONFIG[platform], {
593
- params,
397
+ const res = await http.get('https://api.bugpk.com/api/short_videos', {
398
+ params: { url },
594
399
  timeout: config.timeout
595
400
  });
596
- return res.data;
401
+ debugLog('INFO', `API响应: code=${res.data?.code}, msg=${res.data?.msg}`);
402
+ if (res.data && (res.data.code === 200 || res.data.code === 0)) {
403
+ return parseApiResponse(res.data);
404
+ }
405
+ throw new Error(res.data?.msg || '解析失败');
597
406
  }
598
407
  catch (error) {
599
- lastError = error;
600
- if (i < retryTimes) {
408
+ debugLog('ERROR', `第${i + 1}次请求失败: ${getErrorMessage(error)}`);
409
+ if (i < config.retryTimes)
601
410
  await delay(config.retryInterval * (i + 1));
602
- }
603
411
  }
604
412
  }
605
- throw lastError;
413
+ throw new Error('API请求全部失败');
606
414
  }
607
- async function parse(url) {
415
+ async function parseUrl(url) {
416
+ debugLog('INFO', `解析链接: ${url}`);
608
417
  let realUrl = await resolveShortUrl(url);
609
418
  realUrl = cleanUrl(realUrl);
419
+ debugLog('DEBUG', `实际URL: ${realUrl}`);
610
420
  const platform = getPlatformType(realUrl);
611
421
  if (!platform) {
612
- logger.error(`不支持的平台链接: ${url}`);
613
- return { data: null, success: false, msg: '不支持该平台链接' };
614
- }
615
- const apiUrl = API_CONFIG[platform];
616
- if (!apiUrl) {
617
- logger.error(`该平台暂未配置解析接口: ${platform}`);
618
- return { data: null, success: false, msg: '该平台暂未配置解析接口' };
422
+ debugLog('WARN', `不支持的平台: ${realUrl}`);
423
+ return { info: null, success: false, msg: '不支持该平台链接' };
619
424
  }
620
425
  try {
621
- const resData = await parseWithRetry(realUrl, platform, config.retryTimes);
622
- if (!resData || Object.keys(resData).length === 0) {
623
- logger.error(`API返回空数据: ${url}`);
624
- return { data: null, success: false, msg: '解析失败,API返回空数据' };
625
- }
626
- const isSuccess = resData.code === 0 || resData.code === 200 || resData.code === 1 ||
627
- (resData.msg && (resData.msg.includes('解析成功') || resData.msg.includes('success') || resData.msg.includes('请求成功') || resData.msg === 'video' || resData.msg === 'cv' || resData.msg === 'live')) ||
628
- !!resData.data || !!resData.result || !!resData.video || !!resData.images || !!resData.imgurl;
629
- if (!isSuccess) {
630
- const apiErrorMsg = resData.msg || resData.error || '解析失败';
631
- logger.error(`API返回错误: ${url} - ${apiErrorMsg}`);
632
- return { data: null, success: false, msg: `解析失败: ${apiErrorMsg}` };
633
- }
634
- try {
635
- const parseResult = parseData(resData, config.maxDescLength);
636
- const isAllDefault = parseResult.title === '无标题' &&
637
- parseResult.author === '未知作者' &&
638
- parseResult.desc === '暂无简介';
639
- if (isAllDefault) {
640
- logger.warn(`解析结果均为默认值(可能暂不支持该链接): ${url}`);
641
- return {
642
- data: null,
643
- success: false,
644
- msg: '解析失败: 暂不支持解析该链接'
645
- };
646
- }
647
- logger.info(`解析成功: ${url}`);
648
- return {
649
- data: parseResult,
650
- success: true,
651
- msg: '解析成功'
652
- };
653
- }
654
- catch (parseError) {
655
- const errorMsg = getErrorMessage(parseError);
656
- logger.error(`解析数据失败: ${url} - ${errorMsg}`);
657
- return { data: null, success: false, msg: `解析数据失败: ${errorMsg}` };
658
- }
426
+ const info = await parseViaApi(realUrl);
427
+ debugLog('INFO', `解析成功: ${info.title}`);
428
+ return { info, success: true, msg: '解析成功' };
659
429
  }
660
430
  catch (error) {
661
- const errorMsg = getErrorMessage(error);
662
- let msg = '未知错误';
663
- if (errorMsg.includes('timeout')) {
664
- msg = '请求超时';
665
- }
666
- else if (errorMsg.includes('Network') || errorMsg.includes('network') || errorMsg.includes('404') || errorMsg.includes('500')) {
667
- msg = '网络请求失败';
668
- }
669
- logger.error(`解析请求失败: ${url} - ${errorMsg}`);
670
- return { data: null, success: false, msg };
431
+ debugLog('ERROR', `解析失败: ${getErrorMessage(error)}`);
432
+ return { info: null, success: false, msg: getErrorMessage(error) };
671
433
  }
672
434
  }
673
435
  async function processSingleUrl(session, url) {
674
436
  const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
675
437
  const now = Date.now();
676
- if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000) {
677
- logger.warn(`相同链接重复解析: ${url}`);
678
- return { data: null, success: false, msg: '请勿重复解析相同链接' };
438
+ const last = processed.get(hash);
439
+ if (last && (now - last) < config.sameLinkInterval * 1000) {
440
+ debugLog('WARN', `重复解析: ${url}`);
441
+ return { success: false, msg: '请勿重复解析相同链接' };
679
442
  }
680
443
  processed.set(hash, now);
681
- const result = await parse(url);
682
- if (!result.success)
683
- return { data: null, success: false, msg: result.msg };
684
- const parseData = result.data;
685
- const text = generateFormattedText(parseData, config);
444
+ const result = await parseUrl(url);
445
+ if (!result.info)
446
+ return { success: false, msg: result.msg };
447
+ const text = generateFormattedText(result.info, config);
686
448
  return {
449
+ success: true,
687
450
  data: {
688
451
  text,
689
- cover: parseData.cover,
690
- images: parseData.images,
691
- video: parseData.video,
692
- type: parseData.type,
693
- totalImageCount: parseData.totalImageCount,
694
- live_photo: parseData.live_photo,
695
- h_w: parseData.h_w,
696
- quality_urls: parseData.quality_urls,
697
- default_quality: parseData.default_quality,
698
- download_url: parseData.download_url
699
- },
700
- success: true,
701
- msg: '处理成功'
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
+ }
702
461
  };
703
462
  }
704
463
  async function sendWithTimeout(session, content) {
@@ -707,22 +466,18 @@ function apply(ctx, config) {
707
466
  return await session.send(content);
708
467
  }
709
468
  catch (err) {
710
- const errorMsg = getErrorMessage(err);
711
- logger.error(`发送消息失败: ${errorMsg}`);
712
469
  if (!config.ignoreSendError)
713
470
  throw err;
714
471
  return null;
715
472
  }
716
473
  }
717
474
  try {
718
- const timeoutPromise = new Promise((_, reject) => {
719
- setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout);
720
- });
721
- return await Promise.race([session.send(content), timeoutPromise]);
475
+ return await Promise.race([
476
+ session.send(content),
477
+ new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout))
478
+ ]);
722
479
  }
723
480
  catch (err) {
724
- const errorMsg = getErrorMessage(err);
725
- logger.error(`发送消息超时或失败: ${errorMsg}`);
726
481
  if (!config.ignoreSendError)
727
482
  throw err;
728
483
  return null;
@@ -740,64 +495,39 @@ function apply(ctx, config) {
740
495
  const errors = [];
741
496
  for (const url of urls) {
742
497
  const result = await processSingleUrl(session, url);
743
- if (result.success && result.data) {
498
+ if (result.success)
744
499
  items.push(result.data);
745
- }
746
- else {
500
+ else
747
501
  errors.push({ url, msg: result.msg });
748
- }
749
502
  }
750
503
  if (errors.length > 0) {
751
- const errorLines = errors.map(err => `【${err.url.slice(0, 50)}${err.url.length > 50 ? '...' : ''}】: ${err.msg}`);
752
- const errorMsg = `❌ 解析失败:\n${errorLines.join('\n')}`;
753
- try {
754
- await sendWithTimeout(session, errorMsg);
755
- }
756
- catch (e) {
757
- logger.error(`发送错误消息失败: ${getErrorMessage(e)}`);
758
- }
504
+ const errorMsg = `❌ 解析失败:\n${errors.map((e) => `【${e.url.slice(0, 50)}...】: ${e.msg}`).join('\n')}`;
505
+ await sendWithTimeout(session, errorMsg).catch(() => { });
759
506
  await delay(500);
760
507
  }
761
- if (items.length === 0) {
508
+ if (items.length === 0)
762
509
  return;
763
- }
764
510
  const enableForward = config.enableForward && session.platform === 'onebot';
765
- const forwardMessages = [];
766
511
  const botName = config.botName || '视频解析机器人';
512
+ const forwardMessages = [];
767
513
  for (const item of items) {
768
514
  try {
769
515
  if (enableForward) {
770
516
  if (item.text)
771
517
  forwardMessages.push(buildForwardNode(session, item.text, botName));
772
- if (item.cover && item.type !== '图集') {
518
+ if (item.cover)
773
519
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.cover), botName));
774
- }
775
520
  if (item.video && config.showVideoFile) {
776
- try {
777
- if (config.downloadVideoBeforeSend) {
778
- const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
779
- const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
780
- if (dl.success && dl.filePath) {
781
- forwardMessages.push(buildForwardNode(session, koishi_1.h.file(dl.filePath), botName));
782
- }
783
- else {
784
- forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
785
- }
786
- }
787
- else {
521
+ if (config.downloadVideoBeforeSend) {
522
+ const fname = crypto_1.default.createHash('md5').update(item.video).digest('hex');
523
+ const dl = await downloadVideo(item.video, fname, config.userAgent, config.maxVideoSize, config.downloadThreads);
524
+ if (dl.success)
525
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.file(dl.filePath), botName));
526
+ else
788
527
  forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
789
- }
790
528
  }
791
- catch (e) {
792
- logger.error(`处理视频发送失败: ${getErrorMessage(e)}`);
529
+ else
793
530
  forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
794
- }
795
- }
796
- if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
797
- forwardMessages.push(buildForwardNode(session, `📸 图集内容(共${item.totalImageCount}张)`, botName));
798
- for (const img of item.images) {
799
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(img), botName));
800
- }
801
531
  }
802
532
  }
803
533
  else {
@@ -805,80 +535,44 @@ function apply(ctx, config) {
805
535
  await sendWithTimeout(session, item.text);
806
536
  await delay(300);
807
537
  }
808
- if (item.cover && item.type !== '图集') {
809
- try {
810
- await sendWithTimeout(session, koishi_1.h.image(item.cover));
811
- }
812
- catch (e) {
813
- logger.error(`发送封面失败: ${getErrorMessage(e)}`);
814
- }
538
+ if (item.cover) {
539
+ await sendWithTimeout(session, koishi_1.h.image(item.cover)).catch(() => { });
815
540
  await delay(300);
816
541
  }
817
542
  if (item.video && config.showVideoFile) {
818
543
  try {
819
544
  if (config.downloadVideoBeforeSend) {
820
- const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
821
- const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
822
- if (dl.success && dl.filePath) {
823
- await sendWithTimeout(session, koishi_1.h.file(dl.filePath));
824
- }
825
- else {
826
- await sendWithTimeout(session, koishi_1.h.video(item.video));
827
- }
545
+ const fname = crypto_1.default.createHash('md5').update(item.video).digest('hex');
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));
828
548
  }
829
549
  else {
830
550
  await sendWithTimeout(session, koishi_1.h.video(item.video));
831
551
  }
832
552
  }
833
- catch (e) {
834
- logger.error(`发送视频失败: ${getErrorMessage(e)}`);
553
+ catch {
835
554
  try {
836
555
  await sendWithTimeout(session, koishi_1.h.video(item.video));
837
556
  }
838
- catch (e2) {
839
- logger.error(`发送视频链接也失败: ${getErrorMessage(e2)}`);
840
- }
557
+ catch { }
841
558
  }
842
559
  await delay(500);
843
560
  }
844
- if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
845
- try {
846
- await sendWithTimeout(session, `📸 图集内容(共${item.totalImageCount}张)`);
847
- }
848
- catch (e) {
849
- logger.error(`发送图集提示失败: ${getErrorMessage(e)}`);
850
- }
851
- await delay(300);
852
- for (const img of item.images) {
853
- try {
854
- await sendWithTimeout(session, koishi_1.h.image(img));
855
- }
856
- catch (e) {
857
- logger.error(`发送图集图片失败: ${getErrorMessage(e)}`);
858
- }
859
- await delay(200);
860
- }
861
- }
862
561
  }
863
562
  }
864
- catch (e) {
865
- logger.error(`处理消息发送失败: ${getErrorMessage(e)}`);
866
- }
563
+ catch (e) { }
867
564
  }
868
565
  if (enableForward && forwardMessages.length) {
869
566
  try {
870
567
  await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
871
568
  }
872
- catch (e) {
873
- logger.error(`合并转发失败,降级为逐条发送: ${getErrorMessage(e)}`);
569
+ catch {
874
570
  for (const node of forwardMessages) {
875
571
  try {
876
572
  await sendWithTimeout(session, node.data.content);
877
573
  await delay(300);
878
574
  }
879
- catch (e2) {
880
- logger.error(`降级发送失败: ${getErrorMessage(e2)}`);
881
- }
575
+ catch { }
882
576
  }
883
577
  }
884
578
  }
@@ -894,9 +588,7 @@ function apply(ctx, config) {
894
588
  try {
895
589
  await sendWithTimeout(session, config.waitingTipText);
896
590
  }
897
- catch (e) {
898
- logger.error(`发送等待提示失败: ${getErrorMessage(e)}`);
899
- }
591
+ catch { }
900
592
  }
901
593
  await flush(session, urls);
902
594
  });
@@ -914,13 +606,14 @@ function apply(ctx, config) {
914
606
  });
915
607
  setInterval(() => {
916
608
  const now = Date.now();
917
- processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
609
+ processed.forEach((t, h) => { if (now - t > 86400000)
610
+ processed.delete(h); });
918
611
  }, 3600000);
919
612
  if (config.autoClearCacheInterval > 0) {
920
613
  setInterval(() => {
921
614
  clearAllCache();
922
- logger.info('自动清理缓存完成');
615
+ debugLog('INFO', '自动清理缓存');
923
616
  }, config.autoClearCacheInterval * 60 * 1000);
924
617
  }
925
- logger.info('视频解析插件已启动');
618
+ debugLog('INFO', '插件初始化完成');
926
619
  }
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.8.0",
3
+ "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
4
+ "version": "0.8.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -23,7 +23,6 @@
23
23
  "card",
24
24
  "miniprogram",
25
25
  "bilibili",
26
- "bv",
27
26
  "douyin",
28
27
  "kuaishou",
29
28
  "weibo",
@@ -32,6 +31,23 @@
32
31
  "pipixia",
33
32
  "xigua",
34
33
  "zuiyou",
34
+ "xiaohongshu",
35
+ "jianying",
36
+ "acfun",
37
+ "zhihu",
38
+ "weishi",
39
+ "huya",
40
+ "youtube",
41
+ "tiktok",
42
+ "haokan",
43
+ "meipai",
44
+ "quanmin",
45
+ "twitter",
46
+ "instagram",
47
+ "doubao",
48
+ "jimeng",
49
+ "debug",
50
+ "unified-api",
35
51
  "视频解析"
36
52
  ],
37
53
  "devDependencies": {
package/readme.md CHANGED
@@ -3,18 +3,22 @@
3
3
  ## 项目介绍 (Project Introduction)
4
4
 
5
5
  ### 中文
6
- 这是一个为 Koishi 机器人框架开发的**多平台视频/图集解析插件**,支持自动识别并解析抖音、快手、B站、微博、今日头条、皮皮搞笑、皮皮虾、最右等主流平台的短视频/图集链接。核心特性:
7
- - 🚀 自动识别多平台链接,无需手动指定平台
8
- - 🎨 自定义解析结果格式,支持丰富的变量替换
9
- - 内置防重复解析、接口重试、自动缓存清理等实用功能
10
- - 📤 支持 OneBot 平台消息合并转发,优化展示体验
6
+ 这是一个为 Koishi 机器人框架开发的**全平台视频/图集解析插件**,使用统一API接口,支持自动识别并解析抖音、快手、B站、小红书、微博、YouTube、TikTok、剪映、AcFun、知乎、虎牙等20+主流平台的短视频/图集链接。核心特性:
7
+ - 🌐 统一API解析,覆盖20+热门平台,无需繁琐配置
8
+ - 🤖 自动识别链接来源,即丢即用
9
+ - 🎨 完全自定义的解析结果格式,支持20+丰富变量替换
10
+ - 🐛 内置Debug调试模式,可详细记录所有操作与API交互日志
11
+ - ⚡ 防重复解析、API重试、本地视频下载、多线程加速等实用功能
12
+ - 📤 支持OneBot平台消息合并转发,优化多图文展示体验
11
13
 
12
14
  ### English
13
- This is a **multi-platform video/image parsing plugin** developed for the Koishi bot framework, supporting automatic recognition and parsing of short video/image links from mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, Toutiao, Pipi Funny, Pipi Shrimp, and Zuiyou. Core features:
14
- - 🚀 Automatically recognizes multi-platform links without manual platform specification
15
- - 🎨 Customizable parsing result format with rich variable substitution support
16
- - Built-in duplicate parsing prevention, API retry logic, and automatic cache cleanup
17
- - 📤 Support OneBot platform message forwarding for better display experience
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
+ - 🌐 Unified API parsing, covering 20+ popular platforms without complex configuration
17
+ - 🤖 Auto-detection of link sources, just drop & go
18
+ - 🎨 Fully customizable parsing result format with 20+ variable substitutions
19
+ - 🐛 Built-in Debug mode, recording detailed operations and API interaction logs
20
+ - ⚡ Duplicate parsing prevention, API retry, local video download, multithread acceleration
21
+ - 📤 Support OneBot message forwarding for better image/video display
18
22
 
19
23
  ## 项目仓库 (Repository)
20
24
  - GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
@@ -37,11 +41,13 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
37
41
  | `showWaitingTip` | boolean | true | 解析时显示等待提示 |
38
42
  | `waitingTipText` | string | 正在解析视频,请稍候... | 等待提示文本内容 |
39
43
  | `sameLinkInterval` | number | 180 | 相同链接重复解析间隔(秒),防止频繁解析 |
44
+ | `debug` | boolean | false | 是否开启Debug调试模式,控制台输出详细日志 |
45
+ | `debugFile` | boolean | false | 开启Debug时将日志同时写入本地`debug.log`文件 |
40
46
 
41
47
  ### 统一消息格式
42
48
  | 配置项 | 类型 | 默认值 | 说明 |
43
49
  |--------|------|--------|------|
44
- | `unifiedMessageFormat` | string | 详见下方变量说明 | 自定义解析结果的输出格式,支持变量替换 |
50
+ | `unifiedMessageFormat` | string | 见变量说明 | 自定义解析结果的输出格式,支持变量替换 |
45
51
 
46
52
  ### 内容显示设置
47
53
  | 配置项 | 类型 | 默认值 | 说明 |
@@ -58,7 +64,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
58
64
  | 配置项 | 类型 | 默认值 | 说明 |
59
65
  |--------|------|--------|------|
60
66
  | `timeout` | number | 180000 | API请求超时时间(毫秒) |
61
- | `videoSendTimeout` | number | 0 | 视频消息发送超时时间(毫秒,0为不限制) |
67
+ | `videoSendTimeout` | number | 60000 | 视频消息发送超时时间(毫秒,0为不限制) |
62
68
  | `userAgent` | string | Chrome 124 UA | API请求使用的User-Agent标识 |
63
69
 
64
70
  ### 错误与重试设置
@@ -72,7 +78,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
72
78
  | 配置项 | 类型 | 默认值 | 说明 |
73
79
  |--------|------|--------|------|
74
80
  | `enableForward` | boolean | false | 启用合并转发功能(仅OneBot平台) |
75
- | `downloadVideoBeforeSend` | boolean | false | 发送前先下载视频到本地(再发送文件) |
81
+ | `downloadVideoBeforeSend` | boolean | false | 发送前先下载视频到本地并发送文件 |
76
82
  | `maxVideoSize` | number | 0 | 最大视频下载大小限制(MB,0为不限制) |
77
83
  | `downloadThreads` | number | 0 | 多线程下载线程数(0为单线程,最大10) |
78
84
 
@@ -96,7 +102,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
96
102
  | `${简介}` | 内容简介/描述 | 部分平台 |
97
103
  | `${视频时长}` | 视频时长 | 部分平台 |
98
104
  | `${点赞数}` | 点赞数量 | 所有平台 |
99
- | `${投币数}` | 投币数量 | 部分平台 |
105
+ | `${投币数}` | 投币数量 | 部分平台 (B站) |
100
106
  | `${收藏数}` | 收藏数量 | 所有平台 |
101
107
  | `${转发数}` | 转发/分享数量 | 所有平台 |
102
108
  | `${播放数}` | 播放量 | 部分平台 |
@@ -116,14 +122,31 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
116
122
  ## 支持的平台 (Supported Platforms)
117
123
  | 平台名称 | 关键词识别 | 解析能力 |
118
124
  |----------|------------|----------|
119
- | 哔哩哔哩 (B站) | bilibilib23、B站 | 视频、直播 |
120
- | 抖音 | douyinv.douyin.com | 短视频、图集 |
121
- | 快手 | kuaishouv.kuaishou.com | 短视频、图集 |
122
- | 微博 | weibo、video.weibo.com | 视频、图集 |
123
- | 今日头条 | toutiao、ixigua.com | 短视频 |
124
- | 皮皮搞笑 | pipigx、h5.pipigx.com | 短视频 |
125
- | 皮皮虾 | pipixia、h5.pipix.com | 短视频 |
126
- | 最右 | zuiyou、xiaochuankeji.cn | 短视频 |
125
+ | 哔哩哔哩 (B站) | bilibili, b23.tv, bilibili.com | 视频、直播、图文 |
126
+ | 抖音 | douyin, v.douyin.com | 短视频、图集 |
127
+ | 快手 | kuaishou, v.kuaishou.com | 短视频、图集 |
128
+ | 小红书 | xiaohongshu, xhslink.com | 图文、视频 |
129
+ | 微博 | weibo, video.weibo.com | 视频、图集 |
130
+ | 剪映 / 即梦 | jianying, jimeng.jianying.com | 视频模板 |
131
+ | 今日头条 / 西瓜视频 | toutiao, ixigua.com | 短视频 |
132
+ | AcFun (A站) | acfun, acfun.cn | 视频 |
133
+ | 知乎 | zhihu, zhihu.com | 视频、回答 |
134
+ | 微视 | weishi, weishi.qq.com | 短视频 |
135
+ | 虎牙 | huya, huya.com | 直播、视频 |
136
+ | YouTube (油管) | youtube, youtu.be | 视频 |
137
+ | TikTok (国际版抖音) | tiktok, tiktok.com | 短视频 |
138
+ | 好看视频 | haokan, haokan.baidu.com | 短视频 |
139
+ | 梨视频 | li (video.li) | 短视频 |
140
+ | 美拍 | meipai, meipai.com | 短视频 |
141
+ | 全民直播 | quanmin (quanmin.tv) | 直播 |
142
+ | Twitter / X | twitter, x.com | 视频、图文 |
143
+ | Instagram | instagram, instagram.com | 图文、Reels |
144
+ | 豆包 | doubao (doubao.com) | 视频 |
145
+ | 皮皮搞笑 | pipigx, h5.pipigx.com | 短视频 |
146
+ | 皮皮虾 | pipixia, h5.pipix.com | 短视频 |
147
+ | 最右 | zuiyou, xiaochuankeji.cn | 短视频 |
148
+
149
+ > 注:部分平台解析能力可能因API限制有所差异,具体以实际解析结果为准。
127
150
 
128
151
  ## 项目贡献者 (Contributors)
129
152
 
@@ -131,9 +154,9 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
131
154
  |----------------------|-------------------------|
132
155
  | Minecraft-1314 | 插件完整开发 (Complete plugin development) |
133
156
  | JH-Ahua | BugPk-Api 支持 |
134
- | 星之阁API | 星之阁API 支持 |
135
157
  | shangxue | 灵感来源 |
136
- | (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
158
+
159
+ (欢迎通过 Issues 或 PR 加入贡献者列表)
137
160
 
138
161
  ## 许可协议 (License)
139
162