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.
- package/lib/index.d.ts +115 -53
- package/lib/index.js +851 -267
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,124 +1,420 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
87
|
+
parentPort?.postMessage({ success: false, error: '不支持音频' });
|
|
45
88
|
return;
|
|
46
89
|
}
|
|
47
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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.
|
|
69
|
-
xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com'],
|
|
70
|
-
weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', '
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
144
|
-
if (!
|
|
145
|
-
|
|
146
|
-
const filePath =
|
|
147
|
-
const
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
let author = '';
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
author = '未知作者';
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 (
|
|
518
|
+
if (images.length > 0 && !video)
|
|
519
|
+
type = 'image';
|
|
520
|
+
if (video && (video.endsWith('.m4a') || video.endsWith('.mp3')))
|
|
208
521
|
video = '';
|
|
209
|
-
return {
|
|
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 =
|
|
216
|
-
if (
|
|
217
|
-
|
|
561
|
+
const tempDir = path.join(process.cwd(), 'temp_videos');
|
|
562
|
+
if (fs.existsSync(tempDir)) {
|
|
563
|
+
fs.readdirSync(tempDir).forEach(file => {
|
|
218
564
|
try {
|
|
219
|
-
|
|
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
|
|
229
|
-
|
|
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 =
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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'
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
309
|
-
|
|
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(() =>
|
|
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(() =>
|
|
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 =
|
|
357
|
-
if (
|
|
358
|
-
forwardMessages.push((
|
|
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 (
|
|
371
|
-
forwardMessages.push((
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
(
|
|
385
|
-
|
|
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.
|
|
406
|
-
(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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.
|
|
439
|
-
let videoElem
|
|
963
|
+
if (item.video && config.showVideoFile && forwardMessages.length < 100) {
|
|
964
|
+
let videoElem;
|
|
440
965
|
if (config.downloadVideoBeforeSend) {
|
|
441
966
|
try {
|
|
442
|
-
const filename =
|
|
443
|
-
const filePath = await downloadVideoWithThreads(item.video, filename);
|
|
444
|
-
videoElem =
|
|
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 =
|
|
972
|
+
videoElem = h.video(item.video);
|
|
448
973
|
}
|
|
449
974
|
}
|
|
450
|
-
|
|
975
|
+
else {
|
|
976
|
+
videoElem = h.video(item.video);
|
|
977
|
+
}
|
|
978
|
+
forwardMessages.push(buildForwardNode(session, videoElem, botName));
|
|
451
979
|
}
|
|
452
|
-
if (item.video && config.
|
|
453
|
-
await delay(300);
|
|
980
|
+
if (item.video && config.showVideoUrl && forwardMessages.length < 100) {
|
|
454
981
|
const shortLink = await shortUrl(item.video);
|
|
455
|
-
|
|
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
|
-
|
|
1024
|
+
}
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
await sendTimeout(session, `❌ 处理${item.type}内容失败: ${error.message}`);
|
|
459
1027
|
}
|
|
460
1028
|
}
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
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
|
|
470
|
-
|
|
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
|
-
|
|
499
|
-
if (
|
|
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 =
|
|
517
|
-
if (!
|
|
1102
|
+
const tempDir = path.join(process.cwd(), 'temp_videos');
|
|
1103
|
+
if (!fs.existsSync(tempDir))
|
|
518
1104
|
return;
|
|
519
1105
|
const now = Date.now();
|
|
520
|
-
|
|
1106
|
+
fs.readdirSync(tempDir).forEach(file => {
|
|
521
1107
|
try {
|
|
522
|
-
const stat =
|
|
1108
|
+
const stat = fs.statSync(path.join(tempDir, file));
|
|
523
1109
|
if (now - stat.mtimeMs > 3600000) {
|
|
524
|
-
|
|
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);
|