koishi-plugin-video-parser-all 0.7.8 → 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('显示图文内容'),
@@ -42,7 +38,7 @@ exports.Config = koishi_1.Schema.intersect([
42
38
  }).description('内容长度限制'),
43
39
  koishi_1.Schema.object({
44
40
  timeout: koishi_1.Schema.number().min(0).default(180000).description('API请求超时时间(毫秒)'),
45
- videoSendTimeout: koishi_1.Schema.number().min(0).default(0).description('视频发送超时时间(毫秒,0为不限制)'),
41
+ videoSendTimeout: koishi_1.Schema.number().min(0).default(60000).description('视频发送超时时间(毫秒,0为不限制)'),
46
42
  userAgent: koishi_1.Schema.string().default('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36').description('请求UA标识'),
47
43
  }).description('网络与API设置'),
48
44
  koishi_1.Schema.object({
@@ -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,170 +371,117 @@ 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
- // 【关键修改1】控制台日志改为更精准的提示
641
- logger.warn(`解析结果均为默认值(可能暂不支持该链接): ${url}`);
642
- return {
643
- data: null,
644
- success: false,
645
- msg: '解析失败: 暂不支持解析该链接'
646
- };
647
- }
648
- logger.info(`解析成功: ${url}`);
649
- return {
650
- data: parseResult,
651
- success: true,
652
- msg: '解析成功'
653
- };
654
- }
655
- catch (parseError) {
656
- const errorMsg = getErrorMessage(parseError);
657
- logger.error(`解析数据失败: ${url} - ${errorMsg}`);
658
- return { data: null, success: false, msg: `解析数据失败: ${errorMsg}` };
659
- }
426
+ const info = await parseViaApi(realUrl);
427
+ debugLog('INFO', `解析成功: ${info.title}`);
428
+ return { info, success: true, msg: '解析成功' };
660
429
  }
661
430
  catch (error) {
662
- const errorMsg = getErrorMessage(error);
663
- let msg = '未知错误';
664
- if (errorMsg.includes('timeout')) {
665
- msg = '请求超时';
666
- }
667
- else if (errorMsg.includes('Network') || errorMsg.includes('network') || errorMsg.includes('404') || errorMsg.includes('500')) {
668
- msg = '网络请求失败';
669
- }
670
- logger.error(`解析请求失败: ${url} - ${errorMsg}`);
671
- return { data: null, success: false, msg };
431
+ debugLog('ERROR', `解析失败: ${getErrorMessage(error)}`);
432
+ return { info: null, success: false, msg: getErrorMessage(error) };
672
433
  }
673
434
  }
674
435
  async function processSingleUrl(session, url) {
675
436
  const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
676
437
  const now = Date.now();
677
- if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000) {
678
- logger.warn(`相同链接重复解析: ${url}`);
679
- 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: '请勿重复解析相同链接' };
680
442
  }
681
443
  processed.set(hash, now);
682
- const result = await parse(url);
683
- if (!result.success)
684
- return { data: null, success: false, msg: result.msg };
685
- const parseData = result.data;
686
- 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);
687
448
  return {
449
+ success: true,
688
450
  data: {
689
451
  text,
690
- cover: parseData.cover,
691
- images: parseData.images,
692
- video: parseData.video,
693
- type: parseData.type,
694
- totalImageCount: parseData.totalImageCount,
695
- live_photo: parseData.live_photo,
696
- h_w: parseData.h_w,
697
- quality_urls: parseData.quality_urls,
698
- default_quality: parseData.default_quality,
699
- download_url: parseData.download_url
700
- },
701
- success: true,
702
- 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
+ }
703
461
  };
704
462
  }
705
- async function sendTimeout(session, content) {
463
+ async function sendWithTimeout(session, content) {
706
464
  if (config.videoSendTimeout <= 0) {
707
- return session.send(content).catch((err) => {
708
- const errorMsg = getErrorMessage(err);
709
- logger.error(`发送消息失败: ${errorMsg}`);
465
+ try {
466
+ return await session.send(content);
467
+ }
468
+ catch (err) {
710
469
  if (!config.ignoreSendError)
711
- return null;
470
+ throw err;
712
471
  return null;
713
- });
472
+ }
714
473
  }
715
- return Promise.race([
716
- session.send(content),
717
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.videoSendTimeout))
718
- ]).catch((err) => {
719
- const errorMsg = getErrorMessage(err);
720
- logger.error(`发送消息超时: ${errorMsg}`);
474
+ try {
475
+ return await Promise.race([
476
+ session.send(content),
477
+ new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout))
478
+ ]);
479
+ }
480
+ catch (err) {
721
481
  if (!config.ignoreSendError)
722
- return null;
482
+ throw err;
723
483
  return null;
724
- });
484
+ }
725
485
  }
726
486
  async function flush(session, manualUrls) {
727
487
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
@@ -735,101 +495,84 @@ function apply(ctx, config) {
735
495
  const errors = [];
736
496
  for (const url of urls) {
737
497
  const result = await processSingleUrl(session, url);
738
- if (result.success) {
498
+ if (result.success)
739
499
  items.push(result.data);
740
- }
741
- else {
500
+ else
742
501
  errors.push({ url, msg: result.msg });
743
- }
744
502
  }
745
503
  if (errors.length > 0) {
746
- const errorLines = errors.map(err => `【${err.url.slice(0, 50)}${err.url.length > 50 ? '...' : ''}】: ${err.msg}`);
747
- const errorMsg = `❌ 解析失败:\n${errorLines.join('\n')}`;
748
- await sendTimeout(session, errorMsg);
504
+ const errorMsg = `❌ 解析失败:\n${errors.map((e) => `【${e.url.slice(0, 50)}...】: ${e.msg}`).join('\n')}`;
505
+ await sendWithTimeout(session, errorMsg).catch(() => { });
749
506
  await delay(500);
750
507
  }
751
- // 已删除⚠ 未解析到有效内容提示
752
- if (items.length === 0) {
508
+ if (items.length === 0)
753
509
  return;
754
- }
755
510
  const enableForward = config.enableForward && session.platform === 'onebot';
756
- const forwardMessages = [];
757
511
  const botName = config.botName || '视频解析机器人';
512
+ const forwardMessages = [];
758
513
  for (const item of items) {
759
514
  try {
760
515
  if (enableForward) {
761
516
  if (item.text)
762
517
  forwardMessages.push(buildForwardNode(session, item.text, botName));
763
- if (item.cover && item.type !== '图集') {
518
+ if (item.cover)
764
519
  forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.cover), botName));
765
- }
766
520
  if (item.video && config.showVideoFile) {
767
- try {
768
- if (config.downloadVideoBeforeSend) {
769
- const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
770
- const dl = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
771
- if (dl.success) {
772
- forwardMessages.push(buildForwardNode(session, koishi_1.h.file(dl.filePath), botName));
773
- }
774
- else {
775
- forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
776
- }
777
- }
778
- 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
779
527
  forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
780
- }
781
528
  }
782
- catch (e) {
529
+ else
783
530
  forwardMessages.push(buildForwardNode(session, koishi_1.h.video(item.video), botName));
784
- }
785
- }
786
- if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
787
- forwardMessages.push(buildForwardNode(session, `📸 图集内容(共${item.totalImageCount}张)`, botName));
788
- for (const img of item.images) {
789
- forwardMessages.push(buildForwardNode(session, koishi_1.h.image(img), botName));
790
- }
791
531
  }
792
532
  }
793
533
  else {
794
534
  if (item.text) {
795
- await sendTimeout(session, item.text);
535
+ await sendWithTimeout(session, item.text);
796
536
  await delay(300);
797
537
  }
798
- if (item.cover && item.type !== '图集') {
799
- await sendTimeout(session, koishi_1.h.image(item.cover));
538
+ if (item.cover) {
539
+ await sendWithTimeout(session, koishi_1.h.image(item.cover)).catch(() => { });
800
540
  await delay(300);
801
541
  }
802
542
  if (item.video && config.showVideoFile) {
803
543
  try {
804
- await sendTimeout(session, koishi_1.h.video(item.video));
544
+ if (config.downloadVideoBeforeSend) {
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));
548
+ }
549
+ else {
550
+ await sendWithTimeout(session, koishi_1.h.video(item.video));
551
+ }
805
552
  }
806
- catch (e) {
807
- await sendTimeout(session, koishi_1.h.video(item.video));
553
+ catch {
554
+ try {
555
+ await sendWithTimeout(session, koishi_1.h.video(item.video));
556
+ }
557
+ catch { }
808
558
  }
809
559
  await delay(500);
810
560
  }
811
- if ((item.type === '图集' || item.type === 'image') && item.images?.length) {
812
- await sendTimeout(session, `📸 图集内容(共${item.totalImageCount}张)`);
813
- await delay(300);
814
- for (const img of item.images) {
815
- await sendTimeout(session, koishi_1.h.image(img));
816
- await delay(200);
817
- }
818
- }
819
561
  }
820
562
  }
821
- catch (e) {
822
- logger.error(`处理消息发送失败: ${getErrorMessage(e)}`);
823
- }
563
+ catch (e) { }
824
564
  }
825
565
  if (enableForward && forwardMessages.length) {
826
566
  try {
827
- await sendTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
567
+ await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100)));
828
568
  }
829
- catch (e) {
569
+ catch {
830
570
  for (const node of forwardMessages) {
831
- await sendTimeout(session, node.data.content);
832
- await delay(300);
571
+ try {
572
+ await sendWithTimeout(session, node.data.content);
573
+ await delay(300);
574
+ }
575
+ catch { }
833
576
  }
834
577
  }
835
578
  }
@@ -841,31 +584,36 @@ function apply(ctx, config) {
841
584
  const urls = extractUrl(content);
842
585
  if (!urls.length)
843
586
  return;
844
- if (config.showWaitingTip)
845
- await sendTimeout(session, config.waitingTipText);
587
+ if (config.showWaitingTip) {
588
+ try {
589
+ await sendWithTimeout(session, config.waitingTipText);
590
+ }
591
+ catch { }
592
+ }
846
593
  await flush(session, urls);
847
594
  });
848
595
  ctx.command('parse <url>', '手动解析视频').action(async ({ session }, url) => {
849
596
  const us = extractUrl(url);
850
597
  if (!us.length) {
851
- await sendTimeout(session, '无效的视频链接');
598
+ await sendWithTimeout(session, '无效的视频链接');
852
599
  return;
853
600
  }
854
601
  await flush(session, us);
855
602
  });
856
603
  ctx.command('clear-cache', '清空缓存').action(async ({ session }) => {
857
604
  clearAllCache();
858
- await sendTimeout(session, '✅ 缓存已清空');
605
+ await sendWithTimeout(session, '✅ 缓存已清空');
859
606
  });
860
607
  setInterval(() => {
861
608
  const now = Date.now();
862
- processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
609
+ processed.forEach((t, h) => { if (now - t > 86400000)
610
+ processed.delete(h); });
863
611
  }, 3600000);
864
612
  if (config.autoClearCacheInterval > 0) {
865
613
  setInterval(() => {
866
614
  clearAllCache();
867
- logger.info('自动清理缓存完成');
615
+ debugLog('INFO', '自动清理缓存');
868
616
  }, config.autoClearCacheInterval * 60 * 1000);
869
617
  }
870
- logger.info('视频解析插件已启动');
618
+ debugLog('INFO', '插件初始化完成');
871
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.7.8",
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,7 +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
- | (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
157
+ | shangxue | 灵感来源 |
158
+
159
+ (欢迎通过 Issues 或 PR 加入贡献者列表)
135
160
 
136
161
  ## 许可协议 (License)
137
162