koishi-plugin-video-parser-all 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/index.d.ts +115 -53
  2. package/lib/index.js +821 -228
  3. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -12,48 +12,116 @@ const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const promises_1 = require("stream/promises");
14
14
  const worker_threads_1 = require("worker_threads");
15
- const currentFilePath = path_1.default.join(process.cwd(), 'src', 'index.ts');
15
+ const __filename = typeof __dirname !== 'undefined'
16
+ ? path_1.default.join(__dirname, 'index.js')
17
+ : process.cwd() + '/index.js';
16
18
  exports.name = 'video-parser-all';
17
- exports.Config = koishi_1.Schema.object({
18
- enable: koishi_1.Schema.boolean().default(true).description('【基础设置】启用插件'),
19
- showWaitingTip: koishi_1.Schema.boolean().default(true).description('【基础设置】解析时显示等待提示'),
20
- waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('【基础设置】解析等待时发送的提示文本'),
21
- sameLinkInterval: koishi_1.Schema.number().default(180).min(0).description('【基础设置】重复解析间隔:相同链接的最小解析间隔,防止重复解析(秒)'),
22
- imageParseFormat: koishi_1.Schema.string().role('textarea').default('${标题}\n${UP主}').description('【格式设置】解析结果格式:解析结果的文本格式\n支持变量:${标题} ${UP主} ${简介} ${tab}(制表符) ${~~~}(换行)'),
23
- returnContent: koishi_1.Schema.object({
24
- showImageText: koishi_1.Schema.boolean().default(true).description('【返回内容】显示文本与封面:是否显示解析后的文本和封面图'),
25
- showVideoUrl: koishi_1.Schema.boolean().default(false).description('【返回内容】显示无水印链接:是否显示无水印视频的直链'),
26
- showVideoFile: koishi_1.Schema.boolean().default(true).description('【返回内容】发送视频文件:是否发送视频文件(关闭则仅显示链接)'),
27
- }).description('【返回内容设置】控制解析结果的返回内容'),
28
- maxDescLength: koishi_1.Schema.number().default(200).description('【内容限制】简介最大长度:内容简介的最大字符长度,超出部分会被截断'),
29
- timeout: koishi_1.Schema.number().default(180000).min(0).description('【网络设置】API请求超时:API请求的超时时间(毫秒),0表示不限制'),
30
- ignoreSendError: koishi_1.Schema.boolean().default(true).description('【容错设置】忽略发送错误:忽略消息发送失败的错误,避免插件崩溃'),
31
- enableForward: koishi_1.Schema.boolean().default(false).description('【展示设置】启用合并转发:启用OneBot平台的合并转发功能,优化多内容展示'),
32
- downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('【展示设置】发送前下载视频:发送前先下载视频到本地,再发送文件(仅OneBot)'),
33
- messageBufferDelay: koishi_1.Schema.number().default(0).min(0).description('【性能设置】消息缓冲延迟:消息缓冲延迟,合并短时间内的多个解析请求(秒)'),
34
- retryTimes: koishi_1.Schema.number().default(0).min(0).description('【容错设置】接口重试次数:API解析失败时的重试次数,0表示不重试'),
35
- retryInterval: koishi_1.Schema.number().default(0).min(0).description('【容错设置】重试间隔:每次重试的间隔时间(毫秒)'),
36
- videoSendTimeout: koishi_1.Schema.number().default(0).min(0).description('【网络设置】视频发送超时:视频消息发送的超时时间(毫秒),0表示不限制'),
37
- autoClearCacheInterval: koishi_1.Schema.number().default(60).min(0).description('【缓存设置】自动清理缓存间隔:自动清理解析缓存和临时视频文件的间隔(分钟),0表示不自动清理'),
38
- });
19
+ exports.Config = koishi_1.Schema.intersect([
20
+ koishi_1.Schema.object({
21
+ enable: koishi_1.Schema.boolean().default(true),
22
+ botName: koishi_1.Schema.string().default('视频解析机器人'),
23
+ showWaitingTip: koishi_1.Schema.boolean().default(true),
24
+ waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...'),
25
+ sameLinkInterval: koishi_1.Schema.number().min(0).default(180),
26
+ maxVideoSize: koishi_1.Schema.number().min(0).default(50),
27
+ downloadThreads: koishi_1.Schema.number().min(0).default(4),
28
+ }),
29
+ koishi_1.Schema.object({
30
+ platformEnable: koishi_1.Schema.object({
31
+ bilibili: koishi_1.Schema.boolean().default(true),
32
+ douyin: koishi_1.Schema.boolean().default(true),
33
+ kuaishou: koishi_1.Schema.boolean().default(true),
34
+ xigua: koishi_1.Schema.boolean().default(true),
35
+ xiaohongshu: koishi_1.Schema.boolean().default(true),
36
+ weibo: koishi_1.Schema.boolean().default(true),
37
+ toutiao: koishi_1.Schema.boolean().default(true),
38
+ pipigx: koishi_1.Schema.boolean().default(true),
39
+ pipixia: koishi_1.Schema.boolean().default(true),
40
+ zuiyou: koishi_1.Schema.boolean().default(true),
41
+ })
42
+ }),
43
+ koishi_1.Schema.object({
44
+ platformFormat: koishi_1.Schema.object({
45
+ bilibili: koishi_1.Schema.string().role('textarea').default('标题:${标题}\nUP主:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}'),
46
+ douyin: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}'),
47
+ kuaishou: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n点赞:${点赞数}\n播放:${播放数}\n转发:${转发数}'),
48
+ xigua: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n播放:${播放数}\n点赞:${点赞数}\n视频大小:${视频大小}MB'),
49
+ xiaohongshu: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
50
+ weibo: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
51
+ toutiao: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
52
+ pipigx: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
53
+ pipixia: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
54
+ zuiyou: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}'),
55
+ })
56
+ }),
57
+ koishi_1.Schema.object({
58
+ showImageText: koishi_1.Schema.boolean().default(true),
59
+ showVideoUrl: koishi_1.Schema.boolean().default(false),
60
+ showVideoFile: koishi_1.Schema.boolean().default(true),
61
+ }),
62
+ koishi_1.Schema.object({
63
+ maxDescLength: koishi_1.Schema.number().default(200),
64
+ }),
65
+ koishi_1.Schema.object({
66
+ timeout: koishi_1.Schema.number().min(0).default(180000),
67
+ videoSendTimeout: koishi_1.Schema.number().min(0).default(0),
68
+ 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'),
69
+ bilibiliAccessKey: koishi_1.Schema.string().default(''),
70
+ }),
71
+ koishi_1.Schema.object({
72
+ ignoreSendError: koishi_1.Schema.boolean().default(true),
73
+ retryTimes: koishi_1.Schema.number().min(0).default(0),
74
+ retryInterval: koishi_1.Schema.number().min(0).default(0),
75
+ }),
76
+ koishi_1.Schema.object({
77
+ enableForward: koishi_1.Schema.boolean().default(false),
78
+ downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false),
79
+ }),
80
+ koishi_1.Schema.object({
81
+ messageBufferDelay: koishi_1.Schema.number().min(0).default(0),
82
+ }),
83
+ koishi_1.Schema.object({
84
+ autoClearCacheInterval: koishi_1.Schema.number().min(0).default(0),
85
+ }),
86
+ ]);
39
87
  if (!worker_threads_1.isMainThread) {
40
- const { url, filePath } = worker_threads_1.workerData;
88
+ const workerDataTyped = worker_threads_1.workerData;
89
+ const { url, filePath, maxSize } = workerDataTyped;
41
90
  (async () => {
42
91
  try {
43
92
  if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
44
93
  worker_threads_1.parentPort?.postMessage({ success: false, error: '不支持音频' });
45
94
  return;
46
95
  }
96
+ let downloadedSize = 0;
97
+ const maxSizeBytes = maxSize * 1024 * 1024;
47
98
  const response = await (0, axios_1.default)({
48
99
  url,
49
100
  method: 'GET',
50
101
  responseType: 'stream',
51
102
  timeout: 60000,
52
103
  headers: {
53
- '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'
104
+ 'User-Agent': workerDataTyped.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
54
105
  }
55
106
  });
56
- await (0, promises_1.pipeline)(response.data, fs_1.default.createWriteStream(filePath));
107
+ if (maxSize > 0 && response.headers['content-length']) {
108
+ const contentLength = parseInt(response.headers['content-length']);
109
+ if (contentLength > maxSizeBytes) {
110
+ worker_threads_1.parentPort?.postMessage({ success: false, error: `视频大小超过限制(${maxSize}MB)` });
111
+ return;
112
+ }
113
+ }
114
+ const writeStream = fs_1.default.createWriteStream(filePath);
115
+ response.data.on('data', (chunk) => {
116
+ downloadedSize += chunk.length;
117
+ if (maxSize > 0 && downloadedSize > maxSizeBytes) {
118
+ response.data.destroy();
119
+ writeStream.destroy();
120
+ fs_1.default.unlinkSync(filePath);
121
+ worker_threads_1.parentPort?.postMessage({ success: false, error: `视频大小超过限制(${maxSize}MB)` });
122
+ }
123
+ });
124
+ await (0, promises_1.pipeline)(response.data, writeStream);
57
125
  worker_threads_1.parentPort?.postMessage({ success: true, filePath });
58
126
  }
59
127
  catch (error) {
@@ -64,61 +132,295 @@ if (!worker_threads_1.isMainThread) {
64
132
  const processed = new Map();
65
133
  const linkBuffer = new Map();
66
134
  const PLATFORM_KEYWORDS = {
67
- bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com'],
68
- kuaishou: ['kuaishou', '快手', 'v.kuishou.com', 'www.kuishou.com', 'kwimgs.com'],
69
- xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com'],
70
- weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'svproxy.168299.xyz'],
71
- toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com'],
72
- pipigx: ['pipigx', '皮皮搞笑', 'h5.pipigx.com', 'ippzone.com'],
73
- pipixia: ['pipixia', '皮皮虾', 'h5.pipix.com', 'ppxsign.byteimg.com'],
74
- douyin: ['douyin', '抖音', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com']
135
+ bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com', '哔哩哔哩', 'bilibili.com/opus', 'bilibili.com/video', 'b23.tv', 't.bilibili.com', 'bilibili.com/bangumi'],
136
+ kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app', 'kuaishou.com/short-video'],
137
+ xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com', 'xiaohongshu.com/explore', 'xhslink.com/'],
138
+ weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
139
+ toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', '西瓜视频', 'toutiao.com/video', 'ixigua.com/i'],
140
+ pipigx: ['pipigx', '皮皮搞笑', 'h5.pipigx.com', 'ippzone.com', 'pipigx.com/share'],
141
+ pipixia: ['pipixia', '皮皮虾', 'h5.pipix.com', 'ppxsign.byteimg.com', 'pipix.com/s', 'pipix.com/home'],
142
+ douyin: ['douyin', '抖音', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com', 'douyin.com/video', 'douyin.com/note', 'www.douyin.com', 'tiktok.com'],
143
+ zuiyou: ['zuiyou', '最右', 'xiaochuankeji.cn', 'izuiyou.com', 'izuiyou.com/topic'],
144
+ xigua: ['ixigua', '西瓜视频', 'xigua.com', '699pic.com', 'ixigua.com/video', 'ixigua.com/album'],
145
+ universal_card: ['mini_program', '小程序卡片', 'lightapp', 'json', 'markdown', 'share', 'contact', 'location', 'music', 'forward', 'node', 'mface', 'file']
75
146
  };
76
147
  const API_CONFIG = {
77
148
  universal: 'https://api.bugpk.com/api/short_videos',
149
+ xingzhige: {
150
+ bilibili: { url: 'https://api.xingzhige.com/API/b_parse/', vidUrl: 'https://api.xingzhige.com/API/b_video/', bangumiUrl: 'https://api.xingzhige.com/API/b_bangumi/' },
151
+ kuaishou: { url: 'https://api.xingzhige.com/API/kuaishou/' },
152
+ douyin: { url: 'https://api.xingzhige.com/API/douyin/' },
153
+ xigua: { url: 'https://api.xingzhige.com/API/xigua/' }
154
+ },
78
155
  platform: {
79
156
  bilibili: ['https://api.bugpk.com/api/bilibili'],
80
- kuaishou: [
81
- 'https://api.bugpk.com/api/ksjx',
82
- 'https://api.bugpk.com/api/kuaishou',
83
- 'https://api.bugpk.com/api/ksimg'
84
- ],
85
- xiaohongshu: [
86
- 'https://api.bugpk.com/api/xhsjx',
87
- 'https://api.bugpk.com/api/xhsimg',
88
- 'https://api.bugpk.com/api/xhslive'
89
- ],
90
- weibo: [
91
- 'https://api.bugpk.com/api/weibo',
92
- 'https://api.bugpk.com/api/weibo_v'
93
- ],
157
+ kuaishou: ['https://api.bugpk.com/api/ksjx', 'https://api.bugpk.com/api/kuaishou', 'https://api.bugpk.com/api/ksimg', 'https://api.suyanw.cn/api/kuaishou.php'],
158
+ xiaohongshu: ['https://api.bugpk.com/api/xhsjx', 'https://api.bugpk.com/api/xhsimg', 'https://api.bugpk.com/api/xhslive'],
159
+ weibo: ['https://api.bugpk.com/api/weibo', 'https://api.bugpk.com/api/weibo_v'],
94
160
  toutiao: ['https://api.bugpk.com/api/toutiao'],
95
- pipigx: ['https://api.bugpk.com/api/pipigx'],
161
+ pipigx: ['https://api.bugpk.com/api/pipigx', 'https://api.suyanw.cn/api/pipigx.php'],
96
162
  pipixia: ['https://api.bugpk.com/api/pipixia'],
97
- douyin: [
98
- 'https://api.bugpk.com/api/douyin',
99
- 'https://api.bugpk.com/api/dyjx',
100
- 'https://api.bugpk.com/api/dylive'
101
- ]
163
+ douyin: ['https://api.bugpk.com/api/douyin', 'https://api.bugpk.com/api/dyjx', 'https://api.bugpk.com/api/dylive'],
164
+ zuiyou: ['https://api.suyanw.cn/api/zuiyou.php'],
165
+ xigua: ['https://api.bugpk.com/api/toutiao']
102
166
  }
103
167
  };
168
+ function vid_type_parse(id) {
169
+ const idRegex = [
170
+ { pattern: /av([0-9]+)/i, type: "av" },
171
+ { pattern: /bv([0-9a-zA-Z]+)/i, type: "bv" },
172
+ ];
173
+ for (const rule of idRegex) {
174
+ const match = id.match(rule.pattern);
175
+ if (match) {
176
+ return { type: rule.type, id: match[1] };
177
+ }
178
+ }
179
+ return { type: null, id: null };
180
+ }
181
+ async function fetch_bilibili_official_info(id, userAgent) {
182
+ const vid = vid_type_parse(id);
183
+ let url = '';
184
+ switch (vid.type) {
185
+ case "av":
186
+ url = `https://api.bilibili.com/x/web-interface/view?aid=${vid.id}`;
187
+ break;
188
+ case "bv":
189
+ url = `https://api.bilibili.com/x/web-interface/view?bvid=${vid.id}`;
190
+ break;
191
+ default:
192
+ return null;
193
+ }
194
+ try {
195
+ const response = await axios_1.default.get(url, {
196
+ headers: { 'User-Agent': userAgent },
197
+ timeout: 10000
198
+ });
199
+ return response.data;
200
+ }
201
+ catch (error) {
202
+ return null;
203
+ }
204
+ }
205
+ async function get_bilibili_play_url(bvid, cid, userAgent) {
206
+ try {
207
+ const playUrl = `https://api.bilibili.com/x/player/playurl?fnval=80&cid=${cid}&bvid=${bvid}`;
208
+ const playData = await axios_1.default.get(playUrl, {
209
+ headers: {
210
+ "User-Agent": userAgent,
211
+ "Referer": "https://www.bilibili.com/"
212
+ },
213
+ timeout: 10000
214
+ });
215
+ if (playData.data.code === 0 && playData.data.data && playData.data.data.dash && playData.data.data.dash.video?.[0]?.baseUrl) {
216
+ return {
217
+ url: playData.data.data.dash.video[0].baseUrl,
218
+ duration: playData.data.data.dash.duration
219
+ };
220
+ }
221
+ return null;
222
+ }
223
+ catch (error) {
224
+ return null;
225
+ }
226
+ }
227
+ function extract_bilibili_id(url) {
228
+ const bvMatch = url.match(/BV[a-zA-Z0-9]{10}/);
229
+ if (bvMatch)
230
+ return bvMatch[0];
231
+ const avMatch = url.match(/av\d+/i);
232
+ if (avMatch)
233
+ return avMatch[0];
234
+ return null;
235
+ }
236
+ function extract_bangumi_ids(url) {
237
+ const ssMatch = url.match(/ss(\d+)/);
238
+ const epMatch = url.match(/ep(\d+)/);
239
+ return {
240
+ ss_id: ssMatch ? ssMatch[1] : null,
241
+ ep_id: epMatch ? epMatch[1] : null
242
+ };
243
+ }
244
+ function parse_xingzhige_data(resData, platform) {
245
+ const result = {
246
+ title: '',
247
+ author: '未知作者',
248
+ desc: '',
249
+ cover: '',
250
+ video: '',
251
+ images: [],
252
+ stat: {},
253
+ type: 'video'
254
+ };
255
+ if (platform === 'kuaishou' && resData.jx && resData.jx.length > 0) {
256
+ const item = resData.jx[0];
257
+ result.title = item.title || '';
258
+ result.cover = item.cover || '';
259
+ result.video = item.url || item.video || '';
260
+ result.images = item.images || [];
261
+ result.stat = {
262
+ like: resData.stat?.like || 0,
263
+ comment: resData.stat?.comment || 0,
264
+ view: resData.stat?.view || 0,
265
+ share: resData.stat?.share || 0
266
+ };
267
+ result.type = result.images.length > 0 ? 'image' : 'video';
268
+ }
269
+ else if (platform === 'douyin' && resData.jx && resData.jx.length > 0) {
270
+ const item = resData.jx[0];
271
+ result.title = item.title || '';
272
+ result.cover = item.cover || '';
273
+ result.video = item.url || '';
274
+ result.images = item.images || [];
275
+ result.stat = {
276
+ like: resData.stat?.like || 0,
277
+ comment: resData.stat?.comment || 0,
278
+ collect: resData.stat?.collect || 0,
279
+ share: resData.stat?.share || 0
280
+ };
281
+ result.type = result.images.length > 0 ? 'image' : 'video';
282
+ }
283
+ else if (platform === 'bilibili') {
284
+ result.title = resData.title || '';
285
+ result.author = resData.name || '未知UP主';
286
+ result.desc = resData.desc || '';
287
+ result.cover = resData.fm || '';
288
+ result.video = resData.url || '';
289
+ result.stat = {
290
+ view: resData.view || 0,
291
+ reply: resData.reply || 0,
292
+ favorite: resData.favorite || 0,
293
+ like: resData.like || 0,
294
+ coin: resData.coin || 0,
295
+ share: resData.share || 0,
296
+ danmuku: resData.danmuku || 0,
297
+ duration: resData.duration || 0
298
+ };
299
+ }
300
+ else if (platform === 'bilibili_bangumi') {
301
+ result.title = resData.season?.[0]?.title || resData.bangumi?.[0]?.long_title || '';
302
+ result.desc = resData.season?.[0]?.evaluate || '';
303
+ result.cover = resData.season?.[0]?.cover || '';
304
+ result.video = resData.bangumi?.[0]?.url || '';
305
+ result.stat = {
306
+ duration: resData.bangumi?.[0]?.duration || 0,
307
+ size: resData.bangumi?.[0]?.byte || resData.bangumi?.[0]?.mib || 0,
308
+ like: 0,
309
+ coin: 0,
310
+ favorite: 0,
311
+ share: 0
312
+ };
313
+ }
314
+ else if (platform === 'xigua' && resData.jx && resData.jx.length > 0) {
315
+ const item = resData.jx[0];
316
+ result.title = item.title || '';
317
+ result.cover = item.cover || '';
318
+ result.video = item.url || '';
319
+ result.stat = {
320
+ like: resData.stat?.like || 0,
321
+ view: resData.stat?.view || 0,
322
+ size: item.size || 0,
323
+ duration: 0,
324
+ share: 0
325
+ };
326
+ }
327
+ return result;
328
+ }
329
+ function extractCardUrl(content) {
330
+ let realUrls = [];
331
+ try {
332
+ if (typeof content === 'string') {
333
+ let jsonData = null;
334
+ try {
335
+ jsonData = JSON.parse(content);
336
+ }
337
+ catch (e) {
338
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
339
+ if (jsonMatch)
340
+ jsonData = JSON.parse(jsonMatch[0]);
341
+ }
342
+ if (jsonData) {
343
+ if (jsonData.type === 'json' && jsonData.data?.data) {
344
+ const innerData = jsonData.data.data;
345
+ if (innerData.meta?.news?.jumpUrl)
346
+ realUrls.push(innerData.meta.news.jumpUrl);
347
+ if (innerData.jumpUrl)
348
+ realUrls.push(innerData.jumpUrl);
349
+ }
350
+ if (jsonData.type === 'miniprogram' && jsonData.data) {
351
+ if (jsonData.data.jumpUrl)
352
+ realUrls.push(jsonData.data.jumpUrl);
353
+ if (jsonData.data.pagePath) {
354
+ const appid = jsonData.data.appid || '';
355
+ if (appid === '1108291530') {
356
+ const vidMatch = jsonData.data.pagePath.match(/vid=(\w+)/);
357
+ if (vidMatch)
358
+ realUrls.push(`https://www.bilibili.com/video/${vidMatch[1]}`);
359
+ }
360
+ }
361
+ }
362
+ }
363
+ const jsonMatches = content.match(/\{[\s\S]*"url":\s*"([^"]+)"/gi) || [];
364
+ jsonMatches.forEach(match => {
365
+ const urlMatch = match.match(/"url":\s*"([^"]+)"/);
366
+ if (urlMatch && urlMatch[1])
367
+ realUrls.push(urlMatch[1]);
368
+ });
369
+ const jumpUrlMatches = content.match(/"jumpUrl":\s*"([^"]+)"/gi) || [];
370
+ jumpUrlMatches.forEach(match => {
371
+ const urlMatch = match.match(/"jumpUrl":\s*"([^"]+)"/);
372
+ if (urlMatch && urlMatch[1])
373
+ realUrls.push(urlMatch[1]);
374
+ });
375
+ const miniAppMatches = content.match(/(https?:\/\/[^\s\"\'\>]+)/gi) || [];
376
+ realUrls.push(...miniAppMatches);
377
+ }
378
+ }
379
+ catch (e) { }
380
+ return [...new Set(realUrls)];
381
+ }
382
+ function extractBVorAV(content) {
383
+ const bvMatch = content.match(/BV[a-zA-Z0-9]{10}/);
384
+ if (bvMatch)
385
+ return [bvMatch[0]];
386
+ const avMatch = content.match(/av\d+/i);
387
+ if (avMatch)
388
+ return [avMatch[0]];
389
+ return [];
390
+ }
104
391
  function extractUrl(content) {
105
- const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
392
+ let urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
393
+ const cardUrls = extractCardUrl(content);
394
+ const bvUrls = extractBVorAV(content);
395
+ urlMatches = [...urlMatches, ...cardUrls, ...bvUrls];
106
396
  return urlMatches.filter(url => {
397
+ if (url.startsWith('BV') || url.startsWith('av') || url.startsWith('AV'))
398
+ return true;
107
399
  const lower = url.toLowerCase();
108
400
  return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
109
401
  });
110
402
  }
403
+ function hasPlatformKeyword(content) {
404
+ const lower = content.toLowerCase();
405
+ if (content.match(/BV[a-zA-Z0-9]{10}/) || content.match(/av\d+/i))
406
+ return true;
407
+ return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
408
+ }
111
409
  function getPlatformType(url) {
410
+ if (url.startsWith('BV') || url.startsWith('av') || url.startsWith('AV'))
411
+ return 'bilibili';
112
412
  const lower = url.toLowerCase();
113
- if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k)))
114
- return 'kuaishou';
115
413
  if (PLATFORM_KEYWORDS.bilibili.some(k => lower.includes(k)))
116
414
  return 'bilibili';
415
+ if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k)))
416
+ return 'kuaishou';
117
417
  if (PLATFORM_KEYWORDS.xiaohongshu.some(k => lower.includes(k)))
118
418
  return 'xiaohongshu';
119
419
  if (PLATFORM_KEYWORDS.weibo.some(k => lower.includes(k)))
120
420
  return 'weibo';
121
- if (PLATFORM_KEYWORDS.toutiao.some(k => lower.includes(k)))
421
+ if (PLATFORM_KEYWORDS.xigua.some(k => lower.includes(k)))
422
+ return 'xigua';
423
+ if (PLATFORM_KEYWORDS.toutiao.some(k => lower.includes(k)) && !PLATFORM_KEYWORDS.xigua.some(k => lower.includes(k)))
122
424
  return 'toutiao';
123
425
  if (PLATFORM_KEYWORDS.pipigx.some(k => lower.includes(k)))
124
426
  return 'pipigx';
@@ -126,25 +428,47 @@ function getPlatformType(url) {
126
428
  return 'pipixia';
127
429
  if (PLATFORM_KEYWORDS.douyin.some(k => lower.includes(k)))
128
430
  return 'douyin';
431
+ if (PLATFORM_KEYWORDS.zuiyou.some(k => lower.includes(k)))
432
+ return 'zuiyou';
129
433
  return null;
130
434
  }
435
+ async function resolveShortUrl(url) {
436
+ if (url.startsWith('BV') || url.startsWith('av') || url.startsWith('AV'))
437
+ return url;
438
+ try {
439
+ const res = await axios_1.default.head(url, {
440
+ timeout: 5000,
441
+ maxRedirects: 5,
442
+ headers: {
443
+ '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'
444
+ }
445
+ });
446
+ return res.request.res?.responseUrl || url;
447
+ }
448
+ catch (e) {
449
+ return url;
450
+ }
451
+ }
131
452
  async function shortUrl(url) {
132
453
  try {
133
454
  const res = await axios_1.default.get('https://api.oick.cn/dwz/api.php', { params: { url }, timeout: 5000 });
134
455
  if (res.data.code === 200)
135
456
  return res.data.short_url;
136
457
  }
137
- catch (error) {
138
- }
458
+ catch (error) { }
139
459
  return url;
140
460
  }
141
- async function downloadVideoWithThreads(url, filename) {
461
+ async function downloadVideoWithThreads(url, filename, maxSize, threads, userAgent) {
142
462
  return new Promise((resolve, reject) => {
143
463
  const dir = path_1.default.join(process.cwd(), 'temp_videos');
144
464
  if (!fs_1.default.existsSync(dir))
145
465
  fs_1.default.mkdirSync(dir, { recursive: true });
146
466
  const filePath = path_1.default.join(dir, `${filename}.mp4`);
147
- const worker = new worker_threads_1.Worker(currentFilePath, { workerData: { url, filePath } });
467
+ const workerData = { url, filePath, maxSize, userAgent };
468
+ if (threads > 0) {
469
+ workerData.threads = threads;
470
+ }
471
+ const worker = new worker_threads_1.Worker(__filename, { workerData });
148
472
  worker.on('message', (result) => {
149
473
  if (result.success && result.filePath) {
150
474
  resolve(result.filePath);
@@ -160,53 +484,81 @@ async function downloadVideoWithThreads(url, filename) {
160
484
  });
161
485
  });
162
486
  }
487
+ function formatDuration(seconds) {
488
+ if (!seconds)
489
+ return '00:00';
490
+ const hours = Math.floor(seconds / 3600);
491
+ const minutes = Math.floor((seconds % 3600) / 60);
492
+ const secs = Math.floor(seconds % 60);
493
+ return hours > 0
494
+ ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
495
+ : `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
496
+ }
163
497
  function parseData(data, maxDescLength, platform) {
164
- const type = data.type || 'video';
165
- const title = data.title || data.desc || '无标题';
166
- let author = '';
167
- if (data.author?.name)
168
- author = data.author.name;
169
- else if (data.author)
170
- author = data.author;
171
- else if (data.auther)
172
- author = data.auther;
173
- else if (data.user?.name)
174
- author = data.user.name;
175
- else
176
- author = '未知作者';
177
- const desc = (data.desc || data.description || title).slice(0, maxDescLength);
178
- const cover = data.cover || data.imgurl || data.pic || '';
179
- let images = [];
180
- if (data.images)
181
- images = data.images;
182
- else if (data.imgurl && Array.isArray(data.imgurl))
183
- images = data.imgurl;
184
- let video = '';
185
- if (platform === 'douyin') {
186
- if (typeof data.url === 'string' && data.url.trim() && data.url.startsWith('http')) {
187
- video = data.url;
188
- }
189
- else if (Array.isArray(data.video_backup) && data.video_backup.length > 0) {
190
- video = data.video_backup[0]?.url || '';
191
- }
192
- if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
193
- video = '';
194
- }
195
- else if (platform === 'bilibili') {
196
- if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
197
- const hdVideo = data.videos.find((v) => v.title.includes('1080') || (v.url && v.url.includes('192')) || v.index === 1);
198
- video = hdVideo?.url || data.videos[0]?.url || '';
199
- }
200
- else if (data.url) {
201
- video = data.url;
202
- }
498
+ let type = data.type || 'video';
499
+ let title = data.title || data.desc || '无标题';
500
+ let author = data.author || data.name || '未知作者';
501
+ let desc = (data.desc || data.description || title).slice(0, maxDescLength);
502
+ let cover = data.cover || data.imgurl || data.pic || data.fm || '';
503
+ let images = data.images || [];
504
+ let video = data.video || data.url || data.playUrl || '';
505
+ let duration = data.duration || 0;
506
+ let stat = data.stat || {};
507
+ const durationFormatted = formatDuration(duration);
508
+ if (platform === 'bilibili' && data.data) {
509
+ title = data.data.title || title;
510
+ author = data.data.owner?.name || '未知作者';
511
+ desc = (data.data.desc || title).slice(0, maxDescLength);
512
+ cover = data.data.pic || cover;
513
+ duration = data.data.duration || 0;
514
+ video = data.playUrl || video;
515
+ stat = data.data.stat || stat;
203
516
  }
204
- else {
205
- video = data.url || data.videos?.[0]?.url || data.video_backup?.[0]?.url || '';
517
+ if (platform === 'bilibili_bangumi') {
518
+ title = data.title || title;
519
+ desc = data.desc || desc;
520
+ cover = data.cover || cover;
521
+ video = data.video || video;
522
+ duration = data.stat?.duration || duration;
206
523
  }
207
- if (video.endsWith('.m4a') || video.endsWith('.mp3'))
524
+ if (images.length > 0 && !video)
525
+ type = 'image';
526
+ if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
208
527
  video = '';
209
- return { type, title, author, desc, cover, images, video };
528
+ return {
529
+ type,
530
+ title,
531
+ author,
532
+ desc,
533
+ cover,
534
+ images,
535
+ video,
536
+ duration,
537
+ durationFormatted,
538
+ stat: {
539
+ like: stat.like || 0,
540
+ coin: stat.coin || 0,
541
+ favorite: stat.favorite || 0,
542
+ share: stat.share || 0,
543
+ view: stat.view || 0,
544
+ size: stat.size || 0,
545
+ duration: durationFormatted
546
+ }
547
+ };
548
+ }
549
+ function generateFormattedText(platform, parseData, config) {
550
+ const format = config.platformFormat[platform] || '${标题}\n${作者}';
551
+ return format
552
+ .replace(/\${标题}/g, parseData.title)
553
+ .replace(/\${作者}/g, parseData.author)
554
+ .replace(/\${简介}/g, parseData.desc)
555
+ .replace(/\${视频时长}/g, parseData.stat.duration)
556
+ .replace(/\${点赞数}/g, parseData.stat.like)
557
+ .replace(/\${投币数}/g, parseData.stat.coin)
558
+ .replace(/\${收藏数}/g, parseData.stat.favorite)
559
+ .replace(/\${转发数}/g, parseData.stat.share)
560
+ .replace(/\${播放数}/g, parseData.stat.view)
561
+ .replace(/\${视频大小}/g, parseData.stat.size);
210
562
  }
211
563
  function clearAllCache() {
212
564
  processed.clear();
@@ -218,31 +570,247 @@ function clearAllCache() {
218
570
  try {
219
571
  fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
220
572
  }
221
- catch (error) {
222
- }
573
+ catch (error) { }
223
574
  });
224
575
  }
225
576
  return true;
226
577
  }
227
578
  const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
579
+ function buildForwardNode(session, content, botName) {
580
+ return {
581
+ type: 'node',
582
+ data: {
583
+ name: botName.substring(0, 15),
584
+ uin: session.selfId.toString(),
585
+ content: Array.isArray(content) ? content : [koishi_1.h.text(content)],
586
+ time: Math.floor(Date.now() / 1000)
587
+ }
588
+ };
589
+ }
228
590
  function apply(ctx, config) {
229
591
  if (!worker_threads_1.isMainThread)
230
592
  return;
231
593
  clearAllCache();
232
594
  const http = axios_1.default.create({
233
595
  timeout: config.timeout,
234
- headers: { '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' }
596
+ headers: { 'User-Agent': config.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }
235
597
  });
236
598
  async function parse(url) {
237
- const platform = getPlatformType(url);
238
- if (!platform)
239
- return { data: null, msg: '不支持该平台链接' };
240
- if (platform !== 'toutiao') {
241
- for (let retry = 0; retry <= config.retryTimes; retry++) {
599
+ const realUrl = await resolveShortUrl(url);
600
+ const platform = getPlatformType(realUrl);
601
+ if (!platform || !config.platformEnable[platform]) {
602
+ return { data: null, msg: platform ? '该平台解析已关闭' : '不支持该平台链接' };
603
+ }
604
+ if (platform === 'bilibili') {
605
+ const bangumiIds = extract_bangumi_ids(realUrl);
606
+ if (bangumiIds.ss_id || bangumiIds.ep_id) {
607
+ try {
608
+ const params = {};
609
+ if (bangumiIds.ss_id)
610
+ params.ss_id = bangumiIds.ss_id;
611
+ if (bangumiIds.ep_id)
612
+ params.ep_id = bangumiIds.ep_id;
613
+ if (config.bilibiliAccessKey)
614
+ params.access_key = config.bilibiliAccessKey;
615
+ const res = await http({
616
+ method: 'GET',
617
+ url: API_CONFIG.xingzhige.bilibili.bangumiUrl,
618
+ params
619
+ });
620
+ if (res.data && (res.data.season || res.data.bangumi)) {
621
+ const xgData = parse_xingzhige_data(res.data, 'bilibili_bangumi');
622
+ const parseResult = parseData(xgData, config.maxDescLength, 'bilibili_bangumi');
623
+ return { data: parseResult, msg: 'B站解析成功' };
624
+ }
625
+ }
626
+ catch (error) { }
627
+ }
628
+ try {
629
+ const res = await http({
630
+ method: 'GET',
631
+ url: API_CONFIG.xingzhige.bilibili.vidUrl,
632
+ params: (() => {
633
+ const biliId = realUrl.startsWith('BV') || realUrl.startsWith('av') || realUrl.startsWith('AV') ? realUrl : extract_bilibili_id(realUrl);
634
+ if (!biliId)
635
+ return { url: realUrl };
636
+ const vid = vid_type_parse(biliId);
637
+ return vid.type === 'bv' ? { bvid: vid.id } : { aid: vid.id };
638
+ })()
639
+ });
640
+ if (res.data && (res.data.url || res.data.title)) {
641
+ const xgData = parse_xingzhige_data(res.data, platform);
642
+ const parseResult = parseData(xgData, config.maxDescLength, platform);
643
+ return { data: parseResult, msg: 'B站解析成功' };
644
+ }
645
+ }
646
+ catch (error) {
647
+ try {
648
+ const res = await http.get(API_CONFIG.universal, { params: { url: realUrl } });
649
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
650
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
651
+ return { data: parseResult, msg: 'B站解析成功' };
652
+ }
653
+ }
654
+ catch (e) { }
655
+ try {
656
+ const biliId = realUrl.startsWith('BV') || realUrl.startsWith('av') || realUrl.startsWith('AV') ? realUrl : extract_bilibili_id(realUrl);
657
+ if (biliId) {
658
+ const officialInfo = await fetch_bilibili_official_info(biliId, config.userAgent);
659
+ if (officialInfo && officialInfo.code === 0 && officialInfo.data) {
660
+ const { bvid, cid } = officialInfo.data;
661
+ const playInfo = await get_bilibili_play_url(bvid, cid, config.userAgent);
662
+ if (playInfo) {
663
+ const parseResult = parseData({
664
+ ...officialInfo,
665
+ playUrl: playInfo.url,
666
+ duration: playInfo.duration
667
+ }, config.maxDescLength, platform);
668
+ return { data: parseResult, msg: 'B站解析成功' };
669
+ }
670
+ }
671
+ }
672
+ }
673
+ catch (e) { }
674
+ const platformApis = API_CONFIG.platform.bilibili || [];
675
+ for (const apiUrl of platformApis) {
676
+ try {
677
+ const res = await http.get(apiUrl, { params: { url: realUrl } });
678
+ if ((res.data.code === 0 || res.data.code === 200) && res.data.data) {
679
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
680
+ return { data: parseResult, msg: 'B站解析成功' };
681
+ }
682
+ }
683
+ catch (error) {
684
+ continue;
685
+ }
686
+ }
687
+ }
688
+ return { data: null, msg: 'B站解析失败' };
689
+ }
690
+ if (platform === 'kuaishou') {
691
+ try {
692
+ const res = await http({
693
+ method: 'GET',
694
+ url: API_CONFIG.xingzhige.kuaishou.url,
695
+ params: { url: realUrl }
696
+ });
697
+ if (res.data && res.data.jx && res.data.jx.length > 0) {
698
+ const xgData = parse_xingzhige_data(res.data, platform);
699
+ const parseResult = parseData(xgData, config.maxDescLength, platform);
700
+ return { data: parseResult, msg: '快手解析成功' };
701
+ }
702
+ }
703
+ catch (error) {
242
704
  try {
243
- const res = await http.get(API_CONFIG.universal, { params: { url } });
705
+ const res = await http.get(API_CONFIG.universal, { params: { url: realUrl } });
244
706
  if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
245
707
  const parseResult = parseData(res.data.data, config.maxDescLength, platform);
708
+ return { data: parseResult, msg: '快手解析成功' };
709
+ }
710
+ }
711
+ catch (e) { }
712
+ const platformApis = API_CONFIG.platform.kuaishou || [];
713
+ for (const apiUrl of platformApis) {
714
+ try {
715
+ const res = await http.get(apiUrl, { params: { url: realUrl } });
716
+ if ((res.data.code === 200 || res.data.code === 0) && (res.data.data || res.data.image)) {
717
+ const parseResult = parseData(res.data.data || res.data, config.maxDescLength, platform);
718
+ return { data: parseResult, msg: '快手解析成功' };
719
+ }
720
+ }
721
+ catch (error) {
722
+ continue;
723
+ }
724
+ }
725
+ }
726
+ return { data: null, msg: '快手解析失败' };
727
+ }
728
+ if (platform === 'douyin') {
729
+ try {
730
+ const res = await http({
731
+ method: 'GET',
732
+ url: API_CONFIG.xingzhige.douyin.url,
733
+ params: { url: realUrl }
734
+ });
735
+ if (res.data && res.data.jx && res.data.jx.length > 0) {
736
+ const xgData = parse_xingzhige_data(res.data, platform);
737
+ const parseResult = parseData(xgData, config.maxDescLength, platform);
738
+ return { data: parseResult, msg: '抖音解析成功' };
739
+ }
740
+ }
741
+ catch (error) {
742
+ try {
743
+ const res = await http.get(API_CONFIG.universal, { params: { url: realUrl } });
744
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
745
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
746
+ return { data: parseResult, msg: '抖音解析成功' };
747
+ }
748
+ }
749
+ catch (e) { }
750
+ const platformApis = API_CONFIG.platform.douyin || [];
751
+ for (const apiUrl of platformApis) {
752
+ try {
753
+ const res = await http.get(apiUrl, { params: { url: realUrl } });
754
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
755
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
756
+ return { data: parseResult, msg: '抖音解析成功' };
757
+ }
758
+ }
759
+ catch (error) {
760
+ continue;
761
+ }
762
+ }
763
+ }
764
+ return { data: null, msg: '抖音解析失败' };
765
+ }
766
+ if (platform === 'xigua') {
767
+ try {
768
+ const res = await http({
769
+ method: 'GET',
770
+ url: API_CONFIG.xingzhige.xigua.url,
771
+ params: { url: realUrl }
772
+ });
773
+ if (res.data && res.data.jx && res.data.jx.length > 0) {
774
+ const xgData = parse_xingzhige_data(res.data, platform);
775
+ const parseResult = parseData(xgData, config.maxDescLength, platform);
776
+ return { data: parseResult, msg: '西瓜视频解析成功' };
777
+ }
778
+ }
779
+ catch (error) {
780
+ try {
781
+ const res = await http.get(API_CONFIG.universal, { params: { url: realUrl } });
782
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
783
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
784
+ return { data: parseResult, msg: '西瓜视频解析成功' };
785
+ }
786
+ }
787
+ catch (e) { }
788
+ const platformApis = API_CONFIG.platform.xigua || [];
789
+ for (const apiUrl of platformApis) {
790
+ try {
791
+ const res = await http.get(apiUrl, { params: { url: realUrl } });
792
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
793
+ const parseResult = parseData(res.data.data, config.maxDescLength, platform);
794
+ return { data: parseResult, msg: '西瓜视频解析成功' };
795
+ }
796
+ }
797
+ catch (error) {
798
+ continue;
799
+ }
800
+ }
801
+ }
802
+ return { data: null, msg: '西瓜视频解析失败' };
803
+ }
804
+ // 修复:确保 currentPlatform 不为 null
805
+ const currentPlatform = platform;
806
+ const nonKuaishouPlatforms = ['xiaohongshu', 'weibo', 'toutiao', 'pipigx', 'pipixia', 'zuiyou'];
807
+ if (nonKuaishouPlatforms.includes(currentPlatform)) {
808
+ for (let retry = 0; retry <= config.retryTimes; retry++) {
809
+ try {
810
+ const res = await http.get(API_CONFIG.universal, { params: { url: realUrl } });
811
+ if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
812
+ // currentPlatform 已经被断言为非 null,安全传递给 parseData
813
+ const parseResult = parseData(res.data.data, config.maxDescLength, currentPlatform);
246
814
  return { data: parseResult, msg: '解析成功' };
247
815
  }
248
816
  else if (res.data.code === 201) {
@@ -256,28 +824,44 @@ function apply(ctx, config) {
256
824
  }
257
825
  }
258
826
  }
259
- const platformApis = API_CONFIG.platform[platform] || [];
827
+ const platformApis = API_CONFIG.platform[currentPlatform] || [];
260
828
  for (let apiIndex = 0; apiIndex < platformApis.length; apiIndex++) {
261
829
  const apiUrl = platformApis[apiIndex];
262
830
  for (let retry = 0; retry <= config.retryTimes; retry++) {
263
831
  try {
264
- const res = await http.get(apiUrl, { params: { url } });
265
- if ((res.data.code === 200 || res.data.code === 0) && (res.data.data || (platform === 'kuaishou' && res.data.images))) {
832
+ const res = await http.get(apiUrl, { params: { url: realUrl } });
833
+ let shouldContinue = false;
834
+ if ((res.data.code === 200 || res.data.code === 0)) {
266
835
  let parseResult = null;
267
- if (platform === 'kuaishou' && res.data.images && !res.data.data) {
268
- parseResult = parseData({
269
- title: '快手图集',
270
- author: '未知作者',
271
- images: res.data.images,
272
- type: 'image'
273
- }, config.maxDescLength, platform);
836
+ if (currentPlatform === 'kuaishou') {
837
+ if (res.data.image && !res.data.data) {
838
+ parseResult = parseData({
839
+ title: res.data.data?.title || '快手图集',
840
+ author: res.data.data?.author || '未知作者',
841
+ images: res.data.image,
842
+ type: 'image'
843
+ }, config.maxDescLength, currentPlatform);
844
+ }
845
+ else if (res.data.data) {
846
+ parseResult = parseData(res.data.data, config.maxDescLength, currentPlatform);
847
+ }
848
+ else {
849
+ shouldContinue = true;
850
+ }
274
851
  }
275
852
  else {
276
- parseResult = parseData(res.data.data || res.data, config.maxDescLength, platform);
853
+ if (res.data.data) {
854
+ parseResult = parseData(res.data.data, config.maxDescLength, currentPlatform);
855
+ }
856
+ else {
857
+ shouldContinue = true;
858
+ }
859
+ }
860
+ if (parseResult) {
861
+ return { data: parseResult, msg: '解析成功' };
277
862
  }
278
- return { data: parseResult, msg: '解析成功' };
279
863
  }
280
- else if (retry < config.retryTimes) {
864
+ if (shouldContinue && retry < config.retryTimes) {
281
865
  await delay(config.retryInterval);
282
866
  continue;
283
867
  }
@@ -292,7 +876,7 @@ function apply(ctx, config) {
292
876
  }
293
877
  }
294
878
  }
295
- return { data: null, msg: '所有接口解析失败,请稍后重试' };
879
+ return { data: null, msg: '解析失败,请稍后重试' };
296
880
  }
297
881
  async function processSingleUrl(session, url) {
298
882
  const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
@@ -305,31 +889,29 @@ function apply(ctx, config) {
305
889
  if (!result.data)
306
890
  return { data: null, msg: result.msg };
307
891
  const parseData = result.data;
308
- let text = config.imageParseFormat
309
- .replace(/\${标题}/g, parseData.title)
310
- .replace(/\${UP主}/g, parseData.author)
311
- .replace(/\${简介}/g, parseData.desc)
312
- .replace(/\${tab}/g, '\t')
313
- .replace(/\${~~~}/g, '\n');
892
+ const platform = getPlatformType(url);
893
+ const text = generateFormattedText(platform || 'bilibili', parseData, config);
314
894
  return {
315
- data: {
316
- text,
317
- cover: parseData.cover,
318
- images: parseData.images,
319
- video: parseData.video,
320
- type: parseData.type
321
- },
895
+ data: { text, cover: parseData.cover, images: parseData.images, video: parseData.video, type: parseData.type },
322
896
  msg: 'ok'
323
897
  };
324
898
  }
325
899
  async function sendTimeout(session, content) {
326
900
  if (config.videoSendTimeout <= 0) {
327
- return session.send(content).catch(() => null);
901
+ return session.send(content).catch((err) => {
902
+ if (!config.ignoreSendError)
903
+ ctx.logger.error('发送消息失败:', err.message);
904
+ return null;
905
+ });
328
906
  }
329
907
  return Promise.race([
330
908
  session.send(content),
331
- new Promise((_, reject) => setTimeout(() => reject('timeout'), config.videoSendTimeout))
332
- ]).catch(() => null);
909
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.videoSendTimeout))
910
+ ]).catch((err) => {
911
+ if (!config.ignoreSendError)
912
+ ctx.logger.error('发送消息超时:', err.message);
913
+ return null;
914
+ });
333
915
  }
334
916
  async function flush(session, manualUrls) {
335
917
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
@@ -350,15 +932,13 @@ function apply(ctx, config) {
350
932
  errs.push(`【${url.slice(0, 22)}...】:${result.msg}`);
351
933
  }
352
934
  }
935
+ const enableForward = config.enableForward && session.platform === 'onebot';
353
936
  const forwardMessages = [];
354
- const botName = '视频解析机器人';
937
+ const botName = config.botName || '视频解析机器人';
355
938
  if (errs.length) {
356
- const errorMsg = `⚠️ 部分解析失败\n${errs.join('\n')}`;
357
- if (config.enableForward && session.platform === 'onebot') {
358
- forwardMessages.push((0, koishi_1.h)('message', [
359
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
360
- errorMsg
361
- ]));
939
+ const errorMsg = `⚠ 部分解析失败\n${errs.join('\n')}`;
940
+ if (enableForward) {
941
+ forwardMessages.push(buildForwardNode(session, errorMsg, botName));
362
942
  }
363
943
  else {
364
944
  await sendTimeout(session, errorMsg);
@@ -367,11 +947,8 @@ function apply(ctx, config) {
367
947
  }
368
948
  if (items.length === 0) {
369
949
  const failMsg = `❌ 全部解析失败\n${errs.join('\n')}`;
370
- if (config.enableForward && session.platform === 'onebot') {
371
- forwardMessages.push((0, koishi_1.h)('message', [
372
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
373
- failMsg
374
- ]));
950
+ if (enableForward) {
951
+ forwardMessages.push(buildForwardNode(session, failMsg, botName));
375
952
  }
376
953
  else {
377
954
  await sendTimeout(session, failMsg);
@@ -379,95 +956,109 @@ function apply(ctx, config) {
379
956
  return;
380
957
  }
381
958
  for (const item of items) {
382
- if (config.enableForward && session.platform === 'onebot') {
383
- forwardMessages.push((0, koishi_1.h)('message', [
384
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
385
- item.text
386
- ]));
387
- if (item.cover) {
388
- forwardMessages.push((0, koishi_1.h)('message', [
389
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
390
- koishi_1.h.image(item.cover)
391
- ]));
392
- }
393
- if (item.video && config.returnContent.showVideoFile) {
394
- let videoElem = koishi_1.h.video(item.video);
395
- if (config.downloadVideoBeforeSend) {
396
- try {
397
- const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
398
- const filePath = await downloadVideoWithThreads(item.video, filename);
399
- videoElem = koishi_1.h.file(filePath);
400
- }
401
- catch (error) {
402
- videoElem = koishi_1.h.video(item.video);
403
- }
959
+ try {
960
+ if (enableForward) {
961
+ if (item.text) {
962
+ forwardMessages.push(buildForwardNode(session, item.text, botName));
404
963
  }
405
- forwardMessages.push((0, koishi_1.h)('message', [
406
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
407
- videoElem
408
- ]));
409
- }
410
- if (item.video && config.returnContent.showVideoUrl) {
411
- const shortLink = await shortUrl(item.video);
412
- forwardMessages.push((0, koishi_1.h)('message', [
413
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
414
- `🔗 无水印:${shortLink}`
415
- ]));
416
- }
417
- if (item.type === 'image' && item.images?.length) {
418
- item.images.forEach(imgUrl => {
419
- forwardMessages.push((0, koishi_1.h)('message', [
420
- (0, koishi_1.h)('author', { id: session.selfId, name: botName }),
421
- koishi_1.h.image(imgUrl)
422
- ]));
423
- });
424
- }
425
- }
426
- else {
427
- await sendTimeout(session, item.text);
428
- await delay(300);
429
- if (item.type === 'image' && item.images?.length) {
430
- const imgMsg = (0, koishi_1.h)('message', ...item.images.map(url => koishi_1.h.image(url)));
431
- await sendTimeout(session, imgMsg);
432
- }
433
- else {
434
- if (item.cover) {
435
- await sendTimeout(session, koishi_1.h.image(item.cover));
436
- await delay(300);
964
+ if (item.cover && forwardMessages.length < 100) {
965
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.cover), botName));
966
+ }
967
+ if (item.type === 'image' && item.images?.length) {
968
+ for (let i = 0; i < item.images.length && forwardMessages.length < 100; i++) {
969
+ forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
970
+ }
437
971
  }
438
- if (item.video && config.returnContent.showVideoFile) {
439
- let videoElem = koishi_1.h.video(item.video);
972
+ if (item.video && config.showVideoFile && forwardMessages.length < 100) {
973
+ let videoElem;
440
974
  if (config.downloadVideoBeforeSend) {
441
975
  try {
442
976
  const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
443
- const filePath = await downloadVideoWithThreads(item.video, filename);
977
+ const filePath = await downloadVideoWithThreads(item.video, filename, config.maxVideoSize, config.downloadThreads, config.userAgent);
444
978
  videoElem = koishi_1.h.file(filePath);
445
979
  }
446
980
  catch (error) {
447
981
  videoElem = koishi_1.h.video(item.video);
448
982
  }
449
983
  }
450
- await sendTimeout(session, videoElem);
984
+ else {
985
+ videoElem = koishi_1.h.video(item.video);
986
+ }
987
+ forwardMessages.push(buildForwardNode(session, videoElem, botName));
451
988
  }
452
- if (item.video && config.returnContent.showVideoUrl) {
453
- await delay(300);
989
+ if (item.video && config.showVideoUrl && forwardMessages.length < 100) {
454
990
  const shortLink = await shortUrl(item.video);
455
- await sendTimeout(session, `🔗 无水印:${shortLink}`);
991
+ forwardMessages.push(buildForwardNode(session, `🔗 无水印:${shortLink}`, botName));
992
+ }
993
+ }
994
+ else {
995
+ if (item.text) {
996
+ await sendTimeout(session, item.text);
997
+ await delay(300);
998
+ }
999
+ if (item.type === 'image' && item.images?.length) {
1000
+ const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
1001
+ await sendTimeout(session, imgMsg);
1002
+ }
1003
+ else {
1004
+ if (item.cover) {
1005
+ await sendTimeout(session, koishi_1.h.image(item.cover));
1006
+ await delay(300);
1007
+ }
1008
+ if (item.video && config.showVideoFile) {
1009
+ let videoElem;
1010
+ if (config.downloadVideoBeforeSend) {
1011
+ try {
1012
+ const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
1013
+ const filePath = await downloadVideoWithThreads(item.video, filename, config.maxVideoSize, config.downloadThreads, config.userAgent);
1014
+ videoElem = koishi_1.h.file(filePath);
1015
+ }
1016
+ catch (error) {
1017
+ videoElem = koishi_1.h.video(item.video);
1018
+ }
1019
+ }
1020
+ else {
1021
+ videoElem = koishi_1.h.video(item.video);
1022
+ }
1023
+ await sendTimeout(session, videoElem);
1024
+ }
1025
+ if (item.video && config.showVideoUrl) {
1026
+ await delay(300);
1027
+ const shortLink = await shortUrl(item.video);
1028
+ await sendTimeout(session, `🔗 无水印:${shortLink}`);
1029
+ }
456
1030
  }
1031
+ await delay(1000);
457
1032
  }
458
- await delay(1000);
1033
+ }
1034
+ catch (error) {
1035
+ await sendTimeout(session, `❌ 处理${item.type}内容失败: ${error.message}`);
459
1036
  }
460
1037
  }
461
- if (config.enableForward && session.platform === 'onebot' && forwardMessages.length) {
462
- const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages);
463
- await sendTimeout(session, forwardMsg);
1038
+ if (enableForward && forwardMessages.length) {
1039
+ try {
1040
+ const safeForwardMessages = forwardMessages.slice(0, 100);
1041
+ const forwardMsg = (0, koishi_1.h)('message', { forward: true }, safeForwardMessages);
1042
+ await sendTimeout(session, forwardMsg);
1043
+ }
1044
+ catch (error) {
1045
+ for (const node of forwardMessages) {
1046
+ await sendTimeout(session, node.data.content);
1047
+ await delay(500);
1048
+ }
1049
+ }
464
1050
  }
465
1051
  }
466
1052
  ctx.on('message', async (session) => {
467
1053
  if (!config.enable)
468
1054
  return;
469
- const urls = extractUrl(session.content.trim());
470
- if (!urls.length)
1055
+ const content = session.content.trim();
1056
+ let urls = extractUrl(content);
1057
+ if (urls.length === 0 && hasPlatformKeyword(content)) {
1058
+ const allLinks = content.match(/https?:\/\/[^\s\"\'\>\]]+/gi) || [];
1059
+ urls = allLinks.filter((u) => getPlatformType(u));
1060
+ }
1061
+ if (urls.length === 0)
471
1062
  return;
472
1063
  const key = `${session.platform}:${session.userId}:${session.channelId}`;
473
1064
  if (linkBuffer.has(key)) {
@@ -495,8 +1086,12 @@ function apply(ctx, config) {
495
1086
  .action(async ({ session }, url) => {
496
1087
  if (!url)
497
1088
  return '请输入视频链接';
498
- const urls = extractUrl(url);
499
- if (!urls.length)
1089
+ let urls = extractUrl(url);
1090
+ if (urls.length === 0 && hasPlatformKeyword(url)) {
1091
+ const allLinks = url.match(/https?:\/\/[^\s\"\'\>\]]+/gi) || [];
1092
+ urls = allLinks.filter((u) => getPlatformType(u));
1093
+ }
1094
+ if (urls.length === 0)
500
1095
  return '不支持该链接';
501
1096
  await flush(session, urls);
502
1097
  });
@@ -524,14 +1119,12 @@ function apply(ctx, config) {
524
1119
  fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
525
1120
  }
526
1121
  }
527
- catch (error) {
528
- }
1122
+ catch (error) { }
529
1123
  });
530
1124
  }, 1800000);
531
1125
  if (config.autoClearCacheInterval > 0) {
532
1126
  setInterval(() => {
533
1127
  clearAllCache();
534
- ctx.logger.info('自动清理缓存完成');
535
1128
  }, config.autoClearCacheInterval * 60000);
536
1129
  }
537
1130
  process.on('exit', clearAllCache);