koishi-plugin-bilitester 1.1.0 → 1.3.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/README.md +11 -2
- package/lib/index.js +321 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- Cookie 管理:自动存储和管理登录 Cookie
|
|
9
9
|
- 账号信息查询:查看当前登录账号的详细信息
|
|
10
10
|
- 视频信息查询:获取哔哩哔哩视频的详细信息
|
|
11
|
+
- 视频搜索:搜索哔哩哔哩视频
|
|
11
12
|
- 动态查询:获取用户的最新动态
|
|
12
13
|
- 直播间信息:获取直播间状态和信息
|
|
13
14
|
- 账号信息刷新:更新缓存的账号信息
|
|
@@ -70,10 +71,18 @@ bilitester logout
|
|
|
70
71
|
### 查询视频信息
|
|
71
72
|
|
|
72
73
|
```
|
|
73
|
-
|
|
74
|
+
bilitester video <BV号>
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
例如:`
|
|
77
|
+
例如:`bilitester video BV1xx411c7mD`
|
|
78
|
+
|
|
79
|
+
### 搜索视频
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
bilitester search <关键词>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
例如:`bilitester search 动画`
|
|
77
86
|
|
|
78
87
|
### 查询动态
|
|
79
88
|
|
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;
|
|
@@ -52,6 +110,105 @@ function apply(ctx, config) {
|
|
|
52
110
|
const getCookieString = (sessdata, biliJct, dedeUserId, dedeUserIdCkMd5) => {
|
|
53
111
|
return `SESSDATA=${sessdata}; bili_jct=${biliJct}; DedeUserID=${dedeUserId}; DedeUserID__ckMd5=${dedeUserIdCkMd5}`;
|
|
54
112
|
};
|
|
113
|
+
const formatNumber = (num) => {
|
|
114
|
+
if (num >= 100000000) {
|
|
115
|
+
return (num / 100000000).toFixed(1) + '亿';
|
|
116
|
+
}
|
|
117
|
+
else if (num >= 10000) {
|
|
118
|
+
return (num / 10000).toFixed(1) + '万';
|
|
119
|
+
}
|
|
120
|
+
return num.toString();
|
|
121
|
+
};
|
|
122
|
+
const formatDuration = (seconds) => {
|
|
123
|
+
const hours = Math.floor(seconds / 3600);
|
|
124
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
125
|
+
const secs = seconds % 60;
|
|
126
|
+
if (hours > 0) {
|
|
127
|
+
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const parseVideoId = (input) => {
|
|
134
|
+
const avMatch = input.match(/(?:^|[\s/])(av\d+)(?:[\s/]|$)/i);
|
|
135
|
+
if (avMatch) {
|
|
136
|
+
return { type: 'av', id: avMatch[1] };
|
|
137
|
+
}
|
|
138
|
+
const bvMatch = input.match(/(?:^|[\s/])(BV[\w]+)(?:[\s/]|$)/i);
|
|
139
|
+
if (bvMatch) {
|
|
140
|
+
return { type: 'bv', id: bvMatch[1] };
|
|
141
|
+
}
|
|
142
|
+
return { type: null, id: null };
|
|
143
|
+
};
|
|
144
|
+
const parseVideoLinks = (content) => {
|
|
145
|
+
const links = [];
|
|
146
|
+
const fullLinkPattern = /bilibili\.com\/video\/([ab]v[\w]+)/gi;
|
|
147
|
+
let match;
|
|
148
|
+
while ((match = fullLinkPattern.exec(content)) !== null) {
|
|
149
|
+
links.push({ type: 'Video', id: match[1] });
|
|
150
|
+
}
|
|
151
|
+
const shortLinkPatterns = [
|
|
152
|
+
/b23\.tv\/([\w]+)/gi,
|
|
153
|
+
/bili22\.cn\/([\w]+)/gi,
|
|
154
|
+
/bili23\.cn\/([\w]+)/gi,
|
|
155
|
+
/bili33\.cn\/([\w]+)/gi,
|
|
156
|
+
/bili2233\.cn\/([\w]+)/gi,
|
|
157
|
+
];
|
|
158
|
+
for (const pattern of shortLinkPatterns) {
|
|
159
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
160
|
+
links.push({ type: 'Short', id: match[1] });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return links;
|
|
164
|
+
};
|
|
165
|
+
const resolveShortLink = async (shortId) => {
|
|
166
|
+
try {
|
|
167
|
+
const response = await axios_1.default.get(`https://b23.tv/${shortId}`, {
|
|
168
|
+
maxRedirects: 0,
|
|
169
|
+
validateStatus: (status) => status >= 300 && status < 400,
|
|
170
|
+
});
|
|
171
|
+
const location = response.headers['location'];
|
|
172
|
+
if (location) {
|
|
173
|
+
return location;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error.response && error.response.headers && error.response.headers.location) {
|
|
179
|
+
return error.response.headers.location;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const fetchVideoInfo = async (videoId) => {
|
|
185
|
+
const parsed = parseVideoId(videoId);
|
|
186
|
+
if (!parsed.type || !parsed.id) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
let url;
|
|
191
|
+
if (parsed.type === 'av') {
|
|
192
|
+
url = `https://api.bilibili.com/x/web-interface/view?aid=${parsed.id.replace('av', '')}`;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
url = `https://api.bilibili.com/x/web-interface/view?bvid=${parsed.id}`;
|
|
196
|
+
}
|
|
197
|
+
const response = await axios_1.default.get(url, {
|
|
198
|
+
headers: {
|
|
199
|
+
'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',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
if (response.data.code === 0) {
|
|
203
|
+
return response.data;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error('获取视频信息失败:', error);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
55
212
|
const pollLoginStatus = async (qrcodeKey, userId) => {
|
|
56
213
|
const session = loginSessions.get(qrcodeKey);
|
|
57
214
|
if (!session)
|
|
@@ -260,7 +417,7 @@ function apply(ctx, config) {
|
|
|
260
417
|
return '刷新账号信息失败,请稍后重试';
|
|
261
418
|
}
|
|
262
419
|
});
|
|
263
|
-
cmd.subcommand('video <bvid>', '
|
|
420
|
+
cmd.subcommand('video <bvid>', '获取B站视频信息')
|
|
264
421
|
.action(async ({ session }, bvid) => {
|
|
265
422
|
if (!session) {
|
|
266
423
|
return '无法获取会话信息';
|
|
@@ -269,39 +426,99 @@ function apply(ctx, config) {
|
|
|
269
426
|
try {
|
|
270
427
|
const accounts = await ctx.database.get('bilibili', { userId });
|
|
271
428
|
if (!accounts || accounts.length === 0) {
|
|
272
|
-
return '
|
|
429
|
+
return '您还未登录B站账号,请使用 "bilitester login" 进行登录';
|
|
273
430
|
}
|
|
274
431
|
const acc = accounts[0];
|
|
275
432
|
const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
|
|
276
433
|
const axiosInstance = createAxiosInstance(cookie);
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (response.data.code === 0) {
|
|
281
|
-
const video = response.data.data;
|
|
282
|
-
const duration = Math.floor(video.duration / 60) + ':' + (video.duration % 60).toString().padStart(2, '0');
|
|
283
|
-
const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
|
|
284
|
-
return `视频信息:\n` +
|
|
285
|
-
`标题: ${video.title}\n` +
|
|
286
|
-
`BV号: ${video.bvid}\n` +
|
|
287
|
-
`AV号: ${video.aid}\n` +
|
|
288
|
-
`UP主: ${video.owner.name}\n` +
|
|
289
|
-
`时长: ${duration}\n` +
|
|
290
|
-
`发布时间: ${pubdate}\n` +
|
|
291
|
-
`播放: ${video.stat.view} | 弹幕: ${video.stat.danmaku} | 点赞: ${video.stat.like}\n` +
|
|
292
|
-
`投币: ${video.stat.coin} | 收藏: ${video.stat.favorite} | 分享: ${video.stat.share}\n` +
|
|
293
|
-
`简介: ${video.desc.substring(0, 100)}${video.desc.length > 100 ? '...' : ''}\n` +
|
|
294
|
-
`${koishi_1.h.image(video.pic)}`;
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
return '获取视频信息失败,请检查BV号是否正确';
|
|
434
|
+
const videoInfo = await fetchVideoInfo(bvid);
|
|
435
|
+
if (!videoInfo) {
|
|
436
|
+
return '获取视频信息失败,请检查BV/AV号是否正确';
|
|
298
437
|
}
|
|
438
|
+
const video = videoInfo.data;
|
|
439
|
+
const duration = formatDuration(video.duration);
|
|
440
|
+
const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
|
|
441
|
+
return `视频信息:\n` +
|
|
442
|
+
`标题: ${video.title}\n` +
|
|
443
|
+
`BV号: ${video.bvid}\n` +
|
|
444
|
+
`AV号: ${video.aid}\n` +
|
|
445
|
+
`UP主: ${video.owner.name}\n` +
|
|
446
|
+
`时长: ${duration}\n` +
|
|
447
|
+
`发布时间: ${pubdate}\n` +
|
|
448
|
+
`播放: ${formatNumber(video.stat.view)} | 弹幕: ${formatNumber(video.stat.danmaku)} | 点赞: ${formatNumber(video.stat.like)}\n` +
|
|
449
|
+
`投币: ${formatNumber(video.stat.coin)} | 收藏: ${formatNumber(video.stat.favorite)} | 分享: ${formatNumber(video.stat.share)}\n` +
|
|
450
|
+
`简介: ${video.desc.substring(0, 100)}${video.desc.length > 100 ? '...' : ''}\n` +
|
|
451
|
+
`${koishi_1.h.image(video.pic)}`;
|
|
299
452
|
}
|
|
300
453
|
catch (error) {
|
|
301
454
|
console.error('获取视频信息失败:', error);
|
|
302
455
|
return '获取视频信息失败,请稍后重试';
|
|
303
456
|
}
|
|
304
457
|
});
|
|
458
|
+
cmd.subcommand('parse <link>', '解析B站视频链接')
|
|
459
|
+
.action(async ({ session }, link) => {
|
|
460
|
+
if (!session) {
|
|
461
|
+
return '无法获取会话信息';
|
|
462
|
+
}
|
|
463
|
+
const userId = session.userId || session.guildId || 'unknown';
|
|
464
|
+
try {
|
|
465
|
+
const accounts = await ctx.database.get('bilibili', { userId });
|
|
466
|
+
if (!accounts || accounts.length === 0) {
|
|
467
|
+
return '您还未登录B站账号,请使用 "bilitester login" 进行登录';
|
|
468
|
+
}
|
|
469
|
+
const acc = accounts[0];
|
|
470
|
+
const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
|
|
471
|
+
const axiosInstance = createAxiosInstance(cookie);
|
|
472
|
+
let videoId = null;
|
|
473
|
+
const parsed = parseVideoId(link);
|
|
474
|
+
if (parsed.type && parsed.id) {
|
|
475
|
+
videoId = parsed.id;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
const links = parseVideoLinks(link);
|
|
479
|
+
if (links.length > 0) {
|
|
480
|
+
const videoLink = links[0];
|
|
481
|
+
if (videoLink.type === 'Short') {
|
|
482
|
+
const resolvedLink = await resolveShortLink(videoLink.id);
|
|
483
|
+
if (resolvedLink) {
|
|
484
|
+
const resolvedParsed = parseVideoId(resolvedLink);
|
|
485
|
+
if (resolvedParsed.type && resolvedParsed.id) {
|
|
486
|
+
videoId = resolvedParsed.id;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
videoId = videoLink.id;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (!videoId) {
|
|
496
|
+
return '无法解析视频链接,请检查链接格式是否正确';
|
|
497
|
+
}
|
|
498
|
+
const videoInfo = await fetchVideoInfo(videoId);
|
|
499
|
+
if (!videoInfo) {
|
|
500
|
+
return '获取视频信息失败,请检查链接是否正确';
|
|
501
|
+
}
|
|
502
|
+
const video = videoInfo.data;
|
|
503
|
+
const duration = formatDuration(video.duration);
|
|
504
|
+
const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
|
|
505
|
+
return `视频信息:\n` +
|
|
506
|
+
`标题: ${video.title}\n` +
|
|
507
|
+
`BV号: ${video.bvid}\n` +
|
|
508
|
+
`AV号: ${video.aid}\n` +
|
|
509
|
+
`UP主: ${video.owner.name}\n` +
|
|
510
|
+
`时长: ${duration}\n` +
|
|
511
|
+
`发布时间: ${pubdate}\n` +
|
|
512
|
+
`播放: ${formatNumber(video.stat.view)} | 弹幕: ${formatNumber(video.stat.danmaku)} | 点赞: ${formatNumber(video.stat.like)}\n` +
|
|
513
|
+
`投币: ${formatNumber(video.stat.coin)} | 收藏: ${formatNumber(video.stat.favorite)} | 分享: ${formatNumber(video.stat.share)}\n` +
|
|
514
|
+
`简介: ${video.desc.substring(0, 100)}${video.desc.length > 100 ? '...' : ''}\n` +
|
|
515
|
+
`${koishi_1.h.image(video.pic)}`;
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
console.error('解析视频链接失败:', error);
|
|
519
|
+
return '解析视频链接失败,请稍后重试';
|
|
520
|
+
}
|
|
521
|
+
});
|
|
305
522
|
cmd.subcommand('dynamic', '获取哔哩哔哩动态')
|
|
306
523
|
.action(async ({ session }) => {
|
|
307
524
|
if (!session) {
|
|
@@ -404,13 +621,17 @@ function apply(ctx, config) {
|
|
|
404
621
|
}
|
|
405
622
|
const acc = accounts[0];
|
|
406
623
|
const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
+
},
|
|
414
635
|
});
|
|
415
636
|
if (response.data.code === 0) {
|
|
416
637
|
const { data } = response.data;
|
|
@@ -418,6 +639,8 @@ function apply(ctx, config) {
|
|
|
418
639
|
if (videos.length === 0) {
|
|
419
640
|
return `未找到与"${keyword}"相关的视频`;
|
|
420
641
|
}
|
|
642
|
+
const cacheKey = `search_${userId}_${keyword}_1`;
|
|
643
|
+
searchResultsCache.set(cacheKey, videos);
|
|
421
644
|
let result = `搜索结果:${keyword}\n`;
|
|
422
645
|
result += `共找到 ${data.numResults} 个视频,第 ${data.page}/${data.numPages} 页\n`;
|
|
423
646
|
result += '─'.repeat(40) + '\n';
|
|
@@ -429,13 +652,14 @@ function apply(ctx, config) {
|
|
|
429
652
|
result += ` UP主: ${video.author}\n`;
|
|
430
653
|
result += ` BV号: ${video.bvid}\n`;
|
|
431
654
|
result += ` 时长: ${duration}\n`;
|
|
432
|
-
result += ` 播放: ${video.play} | 弹幕: ${video.video_review} | 收藏: ${video.favorites}\n`;
|
|
655
|
+
result += ` 播放: ${formatNumber(video.play)} | 弹幕: ${formatNumber(video.video_review)} | 收藏: ${formatNumber(video.favorites)}\n`;
|
|
433
656
|
result += ` 发布: ${pubdate}\n`;
|
|
434
657
|
result += '─'.repeat(40) + '\n';
|
|
435
658
|
}
|
|
436
659
|
if (videos.length > 5) {
|
|
437
660
|
result += `还有 ${videos.length - 5} 个结果未显示...\n`;
|
|
438
661
|
}
|
|
662
|
+
result += `\n使用 "bilitester select <序号>" 选择视频`;
|
|
439
663
|
return result;
|
|
440
664
|
}
|
|
441
665
|
else if (response.data.code === -412) {
|
|
@@ -450,6 +674,72 @@ function apply(ctx, config) {
|
|
|
450
674
|
return '搜索视频失败,请稍后重试';
|
|
451
675
|
}
|
|
452
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
|
+
});
|
|
453
743
|
ctx.on('dispose', () => {
|
|
454
744
|
for (const session of loginSessions.values()) {
|
|
455
745
|
if (session.timer) {
|