koishi-plugin-bilitester 1.2.0 → 1.4.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 +148 -17
- package/package.json +1 -1
- package/lib/services/auth.service.d.ts +0 -43
- package/lib/services/auth.service.js +0 -101
- package/lib/services/bilibili.service.d.ts +0 -98
- package/lib/services/bilibili.service.js +0 -99
- package/lib/services/database.service.d.ts +0 -27
- package/lib/services/database.service.js +0 -67
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.name = exports.Config = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
11
|
exports.Config = koishi_1.Schema.object({
|
|
11
12
|
pollInterval: koishi_1.Schema.number()
|
|
12
13
|
.description('轮询扫码状态的时间间隔(毫秒)')
|
|
@@ -16,7 +17,64 @@ exports.Config = koishi_1.Schema.object({
|
|
|
16
17
|
.default(180),
|
|
17
18
|
});
|
|
18
19
|
exports.name = 'bilitester';
|
|
20
|
+
const searchResultsCache = new Map();
|
|
19
21
|
const loginSessions = new Map();
|
|
22
|
+
const MIXIN_KEY_ENC_TAB = [
|
|
23
|
+
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
|
24
|
+
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
|
25
|
+
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
|
26
|
+
36, 20, 34, 44, 52
|
|
27
|
+
];
|
|
28
|
+
const getMixinKey = (orig) => {
|
|
29
|
+
return MIXIN_KEY_ENC_TAB.map(n => orig[n]).join('').slice(0, 32);
|
|
30
|
+
};
|
|
31
|
+
let cachedWbiKeys = null;
|
|
32
|
+
let wbiKeysExpireTime = 0;
|
|
33
|
+
const getWbiKeys = async (cookie) => {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
if (cachedWbiKeys && now - wbiKeysExpireTime < 3600000) {
|
|
36
|
+
return cachedWbiKeys;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const response = await axios_1.default.get('https://api.bilibili.com/x/web-interface/nav', {
|
|
40
|
+
headers: {
|
|
41
|
+
'Cookie': cookie,
|
|
42
|
+
'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',
|
|
43
|
+
'Referer': 'https://www.bilibili.com/',
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const { data } = response.data;
|
|
47
|
+
if (!data || !data.wbi_img) {
|
|
48
|
+
throw new Error('Failed to get WBI keys');
|
|
49
|
+
}
|
|
50
|
+
const img_url = data.wbi_img.img_url;
|
|
51
|
+
const sub_url = data.wbi_img.sub_url;
|
|
52
|
+
const imgKey = img_url.slice(img_url.lastIndexOf('/') + 1, img_url.lastIndexOf('.'));
|
|
53
|
+
const subKey = sub_url.slice(sub_url.lastIndexOf('/') + 1, sub_url.lastIndexOf('.'));
|
|
54
|
+
cachedWbiKeys = { imgKey, subKey };
|
|
55
|
+
wbiKeysExpireTime = now;
|
|
56
|
+
return { imgKey, subKey };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('获取WBI keys失败:', error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const encWbi = (params, imgKey, subKey) => {
|
|
64
|
+
const mixinKey = getMixinKey(imgKey + subKey);
|
|
65
|
+
const currTime = Math.round(Date.now() / 1000);
|
|
66
|
+
const chrFilter = /[!'()*]/g;
|
|
67
|
+
Object.assign(params, { wts: currTime });
|
|
68
|
+
const sortedKeys = Object.keys(params).sort();
|
|
69
|
+
const query = sortedKeys
|
|
70
|
+
.map(key => {
|
|
71
|
+
const value = params[key].toString().replace(chrFilter, '');
|
|
72
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
73
|
+
})
|
|
74
|
+
.join('&');
|
|
75
|
+
const wbiSign = crypto_1.default.createHash('md5').update(query + mixinKey).digest('hex');
|
|
76
|
+
return query + `&w_rid=${wbiSign}`;
|
|
77
|
+
};
|
|
20
78
|
function apply(ctx, config) {
|
|
21
79
|
const pollInterval = config.pollInterval || 2000;
|
|
22
80
|
const qrCodeTimeout = config.qrCodeTimeout || 180;
|
|
@@ -233,7 +291,7 @@ function apply(ctx, config) {
|
|
|
233
291
|
}
|
|
234
292
|
};
|
|
235
293
|
const cmd = ctx.command('bilitester', '哔哩哔哩登录和API调用');
|
|
236
|
-
cmd.subcommand('login', '获取哔哩哔哩登录二维码')
|
|
294
|
+
cmd.subcommand('.login', '获取哔哩哔哩登录二维码')
|
|
237
295
|
.action(async ({ session }) => {
|
|
238
296
|
if (!session) {
|
|
239
297
|
return '无法获取会话信息';
|
|
@@ -277,7 +335,7 @@ function apply(ctx, config) {
|
|
|
277
335
|
return '获取二维码失败,请稍后重试';
|
|
278
336
|
}
|
|
279
337
|
});
|
|
280
|
-
cmd.subcommand('logout', '退出哔哩哔哩登录')
|
|
338
|
+
cmd.subcommand('.logout', '退出哔哩哔哩登录')
|
|
281
339
|
.action(async ({ session }) => {
|
|
282
340
|
if (!session) {
|
|
283
341
|
return '无法获取会话信息';
|
|
@@ -292,7 +350,7 @@ function apply(ctx, config) {
|
|
|
292
350
|
return '退出登录失败,请稍后重试';
|
|
293
351
|
}
|
|
294
352
|
});
|
|
295
|
-
cmd.subcommand('info', '查看当前登录的哔哩哔哩账号信息')
|
|
353
|
+
cmd.subcommand('.info', '查看当前登录的哔哩哔哩账号信息')
|
|
296
354
|
.action(async ({ session }) => {
|
|
297
355
|
if (!session) {
|
|
298
356
|
return '无法获取会话信息';
|
|
@@ -320,7 +378,7 @@ function apply(ctx, config) {
|
|
|
320
378
|
return '获取账号信息失败,请稍后重试';
|
|
321
379
|
}
|
|
322
380
|
});
|
|
323
|
-
cmd.subcommand('refresh', '刷新账号信息')
|
|
381
|
+
cmd.subcommand('.refresh', '刷新账号信息')
|
|
324
382
|
.action(async ({ session }) => {
|
|
325
383
|
if (!session) {
|
|
326
384
|
return '无法获取会话信息';
|
|
@@ -359,7 +417,7 @@ function apply(ctx, config) {
|
|
|
359
417
|
return '刷新账号信息失败,请稍后重试';
|
|
360
418
|
}
|
|
361
419
|
});
|
|
362
|
-
cmd.subcommand('video <bvid>', '获取B站视频信息')
|
|
420
|
+
cmd.subcommand('.video <bvid>', '获取B站视频信息')
|
|
363
421
|
.action(async ({ session }, bvid) => {
|
|
364
422
|
if (!session) {
|
|
365
423
|
return '无法获取会话信息';
|
|
@@ -397,7 +455,7 @@ function apply(ctx, config) {
|
|
|
397
455
|
return '获取视频信息失败,请稍后重试';
|
|
398
456
|
}
|
|
399
457
|
});
|
|
400
|
-
cmd.subcommand('parse <link>', '解析B站视频链接')
|
|
458
|
+
cmd.subcommand('.parse <link>', '解析B站视频链接')
|
|
401
459
|
.action(async ({ session }, link) => {
|
|
402
460
|
if (!session) {
|
|
403
461
|
return '无法获取会话信息';
|
|
@@ -461,7 +519,7 @@ function apply(ctx, config) {
|
|
|
461
519
|
return '解析视频链接失败,请稍后重试';
|
|
462
520
|
}
|
|
463
521
|
});
|
|
464
|
-
cmd.subcommand('dynamic', '获取哔哩哔哩动态')
|
|
522
|
+
cmd.subcommand('.dynamic', '获取哔哩哔哩动态')
|
|
465
523
|
.action(async ({ session }) => {
|
|
466
524
|
if (!session) {
|
|
467
525
|
return '无法获取会话信息';
|
|
@@ -512,7 +570,7 @@ function apply(ctx, config) {
|
|
|
512
570
|
return '获取动态失败,请稍后重试';
|
|
513
571
|
}
|
|
514
572
|
});
|
|
515
|
-
cmd.subcommand('live <roomId>', '获取B站直播间信息')
|
|
573
|
+
cmd.subcommand('.live <roomId>', '获取B站直播间信息')
|
|
516
574
|
.action(async ({ session }, roomId) => {
|
|
517
575
|
if (!session) {
|
|
518
576
|
return '无法获取会话信息';
|
|
@@ -550,7 +608,7 @@ function apply(ctx, config) {
|
|
|
550
608
|
return '获取直播间信息失败,请稍后重试';
|
|
551
609
|
}
|
|
552
610
|
});
|
|
553
|
-
cmd.subcommand('search <keyword>', '搜索B站视频')
|
|
611
|
+
cmd.subcommand('.search <keyword>', '搜索B站视频')
|
|
554
612
|
.action(async ({ session }, keyword) => {
|
|
555
613
|
if (!session) {
|
|
556
614
|
return '无法获取会话信息';
|
|
@@ -563,13 +621,17 @@ function apply(ctx, config) {
|
|
|
563
621
|
}
|
|
564
622
|
const acc = accounts[0];
|
|
565
623
|
const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
624
|
+
const wbiKeys = await getWbiKeys(cookie);
|
|
625
|
+
const wbiSign = encWbi({
|
|
626
|
+
keyword: keyword,
|
|
627
|
+
page: 1,
|
|
628
|
+
}, wbiKeys.imgKey, wbiKeys.subKey);
|
|
629
|
+
const response = await axios_1.default.get(`https://api.bilibili.com/x/web-interface/search/type?search_type=video&keyword=${encodeURIComponent(keyword)}&page=1&${wbiSign}`, {
|
|
630
|
+
headers: {
|
|
631
|
+
'Cookie': cookie,
|
|
632
|
+
'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',
|
|
633
|
+
'Referer': 'https://www.bilibili.com/',
|
|
634
|
+
},
|
|
573
635
|
});
|
|
574
636
|
if (response.data.code === 0) {
|
|
575
637
|
const { data } = response.data;
|
|
@@ -577,6 +639,8 @@ function apply(ctx, config) {
|
|
|
577
639
|
if (videos.length === 0) {
|
|
578
640
|
return `未找到与"${keyword}"相关的视频`;
|
|
579
641
|
}
|
|
642
|
+
const cacheKey = `search_${userId}_${keyword}_1`;
|
|
643
|
+
searchResultsCache.set(cacheKey, videos);
|
|
580
644
|
let result = `搜索结果:${keyword}\n`;
|
|
581
645
|
result += `共找到 ${data.numResults} 个视频,第 ${data.page}/${data.numPages} 页\n`;
|
|
582
646
|
result += '─'.repeat(40) + '\n';
|
|
@@ -588,13 +652,14 @@ function apply(ctx, config) {
|
|
|
588
652
|
result += ` UP主: ${video.author}\n`;
|
|
589
653
|
result += ` BV号: ${video.bvid}\n`;
|
|
590
654
|
result += ` 时长: ${duration}\n`;
|
|
591
|
-
result += ` 播放: ${video.play} | 弹幕: ${video.video_review} | 收藏: ${video.favorites}\n`;
|
|
655
|
+
result += ` 播放: ${formatNumber(video.play)} | 弹幕: ${formatNumber(video.video_review)} | 收藏: ${formatNumber(video.favorites)}\n`;
|
|
592
656
|
result += ` 发布: ${pubdate}\n`;
|
|
593
657
|
result += '─'.repeat(40) + '\n';
|
|
594
658
|
}
|
|
595
659
|
if (videos.length > 5) {
|
|
596
660
|
result += `还有 ${videos.length - 5} 个结果未显示...\n`;
|
|
597
661
|
}
|
|
662
|
+
result += `\n使用 "bilitester select <序号>" 选择视频`;
|
|
598
663
|
return result;
|
|
599
664
|
}
|
|
600
665
|
else if (response.data.code === -412) {
|
|
@@ -609,6 +674,72 @@ function apply(ctx, config) {
|
|
|
609
674
|
return '搜索视频失败,请稍后重试';
|
|
610
675
|
}
|
|
611
676
|
});
|
|
677
|
+
cmd.subcommand('.select <index>', '选择搜索结果中的视频')
|
|
678
|
+
.action(async ({ session }, index) => {
|
|
679
|
+
if (!session) {
|
|
680
|
+
return '无法获取会话信息';
|
|
681
|
+
}
|
|
682
|
+
const userId = session.userId || session.guildId || 'unknown';
|
|
683
|
+
try {
|
|
684
|
+
const cachedResults = Array.from(searchResultsCache.entries()).find(([key]) => key.startsWith(`search_${userId}_`));
|
|
685
|
+
if (!cachedResults || cachedResults[1].length === 0) {
|
|
686
|
+
return '请先使用 "bilitester search <关键词>" 搜索视频';
|
|
687
|
+
}
|
|
688
|
+
const videos = cachedResults[1];
|
|
689
|
+
const idx = parseInt(index);
|
|
690
|
+
if (isNaN(idx) || idx < 1 || idx > videos.length) {
|
|
691
|
+
return `请输入有效的视频序号 (1-${videos.length})`;
|
|
692
|
+
}
|
|
693
|
+
const video = videos[idx - 1];
|
|
694
|
+
const accounts = await ctx.database.get('bilibili', { userId });
|
|
695
|
+
if (!accounts || accounts.length === 0) {
|
|
696
|
+
return '您还未登录B站账号,请使用 "bilitester login" 进行登录';
|
|
697
|
+
}
|
|
698
|
+
const acc = accounts[0];
|
|
699
|
+
const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
|
|
700
|
+
const axiosInstance = createAxiosInstance(cookie);
|
|
701
|
+
const playUrlResponse = await axiosInstance.get(`https://api.bilibili.com/x/player/playurl`, {
|
|
702
|
+
params: {
|
|
703
|
+
bvid: video.bvid,
|
|
704
|
+
fnval: 80,
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
if (playUrlResponse.data.code === 0 && playUrlResponse.data.data && playUrlResponse.data.data.dash) {
|
|
708
|
+
const dash = playUrlResponse.data.data.dash;
|
|
709
|
+
const videoStream = dash.video?.[0];
|
|
710
|
+
const audioStream = dash.audio?.[0];
|
|
711
|
+
if (videoStream && audioStream) {
|
|
712
|
+
const duration = formatDuration(dash.duration);
|
|
713
|
+
const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
|
|
714
|
+
let result = `视频信息:\n`;
|
|
715
|
+
result += `标题: ${video.title}\n`;
|
|
716
|
+
result += `BV号: ${video.bvid}\n`;
|
|
717
|
+
result += `AV号: ${video.aid}\n`;
|
|
718
|
+
result += `UP主: ${video.author}\n`;
|
|
719
|
+
result += `时长: ${duration}\n`;
|
|
720
|
+
result += `发布时间: ${pubdate}\n`;
|
|
721
|
+
result += `播放: ${formatNumber(video.play)} | 弹幕: ${formatNumber(video.video_review)} | 收藏: ${formatNumber(video.favorites)}\n`;
|
|
722
|
+
result += `简介: ${video.description.substring(0, 100)}${video.description.length > 100 ? '...' : ''}\n`;
|
|
723
|
+
result += `${koishi_1.h.image(video.pic)}\n`;
|
|
724
|
+
const videoUrl = videoStream.baseUrl + videoStream.segment_base_url + videoStream.segments[0].url;
|
|
725
|
+
const audioUrl = audioStream.baseUrl + audioStream.segment_base_url + audioStream.segments[0].url;
|
|
726
|
+
result += `\n视频链接:\n${videoUrl}\n`;
|
|
727
|
+
result += `\n音频链接:\n${audioUrl}`;
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
return '获取视频播放链接失败,可能是大会员专属视频';
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
return '获取视频播放链接失败,请稍后重试';
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
console.error('获取视频播放链接失败:', error);
|
|
740
|
+
return '获取视频播放链接失败,请稍后重试';
|
|
741
|
+
}
|
|
742
|
+
});
|
|
612
743
|
ctx.on('dispose', () => {
|
|
613
744
|
for (const session of loginSessions.values()) {
|
|
614
745
|
if (session.timer) {
|
package/package.json
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export interface QRCodeResponse {
|
|
2
|
-
code: number;
|
|
3
|
-
message: string;
|
|
4
|
-
ttl: number;
|
|
5
|
-
data: {
|
|
6
|
-
url: string;
|
|
7
|
-
qrcode_key: string;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
export interface PollResponse {
|
|
11
|
-
code: number;
|
|
12
|
-
message: string;
|
|
13
|
-
ttl: number;
|
|
14
|
-
data: {
|
|
15
|
-
url: string;
|
|
16
|
-
refresh_token: string;
|
|
17
|
-
timestamp: number;
|
|
18
|
-
code: number;
|
|
19
|
-
message: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export interface LoginResult {
|
|
23
|
-
success: boolean;
|
|
24
|
-
sessdata?: string;
|
|
25
|
-
biliJct?: string;
|
|
26
|
-
dedeUserId?: string;
|
|
27
|
-
dedeUserIdCkMd5?: string;
|
|
28
|
-
}
|
|
29
|
-
export interface LoginSession {
|
|
30
|
-
qrcodeKey: string;
|
|
31
|
-
userId: string;
|
|
32
|
-
startTime: number;
|
|
33
|
-
}
|
|
34
|
-
export declare class AuthService {
|
|
35
|
-
private static readonly POLL_INTERVAL;
|
|
36
|
-
private static readonly QR_CODE_TIMEOUT;
|
|
37
|
-
static generateQRCode(): Promise<{
|
|
38
|
-
url: string;
|
|
39
|
-
qrcodeKey: string;
|
|
40
|
-
} | null>;
|
|
41
|
-
static pollLoginStatus(qrcodeKey: string, onLogin: (result: LoginResult) => void, onTimeout: () => void, onError: (error: string) => void): Promise<NodeJS.Timeout>;
|
|
42
|
-
static getCookieString(sessdata: string, biliJct: string, dedeUserId: string, dedeUserIdCkMd5: string): string;
|
|
43
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AuthService = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
class AuthService {
|
|
9
|
-
static async generateQRCode() {
|
|
10
|
-
try {
|
|
11
|
-
const response = await axios_1.default.get('https://passport.bilibili.com/x/passport-login/web/qrcode/generate', {
|
|
12
|
-
headers: {
|
|
13
|
-
'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',
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
const { code, data } = response.data;
|
|
17
|
-
if (code === 0 && data) {
|
|
18
|
-
return {
|
|
19
|
-
url: data.url,
|
|
20
|
-
qrcodeKey: data.qrcode_key,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.error('生成二维码失败:', error);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
static async pollLoginStatus(qrcodeKey, onLogin, onTimeout, onError) {
|
|
31
|
-
return new Promise((resolve) => {
|
|
32
|
-
const timer = setInterval(async () => {
|
|
33
|
-
try {
|
|
34
|
-
const response = await axios_1.default.get('https://passport.bilibili.com/x/passport-login/web/qrcode/poll', {
|
|
35
|
-
params: { qrcode_key: qrcodeKey },
|
|
36
|
-
headers: {
|
|
37
|
-
'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',
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
const { code, data } = response.data;
|
|
41
|
-
if (code === 0) {
|
|
42
|
-
if (data.code === 0) {
|
|
43
|
-
const cookies = response.headers['set-cookie'] || [];
|
|
44
|
-
let sessdata = '';
|
|
45
|
-
let biliJct = '';
|
|
46
|
-
let dedeUserId = '';
|
|
47
|
-
let dedeUserIdCkMd5 = '';
|
|
48
|
-
for (const cookie of cookies) {
|
|
49
|
-
if (cookie.includes('SESSDATA=')) {
|
|
50
|
-
sessdata = cookie.match(/SESSDATA=([^;]+)/)?.[1] || '';
|
|
51
|
-
}
|
|
52
|
-
if (cookie.includes('bili_jct=')) {
|
|
53
|
-
biliJct = cookie.match(/bili_jct=([^;]+)/)?.[1] || '';
|
|
54
|
-
}
|
|
55
|
-
if (cookie.includes('DedeUserID=')) {
|
|
56
|
-
dedeUserId = cookie.match(/DedeUserID=([^;]+)/)?.[1] || '';
|
|
57
|
-
}
|
|
58
|
-
if (cookie.includes('DedeUserID__ckMd5=')) {
|
|
59
|
-
dedeUserIdCkMd5 = cookie.match(/DedeUserID__ckMd5=([^;]+)/)?.[1] || '';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (sessdata && biliJct && dedeUserId) {
|
|
63
|
-
onLogin({
|
|
64
|
-
success: true,
|
|
65
|
-
sessdata,
|
|
66
|
-
biliJct,
|
|
67
|
-
dedeUserId,
|
|
68
|
-
dedeUserIdCkMd5,
|
|
69
|
-
});
|
|
70
|
-
clearInterval(timer);
|
|
71
|
-
resolve(timer);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else if (data.code === 86038) {
|
|
75
|
-
onError('二维码已失效');
|
|
76
|
-
clearInterval(timer);
|
|
77
|
-
resolve(timer);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
console.error('轮询登录状态失败:', error);
|
|
83
|
-
onError('网络错误,请稍后重试');
|
|
84
|
-
clearInterval(timer);
|
|
85
|
-
resolve(timer);
|
|
86
|
-
}
|
|
87
|
-
}, this.POLL_INTERVAL);
|
|
88
|
-
setTimeout(() => {
|
|
89
|
-
clearInterval(timer);
|
|
90
|
-
onTimeout();
|
|
91
|
-
resolve(timer);
|
|
92
|
-
}, this.QR_CODE_TIMEOUT * 1000);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
static getCookieString(sessdata, biliJct, dedeUserId, dedeUserIdCkMd5) {
|
|
96
|
-
return `SESSDATA=${sessdata}; bili_jct=${biliJct}; DedeUserID=${dedeUserId}; DedeUserID__ckMd5=${dedeUserIdCkMd5}`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
exports.AuthService = AuthService;
|
|
100
|
-
AuthService.POLL_INTERVAL = 2000;
|
|
101
|
-
AuthService.QR_CODE_TIMEOUT = 180;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
export interface VideoInfo {
|
|
2
|
-
bvid: string;
|
|
3
|
-
aid: number;
|
|
4
|
-
title: string;
|
|
5
|
-
desc: string;
|
|
6
|
-
pic: string;
|
|
7
|
-
owner: {
|
|
8
|
-
mid: string;
|
|
9
|
-
name: string;
|
|
10
|
-
face: string;
|
|
11
|
-
};
|
|
12
|
-
stat: {
|
|
13
|
-
view: number;
|
|
14
|
-
danmaku: number;
|
|
15
|
-
reply: number;
|
|
16
|
-
favorite: number;
|
|
17
|
-
coin: number;
|
|
18
|
-
share: number;
|
|
19
|
-
like: number;
|
|
20
|
-
};
|
|
21
|
-
duration: number;
|
|
22
|
-
pubdate: number;
|
|
23
|
-
}
|
|
24
|
-
export interface VideoSearchResult {
|
|
25
|
-
bvid: string;
|
|
26
|
-
aid: number;
|
|
27
|
-
title: string;
|
|
28
|
-
pic: string;
|
|
29
|
-
description: string;
|
|
30
|
-
owner: {
|
|
31
|
-
mid: string;
|
|
32
|
-
name: string;
|
|
33
|
-
face: string;
|
|
34
|
-
};
|
|
35
|
-
stat: {
|
|
36
|
-
view: number;
|
|
37
|
-
danmaku: number;
|
|
38
|
-
reply: number;
|
|
39
|
-
favorite: number;
|
|
40
|
-
coin: number;
|
|
41
|
-
share: number;
|
|
42
|
-
like: number;
|
|
43
|
-
};
|
|
44
|
-
duration: number;
|
|
45
|
-
pubdate: number;
|
|
46
|
-
}
|
|
47
|
-
export interface UserInfo {
|
|
48
|
-
isLogin: boolean;
|
|
49
|
-
mid: string;
|
|
50
|
-
uname: string;
|
|
51
|
-
face: string;
|
|
52
|
-
vipStatus: number;
|
|
53
|
-
vipType: number;
|
|
54
|
-
vipDueDate: number;
|
|
55
|
-
level_info: {
|
|
56
|
-
current_level: number;
|
|
57
|
-
current_exp: number;
|
|
58
|
-
next_exp: string | number;
|
|
59
|
-
};
|
|
60
|
-
money: number;
|
|
61
|
-
}
|
|
62
|
-
export interface DynamicResponse {
|
|
63
|
-
cards: Array<{
|
|
64
|
-
card: string;
|
|
65
|
-
desc: {
|
|
66
|
-
type: number;
|
|
67
|
-
dynamic_id: number;
|
|
68
|
-
timestamp: number;
|
|
69
|
-
};
|
|
70
|
-
}>;
|
|
71
|
-
}
|
|
72
|
-
export interface LiveRoomInfo {
|
|
73
|
-
roomid: number;
|
|
74
|
-
uid: number;
|
|
75
|
-
title: string;
|
|
76
|
-
live_status: number;
|
|
77
|
-
keyframe: string;
|
|
78
|
-
cover: string;
|
|
79
|
-
online: number;
|
|
80
|
-
area_name: string;
|
|
81
|
-
parent_area_name: string;
|
|
82
|
-
}
|
|
83
|
-
export interface SearchResponse {
|
|
84
|
-
code: number;
|
|
85
|
-
message: string;
|
|
86
|
-
data: {
|
|
87
|
-
result: VideoSearchResult[];
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
export declare class BilibiliService {
|
|
91
|
-
private axiosInstance;
|
|
92
|
-
constructor(cookie: string);
|
|
93
|
-
getUserInfo(): Promise<UserInfo | null>;
|
|
94
|
-
getVideoInfo(bvid: string): Promise<VideoInfo | null>;
|
|
95
|
-
searchVideos(keyword: string, page?: number): Promise<VideoSearchResult[] | null>;
|
|
96
|
-
getDynamics(uid: string): Promise<DynamicResponse | null>;
|
|
97
|
-
getLiveRoomInfo(roomId: number): Promise<LiveRoomInfo | null>;
|
|
98
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.BilibiliService = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
class BilibiliService {
|
|
9
|
-
constructor(cookie) {
|
|
10
|
-
this.axiosInstance = axios_1.default.create({
|
|
11
|
-
headers: {
|
|
12
|
-
'Cookie': cookie,
|
|
13
|
-
'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',
|
|
14
|
-
'Referer': 'https://www.bilibili.com',
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
async getUserInfo() {
|
|
19
|
-
try {
|
|
20
|
-
const response = await this.axiosInstance.get('https://api.bilibili.com/x/web-interface/nav');
|
|
21
|
-
if (response.data.code === 0 && response.data.data.isLogin) {
|
|
22
|
-
return response.data.data;
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
console.error('获取用户信息失败:', error);
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async getVideoInfo(bvid) {
|
|
32
|
-
try {
|
|
33
|
-
const response = await this.axiosInstance.get('https://api.bilibili.com/x/web-interface/view', {
|
|
34
|
-
params: { bvid }
|
|
35
|
-
});
|
|
36
|
-
if (response.data.code === 0) {
|
|
37
|
-
return response.data.data;
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
console.error('获取视频信息失败:', error);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async searchVideos(keyword, page = 1) {
|
|
47
|
-
try {
|
|
48
|
-
const response = await this.axiosInstance.get('https://api.bilibili.com/x/web-interface/search/all', {
|
|
49
|
-
params: {
|
|
50
|
-
keyword,
|
|
51
|
-
page,
|
|
52
|
-
search_type: 'video',
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
if (response.data.code === 0) {
|
|
56
|
-
return response.data.data.result;
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
console.error('搜索视频失败:', error);
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async getDynamics(uid) {
|
|
66
|
-
try {
|
|
67
|
-
const response = await this.axiosInstance.get('https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new', {
|
|
68
|
-
params: {
|
|
69
|
-
uid,
|
|
70
|
-
type_list: '8',
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
if (response.data.code === 0) {
|
|
74
|
-
return response.data.data;
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
console.error('获取动态失败:', error);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async getLiveRoomInfo(roomId) {
|
|
84
|
-
try {
|
|
85
|
-
const response = await this.axiosInstance.get('https://api.live.bilibili.com/room/v1/Room/get_info', {
|
|
86
|
-
params: { room_id: roomId }
|
|
87
|
-
});
|
|
88
|
-
if (response.data.code === 0) {
|
|
89
|
-
return response.data.data;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error('获取直播间信息失败:', error);
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
exports.BilibiliService = BilibiliService;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Context } from 'koishi';
|
|
2
|
-
export interface BilibiliAccount {
|
|
3
|
-
id: number;
|
|
4
|
-
userId: string;
|
|
5
|
-
sessdata: string;
|
|
6
|
-
biliJct: string;
|
|
7
|
-
dedeUserId: string;
|
|
8
|
-
dedeUserIdCkMd5: string;
|
|
9
|
-
mid: string;
|
|
10
|
-
name: string;
|
|
11
|
-
face: string;
|
|
12
|
-
vipStatus: number;
|
|
13
|
-
vipType: number;
|
|
14
|
-
vipDueDate: number;
|
|
15
|
-
level: number;
|
|
16
|
-
coins: number;
|
|
17
|
-
createdAt: Date;
|
|
18
|
-
updatedAt: Date;
|
|
19
|
-
}
|
|
20
|
-
export declare class DatabaseService {
|
|
21
|
-
private ctx;
|
|
22
|
-
constructor(ctx: Context);
|
|
23
|
-
createAccount(account: Omit<BilibiliAccount, 'id'>): Promise<BilibiliAccount>;
|
|
24
|
-
getAccountByUserId(userId: string): Promise<BilibiliAccount | null>;
|
|
25
|
-
updateAccount(id: number, data: Partial<Omit<BilibiliAccount, 'id' | 'userId' | 'createdAt'>>): Promise<boolean>;
|
|
26
|
-
deleteAccountByUserId(userId: string): Promise<boolean>;
|
|
27
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DatabaseService = void 0;
|
|
4
|
-
class DatabaseService {
|
|
5
|
-
constructor(ctx) {
|
|
6
|
-
this.ctx = ctx;
|
|
7
|
-
this.ctx.model.extend('bilibili', {
|
|
8
|
-
id: 'unsigned',
|
|
9
|
-
userId: 'text',
|
|
10
|
-
sessdata: 'text',
|
|
11
|
-
biliJct: 'text',
|
|
12
|
-
dedeUserId: 'text',
|
|
13
|
-
dedeUserIdCkMd5: 'text',
|
|
14
|
-
mid: 'text',
|
|
15
|
-
name: 'text',
|
|
16
|
-
face: 'text',
|
|
17
|
-
vipStatus: 'integer',
|
|
18
|
-
vipType: 'integer',
|
|
19
|
-
vipDueDate: 'integer',
|
|
20
|
-
level: 'integer',
|
|
21
|
-
coins: 'float',
|
|
22
|
-
createdAt: 'timestamp',
|
|
23
|
-
updatedAt: 'timestamp',
|
|
24
|
-
}, {
|
|
25
|
-
autoInc: false,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
async createAccount(account) {
|
|
29
|
-
const newAccount = {
|
|
30
|
-
...account,
|
|
31
|
-
id: Date.now(),
|
|
32
|
-
};
|
|
33
|
-
await this.ctx.database.create('bilibili', newAccount);
|
|
34
|
-
return newAccount;
|
|
35
|
-
}
|
|
36
|
-
async getAccountByUserId(userId) {
|
|
37
|
-
const accounts = await this.ctx.database.get('bilibili', { userId });
|
|
38
|
-
if (!accounts || accounts.length === 0) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return accounts[0];
|
|
42
|
-
}
|
|
43
|
-
async updateAccount(id, data) {
|
|
44
|
-
try {
|
|
45
|
-
await this.ctx.database.set('bilibili', { id }, {
|
|
46
|
-
...data,
|
|
47
|
-
updatedAt: new Date(),
|
|
48
|
-
});
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
console.error('更新账号信息失败:', error);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async deleteAccountByUserId(userId) {
|
|
57
|
-
try {
|
|
58
|
-
await this.ctx.database.remove('bilibili', { userId });
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
console.error('删除账号信息失败:', error);
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
exports.DatabaseService = DatabaseService;
|