koishi-plugin-bilibili-videolink-analysis 0.6.3 → 1.0.0
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.js +263 -263
- package/package.json +4 -6
- package/readme.md +3 -0
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const logger = new Logger('bilibili-videolink-analysis');
|
|
|
7
7
|
exports.name = 'bilibili-videolink-analysis';
|
|
8
8
|
exports.inject = {
|
|
9
9
|
optional: ['puppeteer'],
|
|
10
|
-
required: ['BiliBiliVideo']
|
|
10
|
+
//required: ['BiliBiliVideo']
|
|
11
11
|
}
|
|
12
12
|
exports.usage = `
|
|
13
13
|
|
|
@@ -24,7 +24,6 @@ exports.usage = `
|
|
|
24
24
|
#### ⚠️ **如果你使用不了本项目,请优先检查:** ⚠️
|
|
25
25
|
#### 若无注册的指令,请关开一下[command插件](/market?keyword=commands+email:shigma10826@gmail.com)(没有指令也不影响解析别人的链接)
|
|
26
26
|
#### 视频内容是否为B站的大会员专属视频/付费视频/充电专属视频
|
|
27
|
-
#### 是否正确配置并启动了[bilibili-login插件](/market?keyword=bilibili-login) (启动即可,不是必须登录)
|
|
28
27
|
#### 接入方法是否支持获取网址链接/小程序卡片消息
|
|
29
28
|
#### 接入方法是否支持视频元素的发送
|
|
30
29
|
#### 发送视频超时/其他网络问题
|
|
@@ -48,7 +47,6 @@ exports.usage = `
|
|
|
48
47
|
特别鸣谢以下项目的支持:
|
|
49
48
|
|
|
50
49
|
- [@summonhim/koishi-plugin-bili-parser](/market?keyword=bili-parser)
|
|
51
|
-
- [koishi-plugin-iirose-media-request](/market?keyword=iirose-media-request)
|
|
52
50
|
|
|
53
51
|
---
|
|
54
52
|
|
|
@@ -74,16 +72,16 @@ exports.Config = Schema.intersect([
|
|
|
74
72
|
Schema.const('4').description('返回视频和视频直链'),
|
|
75
73
|
Schema.const('5').description('返回视频,仅在日志记录视频直链'),
|
|
76
74
|
]).role('radio').default('2').description("是否返回` 视频/视频直链 `"),
|
|
77
|
-
Video_ClarityPriority: Schema.union([
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
]).role('radio').default('1').description("发送的视频清晰度优先策略"),
|
|
75
|
+
//Video_ClarityPriority: Schema.union([
|
|
76
|
+
// Schema.const('1').description('低清晰度优先(低清晰度的视频发得快一点)'),
|
|
77
|
+
// Schema.const('2').description('高清晰度优先(清晰的还是去B站看吧)'),
|
|
78
|
+
//]).role('radio').default('1').description("发送的视频清晰度优先策略"),
|
|
81
79
|
BVnumberParsing: Schema.boolean().default(true).description("是否允许根据`独立的BV号`解析视频 `开启后,可以通过视频的BV号解析视频。` <br> [触发说明见README](https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis)"),
|
|
82
|
-
Maximumduration: Schema.number().default(25).description("允许解析的视频最大时长(分钟)`超过这个时长 就不会发视频`").min(1),
|
|
83
|
-
Maximumduration_tip: Schema.union([
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
]).description("对过长视频的文字提示内容").default('视频太长啦!还是去B站看吧~'),
|
|
80
|
+
//Maximumduration: Schema.number().default(25).description("允许解析的视频最大时长(分钟)`超过这个时长 就不会发视频`").min(1),
|
|
81
|
+
//Maximumduration_tip: Schema.union([
|
|
82
|
+
// Schema.const('不返回文字提示').description('不返回文字提示'),
|
|
83
|
+
// Schema.string().description('返回文字提示(请在右侧填写文字内容)').default('视频太长啦!还是去B站看吧~'),
|
|
84
|
+
//]).description("对过长视频的文字提示内容").default('视频太长啦!还是去B站看吧~'),
|
|
87
85
|
MinimumTimeInterval: Schema.number().default(180).description("若干`秒`内 不再处理相同链接 `防止多bot互相触发 导致的刷屏/性能浪费`").min(1),
|
|
88
86
|
}).description("基础设置"),
|
|
89
87
|
|
|
@@ -110,7 +108,7 @@ exports.Config = Schema.intersect([
|
|
|
110
108
|
]);
|
|
111
109
|
|
|
112
110
|
function apply(ctx, config) {
|
|
113
|
-
|
|
111
|
+
|
|
114
112
|
ctx.middleware(async (session, next) => {
|
|
115
113
|
// 如果允许解析 BV 号,则进行解析
|
|
116
114
|
if (config.BVnumberParsing) {
|
|
@@ -129,7 +127,197 @@ function apply(ctx, config) {
|
|
|
129
127
|
return next();
|
|
130
128
|
});
|
|
131
129
|
|
|
132
|
-
ctx.command('
|
|
130
|
+
ctx.command('B站点播')
|
|
131
|
+
ctx.command('退出登录', '退出B站账号')
|
|
132
|
+
.action(async ({ session }) => {
|
|
133
|
+
const page = await ctx.puppeteer.page();
|
|
134
|
+
await page.goto('https://www.bilibili.com/', { waitUntil: 'networkidle2' });
|
|
135
|
+
|
|
136
|
+
const loginButtonSelector = '.right-entry__outside.go-login-btn';
|
|
137
|
+
const isLoggedIn = await page.$(loginButtonSelector) === null;
|
|
138
|
+
|
|
139
|
+
if (!isLoggedIn) {
|
|
140
|
+
await page.close();
|
|
141
|
+
return '您尚未登录。';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const avatarLinkSelector = '.header-entry-mini';
|
|
145
|
+
const logoutButtonSelector = '.logout-item';
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const avatarElement = await page.$(avatarLinkSelector);
|
|
149
|
+
if (avatarElement) {
|
|
150
|
+
await avatarElement.hover();
|
|
151
|
+
await page.waitForSelector(logoutButtonSelector, { visible: true });
|
|
152
|
+
|
|
153
|
+
await page.click(logoutButtonSelector);
|
|
154
|
+
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
156
|
+
|
|
157
|
+
await page.close();
|
|
158
|
+
return '已成功退出登录。';
|
|
159
|
+
} else {
|
|
160
|
+
await page.close();
|
|
161
|
+
return '找不到用户头像,无法退出登录。';
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
await page.close();
|
|
165
|
+
console.error('Error during logout:', error);
|
|
166
|
+
return '退出登录时出错。';
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
ctx.command('B站点播/登录', '登录B站账号')
|
|
171
|
+
.alias("登陆")
|
|
172
|
+
.action(async ({ session }) => {
|
|
173
|
+
const page = await ctx.puppeteer.page();
|
|
174
|
+
await page.goto('https://www.bilibili.com/', { waitUntil: 'networkidle2' });
|
|
175
|
+
|
|
176
|
+
const loginButtonSelector = '.right-entry__outside.go-login-btn';
|
|
177
|
+
const isLoggedIn = await page.$(loginButtonSelector) === null;
|
|
178
|
+
|
|
179
|
+
if (isLoggedIn) {
|
|
180
|
+
await page.close();
|
|
181
|
+
return '您已经登录了。';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await page.click(loginButtonSelector);
|
|
185
|
+
|
|
186
|
+
const qrCodeSelector = '.login-scan-box img';
|
|
187
|
+
await page.waitForSelector(qrCodeSelector);
|
|
188
|
+
const qrCodeUrl = await page.$eval(qrCodeSelector, img => img.src);
|
|
189
|
+
|
|
190
|
+
await session.send(h.image(qrCodeUrl, 'image/png'));
|
|
191
|
+
await session.send('请扫描二维码进行登录。');
|
|
192
|
+
|
|
193
|
+
let attempts = 0;
|
|
194
|
+
let loginSuccessful = false;
|
|
195
|
+
|
|
196
|
+
while (attempts < 6) {
|
|
197
|
+
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait
|
|
198
|
+
const isStillLoggedIn = await page.$(loginButtonSelector) === null;
|
|
199
|
+
|
|
200
|
+
if (isStillLoggedIn) {
|
|
201
|
+
loginSuccessful = true;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
attempts++;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await page.close();
|
|
209
|
+
|
|
210
|
+
return loginSuccessful ? '登录成功!' : '登录失败,请重试。';
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (config.loggerinfo) {
|
|
214
|
+
ctx.command('B站点播/调试点播 [keyword]', '调试时点播B站视频')
|
|
215
|
+
.option('video', '-v 解析返回视频')
|
|
216
|
+
.option('audio', '-a 解析返回语音')
|
|
217
|
+
.option('link', '-l 解析返回链接')
|
|
218
|
+
.option('page', '-p <page:number> 指定页数', { fallback: '1' })
|
|
219
|
+
.example('调试点播 遠い空へ -v')
|
|
220
|
+
.action(async ({ options, session }, keyword) => {
|
|
221
|
+
if (!keyword) {
|
|
222
|
+
await session.execute('调试点播 -h');
|
|
223
|
+
return '没输入keyword';
|
|
224
|
+
}
|
|
225
|
+
const url = `https://search.bilibili.com/video?keyword=${encodeURIComponent(keyword)}&page=${options.page}&o=30`;
|
|
226
|
+
const page = await ctx.puppeteer.page();
|
|
227
|
+
await page.goto(url, {
|
|
228
|
+
waitUntil: 'networkidle2',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// 获取视频列表并为每个视频元素添加序号
|
|
232
|
+
const videos = await page.evaluate((point) => {
|
|
233
|
+
const items = Array.from(document.querySelectorAll('.video-list-item:not([style*="display: none"])'));
|
|
234
|
+
return items.map((item, index) => {
|
|
235
|
+
const link = item.querySelector('a');
|
|
236
|
+
const href = link?.getAttribute('href') || '';
|
|
237
|
+
const idMatch = href.match(/\/video\/(BV\w+)\//);
|
|
238
|
+
const id = idMatch ? idMatch[1] : '';
|
|
239
|
+
if (!id) {
|
|
240
|
+
const htmlElement = item;
|
|
241
|
+
htmlElement.style.display = 'none';
|
|
242
|
+
} else {
|
|
243
|
+
const overlay = document.createElement('div');
|
|
244
|
+
overlay.style.position = 'absolute';
|
|
245
|
+
overlay.style.top = `${point[0]}%`;
|
|
246
|
+
overlay.style.left = `${point[1]}%`;
|
|
247
|
+
overlay.style.transform = 'translate(-50%, -50%)';
|
|
248
|
+
overlay.style.fontSize = '48px';
|
|
249
|
+
overlay.style.fontWeight = 'bold';
|
|
250
|
+
overlay.style.color = 'black';
|
|
251
|
+
overlay.style.zIndex = '10';
|
|
252
|
+
overlay.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
|
|
253
|
+
overlay.style.padding = '10px';
|
|
254
|
+
overlay.style.borderRadius = '8px';
|
|
255
|
+
overlay.textContent = `${index + 1}`;
|
|
256
|
+
const videoElement = item;
|
|
257
|
+
videoElement.style.position = 'relative';
|
|
258
|
+
videoElement.appendChild(overlay);
|
|
259
|
+
}
|
|
260
|
+
return { id };
|
|
261
|
+
}).filter(video => video.id);
|
|
262
|
+
}, config.point);
|
|
263
|
+
|
|
264
|
+
// 如果开启了日志调试模式,打印获取到的视频信息
|
|
265
|
+
if (config.loggerinfo) {
|
|
266
|
+
ctx.logger.info(options);
|
|
267
|
+
ctx.logger.info(`共找到 ${videos.length} 个视频:`);
|
|
268
|
+
videos.forEach((video, index) => {
|
|
269
|
+
ctx.logger.info(`序号 ${index + 1}: ID - ${video.id}`);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (videos.length === 0) {
|
|
273
|
+
await page.close();
|
|
274
|
+
return '未找到相关视频。';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 动态调整窗口大小以适应视频数量
|
|
278
|
+
const viewportHeight = 200 + videos.length * 100;
|
|
279
|
+
await page.setViewport({
|
|
280
|
+
width: 1440,
|
|
281
|
+
height: viewportHeight,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
let msg;
|
|
285
|
+
// 截取整个页面
|
|
286
|
+
const imgBuf = await page.screenshot({ fullPage: true });
|
|
287
|
+
msg = h.image(imgBuf, 'image/png');
|
|
288
|
+
|
|
289
|
+
await page.close();
|
|
290
|
+
|
|
291
|
+
// 发送截图
|
|
292
|
+
await session.send(msg);
|
|
293
|
+
// 提示用户输入
|
|
294
|
+
await session.send(`请选择视频的序号:`);
|
|
295
|
+
// 等待用户输入
|
|
296
|
+
const userChoice = await session.prompt(config.timeout * 1000);
|
|
297
|
+
const choiceIndex = parseInt(userChoice) - 1;
|
|
298
|
+
if (isNaN(choiceIndex) || choiceIndex < 0 || choiceIndex >= videos.length) {
|
|
299
|
+
return '输入无效,请输入正确的序号。';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 返回用户选择的视频ID
|
|
303
|
+
const chosenVideo = videos[choiceIndex];
|
|
304
|
+
// 如果开启了日志调试模式,打印用户选择的视频信息
|
|
305
|
+
if (config.loggerinfo) {
|
|
306
|
+
ctx.logger.info(`渲染序号设置\noverlay.style.top = ${config.point[0]}% \noverlay.style.left = ${config.point[1]}%`);
|
|
307
|
+
ctx.logger.info(`用户选择了序号 ${choiceIndex + 1}: ID - ${chosenVideo.id}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (config.enable) {
|
|
311
|
+
// 开启自动解析了
|
|
312
|
+
session.content = `https://www.bilibili.com/video/${chosenVideo.id}`;
|
|
313
|
+
const ret = await extractLinks(session, config, ctx, lastProcessedUrls, logger);
|
|
314
|
+
if (ret && !isLinkProcessedRecently(ret, lastProcessedUrls, config, logger)) {
|
|
315
|
+
await processVideoFromLink(session, config, ctx, lastProcessedUrls, logger, ret, options);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
ctx.command('B站点播/点播 [keyword]', '点播B站视频')
|
|
133
321
|
.option('video', '-v 解析返回视频')
|
|
134
322
|
.option('audio', '-a 解析返回语音')
|
|
135
323
|
.option('link', '-l 解析返回链接')
|
|
@@ -138,7 +326,7 @@ function apply(ctx, config) {
|
|
|
138
326
|
.action(async ({ options, session }, keyword) => {
|
|
139
327
|
if (!keyword) {
|
|
140
328
|
await session.execute('点播 -h')
|
|
141
|
-
return '
|
|
329
|
+
return '没输入点播内容'
|
|
142
330
|
}
|
|
143
331
|
|
|
144
332
|
|
|
@@ -151,13 +339,13 @@ function apply(ctx, config) {
|
|
|
151
339
|
|
|
152
340
|
await page.addStyleTag({
|
|
153
341
|
content: `
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
342
|
+
div.bili-header,
|
|
343
|
+
div.login-tip,
|
|
344
|
+
div.v-popover,
|
|
345
|
+
div.right-entry__outside {
|
|
346
|
+
display: none !important;
|
|
347
|
+
}
|
|
348
|
+
`
|
|
161
349
|
})
|
|
162
350
|
// 获取视频列表并为每个视频元素添加序号
|
|
163
351
|
const videos = await page.evaluate((point) => {
|
|
@@ -263,6 +451,26 @@ function apply(ctx, config) {
|
|
|
263
451
|
}
|
|
264
452
|
})
|
|
265
453
|
|
|
454
|
+
async function handleBilibiliMedia(lastretUrl, config) {
|
|
455
|
+
const fullAPIurl = `https://api.xingzhige.com/API/b_parse/?url=${encodeURIComponent(lastretUrl)}`;
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
// 发起请求,解析 Bilibili 视频信息
|
|
459
|
+
const data = await ctx.http.get(fullAPIurl);
|
|
460
|
+
// 检查返回的状态码是否为0,表示成功
|
|
461
|
+
if (data.code === 0 && data.msg === "video" && data.data && data.data.video) {
|
|
462
|
+
const videoData = data.data.video;
|
|
463
|
+
const videoUrl = videoData.url; // 视频直链
|
|
464
|
+
// 返回视频直链
|
|
465
|
+
return videoUrl;
|
|
466
|
+
} else {
|
|
467
|
+
throw new Error("解析视频信息失败或非视频类型内容");
|
|
468
|
+
}
|
|
469
|
+
} catch (error) {
|
|
470
|
+
logger.error("请求解析 API 失败或处理出错:", error);
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
266
474
|
|
|
267
475
|
//判断是否需要解析
|
|
268
476
|
async function isProcessLinks(session, config, ctx, lastProcessedUrls, logger) {
|
|
@@ -324,7 +532,7 @@ function apply(ctx, config) {
|
|
|
324
532
|
//解析视频并返回
|
|
325
533
|
async function processVideoFromLink(session, config, ctx, lastProcessedUrls, logger, ret, options = { video: true }) {
|
|
326
534
|
const lastretUrl = extractLastUrl(ret);
|
|
327
|
-
|
|
535
|
+
|
|
328
536
|
let mediaData = '';
|
|
329
537
|
|
|
330
538
|
if (config.waitTip_Switch) {
|
|
@@ -343,17 +551,8 @@ function apply(ctx, config) {
|
|
|
343
551
|
}
|
|
344
552
|
|
|
345
553
|
if (config.VideoParsing_ToLink) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
bilibilimediaDataURL = mediaData[0].url;
|
|
349
|
-
const videoDuration = mediaData[0].duration; // 提取视频时长,单位为秒
|
|
350
|
-
|
|
351
|
-
if (videoDuration > config.Maximumduration * 60) {
|
|
352
|
-
if (config.Maximumduration_tip) {
|
|
353
|
-
await session.send(config.Maximumduration_tip);
|
|
354
|
-
}
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
554
|
+
const bilibilimediaDataURL = await handleBilibiliMedia(lastretUrl);
|
|
555
|
+
|
|
357
556
|
if (options.link) { // 发送链接
|
|
358
557
|
await session.send(h.text(bilibilimediaDataURL));
|
|
359
558
|
return;
|
|
@@ -442,10 +641,10 @@ function apply(ctx, config) {
|
|
|
442
641
|
this.config = config;
|
|
443
642
|
}
|
|
444
643
|
/**
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
644
|
+
* 解析 ID 类型
|
|
645
|
+
* @param id 视频 ID
|
|
646
|
+
* @returns type: ID 类型, id: 视频 ID
|
|
647
|
+
*/
|
|
449
648
|
vid_type_parse(id) {
|
|
450
649
|
var idRegex = [
|
|
451
650
|
{
|
|
@@ -472,10 +671,10 @@ function apply(ctx, config) {
|
|
|
472
671
|
};
|
|
473
672
|
}
|
|
474
673
|
/**
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
674
|
+
* 根据视频 ID 查找视频信息
|
|
675
|
+
* @param id 视频 ID
|
|
676
|
+
* @returns 视频信息 Json
|
|
677
|
+
*/
|
|
479
678
|
async fetch_video_info(id) {
|
|
480
679
|
var ret;
|
|
481
680
|
const vid = this.vid_type_parse(id);
|
|
@@ -501,10 +700,10 @@ function apply(ctx, config) {
|
|
|
501
700
|
return ret;
|
|
502
701
|
}
|
|
503
702
|
/**
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
703
|
+
* 生成视频信息
|
|
704
|
+
* @param id 视频 ID
|
|
705
|
+
* @returns 文字视频信息
|
|
706
|
+
*/
|
|
508
707
|
async gen_context(id) {
|
|
509
708
|
const info = await this.fetch_video_info(id);
|
|
510
709
|
if (!info || !info["data"])
|
|
@@ -541,10 +740,10 @@ function apply(ctx, config) {
|
|
|
541
740
|
}
|
|
542
741
|
|
|
543
742
|
/**
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
743
|
+
* 链接类型解析
|
|
744
|
+
* @param content 传入消息
|
|
745
|
+
* @returns type: "链接类型", id :"内容ID"
|
|
746
|
+
*/
|
|
548
747
|
function link_type_parser(content) {
|
|
549
748
|
var linkRegex = [
|
|
550
749
|
{
|
|
@@ -610,12 +809,12 @@ function apply(ctx, config) {
|
|
|
610
809
|
}
|
|
611
810
|
|
|
612
811
|
/**
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
812
|
+
* 类型执行器
|
|
813
|
+
* @param ctx Context
|
|
814
|
+
* @param config Config
|
|
815
|
+
* @param element 链接列表
|
|
816
|
+
* @returns 解析来的文本
|
|
817
|
+
*/
|
|
619
818
|
async function type_processer(ctx, config, element) {
|
|
620
819
|
var ret = "";
|
|
621
820
|
switch (element["type"]) {
|
|
@@ -648,10 +847,10 @@ function apply(ctx, config) {
|
|
|
648
847
|
this.config = config;
|
|
649
848
|
}
|
|
650
849
|
/**
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
850
|
+
* 根据短链接重定向获取正常链接
|
|
851
|
+
* @param id 短链接 ID
|
|
852
|
+
* @returns 正常链接
|
|
853
|
+
*/
|
|
655
854
|
async get_redir_url(id) {
|
|
656
855
|
var data = await this.ctx.http.get("https://b23.tv/" + id, {
|
|
657
856
|
redirect: "manual",
|
|
@@ -670,10 +869,10 @@ function apply(ctx, config) {
|
|
|
670
869
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
671
870
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
672
871
|
/**
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
872
|
+
* 检查看看一个url是否返回403,或者无法访问,主要用在通过bilibili官方api拿到的视频流
|
|
873
|
+
* @param url 链接
|
|
874
|
+
* @returns boolean
|
|
875
|
+
*/
|
|
677
876
|
async function checkResponseStatus(url) {
|
|
678
877
|
try {
|
|
679
878
|
const response = await ctx.http(url, {
|
|
@@ -701,205 +900,6 @@ function apply(ctx, config) {
|
|
|
701
900
|
}
|
|
702
901
|
}
|
|
703
902
|
|
|
704
|
-
async function handleBilibiliMedia(bilibiliVideo, originUrl) {
|
|
705
|
-
const GetVideoStream = async (h5videoStream, pcvideoStream, cid) => {
|
|
706
|
-
if (!h5videoStream.data ||
|
|
707
|
-
!pcvideoStream.data ||
|
|
708
|
-
!h5videoStream.data.accept_quality ||
|
|
709
|
-
!pcvideoStream.data.accept_quality ||
|
|
710
|
-
!h5videoStream.data.accept_format ||
|
|
711
|
-
!pcvideoStream.data.accept_format)
|
|
712
|
-
throw new Error('无法获取清晰度信息, 可能该视频为大会员专享或者该视频为付费视频/充电专属视频!或者账号被风控。');
|
|
713
|
-
const h5Quality = h5videoStream.data.accept_quality;
|
|
714
|
-
const pcQuality = pcvideoStream.data.accept_quality;
|
|
715
|
-
if (config.loggerinfo) {
|
|
716
|
-
logger.info(`h5Quality清晰度: ` + h5Quality)
|
|
717
|
-
logger.info(`pcQuality清晰度: ` + pcQuality)
|
|
718
|
-
}
|
|
719
|
-
const CombinedQualityInfo = h5Quality
|
|
720
|
-
.filter((item, index) => !(h5videoStream.data?.accept_format?.includes('flv') && h5videoStream.data.accept_format.split(',')[index].includes('flv')))
|
|
721
|
-
.map(item => ['html5', item])
|
|
722
|
-
.concat(pcQuality
|
|
723
|
-
.filter((item, index) => !(pcvideoStream.data?.accept_format?.includes('flv') && pcvideoStream.data.accept_format.split(',')[index].includes('flv')))
|
|
724
|
-
.map(item => ['pc', item]));
|
|
725
|
-
CombinedQualityInfo.sort((a, b) => {
|
|
726
|
-
if (b[1] === a[1]) {
|
|
727
|
-
// 如果两者数字相等
|
|
728
|
-
if (a[0] === 'html5') {
|
|
729
|
-
// html5排在前面
|
|
730
|
-
return -1;
|
|
731
|
-
}
|
|
732
|
-
else if (b[0] === 'html5') {
|
|
733
|
-
// pc排在前面
|
|
734
|
-
return 1;
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
// 如果都是相同类型,则按照原顺序
|
|
738
|
-
return 0;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
// 根据配置决定排序顺序
|
|
743
|
-
switch (config.Video_ClarityPriority) {
|
|
744
|
-
case '1':
|
|
745
|
-
//logger.info(`低清晰度优先排序,a[1]: ${a[1]}, b[1]: ${b[1]}`);
|
|
746
|
-
return a[1] - b[1]; // 从低到高排序(低清晰度优先)
|
|
747
|
-
case '2':
|
|
748
|
-
//logger.info(`高清晰度优先排序,a[1]: ${a[1]}, b[1]: ${b[1]}`);
|
|
749
|
-
return b[1] - a[1]; // 从高到低排序(高清晰度优先)
|
|
750
|
-
default:
|
|
751
|
-
//logger.warn(`未知的视频清晰度优先级配置: ${config.Video_ClarityPriority}`);
|
|
752
|
-
return 0; // 默认保持原顺序
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
outerLoop: for (const [index, item] of CombinedQualityInfo.entries()) {
|
|
757
|
-
|
|
758
|
-
videoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cid, item[1], item[0], 1);
|
|
759
|
-
|
|
760
|
-
if (!videoStream || !videoStream.data || !videoStream.data.durl) {
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
if (await checkResponseStatus(videoStream.data.durl[0].url) === true) {
|
|
764
|
-
break outerLoop;
|
|
765
|
-
}
|
|
766
|
-
const isLastItem = index === CombinedQualityInfo.length - 1;
|
|
767
|
-
if (isLastItem) {
|
|
768
|
-
throw new Error('在尝试了全部清晰度和平台后,无法获取流媒体');
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
return videoStream;
|
|
772
|
-
};
|
|
773
|
-
const duration = [];
|
|
774
|
-
const cids = [];
|
|
775
|
-
const cover = [];
|
|
776
|
-
const name = [];
|
|
777
|
-
const type = [];
|
|
778
|
-
const singer = [];
|
|
779
|
-
const link = [];
|
|
780
|
-
const origin = [];
|
|
781
|
-
const bitRate = [];
|
|
782
|
-
const url = [];
|
|
783
|
-
let bvid;
|
|
784
|
-
if (originUrl.includes('http') && originUrl.includes('video')) {
|
|
785
|
-
originUrl = originUrl.replace(/\?/g, '/');
|
|
786
|
-
bvid = originUrl.split('/video/')[1].split('/')[0];
|
|
787
|
-
}
|
|
788
|
-
else if (originUrl.includes('BV') || originUrl.includes('bv')) {
|
|
789
|
-
bvid = originUrl;
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
const mediaData = returnErrorMediaData(['暂不支持']);
|
|
793
|
-
return mediaData;
|
|
794
|
-
}
|
|
795
|
-
const videoInfo = await bilibiliVideo.getBilibiliVideoDetail(null, bvid);
|
|
796
|
-
if (!videoInfo || !videoInfo.data) {
|
|
797
|
-
const mediaData = returnErrorMediaData(['这个不是正确的bv号']);
|
|
798
|
-
return mediaData;
|
|
799
|
-
}
|
|
800
|
-
videoInfo.data.pages.forEach((page) => {
|
|
801
|
-
if (!videoInfo.data)
|
|
802
|
-
return;
|
|
803
|
-
cids.push(page.cid);
|
|
804
|
-
cover.push(videoInfo.data.pic);
|
|
805
|
-
type.push('video');
|
|
806
|
-
singer.push(videoInfo.data.owner.name);
|
|
807
|
-
link.push(`https://www.bilibili.com/video/${bvid}`);
|
|
808
|
-
duration.push(page.duration + 1 || videoInfo.data.duration + 1);
|
|
809
|
-
origin.push('bilibili');
|
|
810
|
-
if (videoInfo.data.pages.length <= 1) {
|
|
811
|
-
name.push(videoInfo.data.title);
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
name.push(`${videoInfo.data.title} - P${page.part}`);
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
const avid = videoInfo.data.aid;
|
|
818
|
-
let videoStream;
|
|
819
|
-
|
|
820
|
-
const h5videoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cids[0], 112, 'html5', 1);
|
|
821
|
-
const pcvideoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cids[0], 112, 'pc', 1);
|
|
822
|
-
if (!h5videoStream || !pcvideoStream)
|
|
823
|
-
return returnErrorMediaData(['无法获取B站视频流']);
|
|
824
|
-
|
|
825
|
-
const cid = cids[0];
|
|
826
|
-
videoStream = await GetVideoStream(h5videoStream, pcvideoStream, cid);
|
|
827
|
-
if (!videoStream || !videoStream.data || !videoStream.data.quality || !videoStream.data.durl)
|
|
828
|
-
return returnErrorMediaData(['无法获取videoStream信息']);
|
|
829
|
-
bitRate.push(videoStream.data.quality);
|
|
830
|
-
url.push(videoStream.data.durl[0].url);
|
|
831
|
-
/*
|
|
832
|
-
for (const cid of cids) {
|
|
833
|
-
videoStream = await GetVideoStream(h5videoStream, pcvideoStream, cid);
|
|
834
|
-
if (!videoStream || !videoStream.data || !videoStream.data.quality || !videoStream.data.durl)
|
|
835
|
-
return returnErrorMediaData(['无法获取videoStream信息']);
|
|
836
|
-
bitRate.push(videoStream.data.quality);
|
|
837
|
-
url.push(videoStream.data.durl[0].url);
|
|
838
|
-
}
|
|
839
|
-
*/
|
|
840
|
-
const mediaData = returnCompleteMediaData(type, name, singer, cover, url, duration, bitRate, [], origin, link);
|
|
841
|
-
return mediaData;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
/**
|
|
845
|
-
* 返回包含错误信息的mediaData
|
|
846
|
-
* @param errorMsg 错误信息
|
|
847
|
-
* @return mediaData
|
|
848
|
-
*/
|
|
849
|
-
function returnErrorMediaData(errorMsgs) {
|
|
850
|
-
const errorMediaDataArray = [];
|
|
851
|
-
for (const errorMsg of errorMsgs) {
|
|
852
|
-
const mediaData = {
|
|
853
|
-
type: 'music',
|
|
854
|
-
name: '0',
|
|
855
|
-
signer: '0',
|
|
856
|
-
cover: '0',
|
|
857
|
-
link: '0',
|
|
858
|
-
url: '0',
|
|
859
|
-
duration: 0,
|
|
860
|
-
bitRate: 0,
|
|
861
|
-
lyrics: null,
|
|
862
|
-
origin: null,
|
|
863
|
-
error: errorMsg,
|
|
864
|
-
};
|
|
865
|
-
errorMediaDataArray.push(mediaData);
|
|
866
|
-
}
|
|
867
|
-
return errorMediaDataArray;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* 返回完整的mediaData
|
|
872
|
-
* @param type 类型
|
|
873
|
-
* @param name 标题
|
|
874
|
-
* @param signer 创作者
|
|
875
|
-
* @param cover 封面图url
|
|
876
|
-
* @param url 链接
|
|
877
|
-
* @param duration 时长
|
|
878
|
-
* @param bitRate 比特率
|
|
879
|
-
* @return mediaData
|
|
880
|
-
*/
|
|
881
|
-
function returnCompleteMediaData(typeList, nameList, signerList, coverList, urlList, durationList, bitRateList, lyricsList = [], origin = [], linkList = [], commentList) {
|
|
882
|
-
const mediaDataArray = [];
|
|
883
|
-
for (let i = 0; i < urlList.length; i++) {
|
|
884
|
-
const mediaData = {
|
|
885
|
-
type: typeList[i],
|
|
886
|
-
name: nameList[i],
|
|
887
|
-
signer: signerList[i],
|
|
888
|
-
cover: coverList[i],
|
|
889
|
-
link: linkList[i] || urlList[i],
|
|
890
|
-
url: urlList[i],
|
|
891
|
-
duration: durationList[i],
|
|
892
|
-
bitRate: bitRateList[i],
|
|
893
|
-
lyrics: lyricsList[i] || null,
|
|
894
|
-
origin: origin[i] || null,
|
|
895
|
-
comment: commentList?.[i] || undefined,
|
|
896
|
-
error: null,
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
mediaDataArray.push(mediaData);
|
|
900
|
-
}
|
|
901
|
-
return mediaDataArray;
|
|
902
|
-
}
|
|
903
903
|
|
|
904
904
|
}
|
|
905
905
|
exports.apply = apply;
|
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-bilibili-videolink-analysis",
|
|
3
|
-
"description": "[<ruby>Bilibili视频解析<rp>(</rp><rt>点我查看食用方法</rt><rp>)</rp></ruby>](https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis)解析B
|
|
3
|
+
"description": "[<ruby>Bilibili视频解析<rp>(</rp><rt>点我查看食用方法</rt><rp>)</rp></ruby>](https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis)解析B站链接(支持小程序卡片)支持搜索点播功能!灵感来自完美的 [bili-parser](/market?keyword=bili-parser) !",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "1.0.0",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"typings": "lib/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"lib",
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"scripts": {},
|
|
13
12
|
"homepage": "https://github.com/shangxueink/koishi-shangxue-apps/tree/main/",
|
|
14
13
|
"bugs": {
|
|
15
14
|
"url": "https://github.com/shangxueink/koishi-shangxue-apps/issues"
|
|
@@ -23,7 +22,6 @@
|
|
|
23
22
|
"bilibili-videolink-analysis"
|
|
24
23
|
],
|
|
25
24
|
"peerDependencies": {
|
|
26
|
-
"koishi": "^4.16.8"
|
|
27
|
-
"koishi-plugin-bilibili-login": "^0.1.50"
|
|
25
|
+
"koishi": "^4.16.8"
|
|
28
26
|
}
|
|
29
|
-
}
|
|
27
|
+
}
|