koishi-plugin-video-parser-all 1.3.4 → 1.3.6
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 +166 -20
- package/lib/index.js +457 -272
- package/package.json +19 -5
- package/readme.md +80 -59
package/lib/index.js
CHANGED
|
@@ -71,33 +71,71 @@ exports.name = 'video-parser-all';
|
|
|
71
71
|
exports.Config = koishi_1.Schema.intersect([
|
|
72
72
|
koishi_1.Schema.object({
|
|
73
73
|
enable: koishi_1.Schema.boolean().default(true).description('是否启用视频解析插件'),
|
|
74
|
-
botName: koishi_1.Schema.string().default('视频解析机器人').description('
|
|
74
|
+
botName: koishi_1.Schema.string().default('视频解析机器人').description('合并转发中显示的昵称'),
|
|
75
75
|
showWaitingTip: koishi_1.Schema.boolean().default(true).description('解析时显示等待提示'),
|
|
76
|
-
debug: koishi_1.Schema.boolean().default(false).description('
|
|
77
|
-
|
|
76
|
+
debug: koishi_1.Schema.boolean().default(false).description('开启调试日志'),
|
|
77
|
+
platformEnabled: koishi_1.Schema.object({
|
|
78
|
+
bilibili: koishi_1.Schema.boolean().default(true).description('哔哩哔哩'),
|
|
79
|
+
douyin: koishi_1.Schema.boolean().default(true).description('抖音'),
|
|
80
|
+
kuaishou: koishi_1.Schema.boolean().default(true).description('快手'),
|
|
81
|
+
xiaohongshu: koishi_1.Schema.boolean().default(true).description('小红书'),
|
|
82
|
+
weibo: koishi_1.Schema.boolean().default(true).description('微博'),
|
|
83
|
+
xigua: koishi_1.Schema.boolean().default(true).description('西瓜视频'),
|
|
84
|
+
youtube: koishi_1.Schema.boolean().default(true).description('YouTube'),
|
|
85
|
+
tiktok: koishi_1.Schema.boolean().default(true).description('TikTok'),
|
|
86
|
+
acfun: koishi_1.Schema.boolean().default(true).description('AcFun(A站)'),
|
|
87
|
+
zhihu: koishi_1.Schema.boolean().default(true).description('知乎'),
|
|
88
|
+
weishi: koishi_1.Schema.boolean().default(true).description('微视'),
|
|
89
|
+
huya: koishi_1.Schema.boolean().default(true).description('虎牙'),
|
|
90
|
+
haokan: koishi_1.Schema.boolean().default(true).description('好看视频'),
|
|
91
|
+
meipai: koishi_1.Schema.boolean().default(true).description('美拍'),
|
|
92
|
+
twitter: koishi_1.Schema.boolean().default(true).description('Twitter/X'),
|
|
93
|
+
instagram: koishi_1.Schema.boolean().default(true).description('Instagram'),
|
|
94
|
+
doubao: koishi_1.Schema.boolean().default(true).description('豆包'),
|
|
95
|
+
oasis: koishi_1.Schema.boolean().default(true).description('绿洲'),
|
|
96
|
+
wechat_channel: koishi_1.Schema.boolean().default(true).description('视频号'),
|
|
97
|
+
lishi: koishi_1.Schema.boolean().default(true).description('梨视频'),
|
|
98
|
+
quanmin: koishi_1.Schema.boolean().default(true).description('全民直播'),
|
|
99
|
+
pipigx: koishi_1.Schema.boolean().default(true).description('皮皮搞笑'),
|
|
100
|
+
pipixia: koishi_1.Schema.boolean().default(true).description('皮皮虾'),
|
|
101
|
+
zuiyou: koishi_1.Schema.boolean().default(true).description('最右'),
|
|
102
|
+
}).description('各平台解析开关'),
|
|
103
|
+
}).description('基本设置'),
|
|
78
104
|
koishi_1.Schema.object({
|
|
79
|
-
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n
|
|
80
|
-
}).description('
|
|
105
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('文字格式,支持变量,空行自动隐藏'),
|
|
106
|
+
}).description('消息格式'),
|
|
81
107
|
koishi_1.Schema.object({
|
|
82
|
-
showImageText: koishi_1.Schema.boolean().default(true).description('
|
|
83
|
-
showCoverImage: koishi_1.Schema.boolean().default(true).description('
|
|
84
|
-
showMusicCover: koishi_1.Schema.boolean().default(true).description('
|
|
85
|
-
showImageFile: koishi_1.Schema.boolean().default(true).description('
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以文件形式发送(关闭则只发送链接)'),
|
|
91
|
-
forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频后发送'),
|
|
92
|
-
videoDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('视频下载超时(毫秒)'),
|
|
93
|
-
tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
|
|
94
|
-
maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制'),
|
|
95
|
-
maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介最大长度(字符)'),
|
|
96
|
-
maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('批量解析最大并发数'),
|
|
97
|
-
}).description('内容显示设置'),
|
|
108
|
+
showImageText: koishi_1.Schema.boolean().default(true).description('发送文字内容'),
|
|
109
|
+
showCoverImage: koishi_1.Schema.boolean().default(true).description('发送封面图片'),
|
|
110
|
+
showMusicCover: koishi_1.Schema.boolean().default(true).description('发送音乐封面图片'),
|
|
111
|
+
showImageFile: koishi_1.Schema.boolean().default(true).description('封面/图片是否以图片形式发送(关闭则只发送链接)'),
|
|
112
|
+
showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以视频形式发送(关闭则只发送链接)'),
|
|
113
|
+
forceDownloadImage: koishi_1.Schema.boolean().default(false).description('强制下载封面/图片'),
|
|
114
|
+
forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频'),
|
|
115
|
+
}).description('媒体发送'),
|
|
98
116
|
koishi_1.Schema.object({
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
showMusicVoice: koishi_1.Schema.boolean().default(false).description('音乐链接以语音形式发送'),
|
|
118
|
+
showMusicVoiceFile: koishi_1.Schema.boolean().default(true).description('音乐语音是否以文件形式发送(关闭则只发送链接)'),
|
|
119
|
+
forceDownloadMusicVoice: koishi_1.Schema.boolean().default(false).description('强制下载音乐语音'),
|
|
120
|
+
}).description('音乐语音(需 silk 和 ffmpeg)'),
|
|
121
|
+
koishi_1.Schema.object({
|
|
122
|
+
maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介长度上限'),
|
|
123
|
+
maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('解析最大并发数'),
|
|
124
|
+
downloadConcurrency: koishi_1.Schema.number().min(1).step(1).default(3).description('下载线程数'),
|
|
125
|
+
mediaDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('统一下载超时 (ms)'),
|
|
126
|
+
maxMediaSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载文件大小 (MB),0 为不限制'),
|
|
127
|
+
downloadEngine: koishi_1.Schema.union([
|
|
128
|
+
koishi_1.Schema.const('internal').description('内置下载'),
|
|
129
|
+
koishi_1.Schema.const('aria2').description('aria2 下载'),
|
|
130
|
+
]).default('internal').description('下载引擎'),
|
|
131
|
+
aria2Host: koishi_1.Schema.string().default('127.0.0.1').description('aria2 RPC 地址'),
|
|
132
|
+
aria2Port: koishi_1.Schema.number().default(6800).description('aria2 RPC 端口'),
|
|
133
|
+
aria2Secret: koishi_1.Schema.string().default('').description('aria2 RPC 密钥'),
|
|
134
|
+
resumeDownload: koishi_1.Schema.boolean().default(true).description('启用断点续传(仅 aria2 模式)'),
|
|
135
|
+
}).description('性能与限制'),
|
|
136
|
+
koishi_1.Schema.object({
|
|
137
|
+
timeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('API 请求超时 (ms)'),
|
|
138
|
+
videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('消息发送超时 (ms)'),
|
|
101
139
|
userAgent: koishi_1.Schema.string().default('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36').description('User-Agent'),
|
|
102
140
|
proxy: koishi_1.Schema.object({
|
|
103
141
|
enabled: koishi_1.Schema.boolean().default(false).description('启用代理'),
|
|
@@ -116,22 +154,29 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
116
154
|
name: koishi_1.Schema.string().required().description('头名称'),
|
|
117
155
|
value: koishi_1.Schema.string().required().description('头值'),
|
|
118
156
|
})).default([]).description('自定义请求头'),
|
|
119
|
-
}).description('
|
|
157
|
+
}).description('网络与请求'),
|
|
120
158
|
koishi_1.Schema.object({
|
|
121
159
|
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送失败'),
|
|
122
160
|
retryTimes: koishi_1.Schema.number().min(0).step(1).default(3).description('重试次数'),
|
|
123
|
-
retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot)'),
|
|
127
|
-
}).description('发送方式'),
|
|
161
|
+
retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('重试间隔 (ms)'),
|
|
162
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('合并转发(OneBot/Satori)'),
|
|
163
|
+
}).description('发送与重试'),
|
|
128
164
|
koishi_1.Schema.object({
|
|
129
|
-
deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('
|
|
130
|
-
cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('
|
|
131
|
-
|
|
165
|
+
deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('去重间隔 (s)'),
|
|
166
|
+
cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('缓存时间 (s)'),
|
|
167
|
+
cacheDir: koishi_1.Schema.string().default('./temp_cache').description('统一临时目录'),
|
|
168
|
+
}).description('缓存与临时文件'),
|
|
132
169
|
koishi_1.Schema.object({
|
|
133
|
-
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').
|
|
134
|
-
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').
|
|
170
|
+
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').hidden(),
|
|
171
|
+
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').hidden(),
|
|
172
|
+
apiKeys: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
173
|
+
key: koishi_1.Schema.string().required().description('API Key'),
|
|
174
|
+
weight: koishi_1.Schema.number().min(1).default(1).description('权重(负载均衡模式)'),
|
|
175
|
+
})).default([]).description('多 API 密钥(轮换使用)'),
|
|
176
|
+
rotationMode: koishi_1.Schema.union([
|
|
177
|
+
koishi_1.Schema.const('sequential').description('顺序模式(无效时切换)'),
|
|
178
|
+
koishi_1.Schema.const('load_balance').description('负载均衡模式(轮询)'),
|
|
179
|
+
]).default('sequential').description('密钥轮换模式'),
|
|
135
180
|
platformDedicatedFirst: koishi_1.Schema.object({
|
|
136
181
|
bilibili: koishi_1.Schema.boolean().default(false).description('哔哩哔哩'),
|
|
137
182
|
douyin: koishi_1.Schema.boolean().default(false).description('抖音'),
|
|
@@ -141,7 +186,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
141
186
|
xigua: koishi_1.Schema.boolean().default(false).description('西瓜视频'),
|
|
142
187
|
youtube: koishi_1.Schema.boolean().default(false).description('YouTube'),
|
|
143
188
|
tiktok: koishi_1.Schema.boolean().default(false).description('TikTok'),
|
|
144
|
-
acfun: koishi_1.Schema.boolean().default(false).description('AcFun'),
|
|
189
|
+
acfun: koishi_1.Schema.boolean().default(false).description('AcFun(A站)'),
|
|
145
190
|
zhihu: koishi_1.Schema.boolean().default(false).description('知乎'),
|
|
146
191
|
weishi: koishi_1.Schema.boolean().default(false).description('微视'),
|
|
147
192
|
huya: koishi_1.Schema.boolean().default(false).description('虎牙'),
|
|
@@ -150,9 +195,13 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
150
195
|
twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X'),
|
|
151
196
|
instagram: koishi_1.Schema.boolean().default(false).description('Instagram'),
|
|
152
197
|
doubao: koishi_1.Schema.boolean().default(false).description('豆包'),
|
|
153
|
-
doubao_chat: koishi_1.Schema.boolean().default(false).description('豆包对话'),
|
|
154
198
|
oasis: koishi_1.Schema.boolean().default(false).description('绿洲'),
|
|
155
199
|
wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号'),
|
|
200
|
+
lishi: koishi_1.Schema.boolean().default(false).description('梨视频'),
|
|
201
|
+
quanmin: koishi_1.Schema.boolean().default(false).description('全民直播'),
|
|
202
|
+
pipigx: koishi_1.Schema.boolean().default(false).description('皮皮搞笑'),
|
|
203
|
+
pipixia: koishi_1.Schema.boolean().default(false).description('皮皮虾'),
|
|
204
|
+
zuiyou: koishi_1.Schema.boolean().default(false).description('最右'),
|
|
156
205
|
}).description('优先使用专属 API'),
|
|
157
206
|
customApis: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
158
207
|
platform: koishi_1.Schema.union([
|
|
@@ -164,7 +213,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
164
213
|
koishi_1.Schema.const('xigua').description('西瓜视频'),
|
|
165
214
|
koishi_1.Schema.const('youtube').description('YouTube'),
|
|
166
215
|
koishi_1.Schema.const('tiktok').description('TikTok'),
|
|
167
|
-
koishi_1.Schema.const('acfun').description('AcFun'),
|
|
216
|
+
koishi_1.Schema.const('acfun').description('AcFun(A站)'),
|
|
168
217
|
koishi_1.Schema.const('zhihu').description('知乎'),
|
|
169
218
|
koishi_1.Schema.const('weishi').description('微视'),
|
|
170
219
|
koishi_1.Schema.const('huya').description('虎牙'),
|
|
@@ -173,7 +222,6 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
173
222
|
koishi_1.Schema.const('twitter').description('Twitter/X'),
|
|
174
223
|
koishi_1.Schema.const('instagram').description('Instagram'),
|
|
175
224
|
koishi_1.Schema.const('doubao').description('豆包'),
|
|
176
|
-
koishi_1.Schema.const('doubao_chat').description('豆包对话'),
|
|
177
225
|
koishi_1.Schema.const('oasis').description('绿洲'),
|
|
178
226
|
koishi_1.Schema.const('wechat_channel').description('视频号'),
|
|
179
227
|
]).description('平台'),
|
|
@@ -186,7 +234,34 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
186
234
|
]).default('Bearer').description('认证头类型'),
|
|
187
235
|
customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
|
|
188
236
|
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
|
|
189
|
-
})).default([]).description('
|
|
237
|
+
})).default([]).description('覆盖内置平台 API'),
|
|
238
|
+
customPlatforms: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
239
|
+
name: koishi_1.Schema.string().required().description('平台名称'),
|
|
240
|
+
exampleUrl: koishi_1.Schema.string().description('示例链接'),
|
|
241
|
+
keywords: koishi_1.Schema.string().required().description('关键词(逗号分隔)'),
|
|
242
|
+
apiUrl: koishi_1.Schema.string().required().description('解析 API'),
|
|
243
|
+
apiKey: koishi_1.Schema.string().default('').description('API Key'),
|
|
244
|
+
authHeaderType: koishi_1.Schema.union([
|
|
245
|
+
koishi_1.Schema.const('Bearer').description('Bearer'),
|
|
246
|
+
koishi_1.Schema.const('X-API-Key').description('X-API-Key'),
|
|
247
|
+
koishi_1.Schema.const('Custom').description('自定义'),
|
|
248
|
+
]).default('Bearer').description('认证头类型'),
|
|
249
|
+
customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
|
|
250
|
+
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
|
|
251
|
+
proxy: koishi_1.Schema.object({
|
|
252
|
+
enabled: koishi_1.Schema.boolean().default(false).description('启用独立代理'),
|
|
253
|
+
protocol: koishi_1.Schema.union([
|
|
254
|
+
koishi_1.Schema.const('http').description('HTTP'),
|
|
255
|
+
koishi_1.Schema.const('https').description('HTTPS'),
|
|
256
|
+
]).default('http').description('协议'),
|
|
257
|
+
host: koishi_1.Schema.string().default('127.0.0.1').description('地址'),
|
|
258
|
+
port: koishi_1.Schema.number().default(7890).description('端口'),
|
|
259
|
+
auth: koishi_1.Schema.object({
|
|
260
|
+
username: koishi_1.Schema.string().default('').description('用户名'),
|
|
261
|
+
password: koishi_1.Schema.string().default('').description('密码'),
|
|
262
|
+
}).description('认证'),
|
|
263
|
+
}).description('独立代理(覆盖全局代理)'),
|
|
264
|
+
})).default([]).description('自定义新平台'),
|
|
190
265
|
globalFieldMapping: koishi_1.Schema.string().role('textarea').default('{\n' +
|
|
191
266
|
' "title": "data.title",\n' +
|
|
192
267
|
' "desc": "data.description",\n' +
|
|
@@ -210,14 +285,14 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
210
285
|
' "music_cover": "data.music.cover",\n' +
|
|
211
286
|
' "music_url": "data.music.url"\n' +
|
|
212
287
|
'}').description('全局字段映射 JSON'),
|
|
213
|
-
}).description('API
|
|
288
|
+
}).description('API 与平台'),
|
|
214
289
|
koishi_1.Schema.object({
|
|
215
290
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示'),
|
|
216
291
|
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持提示'),
|
|
217
292
|
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示'),
|
|
218
293
|
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('错误前缀'),
|
|
219
|
-
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('
|
|
220
|
-
}).description('
|
|
294
|
+
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误格式'),
|
|
295
|
+
}).description('界面文本'),
|
|
221
296
|
]);
|
|
222
297
|
const logger = new koishi_1.Logger(exports.name);
|
|
223
298
|
let debugEnabled = false;
|
|
@@ -226,7 +301,7 @@ function debugLog(level, ...args) {
|
|
|
226
301
|
return;
|
|
227
302
|
logger.info(`[${new Date().toISOString()}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}`);
|
|
228
303
|
}
|
|
229
|
-
const
|
|
304
|
+
const BUILTIN_LINK_RULES = [
|
|
230
305
|
{ pattern: /https?:\/\/(?:www\.)?bilibili\.com\/video\/([ab]v[0-9a-zA-Z_-]+)/gi, type: 'bilibili' },
|
|
231
306
|
{ pattern: /https?:\/\/b23\.tv\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
232
307
|
{ pattern: /https?:\/\/bili\d+\.cn\/[0-9a-zA-Z_-]{5,}/gi, type: 'bilibili' },
|
|
@@ -253,16 +328,41 @@ const LINK_RULES = [
|
|
|
253
328
|
{ pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
254
329
|
{ pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_-]{10,}/gi, type: 'instagram' },
|
|
255
330
|
{ pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}/gi, type: 'doubao' },
|
|
256
|
-
{ pattern: /https?:\/\/(?:www\.)?doubao\.com\/thread\/[0-9a-zA-Z_-]+/gi, type: 'doubao_chat' },
|
|
257
331
|
{ pattern: /https?:\/\/(?:www\.)?oasis\.weibo\.com\/v\/[0-9a-zA-Z_-]+/gi, type: 'oasis' },
|
|
258
332
|
{ pattern: /https?:\/\/channels\.weixin\.qq\.com\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
|
|
259
333
|
{ pattern: /https?:\/\/weixin\.qq\.com\/sph\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
|
|
334
|
+
{ pattern: /https?:\/\/(?:www\.)?pearvideo\.com\/video_\d+/gi, type: 'lishi' },
|
|
335
|
+
{ pattern: /https?:\/\/video\.li\/[0-9a-zA-Z_-]{3,}/gi, type: 'lishi' },
|
|
336
|
+
{ pattern: /https?:\/\/(?:www\.)?quanmin\.tv\/\w+/gi, type: 'quanmin' },
|
|
337
|
+
{ pattern: /https?:\/\/(?:www\.)?quanmintv\.cn\/\w+/gi, type: 'quanmin' },
|
|
338
|
+
{ pattern: /https?:\/\/h5\.pipigx\.com\/pp\/post\/\d+/gi, type: 'pipigx' },
|
|
339
|
+
{ pattern: /https?:\/\/(?:www\.)?ippzone\.com\/\w+/gi, type: 'pipigx' },
|
|
340
|
+
{ pattern: /https?:\/\/(?:h5|www)\.pipix\.com\/\w+/gi, type: 'pipixia' },
|
|
341
|
+
{ pattern: /https?:\/\/(?:www\.)?pipixia\.com\/\w+/gi, type: 'pipixia' },
|
|
342
|
+
{ pattern: /https?:\/\/share\.xiaochuankeji\.cn\/hybrid\/share\/post\?pid=\d+/gi, type: 'zuiyou' },
|
|
343
|
+
{ pattern: /https?:\/\/(?:h5|www)\.izuiyou\.com\/\w+/gi, type: 'zuiyou' },
|
|
260
344
|
];
|
|
261
|
-
function
|
|
345
|
+
function buildCustomLinkRules(customPlatforms) {
|
|
346
|
+
if (!Array.isArray(customPlatforms) || customPlatforms.length === 0)
|
|
347
|
+
return [];
|
|
348
|
+
return customPlatforms
|
|
349
|
+
.filter(p => p.keywords)
|
|
350
|
+
.map(p => {
|
|
351
|
+
const keywords = p.keywords.split(',').map((s) => s.trim()).filter(Boolean);
|
|
352
|
+
if (keywords.length === 0)
|
|
353
|
+
return null;
|
|
354
|
+
const escaped = keywords.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
355
|
+
const pattern = new RegExp('https?://[^/\\s]*(' + escaped.join('|') + ')[^\\s]*', 'gi');
|
|
356
|
+
return { pattern, type: `custom_${p.name}` };
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
}
|
|
360
|
+
function linkTypeParser(content, customRules) {
|
|
262
361
|
content = content.replace(/\\\//g, '/');
|
|
362
|
+
const allRules = [...BUILTIN_LINK_RULES, ...customRules];
|
|
263
363
|
const matches = [];
|
|
264
364
|
const seen = new Set();
|
|
265
|
-
for (const rule of
|
|
365
|
+
for (const rule of allRules) {
|
|
266
366
|
let match;
|
|
267
367
|
rule.pattern.lastIndex = 0;
|
|
268
368
|
while ((match = rule.pattern.exec(content)) !== null) {
|
|
@@ -275,9 +375,9 @@ function linkTypeParser(content) {
|
|
|
275
375
|
}
|
|
276
376
|
return matches;
|
|
277
377
|
}
|
|
278
|
-
function extractAllUrlsFromMessage(session) {
|
|
378
|
+
function extractAllUrlsFromMessage(session, customRules) {
|
|
279
379
|
const content = session.content?.trim() || '';
|
|
280
|
-
const matchedLinks = linkTypeParser(content);
|
|
380
|
+
const matchedLinks = linkTypeParser(content, customRules);
|
|
281
381
|
const cardsContent = [];
|
|
282
382
|
if (session.elements) {
|
|
283
383
|
for (const elem of session.elements) {
|
|
@@ -303,7 +403,7 @@ function extractAllUrlsFromMessage(session) {
|
|
|
303
403
|
}
|
|
304
404
|
}
|
|
305
405
|
for (const cardContent of cardsContent) {
|
|
306
|
-
matchedLinks.push(...linkTypeParser(cardContent));
|
|
406
|
+
matchedLinks.push(...linkTypeParser(cardContent, customRules));
|
|
307
407
|
}
|
|
308
408
|
const seen = new Set();
|
|
309
409
|
const result = [];
|
|
@@ -497,8 +597,6 @@ function parseApiResponse(raw, maxDescLen, fieldMapping) {
|
|
|
497
597
|
const durRaw = mapField('duration', () => data.duration);
|
|
498
598
|
if (durRaw) {
|
|
499
599
|
duration = typeof durRaw === 'string' ? parseInt(durRaw, 10) : Number(durRaw);
|
|
500
|
-
if (duration > 3600)
|
|
501
|
-
duration = Math.floor(duration / 1000);
|
|
502
600
|
}
|
|
503
601
|
}
|
|
504
602
|
let publishTime = 0;
|
|
@@ -533,8 +631,11 @@ function generateFormattedText(p, format) {
|
|
|
533
631
|
'音乐标题': p.music.title || '',
|
|
534
632
|
'音乐作者': p.music.author || '',
|
|
535
633
|
'音乐封面': p.music.cover || '',
|
|
536
|
-
'音乐链接': p.music.url || '',
|
|
537
634
|
};
|
|
635
|
+
const varReplacements = Object.entries(vars).map(([key, val]) => ({
|
|
636
|
+
regex: new RegExp(`\\$\\{${key}\\}`, 'g'),
|
|
637
|
+
value: val,
|
|
638
|
+
}));
|
|
538
639
|
const lines = format.split('\n');
|
|
539
640
|
const resultLines = [];
|
|
540
641
|
for (const line of lines) {
|
|
@@ -553,8 +654,8 @@ function generateFormattedText(p, format) {
|
|
|
553
654
|
continue;
|
|
554
655
|
}
|
|
555
656
|
let newLine = line;
|
|
556
|
-
for (const
|
|
557
|
-
newLine = newLine.replace(
|
|
657
|
+
for (const { regex, value } of varReplacements) {
|
|
658
|
+
newLine = newLine.replace(regex, value);
|
|
558
659
|
}
|
|
559
660
|
resultLines.push(newLine);
|
|
560
661
|
}
|
|
@@ -592,6 +693,8 @@ function parseFieldMapping(mappingStr) {
|
|
|
592
693
|
}
|
|
593
694
|
}
|
|
594
695
|
function apply(ctx, config) {
|
|
696
|
+
// @ts-expect-error koishi runtime supports optional service dependencies
|
|
697
|
+
ctx.using(['downloads', 'silk', 'ffmpeg'], { optional: true });
|
|
595
698
|
debugEnabled = config.debug || false;
|
|
596
699
|
debugLog('INFO', 'plugin start');
|
|
597
700
|
const dedupCache = new SimpleLRUCache(1000, config.deduplicationInterval * 1000);
|
|
@@ -605,45 +708,103 @@ function apply(ctx, config) {
|
|
|
605
708
|
parseErrorItemFormat: config.parseErrorItemFormat || '【${url}】: ${msg}',
|
|
606
709
|
};
|
|
607
710
|
const proxyConfig = config.proxy || {};
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
711
|
+
const cacheDir = config.cacheDir || './temp_cache';
|
|
712
|
+
const customPlatforms = (config.customPlatforms || []).map((p) => ({
|
|
713
|
+
name: p.name,
|
|
714
|
+
apiUrl: p.apiUrl,
|
|
715
|
+
apiKey: p.apiKey || '',
|
|
716
|
+
authHeaderType: p.authHeaderType || 'Bearer',
|
|
717
|
+
customHeaderName: p.customHeaderName || 'X-API-Key',
|
|
718
|
+
fieldMapping: parseFieldMapping(p.fieldMapping),
|
|
719
|
+
proxy: p.proxy || null
|
|
720
|
+
}));
|
|
721
|
+
const downloadLimiter = new ConcurrencyLimiter(config.downloadConcurrency || 3);
|
|
722
|
+
const mediaDownloadTimeout = config.mediaDownloadTimeout ?? 120000;
|
|
723
|
+
const maxMediaSize = config.maxMediaSize ?? 0;
|
|
724
|
+
const downloadEngine = config.downloadEngine || 'internal';
|
|
725
|
+
let aria2 = null;
|
|
726
|
+
if (downloadEngine === 'aria2') {
|
|
727
|
+
try {
|
|
728
|
+
const Aria2 = require('aria2');
|
|
729
|
+
aria2 = new Aria2({
|
|
730
|
+
host: config.aria2Host || '127.0.0.1',
|
|
731
|
+
port: config.aria2Port || 6800,
|
|
732
|
+
secure: false,
|
|
733
|
+
secret: config.aria2Secret || '',
|
|
734
|
+
path: '/jsonrpc'
|
|
735
|
+
});
|
|
736
|
+
aria2.open();
|
|
737
|
+
logger.info('aria2 连接成功');
|
|
738
|
+
}
|
|
739
|
+
catch (e) {
|
|
740
|
+
logger.warn('aria2 连接失败,回退到内置下载');
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const apiKeyList = (config.apiKeys || []).map((k) => ({
|
|
744
|
+
key: k.key,
|
|
745
|
+
weight: k.weight || 1,
|
|
746
|
+
lastUsed: 0
|
|
747
|
+
}));
|
|
748
|
+
let keyIndex = 0;
|
|
749
|
+
function getNextApiKey() {
|
|
750
|
+
if (apiKeyList.length === 0)
|
|
751
|
+
return '';
|
|
752
|
+
if (config.rotationMode === 'load_balance') {
|
|
753
|
+
const totalWeight = apiKeyList.reduce((sum, k) => sum + k.weight, 0);
|
|
754
|
+
let rand = Math.random() * totalWeight;
|
|
755
|
+
for (const k of apiKeyList) {
|
|
756
|
+
rand -= k.weight;
|
|
757
|
+
if (rand <= 0)
|
|
758
|
+
return k.key;
|
|
759
|
+
}
|
|
760
|
+
return apiKeyList[0].key;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
const current = apiKeyList[keyIndex % apiKeyList.length];
|
|
764
|
+
keyIndex++;
|
|
765
|
+
return current.key;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function markApiKeyInvalid(key) {
|
|
769
|
+
if (config.rotationMode === 'sequential') {
|
|
770
|
+
const idx = apiKeyList.findIndex(k => k.key === key);
|
|
771
|
+
if (idx !== -1)
|
|
772
|
+
apiKeyList.splice(idx, 1);
|
|
614
773
|
}
|
|
615
|
-
};
|
|
616
|
-
if (proxyConfig.enabled && proxyConfig.host) {
|
|
617
|
-
axiosConfig.proxy = {
|
|
618
|
-
protocol: proxyConfig.protocol || 'http',
|
|
619
|
-
host: proxyConfig.host,
|
|
620
|
-
port: proxyConfig.port || 7890,
|
|
621
|
-
auth: proxyConfig.auth?.username ? {
|
|
622
|
-
username: proxyConfig.auth.username,
|
|
623
|
-
password: proxyConfig.auth.password || ''
|
|
624
|
-
} : undefined
|
|
625
|
-
};
|
|
626
774
|
}
|
|
627
|
-
const http = axios_1.default.create(axiosConfig);
|
|
628
|
-
const defaultDedicatedApis = {
|
|
629
|
-
bilibili: 'https://api.bugpk.com/api/bilibili',
|
|
630
|
-
douyin: 'https://api.bugpk.com/api/douyin',
|
|
631
|
-
doubao: 'https://api.bugpk.com/api/dbvideos',
|
|
632
|
-
doubao_chat: 'https://api.bugpk.com/api/dbduihua',
|
|
633
|
-
kuaishou: 'https://api.bugpk.com/api/kuaishou',
|
|
634
|
-
xiaohongshu: 'https://api.bugpk.com/api/xhs',
|
|
635
|
-
jimeng: 'https://api.bugpk.com/api/jimengai',
|
|
636
|
-
toutiao: 'https://api.bugpk.com/api/toutiao',
|
|
637
|
-
weibo: 'https://api.bugpk.com/api/weibo',
|
|
638
|
-
huya: 'https://api.bugpk.com/api/huya',
|
|
639
|
-
pipigx: 'https://api.bugpk.com/api/pipigx',
|
|
640
|
-
pipixia: 'https://api.bugpk.com/api/pipixia',
|
|
641
|
-
zuiyou: 'https://api.bugpk.com/api/zuiyou',
|
|
642
|
-
wechat_channel: 'https://api.bugpk.com/api/wxsph',
|
|
643
|
-
};
|
|
644
|
-
const backupSupportedPlatforms = new Set(['douyin', 'xiaohongshu', 'instagram', 'jimeng']);
|
|
645
775
|
function getPlatformConfig(type) {
|
|
776
|
+
if (type.startsWith('custom_')) {
|
|
777
|
+
const name = type.slice(7);
|
|
778
|
+
const custom = customPlatforms.find(p => p.name === name);
|
|
779
|
+
if (custom) {
|
|
780
|
+
return {
|
|
781
|
+
apiUrl: custom.apiUrl,
|
|
782
|
+
dedicatedFirst: true,
|
|
783
|
+
apiKey: custom.apiKey || getNextApiKey(),
|
|
784
|
+
authHeaderType: custom.authHeaderType,
|
|
785
|
+
customHeaderName: custom.customHeaderName,
|
|
786
|
+
fieldMapping: custom.fieldMapping,
|
|
787
|
+
customProxy: custom.proxy
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
return { apiUrl: null, dedicatedFirst: false, apiKey: '', authHeaderType: 'Bearer', customHeaderName: 'X-API-Key' };
|
|
791
|
+
}
|
|
646
792
|
const custom = config.customApis?.find((item) => item.platform === type);
|
|
793
|
+
const defaultDedicatedApis = {
|
|
794
|
+
bilibili: 'https://api.bugpk.com/api/bilibili',
|
|
795
|
+
douyin: 'https://api.bugpk.com/api/douyin',
|
|
796
|
+
doubao: 'https://api.bugpk.com/api/dbvideos',
|
|
797
|
+
kuaishou: 'https://api.bugpk.com/api/kuaishou',
|
|
798
|
+
xiaohongshu: 'https://api.bugpk.com/api/xhs',
|
|
799
|
+
jimeng: 'https://api.bugpk.com/api/jimengai',
|
|
800
|
+
toutiao: 'https://api.bugpk.com/api/toutiao',
|
|
801
|
+
weibo: 'https://api.bugpk.com/api/weibo',
|
|
802
|
+
huya: 'https://api.bugpk.com/api/huya',
|
|
803
|
+
pipigx: 'https://api.bugpk.com/api/pipigx',
|
|
804
|
+
pipixia: 'https://api.bugpk.com/api/pipixia',
|
|
805
|
+
zuiyou: 'https://api.bugpk.com/api/zuiyou',
|
|
806
|
+
wechat_channel: 'https://api.bugpk.com/api/wxsph',
|
|
807
|
+
};
|
|
647
808
|
let apiUrl = defaultDedicatedApis[type] || null;
|
|
648
809
|
let apiKey = '';
|
|
649
810
|
let authHeaderType = 'Bearer';
|
|
@@ -651,11 +812,14 @@ function apply(ctx, config) {
|
|
|
651
812
|
let fieldMapping = undefined;
|
|
652
813
|
if (custom && custom.apiUrl) {
|
|
653
814
|
apiUrl = custom.apiUrl;
|
|
654
|
-
apiKey = custom.apiKey ||
|
|
815
|
+
apiKey = custom.apiKey || getNextApiKey();
|
|
655
816
|
authHeaderType = custom.authHeaderType || 'Bearer';
|
|
656
817
|
customHeaderName = custom.customHeaderName || 'X-API-Key';
|
|
657
818
|
fieldMapping = parseFieldMapping(custom.fieldMapping);
|
|
658
819
|
}
|
|
820
|
+
else {
|
|
821
|
+
apiKey = getNextApiKey();
|
|
822
|
+
}
|
|
659
823
|
const dedicatedFirst = config.platformDedicatedFirst?.[type] ?? false;
|
|
660
824
|
if (!fieldMapping) {
|
|
661
825
|
fieldMapping = parseFieldMapping(config.globalFieldMapping);
|
|
@@ -688,63 +852,85 @@ function apply(ctx, config) {
|
|
|
688
852
|
return cleanUrl(url);
|
|
689
853
|
}
|
|
690
854
|
}
|
|
691
|
-
async function
|
|
692
|
-
if (!
|
|
693
|
-
throw new Error('
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (maxSizeBytes > 0 && contentLength > maxSizeBytes) {
|
|
719
|
-
writer.destroy();
|
|
720
|
-
await promises_1.default.unlink(filePath).catch(() => { });
|
|
721
|
-
throw new Error(`视频文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${config.maxVideoSize}MB)`);
|
|
722
|
-
}
|
|
723
|
-
try {
|
|
724
|
-
await (0, promises_2.pipeline)(response.data, writer);
|
|
725
|
-
return filePath;
|
|
855
|
+
async function downloadFile(url, timeout, maxSize, filePrefix, fileExts) {
|
|
856
|
+
if (!url)
|
|
857
|
+
throw new Error('链接为空');
|
|
858
|
+
await promises_1.default.mkdir(cacheDir, { recursive: true });
|
|
859
|
+
const extRegexCache = {};
|
|
860
|
+
const ext = fileExts.find(e => {
|
|
861
|
+
const r = extRegexCache[e] || (extRegexCache[e] = new RegExp('\\.' + e + '(\\?|$)', 'i'));
|
|
862
|
+
return r.test(url);
|
|
863
|
+
}) || fileExts[0];
|
|
864
|
+
const fileName = `${filePrefix}_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
865
|
+
const filePath = path_1.default.resolve(cacheDir, fileName);
|
|
866
|
+
if (ctx.downloads) {
|
|
867
|
+
try {
|
|
868
|
+
const dest = await ctx.downloads.download(url, path_1.default.join(cacheDir, fileName), {
|
|
869
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
|
|
870
|
+
timeout
|
|
871
|
+
});
|
|
872
|
+
const stat = await promises_1.default.stat(dest);
|
|
873
|
+
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
874
|
+
await promises_1.default.unlink(dest).catch(() => { });
|
|
875
|
+
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
876
|
+
}
|
|
877
|
+
return dest;
|
|
878
|
+
}
|
|
879
|
+
catch (e) {
|
|
880
|
+
debugLog('ERROR', `downloads 服务下载失败,回退: ${getErrorMessage(e)}`);
|
|
881
|
+
}
|
|
726
882
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
883
|
+
if (aria2 && config.resumeDownload) {
|
|
884
|
+
try {
|
|
885
|
+
const gid = await aria2.call('aria2.addUri', [url], {
|
|
886
|
+
dir: cacheDir,
|
|
887
|
+
out: fileName,
|
|
888
|
+
split: 4,
|
|
889
|
+
continue: true,
|
|
890
|
+
maxConnectionPerServer: 5,
|
|
891
|
+
timeout: timeout / 1000,
|
|
892
|
+
maxFileNotFound: 5,
|
|
893
|
+
maxTries: 5,
|
|
894
|
+
retryWait: 2,
|
|
895
|
+
header: [`User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`, `Referer: https://www.baidu.com/`]
|
|
896
|
+
});
|
|
897
|
+
let completed = false;
|
|
898
|
+
const ariaStartTime = Date.now();
|
|
899
|
+
while (!completed) {
|
|
900
|
+
if (Date.now() - ariaStartTime > timeout) {
|
|
901
|
+
await aria2.call('aria2.remove', gid).catch(() => { });
|
|
902
|
+
throw new Error('aria2下载超时');
|
|
903
|
+
}
|
|
904
|
+
const status = await aria2.call('aria2.tellStatus', gid);
|
|
905
|
+
if (status.status === 'complete') {
|
|
906
|
+
completed = true;
|
|
907
|
+
}
|
|
908
|
+
else if (status.status === 'error' || status.status === 'removed') {
|
|
909
|
+
throw new Error('aria2下载失败');
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
await delay(1000);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const stat = await promises_1.default.stat(filePath);
|
|
916
|
+
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
917
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
918
|
+
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
919
|
+
}
|
|
920
|
+
return filePath;
|
|
921
|
+
}
|
|
922
|
+
catch (e) {
|
|
923
|
+
debugLog('ERROR', `aria2下载失败,回退内置下载: ${getErrorMessage(e)}`);
|
|
924
|
+
}
|
|
730
925
|
}
|
|
731
|
-
}
|
|
732
|
-
async function downloadImageFile(imageUrl) {
|
|
733
|
-
if (!imageUrl)
|
|
734
|
-
throw new Error('图片链接为空');
|
|
735
|
-
const imgTempDir = config.imageTempDir || './temp_images';
|
|
736
|
-
await promises_1.default.mkdir(imgTempDir, { recursive: true });
|
|
737
|
-
const ext = imageUrl.match(/\.(png|jpg|jpeg|gif|webp)(\?|$)/i)?.[1] || 'jpg';
|
|
738
|
-
const fileName = `img_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
739
|
-
const filePath = path_1.default.resolve(imgTempDir, fileName);
|
|
740
926
|
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
741
927
|
let response;
|
|
742
928
|
try {
|
|
743
929
|
response = await http({
|
|
744
930
|
method: 'GET',
|
|
745
|
-
url
|
|
931
|
+
url,
|
|
746
932
|
responseType: 'stream',
|
|
747
|
-
timeout
|
|
933
|
+
timeout,
|
|
748
934
|
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.baidu.com/' },
|
|
749
935
|
maxRedirects: 5,
|
|
750
936
|
validateStatus: (status) => status >= 200 && status < 300,
|
|
@@ -753,14 +939,14 @@ function apply(ctx, config) {
|
|
|
753
939
|
catch (e) {
|
|
754
940
|
writer.destroy();
|
|
755
941
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
756
|
-
throw new Error(
|
|
942
|
+
throw new Error(`下载失败: ${getErrorMessage(e)}`);
|
|
757
943
|
}
|
|
758
|
-
const maxSizeBytes =
|
|
944
|
+
const maxSizeBytes = maxSize * 1024 * 1024;
|
|
759
945
|
const contentLength = Number(response.headers['content-length'] || 0);
|
|
760
946
|
if (maxSizeBytes > 0 && contentLength > maxSizeBytes) {
|
|
761
947
|
writer.destroy();
|
|
762
948
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
763
|
-
throw new Error(
|
|
949
|
+
throw new Error(`文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
764
950
|
}
|
|
765
951
|
try {
|
|
766
952
|
await (0, promises_2.pipeline)(response.data, writer);
|
|
@@ -768,122 +954,68 @@ function apply(ctx, config) {
|
|
|
768
954
|
}
|
|
769
955
|
catch (e) {
|
|
770
956
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
771
|
-
throw new Error(
|
|
957
|
+
throw new Error(`写入文件失败: ${getErrorMessage(e)}`);
|
|
772
958
|
}
|
|
773
959
|
}
|
|
774
|
-
async function
|
|
775
|
-
if (!
|
|
960
|
+
async function sendMedia(session, url, type, forceDownload, showFile) {
|
|
961
|
+
if (!url)
|
|
776
962
|
return;
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
963
|
+
await downloadLimiter.acquire();
|
|
964
|
+
try {
|
|
965
|
+
const sendLink = async () => { await sendWithTimeout(session, `${type === 'audio' ? '音乐' : type === 'video' ? '视频' : '图片'}链接:${url}`).catch(() => { }); };
|
|
966
|
+
const extMap = {
|
|
967
|
+
image: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
|
|
968
|
+
video: ['mp4'],
|
|
969
|
+
audio: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a']
|
|
970
|
+
};
|
|
971
|
+
const prefixMap = { image: 'img', video: 'video', audio: 'music' };
|
|
972
|
+
const sendFunc = type === 'audio' ? koishi_1.h.audio : type === 'video' ? koishi_1.h.video : koishi_1.h.image;
|
|
973
|
+
if (forceDownload) {
|
|
786
974
|
try {
|
|
787
|
-
await
|
|
975
|
+
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
|
|
976
|
+
try {
|
|
977
|
+
await sendWithTimeout(session, sendFunc(`file://${localPath}`));
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
await promises_1.default.unlink(localPath).catch(() => { });
|
|
981
|
+
}
|
|
788
982
|
return;
|
|
789
983
|
}
|
|
790
|
-
catch {
|
|
791
|
-
|
|
984
|
+
catch (e) {
|
|
985
|
+
debugLog('ERROR', `强制下载${type}失败,尝试URL发送:`, getErrorMessage(e));
|
|
986
|
+
try {
|
|
987
|
+
await sendWithTimeout(session, sendFunc(url));
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
await sendLink();
|
|
991
|
+
}
|
|
792
992
|
}
|
|
993
|
+
return;
|
|
793
994
|
}
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
if (!config.showImageFile) {
|
|
797
|
-
await sendLink();
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
try {
|
|
801
|
-
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
802
|
-
}
|
|
803
|
-
catch {
|
|
804
|
-
try {
|
|
805
|
-
const localPath = await downloadImageFile(imageUrl);
|
|
806
|
-
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
807
|
-
}
|
|
808
|
-
catch {
|
|
995
|
+
if (!showFile) {
|
|
809
996
|
await sendLink();
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
async function sendMusicCover(session, imageUrl) {
|
|
814
|
-
if (!config.showMusicCover)
|
|
815
|
-
return;
|
|
816
|
-
const sendLink = async () => { await sendWithTimeout(session, `图片链接:${imageUrl}`).catch(() => { }); };
|
|
817
|
-
if (config.forceDownloadImage) {
|
|
818
|
-
try {
|
|
819
|
-
const localPath = await downloadImageFile(imageUrl);
|
|
820
|
-
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
821
997
|
return;
|
|
822
998
|
}
|
|
823
|
-
catch (e) {
|
|
824
|
-
debugLog('ERROR', '强制下载音乐封面失败,尝试URL发送:', getErrorMessage(e));
|
|
825
|
-
try {
|
|
826
|
-
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
catch {
|
|
830
|
-
await sendLink();
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
if (!config.showImageFile) {
|
|
836
|
-
await sendLink();
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
try {
|
|
840
|
-
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
841
|
-
}
|
|
842
|
-
catch {
|
|
843
999
|
try {
|
|
844
|
-
|
|
845
|
-
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
1000
|
+
await sendWithTimeout(session, sendFunc(url));
|
|
846
1001
|
}
|
|
847
1002
|
catch {
|
|
848
|
-
await sendLink();
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async function sendVideoFile(session, videoUrl) {
|
|
853
|
-
if (!videoUrl)
|
|
854
|
-
return;
|
|
855
|
-
if (!config.showVideoFile)
|
|
856
|
-
return await sendWithTimeout(session, `视频链接:${videoUrl}`);
|
|
857
|
-
const sendLink = async () => { await sendWithTimeout(session, `视频链接:${videoUrl}`).catch(() => { }); };
|
|
858
|
-
if (config.forceDownloadVideo) {
|
|
859
|
-
try {
|
|
860
|
-
const tempFilePath = await downloadVideoFile(videoUrl);
|
|
861
|
-
await sendWithTimeout(session, koishi_1.h.video(`file://${tempFilePath}`));
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
catch (e) {
|
|
865
|
-
debugLog('ERROR', '强制下载视频失败,尝试URL发送:', getErrorMessage(e));
|
|
866
1003
|
try {
|
|
867
|
-
await
|
|
868
|
-
|
|
1004
|
+
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
|
|
1005
|
+
try {
|
|
1006
|
+
await sendWithTimeout(session, sendFunc(`file://${localPath}`));
|
|
1007
|
+
}
|
|
1008
|
+
finally {
|
|
1009
|
+
await promises_1.default.unlink(localPath).catch(() => { });
|
|
1010
|
+
}
|
|
869
1011
|
}
|
|
870
1012
|
catch {
|
|
871
1013
|
await sendLink();
|
|
872
1014
|
}
|
|
873
1015
|
}
|
|
874
|
-
return;
|
|
875
1016
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
879
|
-
catch {
|
|
880
|
-
try {
|
|
881
|
-
const tempFilePath = await downloadVideoFile(videoUrl);
|
|
882
|
-
await sendWithTimeout(session, koishi_1.h.video(`file://${tempFilePath}`));
|
|
883
|
-
}
|
|
884
|
-
catch {
|
|
885
|
-
await sendLink();
|
|
886
|
-
}
|
|
1017
|
+
finally {
|
|
1018
|
+
downloadLimiter.release();
|
|
887
1019
|
}
|
|
888
1020
|
}
|
|
889
1021
|
async function flush(session, matches) {
|
|
@@ -894,6 +1026,11 @@ function apply(ctx, config) {
|
|
|
894
1026
|
const promises = matches.map(async (match) => {
|
|
895
1027
|
await limiter.acquire();
|
|
896
1028
|
try {
|
|
1029
|
+
const platformEnabled = config.platformEnabled?.[match.type] ?? true;
|
|
1030
|
+
if (!platformEnabled && !match.type.startsWith('custom_')) {
|
|
1031
|
+
debugLog('INFO', `平台 ${match.type} 已禁用,跳过链接: ${match.url}`);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
897
1034
|
if (config.deduplicationInterval > 0) {
|
|
898
1035
|
const lastTime = dedupCache.get(match.url);
|
|
899
1036
|
if (lastTime && (Date.now() - lastTime < config.deduplicationInterval * 1000)) {
|
|
@@ -904,8 +1041,9 @@ function apply(ctx, config) {
|
|
|
904
1041
|
}
|
|
905
1042
|
}
|
|
906
1043
|
debugLog('INFO', `解析链接: ${match.url} (${match.type})`);
|
|
907
|
-
const
|
|
908
|
-
const
|
|
1044
|
+
const platformConf = getPlatformConfig(match.type);
|
|
1045
|
+
const fieldMapping = platformConf.fieldMapping;
|
|
1046
|
+
const result = await processSingleUrl(match.url, match.type, fieldMapping, platformConf);
|
|
909
1047
|
if (result.success) {
|
|
910
1048
|
items.push(result.data);
|
|
911
1049
|
if (config.deduplicationInterval > 0)
|
|
@@ -925,7 +1063,7 @@ function apply(ctx, config) {
|
|
|
925
1063
|
await sendWithTimeout(session, `${texts.parseErrorPrefix}\n${errors.join('\n')}`);
|
|
926
1064
|
if (!items.length)
|
|
927
1065
|
return;
|
|
928
|
-
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
1066
|
+
const enableForward = config.enableForward && (session.platform === 'onebot' || session.platform === 'satori');
|
|
929
1067
|
const botName = config.botName || '视频解析机器人';
|
|
930
1068
|
if (enableForward) {
|
|
931
1069
|
const forwardMessages = [];
|
|
@@ -947,6 +1085,9 @@ function apply(ctx, config) {
|
|
|
947
1085
|
}
|
|
948
1086
|
if (p.video)
|
|
949
1087
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(p.video), botName));
|
|
1088
|
+
if (config.showMusicVoice && p.music.url) {
|
|
1089
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.audio(p.music.url), botName));
|
|
1090
|
+
}
|
|
950
1091
|
}
|
|
951
1092
|
if (forwardMessages.length) {
|
|
952
1093
|
try {
|
|
@@ -970,37 +1111,41 @@ function apply(ctx, config) {
|
|
|
970
1111
|
await delay(300);
|
|
971
1112
|
}
|
|
972
1113
|
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
973
|
-
await
|
|
1114
|
+
await sendMedia(session, p.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
974
1115
|
await delay(300);
|
|
975
1116
|
}
|
|
976
1117
|
if (config.showMusicCover && p.music.cover) {
|
|
977
|
-
await
|
|
1118
|
+
await sendMedia(session, p.music.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
978
1119
|
await delay(300);
|
|
979
1120
|
}
|
|
980
1121
|
if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
981
|
-
await
|
|
1122
|
+
await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile).catch(() => { });
|
|
982
1123
|
await delay(500);
|
|
983
1124
|
}
|
|
984
1125
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
985
1126
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
986
1127
|
for (const imgUrl of imageUrls) {
|
|
987
|
-
await
|
|
1128
|
+
await sendMedia(session, imgUrl, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
988
1129
|
await delay(200);
|
|
989
1130
|
}
|
|
990
1131
|
}
|
|
1132
|
+
if (config.showMusicVoice && p.music.url) {
|
|
1133
|
+
await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile).catch(() => { });
|
|
1134
|
+
await delay(300);
|
|
1135
|
+
}
|
|
991
1136
|
}
|
|
992
1137
|
}
|
|
993
1138
|
debugLog('INFO', '处理完成');
|
|
994
1139
|
}
|
|
995
|
-
async function fetchApi(url, type, fieldMapping) {
|
|
1140
|
+
async function fetchApi(url, type, fieldMapping, platformConf) {
|
|
996
1141
|
const cacheKey = url;
|
|
997
1142
|
const cached = urlCacheLocal.get(cacheKey);
|
|
998
1143
|
if (cached && cached.expire > Date.now())
|
|
999
1144
|
return cached.data;
|
|
1000
|
-
const { apiUrl: dedicatedUrl, dedicatedFirst, apiKey, authHeaderType, customHeaderName } = getPlatformConfig(type);
|
|
1145
|
+
const { apiUrl: dedicatedUrl, dedicatedFirst, apiKey, authHeaderType, customHeaderName, customProxy } = platformConf || getPlatformConfig(type);
|
|
1001
1146
|
const primaryApi = config.primaryApiUrl || 'https://api.bugpk.com/api/short_videos';
|
|
1002
1147
|
const backupApi = config.backupApiUrl || 'https://api.bugpk.com/api/svparse';
|
|
1003
|
-
const backupAllowed =
|
|
1148
|
+
const backupAllowed = new Set(['douyin', 'xiaohongshu', 'instagram', 'jimeng']).has(type);
|
|
1004
1149
|
const apiList = [];
|
|
1005
1150
|
if (dedicatedFirst && dedicatedUrl) {
|
|
1006
1151
|
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
@@ -1015,6 +1160,9 @@ function apply(ctx, config) {
|
|
|
1015
1160
|
if (dedicatedUrl)
|
|
1016
1161
|
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
1017
1162
|
}
|
|
1163
|
+
if (type.startsWith('custom_') && apiList.length === 0 && dedicatedUrl) {
|
|
1164
|
+
apiList.push({ url: dedicatedUrl, label: `自定义API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
1165
|
+
}
|
|
1018
1166
|
const customHeaders = config.customHeaders || [];
|
|
1019
1167
|
let lastError = null;
|
|
1020
1168
|
for (const api of apiList) {
|
|
@@ -1033,12 +1181,28 @@ function apply(ctx, config) {
|
|
|
1033
1181
|
const authHeaders = buildAuthHeaders(api.apiKey, api.authHeaderType || 'Bearer', api.customHeaderName || 'X-API-Key');
|
|
1034
1182
|
Object.assign(headers, authHeaders);
|
|
1035
1183
|
}
|
|
1036
|
-
const
|
|
1184
|
+
const proxyToUse = customProxy && customProxy.enabled ? customProxy : (proxyConfig.enabled ? proxyConfig : undefined);
|
|
1185
|
+
const axiosConfigLocal = {
|
|
1186
|
+
params: { url },
|
|
1187
|
+
timeout: config.timeout,
|
|
1188
|
+
headers,
|
|
1189
|
+
proxy: proxyToUse && proxyToUse.host ? {
|
|
1190
|
+
protocol: proxyToUse.protocol || 'http',
|
|
1191
|
+
host: proxyToUse.host,
|
|
1192
|
+
port: proxyToUse.port || 7890,
|
|
1193
|
+
auth: proxyToUse.auth?.username ? { username: proxyToUse.auth.username, password: proxyToUse.auth.password || '' } : undefined
|
|
1194
|
+
} : undefined
|
|
1195
|
+
};
|
|
1196
|
+
const res = await http.get(api.url, axiosConfigLocal);
|
|
1037
1197
|
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
1038
1198
|
const parsed = parseApiResponse(res.data, config.maxDescLength, api.fieldMapping);
|
|
1039
1199
|
urlCacheLocal.set(cacheKey, { data: parsed, expire: Date.now() + cacheTTL });
|
|
1040
1200
|
return parsed;
|
|
1041
1201
|
}
|
|
1202
|
+
if (res.data?.code === 403 || res.data?.code === 401) {
|
|
1203
|
+
if (api.apiKey)
|
|
1204
|
+
markApiKeyInvalid(api.apiKey);
|
|
1205
|
+
}
|
|
1042
1206
|
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
1043
1207
|
}
|
|
1044
1208
|
catch (error) {
|
|
@@ -1052,12 +1216,12 @@ function apply(ctx, config) {
|
|
|
1052
1216
|
}
|
|
1053
1217
|
throw lastError || new Error('所有API请求全部失败');
|
|
1054
1218
|
}
|
|
1055
|
-
async function parseUrl(url, type, fieldMapping) {
|
|
1219
|
+
async function parseUrl(url, type, fieldMapping, platformConf) {
|
|
1056
1220
|
const realUrl = await resolveShortUrl(url);
|
|
1057
1221
|
const candidates = [...new Set([realUrl, url])];
|
|
1058
1222
|
for (const candidate of candidates) {
|
|
1059
1223
|
try {
|
|
1060
|
-
const info = await fetchApi(candidate, type, fieldMapping);
|
|
1224
|
+
const info = await fetchApi(candidate, type, fieldMapping, platformConf);
|
|
1061
1225
|
if (info.video || info.images.length > 0 || info.live_photo.length > 0)
|
|
1062
1226
|
return { success: true, data: info };
|
|
1063
1227
|
debugLog('WARN', `解析成功但无内容: ${candidate}`);
|
|
@@ -1068,8 +1232,8 @@ function apply(ctx, config) {
|
|
|
1068
1232
|
}
|
|
1069
1233
|
return { success: false, msg: texts.unsupportedPlatformText };
|
|
1070
1234
|
}
|
|
1071
|
-
async function processSingleUrl(url, type, fieldMapping) {
|
|
1072
|
-
const result = await parseUrl(url, type, fieldMapping);
|
|
1235
|
+
async function processSingleUrl(url, type, fieldMapping, platformConf) {
|
|
1236
|
+
const result = await parseUrl(url, type, fieldMapping, platformConf);
|
|
1073
1237
|
if (!result.success)
|
|
1074
1238
|
return { success: false, msg: result.msg, url };
|
|
1075
1239
|
const text = generateFormattedText(result.data, config.unifiedMessageFormat);
|
|
@@ -1100,6 +1264,27 @@ function apply(ctx, config) {
|
|
|
1100
1264
|
}
|
|
1101
1265
|
return null;
|
|
1102
1266
|
}
|
|
1267
|
+
const customRules = buildCustomLinkRules(config.customPlatforms || []);
|
|
1268
|
+
const axiosConfig = {
|
|
1269
|
+
timeout: config.timeout,
|
|
1270
|
+
headers: {
|
|
1271
|
+
'User-Agent': config.userAgent,
|
|
1272
|
+
'Referer': 'https://www.baidu.com/',
|
|
1273
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
if (proxyConfig.enabled && proxyConfig.host) {
|
|
1277
|
+
axiosConfig.proxy = {
|
|
1278
|
+
protocol: proxyConfig.protocol || 'http',
|
|
1279
|
+
host: proxyConfig.host,
|
|
1280
|
+
port: proxyConfig.port || 7890,
|
|
1281
|
+
auth: proxyConfig.auth?.username ? {
|
|
1282
|
+
username: proxyConfig.auth.username,
|
|
1283
|
+
password: proxyConfig.auth.password || ''
|
|
1284
|
+
} : undefined
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
const http = axios_1.default.create(axiosConfig);
|
|
1103
1288
|
ctx.on('message', async (session) => {
|
|
1104
1289
|
if (!config.enable)
|
|
1105
1290
|
return;
|
|
@@ -1109,7 +1294,7 @@ function apply(ctx, config) {
|
|
|
1109
1294
|
return;
|
|
1110
1295
|
if (session.selfId === session.userId)
|
|
1111
1296
|
return;
|
|
1112
|
-
const matches = extractAllUrlsFromMessage(session);
|
|
1297
|
+
const matches = extractAllUrlsFromMessage(session, customRules);
|
|
1113
1298
|
if (!matches.length)
|
|
1114
1299
|
return;
|
|
1115
1300
|
debugLog('INFO', `检测到 ${matches.length} 个链接`);
|
|
@@ -1128,7 +1313,7 @@ function apply(ctx, config) {
|
|
|
1128
1313
|
await sendWithTimeout(session, texts.invalidLinkText);
|
|
1129
1314
|
return;
|
|
1130
1315
|
}
|
|
1131
|
-
const matches = linkTypeParser(url);
|
|
1316
|
+
const matches = linkTypeParser(url, customRules);
|
|
1132
1317
|
if (!matches.length) {
|
|
1133
1318
|
await sendWithTimeout(session, texts.invalidLinkText);
|
|
1134
1319
|
return;
|
|
@@ -1143,17 +1328,16 @@ function apply(ctx, config) {
|
|
|
1143
1328
|
});
|
|
1144
1329
|
const tempCleanupInterval = setInterval(async () => {
|
|
1145
1330
|
try {
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1331
|
+
const files = await promises_1.default.readdir(cacheDir);
|
|
1332
|
+
const now = Date.now();
|
|
1333
|
+
for (const file of files) {
|
|
1334
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
|
|
1335
|
+
(file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
|
|
1336
|
+
(file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
|
|
1337
|
+
const filePath = path_1.default.join(cacheDir, file);
|
|
1338
|
+
const stats = await promises_1.default.stat(filePath);
|
|
1339
|
+
if (now - stats.mtimeMs > 3600000) {
|
|
1340
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
1157
1341
|
}
|
|
1158
1342
|
}
|
|
1159
1343
|
}
|
|
@@ -1164,19 +1348,20 @@ function apply(ctx, config) {
|
|
|
1164
1348
|
}, 3600000);
|
|
1165
1349
|
ctx.on('dispose', () => {
|
|
1166
1350
|
clearInterval(tempCleanupInterval);
|
|
1351
|
+
if (aria2)
|
|
1352
|
+
aria2.close();
|
|
1167
1353
|
urlCacheLocal.clear();
|
|
1168
1354
|
dedupCache.clear();
|
|
1169
1355
|
debugLog('INFO', '插件已卸载');
|
|
1170
1356
|
});
|
|
1171
1357
|
process.on('beforeExit', async () => {
|
|
1172
1358
|
try {
|
|
1173
|
-
const
|
|
1174
|
-
for (const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1359
|
+
const files = await promises_1.default.readdir(cacheDir);
|
|
1360
|
+
for (const file of files) {
|
|
1361
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
|
|
1362
|
+
(file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
|
|
1363
|
+
(file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
|
|
1364
|
+
await promises_1.default.unlink(path_1.default.join(cacheDir, file)).catch(() => { });
|
|
1180
1365
|
}
|
|
1181
1366
|
}
|
|
1182
1367
|
}
|