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

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