koishi-plugin-video-parser-all 0.1.1 → 0.1.2
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 +1 -0
- package/lib/index.js +130 -101
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -18,13 +18,11 @@ exports.Config = koishi_1.Schema.object({
|
|
|
18
18
|
.role('textarea')
|
|
19
19
|
.default(`\${标题} \${tab} \${UP主}
|
|
20
20
|
\${简介}
|
|
21
|
-
点赞:\${点赞} \${tab} 投币:\${投币}
|
|
22
|
-
收藏:\${收藏} \${tab} 转发:\${转发}
|
|
23
|
-
观看:\${观看} \${tab} 弹幕:\${弹幕}
|
|
24
21
|
\${~~~}
|
|
25
22
|
\${封面}`),
|
|
26
23
|
showVideoUrl: koishi_1.Schema.boolean().default(false),
|
|
27
24
|
maxDescLength: koishi_1.Schema.number().default(200),
|
|
25
|
+
bugpkUniversalApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos'),
|
|
28
26
|
bugpkDouyinMainApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin'),
|
|
29
27
|
bugpkDouyinBackupApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/dyjx'),
|
|
30
28
|
bugpkKuaishouApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx'),
|
|
@@ -33,20 +31,103 @@ exports.Config = koishi_1.Schema.object({
|
|
|
33
31
|
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略消息发送失败的错误(避免日志刷屏)'),
|
|
34
32
|
});
|
|
35
33
|
const processed = new Map();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
bilibili: /(bilibili\.com|b23\.tv|哔哩哔哩|B站)/i
|
|
34
|
+
const PLATFORM_KEYWORDS = {
|
|
35
|
+
bilibili: ['bilibili', 'b23', 'B站'],
|
|
36
|
+
kuaishou: ['kuaishou', '快手'],
|
|
37
|
+
douyin: ['douyin', '抖音']
|
|
41
38
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
function extractUrl(content) {
|
|
40
|
+
const urlMatch = content.match(/https?:\/\/[^\s]+/i);
|
|
41
|
+
return urlMatch ? urlMatch[0] : null;
|
|
42
|
+
}
|
|
43
|
+
function hasPlatformKeyword(content) {
|
|
44
|
+
const lowerContent = content.toLowerCase();
|
|
45
|
+
return Object.values(PLATFORM_KEYWORDS).some(keywords => keywords.some(keyword => {
|
|
46
|
+
const target = /[\u4e00-\u9fa5]/.test(keyword) ? keyword : keyword.toLowerCase();
|
|
47
|
+
return lowerContent.includes(target);
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
function getPlatformType(url) {
|
|
51
|
+
const lowerUrl = url.toLowerCase();
|
|
52
|
+
if (PLATFORM_KEYWORDS.douyin.some(k => lowerUrl.includes(k.toLowerCase())))
|
|
53
|
+
return 'douyin';
|
|
54
|
+
if (PLATFORM_KEYWORDS.kuaishou.some(k => lowerUrl.includes(k.toLowerCase())))
|
|
55
|
+
return 'kuaishou';
|
|
56
|
+
if (PLATFORM_KEYWORDS.bilibili.some(k => lowerUrl.includes(k.toLowerCase())))
|
|
57
|
+
return 'bilibili';
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function parseUniversalApiData(data) {
|
|
61
|
+
return {
|
|
62
|
+
title: data.title || '无标题',
|
|
63
|
+
author: data.author || '未知作者',
|
|
64
|
+
desc: data.title || '无简介',
|
|
65
|
+
digg: data.like || 0,
|
|
66
|
+
coin: 0,
|
|
67
|
+
collect: 0,
|
|
68
|
+
share: 0,
|
|
69
|
+
play: 0,
|
|
70
|
+
danmaku: 0,
|
|
71
|
+
cover: data.cover || '',
|
|
72
|
+
video: data.url || ''
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function parseDouyin(data) {
|
|
76
|
+
const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
|
|
77
|
+
return {
|
|
78
|
+
title: data.title || '无标题',
|
|
79
|
+
author: data.author?.name || '未知作者',
|
|
80
|
+
desc: data.desc || data.title || '无简介',
|
|
81
|
+
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
82
|
+
cover: data.cover || '',
|
|
83
|
+
video: videoUrl
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function parseKuaishou(data) {
|
|
87
|
+
return {
|
|
88
|
+
title: data.title || '无标题',
|
|
89
|
+
author: '未知作者',
|
|
90
|
+
desc: data.title || '无简介',
|
|
91
|
+
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
92
|
+
cover: data.cover || '',
|
|
93
|
+
video: data.url || ''
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseBilibili(data) {
|
|
97
|
+
const title = data.title || data.Title || '无标题';
|
|
98
|
+
const cover = data.cover || data.imgurl || data.cover_url || data.pic || '';
|
|
99
|
+
const author = data.user?.name || data.author || '未知UP主';
|
|
100
|
+
const desc = data.description || data.desc || title || '无简介';
|
|
101
|
+
let videoUrl = '';
|
|
102
|
+
if (data.url)
|
|
103
|
+
videoUrl = data.url;
|
|
104
|
+
else if (data.video_url)
|
|
105
|
+
videoUrl = data.video_url;
|
|
106
|
+
else if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
|
|
107
|
+
videoUrl = data.videos.reduce((prev, curr) => {
|
|
108
|
+
return (prev.size || 0) > (curr.size || 0) ? prev : curr;
|
|
109
|
+
}).url || '';
|
|
110
|
+
}
|
|
111
|
+
else if (data.play)
|
|
112
|
+
videoUrl = data.play;
|
|
113
|
+
else if (data.durl && Array.isArray(data.durl))
|
|
114
|
+
videoUrl = data.durl[0]?.url || '';
|
|
115
|
+
const digg = data.like || data.digg || 0;
|
|
116
|
+
const coin = data.coin || 0;
|
|
117
|
+
const collect = data.favorite || data.collect || 0;
|
|
118
|
+
const share = data.share || 0;
|
|
119
|
+
const play = data.play || data.view || 0;
|
|
120
|
+
const danmaku = data.danmaku || data.comment || 0;
|
|
121
|
+
return {
|
|
122
|
+
title,
|
|
123
|
+
author,
|
|
124
|
+
desc,
|
|
125
|
+
digg, coin, collect, share, play, danmaku,
|
|
126
|
+
cover,
|
|
127
|
+
video: videoUrl
|
|
128
|
+
};
|
|
47
129
|
}
|
|
48
130
|
function apply(ctx, config) {
|
|
49
|
-
// 修复:移除axios不支持的retry配置,保留核心请求配置
|
|
50
131
|
const request = axios_1.default.create({
|
|
51
132
|
timeout: config.timeout,
|
|
52
133
|
headers: {
|
|
@@ -55,50 +136,21 @@ function apply(ctx, config) {
|
|
|
55
136
|
'Accept': 'application/json, text/plain, */*'
|
|
56
137
|
}
|
|
57
138
|
});
|
|
58
|
-
// 抖音解析
|
|
59
|
-
function parseDouyin(data) {
|
|
60
|
-
const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
|
|
61
|
-
return {
|
|
62
|
-
title: data.title || '无标题',
|
|
63
|
-
author: data.author?.name || '未知作者',
|
|
64
|
-
desc: data.desc || data.title || '无简介',
|
|
65
|
-
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
66
|
-
cover: data.cover || '',
|
|
67
|
-
video: videoUrl
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
// 快手解析
|
|
71
|
-
function parseKuaishou(data) {
|
|
72
|
-
return {
|
|
73
|
-
title: data.title || '无标题',
|
|
74
|
-
author: '未知作者',
|
|
75
|
-
desc: data.title || '无简介',
|
|
76
|
-
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
77
|
-
cover: data.cover || '',
|
|
78
|
-
video: data.url || ''
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
// B站解析(增强:兼容更多返回格式)
|
|
82
|
-
function parseBilibili(data) {
|
|
83
|
-
const title = data.title || data.Title || '';
|
|
84
|
-
const cover = data.cover || data.imgurl || data.cover_url || '';
|
|
85
|
-
const author = data.user?.name || data.author || '未知UP主';
|
|
86
|
-
const desc = data.description || data.desc || title || '无简介';
|
|
87
|
-
const videoUrl = data.url || (data.videos?.length > 0 ? data.videos[0].url : data.video_url || '');
|
|
88
|
-
return {
|
|
89
|
-
title,
|
|
90
|
-
author,
|
|
91
|
-
desc,
|
|
92
|
-
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
93
|
-
cover,
|
|
94
|
-
video: videoUrl
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// 核心:只走专属接口,增强异常捕获
|
|
98
139
|
async function parseVideo(url) {
|
|
99
140
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
141
|
+
ctx.logger.debug(`优先调用通用API: ${config.bugpkUniversalApi}`);
|
|
142
|
+
const res = await request.get(config.bugpkUniversalApi, { params: { url } });
|
|
143
|
+
if (res.data.code === 200 && res.data.data) {
|
|
144
|
+
ctx.logger.debug('通用API解析成功');
|
|
145
|
+
return parseUniversalApiData(res.data.data);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
ctx.logger.debug(`通用API调用失败: ${e.message},尝试平台专属API`);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const platform = getPlatformType(url);
|
|
153
|
+
if (platform === 'douyin') {
|
|
102
154
|
try {
|
|
103
155
|
const res = await request.get(config.bugpkDouyinMainApi, { params: { url } });
|
|
104
156
|
if (res.data.code === 200 && res.data.data)
|
|
@@ -116,8 +168,7 @@ function apply(ctx, config) {
|
|
|
116
168
|
ctx.logger.debug(`抖音备用接口失败: ${e.message}`);
|
|
117
169
|
}
|
|
118
170
|
}
|
|
119
|
-
|
|
120
|
-
if (PLATFORM_REGEX.kuaishou.test(url)) {
|
|
171
|
+
if (platform === 'kuaishou') {
|
|
121
172
|
try {
|
|
122
173
|
const res = await request.get(config.bugpkKuaishouApi, { params: { url } });
|
|
123
174
|
if (res.data.code === 200 && res.data.data)
|
|
@@ -127,8 +178,7 @@ function apply(ctx, config) {
|
|
|
127
178
|
ctx.logger.debug(`快手接口失败: ${e.message}`);
|
|
128
179
|
}
|
|
129
180
|
}
|
|
130
|
-
|
|
131
|
-
if (PLATFORM_REGEX.bilibili.test(url)) {
|
|
181
|
+
if (platform === 'bilibili') {
|
|
132
182
|
try {
|
|
133
183
|
const res = await request.get(config.bugpkBilibiliApi, { params: { url } });
|
|
134
184
|
if (res.data.code === 200 && res.data.data)
|
|
@@ -140,18 +190,16 @@ function apply(ctx, config) {
|
|
|
140
190
|
}
|
|
141
191
|
}
|
|
142
192
|
catch (e) {
|
|
143
|
-
ctx.logger.error(
|
|
193
|
+
ctx.logger.error(`平台专属API解析异常: ${e.message}`);
|
|
144
194
|
}
|
|
145
|
-
// 兜底返回(更友好的提示)
|
|
146
195
|
return {
|
|
147
196
|
title: '解析失败',
|
|
148
197
|
author: '',
|
|
149
|
-
desc: '
|
|
198
|
+
desc: '通用接口和平台专属接口均解析失败,请稍后重试',
|
|
150
199
|
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
151
200
|
cover: '', video: ''
|
|
152
201
|
};
|
|
153
202
|
}
|
|
154
|
-
// 发送结果(核心修复:所有send操作加try/catch,避免中断)
|
|
155
203
|
async function sendResult(session, data) {
|
|
156
204
|
try {
|
|
157
205
|
let text = config.imageParseFormat
|
|
@@ -167,37 +215,26 @@ function apply(ctx, config) {
|
|
|
167
215
|
.replace(/\${tab}/g, '\t')
|
|
168
216
|
.replace(/\${~~~}/g, '——————————————');
|
|
169
217
|
const [beforeCover, afterCover] = text.split('\${封面}');
|
|
170
|
-
|
|
171
|
-
if (beforeCover && beforeCover.trim())
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (data.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
if (afterCover && afterCover.trim()) {
|
|
184
|
-
await session.send(afterCover.trim()).catch(e => {
|
|
218
|
+
const msgParts = [];
|
|
219
|
+
if (beforeCover && beforeCover.trim())
|
|
220
|
+
msgParts.push(beforeCover.trim());
|
|
221
|
+
if (data.cover)
|
|
222
|
+
msgParts.push(koishi_1.h.image(data.cover));
|
|
223
|
+
if (afterCover && afterCover.trim())
|
|
224
|
+
msgParts.push(afterCover.trim());
|
|
225
|
+
if (data.video && config.showVideoUrl)
|
|
226
|
+
msgParts.push(`🔗 无水印链接:${data.video}`);
|
|
227
|
+
if (msgParts.length > 0) {
|
|
228
|
+
await session.send(msgParts.join('\n')).catch(e => {
|
|
185
229
|
if (!config.ignoreSendError)
|
|
186
|
-
ctx.logger.warn(
|
|
230
|
+
ctx.logger.warn(`发送合并消息失败: ${e.message}`);
|
|
187
231
|
});
|
|
188
232
|
}
|
|
189
|
-
if (data.video) {
|
|
233
|
+
if (data.video && !config.showVideoUrl) {
|
|
190
234
|
try {
|
|
191
235
|
await session.send(koishi_1.h.video(data.video)).catch(e => {
|
|
192
|
-
|
|
193
|
-
session.send(`📥 无水印视频链接:${data.video}`).catch(() => { });
|
|
236
|
+
session.send(`📥 无水印视频:${data.video}`).catch(() => { });
|
|
194
237
|
});
|
|
195
|
-
if (config.showVideoUrl) {
|
|
196
|
-
await session.send(`🔗 无水印链接:${data.video}`).catch(e => {
|
|
197
|
-
if (!config.ignoreSendError)
|
|
198
|
-
ctx.logger.warn(`发送链接失败: ${e.message}`);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
238
|
}
|
|
202
239
|
catch (e) {
|
|
203
240
|
await session.send(`📥 无水印视频:${data.video}`).catch(() => { });
|
|
@@ -209,34 +246,27 @@ function apply(ctx, config) {
|
|
|
209
246
|
ctx.logger.error(`发送结果异常: ${e.message}`);
|
|
210
247
|
}
|
|
211
248
|
}
|
|
212
|
-
// 消息监听(增强:加全局异常捕获 + 严格的平台链接校验)
|
|
213
249
|
ctx.on('message', async (session) => {
|
|
214
250
|
try {
|
|
215
251
|
if (!config.enable)
|
|
216
252
|
return;
|
|
217
253
|
const content = session.content.trim();
|
|
218
|
-
|
|
219
|
-
const urlMatch = content.match(/https?:\/\/[^\s]+/);
|
|
220
|
-
if (!urlMatch)
|
|
254
|
+
if (!hasPlatformKeyword(content))
|
|
221
255
|
return;
|
|
222
|
-
const url =
|
|
223
|
-
|
|
224
|
-
if (!isTargetPlatformUrl(url))
|
|
256
|
+
const url = extractUrl(content);
|
|
257
|
+
if (!url)
|
|
225
258
|
return;
|
|
226
|
-
// 去重逻辑
|
|
227
259
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
228
260
|
const now = Date.now();
|
|
229
261
|
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
230
262
|
return;
|
|
231
263
|
processed.set(hash, now);
|
|
232
|
-
// 发送等待提示(加异常捕获)
|
|
233
264
|
if (config.showWaitingTip) {
|
|
234
265
|
await session.send(config.waitingTipText).catch(e => {
|
|
235
266
|
if (!config.ignoreSendError)
|
|
236
267
|
ctx.logger.warn(`发送等待提示失败: ${e.message}`);
|
|
237
268
|
});
|
|
238
269
|
}
|
|
239
|
-
// 解析并发送结果
|
|
240
270
|
const data = await parseVideo(url);
|
|
241
271
|
await sendResult(session, data);
|
|
242
272
|
}
|
|
@@ -244,10 +274,9 @@ function apply(ctx, config) {
|
|
|
244
274
|
ctx.logger.error(`消息处理异常: ${e.message}`);
|
|
245
275
|
}
|
|
246
276
|
});
|
|
247
|
-
// 定时清理缓存
|
|
248
277
|
setInterval(() => {
|
|
249
278
|
const now = Date.now();
|
|
250
279
|
processed.forEach((t, k) => now - t > 86400000 && processed.delete(k));
|
|
251
280
|
}, 3600000);
|
|
252
|
-
ctx.logger.info('
|
|
281
|
+
ctx.logger.info('视频解析插件加载完成');
|
|
253
282
|
}
|