koishi-plugin-video-parser-all 0.2.3 → 0.2.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 +4 -2
- package/lib/index.js +119 -46
- package/package.json +11 -3
- package/readme.md +47 -3
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export declare const name = "video-parser-all";
|
|
|
3
3
|
export interface Config {
|
|
4
4
|
enable: boolean;
|
|
5
5
|
showWaitingTip: boolean;
|
|
6
|
-
revokeWaitingTip: boolean;
|
|
7
6
|
waitingTipText: string;
|
|
8
7
|
sameLinkInterval: number;
|
|
9
8
|
imageParseFormat: string;
|
|
@@ -20,8 +19,11 @@ export interface Config {
|
|
|
20
19
|
messageBufferDelay: number;
|
|
21
20
|
retryTimes: number;
|
|
22
21
|
retryInterval: number;
|
|
23
|
-
|
|
22
|
+
apiMode: 'builtin' | 'custom';
|
|
23
|
+
builtinApiUrl: string;
|
|
24
|
+
customApiUrl: string;
|
|
24
25
|
videoSendTimeout: number;
|
|
26
|
+
autoClearCacheInterval: number;
|
|
25
27
|
}
|
|
26
28
|
export declare const Config: Schema<Config>;
|
|
27
29
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -16,11 +16,10 @@ exports.name = 'video-parser-all';
|
|
|
16
16
|
exports.Config = koishi_1.Schema.object({
|
|
17
17
|
enable: koishi_1.Schema.boolean().default(true).description('启用插件'),
|
|
18
18
|
showWaitingTip: koishi_1.Schema.boolean().default(true).description('解析时显示等待提示'),
|
|
19
|
-
revokeWaitingTip: koishi_1.Schema.boolean().default(true).description('解析完成后撤回等待提示'),
|
|
20
19
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示文本'),
|
|
21
20
|
sameLinkInterval: koishi_1.Schema.number().default(0).min(0).description('相同链接解析间隔(秒)'),
|
|
22
|
-
imageParseFormat: koishi_1.Schema.string().role('textarea').default('${标题}\n${
|
|
23
|
-
支持变量:\${标题} \${UP主} \${简介} \${
|
|
21
|
+
imageParseFormat: koishi_1.Schema.string().role('textarea').default('${标题}\n${UP主}').description(`解析结果格式
|
|
22
|
+
支持变量:\${标题} \${UP主} \${简介} \${tab} \${~~~}`),
|
|
24
23
|
returnContent: koishi_1.Schema.object({
|
|
25
24
|
showImageText: koishi_1.Schema.boolean().default(true).description('显示文本与封面'),
|
|
26
25
|
showVideoUrl: koishi_1.Schema.boolean().default(false).description('显示无水印链接'),
|
|
@@ -34,7 +33,11 @@ exports.Config = koishi_1.Schema.object({
|
|
|
34
33
|
messageBufferDelay: koishi_1.Schema.number().default(0).min(0).description('消息缓冲延迟(秒)'),
|
|
35
34
|
retryTimes: koishi_1.Schema.number().default(0).min(0).description('接口重试次数'),
|
|
36
35
|
retryInterval: koishi_1.Schema.number().default(0).min(0).description('重试间隔(毫秒)'),
|
|
37
|
-
|
|
36
|
+
apiMode: koishi_1.Schema.union([
|
|
37
|
+
koishi_1.Schema.const('builtin').description('使用内置API'),
|
|
38
|
+
koishi_1.Schema.const('custom').description('使用自定义API')
|
|
39
|
+
]).default('builtin').description('API使用模式'),
|
|
40
|
+
builtinApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description(`内置API地址
|
|
38
41
|
解析失败可能原因:
|
|
39
42
|
1. 视频为私密/付费/下架/限制
|
|
40
43
|
2. 接口限流或维护
|
|
@@ -42,10 +45,12 @@ exports.Config = koishi_1.Schema.object({
|
|
|
42
45
|
4. 平台链接格式更新
|
|
43
46
|
注意事项:
|
|
44
47
|
1. 勿频繁解析相同链接
|
|
45
|
-
2. 仅支持快手/B
|
|
48
|
+
2. 仅支持快手/B站/小红书/微博公开视频
|
|
46
49
|
3. 公共接口不保证100%成功
|
|
47
50
|
4. 失败可检查链接或稍后重试`),
|
|
51
|
+
customApiUrl: koishi_1.Schema.string().default('').description('自定义API地址(优先使用)'),
|
|
48
52
|
videoSendTimeout: koishi_1.Schema.number().default(0).min(0).description('视频发送超时(毫秒)'),
|
|
53
|
+
autoClearCacheInterval: koishi_1.Schema.number().default(0).min(0).description('自动清理缓存间隔(分钟),0表示不自动清理'),
|
|
49
54
|
});
|
|
50
55
|
if (!worker_threads_1.isMainThread) {
|
|
51
56
|
const { url, filePath } = worker_threads_1.workerData;
|
|
@@ -76,7 +81,9 @@ const processed = new Map();
|
|
|
76
81
|
const linkBuffer = new Map();
|
|
77
82
|
const PLATFORM_KEYWORDS = {
|
|
78
83
|
bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com'],
|
|
79
|
-
kuaishou: ['kuaishou', '快手', 'v.kuishou.com', 'www.
|
|
84
|
+
kuaishou: ['kuaishou', '快手', 'v.kuishou.com', 'www.kuishou.com', 'kwimgs.com'],
|
|
85
|
+
xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com'],
|
|
86
|
+
weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'svproxy.168299.xyz']
|
|
80
87
|
};
|
|
81
88
|
function extractUrl(content) {
|
|
82
89
|
const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
|
|
@@ -95,6 +102,10 @@ function getPlatformType(url) {
|
|
|
95
102
|
return 'kuaishou';
|
|
96
103
|
if (PLATFORM_KEYWORDS.bilibili.some(k => lower.includes(k)))
|
|
97
104
|
return 'bilibili';
|
|
105
|
+
if (PLATFORM_KEYWORDS.xiaohongshu.some(k => lower.includes(k)))
|
|
106
|
+
return 'xiaohongshu';
|
|
107
|
+
if (PLATFORM_KEYWORDS.weibo.some(k => lower.includes(k)))
|
|
108
|
+
return 'weibo';
|
|
98
109
|
return null;
|
|
99
110
|
}
|
|
100
111
|
async function shortUrl(url) {
|
|
@@ -121,7 +132,7 @@ async function downloadVideoWithThreads(url, filename) {
|
|
|
121
132
|
function parseData(data, maxDescLength) {
|
|
122
133
|
const type = data.type || 'video';
|
|
123
134
|
const title = data.title || data.desc || '无标题';
|
|
124
|
-
const author = data.author || data.auther || data.user?.name || '未知作者';
|
|
135
|
+
const author = data.author?.name || data.author || data.auther || data.user?.name || '未知作者';
|
|
125
136
|
const desc = (data.desc || data.description || title).slice(0, maxDescLength);
|
|
126
137
|
const cover = data.cover || data.imgurl || data.pic || '';
|
|
127
138
|
const images = data.images || [];
|
|
@@ -153,6 +164,12 @@ function apply(ctx, config) {
|
|
|
153
164
|
if (!worker_threads_1.isMainThread)
|
|
154
165
|
return;
|
|
155
166
|
clearAllCache();
|
|
167
|
+
const getApiUrl = () => {
|
|
168
|
+
if (config.apiMode === 'custom' && config.customApiUrl.trim()) {
|
|
169
|
+
return config.customApiUrl.trim();
|
|
170
|
+
}
|
|
171
|
+
return config.builtinApiUrl;
|
|
172
|
+
};
|
|
156
173
|
const http = axios_1.default.create({
|
|
157
174
|
timeout: config.timeout,
|
|
158
175
|
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' }
|
|
@@ -163,7 +180,7 @@ function apply(ctx, config) {
|
|
|
163
180
|
return { data: null, msg: '不支持该平台链接' };
|
|
164
181
|
for (let i = 0; i <= config.retryTimes; i++) {
|
|
165
182
|
try {
|
|
166
|
-
const res = await http.get(
|
|
183
|
+
const res = await http.get(getApiUrl(), { params: { url } });
|
|
167
184
|
if ((res.data.code === 200 || res.data.code === 0) && res.data.data) {
|
|
168
185
|
return { data: parseData(res.data.data, config.maxDescLength), msg: '解析成功' };
|
|
169
186
|
}
|
|
@@ -194,26 +211,10 @@ function apply(ctx, config) {
|
|
|
194
211
|
.replace(/\${标题}/g, d.title)
|
|
195
212
|
.replace(/\${UP主}/g, d.author)
|
|
196
213
|
.replace(/\${简介}/g, d.desc)
|
|
197
|
-
.replace(/\${
|
|
198
|
-
.replace(/\${
|
|
199
|
-
.replace(/\${收藏}/g, '0')
|
|
200
|
-
.replace(/\${转发}/g, '0')
|
|
201
|
-
.replace(/\${观看}/g, '0')
|
|
202
|
-
.replace(/\${弹幕}/g, '0')
|
|
203
|
-
.replace(/\${tab}/g, '\t').replace(/\${~~~}/g, '\n');
|
|
214
|
+
.replace(/\${tab}/g, '\t')
|
|
215
|
+
.replace(/\${~~~}/g, '\n');
|
|
204
216
|
return { data: { text, cover: d.cover, images: d.images, video: d.video, type: d.type }, msg: 'ok' };
|
|
205
217
|
}
|
|
206
|
-
async function revokeTip(session, key) {
|
|
207
|
-
if (!config.revokeWaitingTip || session.platform !== 'onebot')
|
|
208
|
-
return;
|
|
209
|
-
const buf = linkBuffer.get(key);
|
|
210
|
-
if (!buf?.tipMsgId)
|
|
211
|
-
return;
|
|
212
|
-
try {
|
|
213
|
-
await session.bot.deleteMessage(session.channelId, buf.tipMsgId.toString());
|
|
214
|
-
}
|
|
215
|
-
catch { }
|
|
216
|
-
}
|
|
217
218
|
async function sendTimeout(session, c) {
|
|
218
219
|
if (config.videoSendTimeout <= 0)
|
|
219
220
|
return session.send(c).catch(() => null);
|
|
@@ -226,7 +227,6 @@ function apply(ctx, config) {
|
|
|
226
227
|
if (buf) {
|
|
227
228
|
clearTimeout(buf.timer);
|
|
228
229
|
linkBuffer.delete(key);
|
|
229
|
-
await revokeTip(session, key);
|
|
230
230
|
}
|
|
231
231
|
const items = [];
|
|
232
232
|
const errs = [];
|
|
@@ -234,44 +234,111 @@ function apply(ctx, config) {
|
|
|
234
234
|
const one = await processSingleUrl(session, u);
|
|
235
235
|
one.data ? items.push(one.data) : errs.push(`【${u.slice(0, 22)}...】:${one.msg}`);
|
|
236
236
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
237
|
+
const forwardMessages = [];
|
|
238
|
+
const botName = '视频解析机器人';
|
|
241
239
|
if (errs.length) {
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
const errorMsg = `⚠️ 部分解析失败\n${errs.join('\n')}`;
|
|
241
|
+
if (config.enableForward && session.platform === 'onebot') {
|
|
242
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
243
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
244
|
+
errorMsg
|
|
245
|
+
]));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
await sendTimeout(session, errorMsg);
|
|
249
|
+
await delay(600);
|
|
250
|
+
}
|
|
244
251
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
if (items.length === 0) {
|
|
253
|
+
const failMsg = `❌ 全部解析失败\n${errs.join('\n')}`;
|
|
254
|
+
if (config.enableForward && session.platform === 'onebot') {
|
|
255
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
256
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
257
|
+
failMsg
|
|
258
|
+
]));
|
|
251
259
|
}
|
|
252
260
|
else {
|
|
261
|
+
await sendTimeout(session, failMsg);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const it of items) {
|
|
266
|
+
if (config.enableForward && session.platform === 'onebot') {
|
|
267
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
268
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
269
|
+
it.text
|
|
270
|
+
]));
|
|
253
271
|
if (it.cover) {
|
|
254
|
-
|
|
255
|
-
|
|
272
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
273
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
274
|
+
koishi_1.h.image(it.cover)
|
|
275
|
+
]));
|
|
256
276
|
}
|
|
257
277
|
if (it.video && config.returnContent.showVideoFile) {
|
|
258
278
|
let vid = koishi_1.h.video(it.video);
|
|
259
|
-
if (config.downloadVideoBeforeSend
|
|
279
|
+
if (config.downloadVideoBeforeSend) {
|
|
260
280
|
try {
|
|
261
281
|
const name = crypto_1.default.createHash('md5').update(it.video).digest('hex');
|
|
262
282
|
vid = koishi_1.h.file(await downloadVideoWithThreads(it.video, name));
|
|
263
283
|
}
|
|
264
284
|
catch { }
|
|
265
285
|
}
|
|
266
|
-
|
|
286
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
287
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
288
|
+
vid
|
|
289
|
+
]));
|
|
267
290
|
}
|
|
268
291
|
if (it.video && config.returnContent.showVideoUrl) {
|
|
269
|
-
await delay(300);
|
|
270
292
|
const s = await shortUrl(it.video);
|
|
271
|
-
|
|
293
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
294
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
295
|
+
`🔗 无水印:${s}`
|
|
296
|
+
]));
|
|
297
|
+
}
|
|
298
|
+
if (it.type === 'image' && it.images?.length) {
|
|
299
|
+
it.images.forEach(imgUrl => {
|
|
300
|
+
forwardMessages.push((0, koishi_1.h)('message', [
|
|
301
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: botName }),
|
|
302
|
+
koishi_1.h.image(imgUrl)
|
|
303
|
+
]));
|
|
304
|
+
});
|
|
272
305
|
}
|
|
273
306
|
}
|
|
274
|
-
|
|
307
|
+
else {
|
|
308
|
+
await sendTimeout(session, it.text);
|
|
309
|
+
await delay(300);
|
|
310
|
+
if (it.type === 'image' && it.images?.length) {
|
|
311
|
+
const msg = (0, koishi_1.h)('message', ...it.images.map(u => koishi_1.h.image(u)));
|
|
312
|
+
await sendTimeout(session, msg);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
if (it.cover) {
|
|
316
|
+
await sendTimeout(session, koishi_1.h.image(it.cover));
|
|
317
|
+
await delay(300);
|
|
318
|
+
}
|
|
319
|
+
if (it.video && config.returnContent.showVideoFile) {
|
|
320
|
+
let vid = koishi_1.h.video(it.video);
|
|
321
|
+
if (config.downloadVideoBeforeSend) {
|
|
322
|
+
try {
|
|
323
|
+
const name = crypto_1.default.createHash('md5').update(it.video).digest('hex');
|
|
324
|
+
vid = koishi_1.h.file(await downloadVideoWithThreads(it.video, name));
|
|
325
|
+
}
|
|
326
|
+
catch { }
|
|
327
|
+
}
|
|
328
|
+
await sendTimeout(session, vid);
|
|
329
|
+
}
|
|
330
|
+
if (it.video && config.returnContent.showVideoUrl) {
|
|
331
|
+
await delay(300);
|
|
332
|
+
const s = await shortUrl(it.video);
|
|
333
|
+
await sendTimeout(session, `🔗 无水印:${s}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
await delay(1000);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (config.enableForward && session.platform === 'onebot' && forwardMessages.length) {
|
|
340
|
+
const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages);
|
|
341
|
+
await sendTimeout(session, forwardMsg);
|
|
275
342
|
}
|
|
276
343
|
}
|
|
277
344
|
ctx.on('message', async (session) => {
|
|
@@ -334,6 +401,12 @@ function apply(ctx, config) {
|
|
|
334
401
|
catch { }
|
|
335
402
|
});
|
|
336
403
|
}, 1800000);
|
|
404
|
+
if (config.autoClearCacheInterval > 0) {
|
|
405
|
+
setInterval(() => {
|
|
406
|
+
clearAllCache();
|
|
407
|
+
ctx.logger.info('自动清理缓存完成');
|
|
408
|
+
}, config.autoClearCacheInterval * 60000);
|
|
409
|
+
}
|
|
337
410
|
process.on('exit', clearAllCache);
|
|
338
411
|
ctx.logger.info('视频解析插件已加载');
|
|
339
412
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-video-parser-all",
|
|
3
|
-
"description": "Koishi 视频解析插件,支持抖音/快手/B
|
|
4
|
-
"version": "0.2.
|
|
3
|
+
"description": "Koishi 视频解析插件,支持抖音/快手/B站/小红书/微博视频链接解析",
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -35,5 +35,13 @@
|
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@koishijs/plugin-console": "^5.30.4",
|
|
37
37
|
"koishi": "^4.18.7"
|
|
38
|
-
}
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/Minecraft-1314/koishi-plugin-video-parser-all.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/Minecraft-1314/koishi-plugin-video-parser-all/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Minecraft-1314/koishi-plugin-video-parser-all#readme"
|
|
39
47
|
}
|
package/readme.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
|
-
# koishi-plugin-video-parser
|
|
1
|
+
# koishi-plugin-video-parser-all
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 项目介绍 (Project Introduction)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### 中文
|
|
6
|
+
这是一个为 Koishi 机器人框架开发的**多平台视频解析插件**,支持自动识别并解析抖音、快手、B站、小红书、微博等主流短视频平台链接。核心特性:
|
|
7
|
+
- 自动识别多平台视频链接,无需手动指定平台
|
|
8
|
+
- 自定义解析结果格式、返回内容类型(封面/链接/视频文件)
|
|
9
|
+
- 内置防重复解析、接口重试、自动缓存清理等实用功能
|
|
10
|
+
- 支持 OneBot 平台消息合并转发,优化展示体验
|
|
11
|
+
- 可切换内置 API 或自定义 API,适配不同网络环境
|
|
12
|
+
|
|
13
|
+
### English
|
|
14
|
+
A multi-platform video parsing plugin for the Koishi bot framework, supporting automatic recognition and parsing of video links from Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo and other mainstream platforms. Core features:
|
|
15
|
+
- Auto-detect video links across platforms without manual specification
|
|
16
|
+
- Customize result formatting and output types (cover/link/video file)
|
|
17
|
+
- Built-in duplicate prevention, retry logic, auto cache cleanup
|
|
18
|
+
- Support OneBot message forwarding for better display experience
|
|
19
|
+
- Switchable between built-in and custom API for different network environments
|
|
20
|
+
|
|
21
|
+
## 项目仓库 (Repository)
|
|
22
|
+
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
|
|
23
|
+
- Issues: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all/issues`
|
|
24
|
+
|
|
25
|
+
## 核心指令 (Core Commands)
|
|
26
|
+
|
|
27
|
+
parse (手动解析)(Manual parsing)
|
|
28
|
+
|
|
29
|
+
clear-cache (清理缓存)(Clear cache)
|
|
30
|
+
|
|
31
|
+
## 项目贡献者 (Contributors)
|
|
32
|
+
|
|
33
|
+
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
34
|
+
|----------------------|-------------------------|
|
|
35
|
+
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
36
|
+
| JH-Ahua | BugPk-Api |
|
|
37
|
+
| (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
|
|
38
|
+
|
|
39
|
+
## 许可协议 (License)
|
|
40
|
+
|
|
41
|
+
本项目采用 MIT 许可证,详情参见 [LICENSE](LICENSE) 文件。
|
|
42
|
+
|
|
43
|
+
This project is licensed under the MIT License, see the [LICENSE](LICENSE) file for details.
|
|
44
|
+
|
|
45
|
+
## 支持我们 (Support Us)
|
|
46
|
+
|
|
47
|
+
如果这个项目对您有帮助,欢迎点亮右上角的 Star ⭐ 支持我们,这将是对所有贡献者最大的鼓励!
|
|
48
|
+
|
|
49
|
+
If this project is helpful to you, please feel free to star it in the upper right corner ⭐ to support us, which will be the greatest encouragement to all contributors!
|