koishi-plugin-bilibili-videolink-analysis 0.6.2 → 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 +270 -273
- package/package.json +4 -6
- package/readme.md +7 -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,8 +108,15 @@ exports.Config = Schema.intersect([
|
|
|
110
108
|
]);
|
|
111
109
|
|
|
112
110
|
function apply(ctx, config) {
|
|
113
|
-
|
|
111
|
+
|
|
114
112
|
ctx.middleware(async (session, next) => {
|
|
113
|
+
// 如果允许解析 BV 号,则进行解析
|
|
114
|
+
if (config.BVnumberParsing) {
|
|
115
|
+
const bvUrls = convertBVToUrl(session.content);
|
|
116
|
+
if (bvUrls.length > 0) {
|
|
117
|
+
session.content += '\n' + bvUrls.join('\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
115
120
|
const links = await isProcessLinks(session, config, ctx, lastProcessedUrls, logger); // 判断是否需要解析
|
|
116
121
|
if (links) {
|
|
117
122
|
const ret = await extractLinks(session, config, ctx, lastProcessedUrls, logger); // 提取链接
|
|
@@ -122,7 +127,197 @@ function apply(ctx, config) {
|
|
|
122
127
|
return next();
|
|
123
128
|
});
|
|
124
129
|
|
|
125
|
-
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站视频')
|
|
126
321
|
.option('video', '-v 解析返回视频')
|
|
127
322
|
.option('audio', '-a 解析返回语音')
|
|
128
323
|
.option('link', '-l 解析返回链接')
|
|
@@ -131,7 +326,7 @@ function apply(ctx, config) {
|
|
|
131
326
|
.action(async ({ options, session }, keyword) => {
|
|
132
327
|
if (!keyword) {
|
|
133
328
|
await session.execute('点播 -h')
|
|
134
|
-
return '
|
|
329
|
+
return '没输入点播内容'
|
|
135
330
|
}
|
|
136
331
|
|
|
137
332
|
|
|
@@ -144,13 +339,13 @@ function apply(ctx, config) {
|
|
|
144
339
|
|
|
145
340
|
await page.addStyleTag({
|
|
146
341
|
content: `
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
342
|
+
div.bili-header,
|
|
343
|
+
div.login-tip,
|
|
344
|
+
div.v-popover,
|
|
345
|
+
div.right-entry__outside {
|
|
346
|
+
display: none !important;
|
|
347
|
+
}
|
|
348
|
+
`
|
|
154
349
|
})
|
|
155
350
|
// 获取视频列表并为每个视频元素添加序号
|
|
156
351
|
const videos = await page.evaluate((point) => {
|
|
@@ -256,21 +451,31 @@ function apply(ctx, config) {
|
|
|
256
451
|
}
|
|
257
452
|
})
|
|
258
453
|
|
|
454
|
+
async function handleBilibiliMedia(lastretUrl, config) {
|
|
455
|
+
const fullAPIurl = `https://api.xingzhige.com/API/b_parse/?url=${encodeURIComponent(lastretUrl)}`;
|
|
259
456
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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("解析视频信息失败或非视频类型内容");
|
|
269
468
|
}
|
|
469
|
+
} catch (error) {
|
|
470
|
+
logger.error("请求解析 API 失败或处理出错:", error);
|
|
471
|
+
return null;
|
|
270
472
|
}
|
|
473
|
+
}
|
|
271
474
|
|
|
475
|
+
//判断是否需要解析
|
|
476
|
+
async function isProcessLinks(session, config, ctx, lastProcessedUrls, logger) {
|
|
272
477
|
// 解析内容中的链接
|
|
273
|
-
const links = link_type_parser(content);
|
|
478
|
+
const links = link_type_parser(session.content);
|
|
274
479
|
if (links.length === 0) {
|
|
275
480
|
return false; // 如果没有找到链接,返回 false
|
|
276
481
|
}
|
|
@@ -327,7 +532,7 @@ function apply(ctx, config) {
|
|
|
327
532
|
//解析视频并返回
|
|
328
533
|
async function processVideoFromLink(session, config, ctx, lastProcessedUrls, logger, ret, options = { video: true }) {
|
|
329
534
|
const lastretUrl = extractLastUrl(ret);
|
|
330
|
-
|
|
535
|
+
|
|
331
536
|
let mediaData = '';
|
|
332
537
|
|
|
333
538
|
if (config.waitTip_Switch) {
|
|
@@ -346,17 +551,8 @@ function apply(ctx, config) {
|
|
|
346
551
|
}
|
|
347
552
|
|
|
348
553
|
if (config.VideoParsing_ToLink) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
bilibilimediaDataURL = mediaData[0].url;
|
|
352
|
-
const videoDuration = mediaData[0].duration; // 提取视频时长,单位为秒
|
|
353
|
-
|
|
354
|
-
if (videoDuration > config.Maximumduration * 60) {
|
|
355
|
-
if (config.Maximumduration_tip) {
|
|
356
|
-
await session.send(config.Maximumduration_tip);
|
|
357
|
-
}
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
554
|
+
const bilibilimediaDataURL = await handleBilibiliMedia(lastretUrl);
|
|
555
|
+
|
|
360
556
|
if (options.link) { // 发送链接
|
|
361
557
|
await session.send(h.text(bilibilimediaDataURL));
|
|
362
558
|
return;
|
|
@@ -445,10 +641,10 @@ function apply(ctx, config) {
|
|
|
445
641
|
this.config = config;
|
|
446
642
|
}
|
|
447
643
|
/**
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
644
|
+
* 解析 ID 类型
|
|
645
|
+
* @param id 视频 ID
|
|
646
|
+
* @returns type: ID 类型, id: 视频 ID
|
|
647
|
+
*/
|
|
452
648
|
vid_type_parse(id) {
|
|
453
649
|
var idRegex = [
|
|
454
650
|
{
|
|
@@ -475,10 +671,10 @@ function apply(ctx, config) {
|
|
|
475
671
|
};
|
|
476
672
|
}
|
|
477
673
|
/**
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
674
|
+
* 根据视频 ID 查找视频信息
|
|
675
|
+
* @param id 视频 ID
|
|
676
|
+
* @returns 视频信息 Json
|
|
677
|
+
*/
|
|
482
678
|
async fetch_video_info(id) {
|
|
483
679
|
var ret;
|
|
484
680
|
const vid = this.vid_type_parse(id);
|
|
@@ -504,10 +700,10 @@ function apply(ctx, config) {
|
|
|
504
700
|
return ret;
|
|
505
701
|
}
|
|
506
702
|
/**
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
703
|
+
* 生成视频信息
|
|
704
|
+
* @param id 视频 ID
|
|
705
|
+
* @returns 文字视频信息
|
|
706
|
+
*/
|
|
511
707
|
async gen_context(id) {
|
|
512
708
|
const info = await this.fetch_video_info(id);
|
|
513
709
|
if (!info || !info["data"])
|
|
@@ -544,10 +740,10 @@ function apply(ctx, config) {
|
|
|
544
740
|
}
|
|
545
741
|
|
|
546
742
|
/**
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
743
|
+
* 链接类型解析
|
|
744
|
+
* @param content 传入消息
|
|
745
|
+
* @returns type: "链接类型", id :"内容ID"
|
|
746
|
+
*/
|
|
551
747
|
function link_type_parser(content) {
|
|
552
748
|
var linkRegex = [
|
|
553
749
|
{
|
|
@@ -613,12 +809,12 @@ function apply(ctx, config) {
|
|
|
613
809
|
}
|
|
614
810
|
|
|
615
811
|
/**
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
812
|
+
* 类型执行器
|
|
813
|
+
* @param ctx Context
|
|
814
|
+
* @param config Config
|
|
815
|
+
* @param element 链接列表
|
|
816
|
+
* @returns 解析来的文本
|
|
817
|
+
*/
|
|
622
818
|
async function type_processer(ctx, config, element) {
|
|
623
819
|
var ret = "";
|
|
624
820
|
switch (element["type"]) {
|
|
@@ -651,10 +847,10 @@ function apply(ctx, config) {
|
|
|
651
847
|
this.config = config;
|
|
652
848
|
}
|
|
653
849
|
/**
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
850
|
+
* 根据短链接重定向获取正常链接
|
|
851
|
+
* @param id 短链接 ID
|
|
852
|
+
* @returns 正常链接
|
|
853
|
+
*/
|
|
658
854
|
async get_redir_url(id) {
|
|
659
855
|
var data = await this.ctx.http.get("https://b23.tv/" + id, {
|
|
660
856
|
redirect: "manual",
|
|
@@ -673,10 +869,10 @@ function apply(ctx, config) {
|
|
|
673
869
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
674
870
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
675
871
|
/**
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
872
|
+
* 检查看看一个url是否返回403,或者无法访问,主要用在通过bilibili官方api拿到的视频流
|
|
873
|
+
* @param url 链接
|
|
874
|
+
* @returns boolean
|
|
875
|
+
*/
|
|
680
876
|
async function checkResponseStatus(url) {
|
|
681
877
|
try {
|
|
682
878
|
const response = await ctx.http(url, {
|
|
@@ -704,205 +900,6 @@ function apply(ctx, config) {
|
|
|
704
900
|
}
|
|
705
901
|
}
|
|
706
902
|
|
|
707
|
-
async function handleBilibiliMedia(bilibiliVideo, originUrl) {
|
|
708
|
-
const GetVideoStream = async (h5videoStream, pcvideoStream, cid) => {
|
|
709
|
-
if (!h5videoStream.data ||
|
|
710
|
-
!pcvideoStream.data ||
|
|
711
|
-
!h5videoStream.data.accept_quality ||
|
|
712
|
-
!pcvideoStream.data.accept_quality ||
|
|
713
|
-
!h5videoStream.data.accept_format ||
|
|
714
|
-
!pcvideoStream.data.accept_format)
|
|
715
|
-
throw new Error('无法获取清晰度信息, 可能该视频为大会员专享或者该视频为付费视频/充电专属视频!或者账号被风控。');
|
|
716
|
-
const h5Quality = h5videoStream.data.accept_quality;
|
|
717
|
-
const pcQuality = pcvideoStream.data.accept_quality;
|
|
718
|
-
if (config.loggerinfo) {
|
|
719
|
-
logger.info(`h5Quality清晰度: ` + h5Quality)
|
|
720
|
-
logger.info(`pcQuality清晰度: ` + pcQuality)
|
|
721
|
-
}
|
|
722
|
-
const CombinedQualityInfo = h5Quality
|
|
723
|
-
.filter((item, index) => !(h5videoStream.data?.accept_format?.includes('flv') && h5videoStream.data.accept_format.split(',')[index].includes('flv')))
|
|
724
|
-
.map(item => ['html5', item])
|
|
725
|
-
.concat(pcQuality
|
|
726
|
-
.filter((item, index) => !(pcvideoStream.data?.accept_format?.includes('flv') && pcvideoStream.data.accept_format.split(',')[index].includes('flv')))
|
|
727
|
-
.map(item => ['pc', item]));
|
|
728
|
-
CombinedQualityInfo.sort((a, b) => {
|
|
729
|
-
if (b[1] === a[1]) {
|
|
730
|
-
// 如果两者数字相等
|
|
731
|
-
if (a[0] === 'html5') {
|
|
732
|
-
// html5排在前面
|
|
733
|
-
return -1;
|
|
734
|
-
}
|
|
735
|
-
else if (b[0] === 'html5') {
|
|
736
|
-
// pc排在前面
|
|
737
|
-
return 1;
|
|
738
|
-
}
|
|
739
|
-
else {
|
|
740
|
-
// 如果都是相同类型,则按照原顺序
|
|
741
|
-
return 0;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
// 根据配置决定排序顺序
|
|
746
|
-
switch (config.Video_ClarityPriority) {
|
|
747
|
-
case '1':
|
|
748
|
-
//logger.info(`低清晰度优先排序,a[1]: ${a[1]}, b[1]: ${b[1]}`);
|
|
749
|
-
return a[1] - b[1]; // 从低到高排序(低清晰度优先)
|
|
750
|
-
case '2':
|
|
751
|
-
//logger.info(`高清晰度优先排序,a[1]: ${a[1]}, b[1]: ${b[1]}`);
|
|
752
|
-
return b[1] - a[1]; // 从高到低排序(高清晰度优先)
|
|
753
|
-
default:
|
|
754
|
-
//logger.warn(`未知的视频清晰度优先级配置: ${config.Video_ClarityPriority}`);
|
|
755
|
-
return 0; // 默认保持原顺序
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
outerLoop: for (const [index, item] of CombinedQualityInfo.entries()) {
|
|
760
|
-
|
|
761
|
-
videoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cid, item[1], item[0], 1);
|
|
762
|
-
|
|
763
|
-
if (!videoStream || !videoStream.data || !videoStream.data.durl) {
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (await checkResponseStatus(videoStream.data.durl[0].url) === true) {
|
|
767
|
-
break outerLoop;
|
|
768
|
-
}
|
|
769
|
-
const isLastItem = index === CombinedQualityInfo.length - 1;
|
|
770
|
-
if (isLastItem) {
|
|
771
|
-
throw new Error('在尝试了全部清晰度和平台后,无法获取流媒体');
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return videoStream;
|
|
775
|
-
};
|
|
776
|
-
const duration = [];
|
|
777
|
-
const cids = [];
|
|
778
|
-
const cover = [];
|
|
779
|
-
const name = [];
|
|
780
|
-
const type = [];
|
|
781
|
-
const singer = [];
|
|
782
|
-
const link = [];
|
|
783
|
-
const origin = [];
|
|
784
|
-
const bitRate = [];
|
|
785
|
-
const url = [];
|
|
786
|
-
let bvid;
|
|
787
|
-
if (originUrl.includes('http') && originUrl.includes('video')) {
|
|
788
|
-
originUrl = originUrl.replace(/\?/g, '/');
|
|
789
|
-
bvid = originUrl.split('/video/')[1].split('/')[0];
|
|
790
|
-
}
|
|
791
|
-
else if (originUrl.includes('BV') || originUrl.includes('bv')) {
|
|
792
|
-
bvid = originUrl;
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
const mediaData = returnErrorMediaData(['暂不支持']);
|
|
796
|
-
return mediaData;
|
|
797
|
-
}
|
|
798
|
-
const videoInfo = await bilibiliVideo.getBilibiliVideoDetail(null, bvid);
|
|
799
|
-
if (!videoInfo || !videoInfo.data) {
|
|
800
|
-
const mediaData = returnErrorMediaData(['这个不是正确的bv号']);
|
|
801
|
-
return mediaData;
|
|
802
|
-
}
|
|
803
|
-
videoInfo.data.pages.forEach((page) => {
|
|
804
|
-
if (!videoInfo.data)
|
|
805
|
-
return;
|
|
806
|
-
cids.push(page.cid);
|
|
807
|
-
cover.push(videoInfo.data.pic);
|
|
808
|
-
type.push('video');
|
|
809
|
-
singer.push(videoInfo.data.owner.name);
|
|
810
|
-
link.push(`https://www.bilibili.com/video/${bvid}`);
|
|
811
|
-
duration.push(page.duration + 1 || videoInfo.data.duration + 1);
|
|
812
|
-
origin.push('bilibili');
|
|
813
|
-
if (videoInfo.data.pages.length <= 1) {
|
|
814
|
-
name.push(videoInfo.data.title);
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
name.push(`${videoInfo.data.title} - P${page.part}`);
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
const avid = videoInfo.data.aid;
|
|
821
|
-
let videoStream;
|
|
822
|
-
|
|
823
|
-
const h5videoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cids[0], 112, 'html5', 1);
|
|
824
|
-
const pcvideoStream = await bilibiliVideo.getBilibiliVideoStream(avid, bvid, cids[0], 112, 'pc', 1);
|
|
825
|
-
if (!h5videoStream || !pcvideoStream)
|
|
826
|
-
return returnErrorMediaData(['无法获取B站视频流']);
|
|
827
|
-
|
|
828
|
-
const cid = cids[0];
|
|
829
|
-
videoStream = await GetVideoStream(h5videoStream, pcvideoStream, cid);
|
|
830
|
-
if (!videoStream || !videoStream.data || !videoStream.data.quality || !videoStream.data.durl)
|
|
831
|
-
return returnErrorMediaData(['无法获取videoStream信息']);
|
|
832
|
-
bitRate.push(videoStream.data.quality);
|
|
833
|
-
url.push(videoStream.data.durl[0].url);
|
|
834
|
-
/*
|
|
835
|
-
for (const cid of cids) {
|
|
836
|
-
videoStream = await GetVideoStream(h5videoStream, pcvideoStream, cid);
|
|
837
|
-
if (!videoStream || !videoStream.data || !videoStream.data.quality || !videoStream.data.durl)
|
|
838
|
-
return returnErrorMediaData(['无法获取videoStream信息']);
|
|
839
|
-
bitRate.push(videoStream.data.quality);
|
|
840
|
-
url.push(videoStream.data.durl[0].url);
|
|
841
|
-
}
|
|
842
|
-
*/
|
|
843
|
-
const mediaData = returnCompleteMediaData(type, name, singer, cover, url, duration, bitRate, [], origin, link);
|
|
844
|
-
return mediaData;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* 返回包含错误信息的mediaData
|
|
849
|
-
* @param errorMsg 错误信息
|
|
850
|
-
* @return mediaData
|
|
851
|
-
*/
|
|
852
|
-
function returnErrorMediaData(errorMsgs) {
|
|
853
|
-
const errorMediaDataArray = [];
|
|
854
|
-
for (const errorMsg of errorMsgs) {
|
|
855
|
-
const mediaData = {
|
|
856
|
-
type: 'music',
|
|
857
|
-
name: '0',
|
|
858
|
-
signer: '0',
|
|
859
|
-
cover: '0',
|
|
860
|
-
link: '0',
|
|
861
|
-
url: '0',
|
|
862
|
-
duration: 0,
|
|
863
|
-
bitRate: 0,
|
|
864
|
-
lyrics: null,
|
|
865
|
-
origin: null,
|
|
866
|
-
error: errorMsg,
|
|
867
|
-
};
|
|
868
|
-
errorMediaDataArray.push(mediaData);
|
|
869
|
-
}
|
|
870
|
-
return errorMediaDataArray;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* 返回完整的mediaData
|
|
875
|
-
* @param type 类型
|
|
876
|
-
* @param name 标题
|
|
877
|
-
* @param signer 创作者
|
|
878
|
-
* @param cover 封面图url
|
|
879
|
-
* @param url 链接
|
|
880
|
-
* @param duration 时长
|
|
881
|
-
* @param bitRate 比特率
|
|
882
|
-
* @return mediaData
|
|
883
|
-
*/
|
|
884
|
-
function returnCompleteMediaData(typeList, nameList, signerList, coverList, urlList, durationList, bitRateList, lyricsList = [], origin = [], linkList = [], commentList) {
|
|
885
|
-
const mediaDataArray = [];
|
|
886
|
-
for (let i = 0; i < urlList.length; i++) {
|
|
887
|
-
const mediaData = {
|
|
888
|
-
type: typeList[i],
|
|
889
|
-
name: nameList[i],
|
|
890
|
-
signer: signerList[i],
|
|
891
|
-
cover: coverList[i],
|
|
892
|
-
link: linkList[i] || urlList[i],
|
|
893
|
-
url: urlList[i],
|
|
894
|
-
duration: durationList[i],
|
|
895
|
-
bitRate: bitRateList[i],
|
|
896
|
-
lyrics: lyricsList[i] || null,
|
|
897
|
-
origin: origin[i] || null,
|
|
898
|
-
comment: commentList?.[i] || undefined,
|
|
899
|
-
error: null,
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
mediaDataArray.push(mediaData);
|
|
903
|
-
}
|
|
904
|
-
return mediaDataArray;
|
|
905
|
-
}
|
|
906
903
|
|
|
907
904
|
}
|
|
908
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
|
+
}
|
package/readme.md
CHANGED
|
@@ -178,6 +178,13 @@ https://www.bilibili.com/video/BV1ii421Q7oj
|
|
|
178
178
|
<details>
|
|
179
179
|
<summary>点击此处 可查看更新日志</summary>
|
|
180
180
|
|
|
181
|
+
- **1.0.0**
|
|
182
|
+
- 不想写了
|
|
183
|
+
|
|
184
|
+
- **0.6.3**
|
|
185
|
+
- 修复单独的bv号解析报错
|
|
186
|
+
- 0.6.2更新了点播,忘写了
|
|
187
|
+
|
|
181
188
|
- **0.6.1**
|
|
182
189
|
- 支持点播功能,使用puppeteer进行网页截图,并且加以渲染序号,以便选择
|
|
183
190
|
- 模块化中间件部分内容,方便调用
|