koishi-plugin-bilitester 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +11 -2
  2. package/lib/index.js +182 -23
  3. 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
- bilibili video <BV号>
74
+ bilitester video <BV号>
74
75
  ```
75
76
 
76
- 例如:`bilibili video BV1xx411c7mD`
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
@@ -52,6 +52,105 @@ function apply(ctx, config) {
52
52
  const getCookieString = (sessdata, biliJct, dedeUserId, dedeUserIdCkMd5) => {
53
53
  return `SESSDATA=${sessdata}; bili_jct=${biliJct}; DedeUserID=${dedeUserId}; DedeUserID__ckMd5=${dedeUserIdCkMd5}`;
54
54
  };
55
+ const formatNumber = (num) => {
56
+ if (num >= 100000000) {
57
+ return (num / 100000000).toFixed(1) + '亿';
58
+ }
59
+ else if (num >= 10000) {
60
+ return (num / 10000).toFixed(1) + '万';
61
+ }
62
+ return num.toString();
63
+ };
64
+ const formatDuration = (seconds) => {
65
+ const hours = Math.floor(seconds / 3600);
66
+ const minutes = Math.floor((seconds % 3600) / 60);
67
+ const secs = seconds % 60;
68
+ if (hours > 0) {
69
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
70
+ }
71
+ else {
72
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
73
+ }
74
+ };
75
+ const parseVideoId = (input) => {
76
+ const avMatch = input.match(/(?:^|[\s/])(av\d+)(?:[\s/]|$)/i);
77
+ if (avMatch) {
78
+ return { type: 'av', id: avMatch[1] };
79
+ }
80
+ const bvMatch = input.match(/(?:^|[\s/])(BV[\w]+)(?:[\s/]|$)/i);
81
+ if (bvMatch) {
82
+ return { type: 'bv', id: bvMatch[1] };
83
+ }
84
+ return { type: null, id: null };
85
+ };
86
+ const parseVideoLinks = (content) => {
87
+ const links = [];
88
+ const fullLinkPattern = /bilibili\.com\/video\/([ab]v[\w]+)/gi;
89
+ let match;
90
+ while ((match = fullLinkPattern.exec(content)) !== null) {
91
+ links.push({ type: 'Video', id: match[1] });
92
+ }
93
+ const shortLinkPatterns = [
94
+ /b23\.tv\/([\w]+)/gi,
95
+ /bili22\.cn\/([\w]+)/gi,
96
+ /bili23\.cn\/([\w]+)/gi,
97
+ /bili33\.cn\/([\w]+)/gi,
98
+ /bili2233\.cn\/([\w]+)/gi,
99
+ ];
100
+ for (const pattern of shortLinkPatterns) {
101
+ while ((match = pattern.exec(content)) !== null) {
102
+ links.push({ type: 'Short', id: match[1] });
103
+ }
104
+ }
105
+ return links;
106
+ };
107
+ const resolveShortLink = async (shortId) => {
108
+ try {
109
+ const response = await axios_1.default.get(`https://b23.tv/${shortId}`, {
110
+ maxRedirects: 0,
111
+ validateStatus: (status) => status >= 300 && status < 400,
112
+ });
113
+ const location = response.headers['location'];
114
+ if (location) {
115
+ return location;
116
+ }
117
+ return null;
118
+ }
119
+ catch (error) {
120
+ if (error.response && error.response.headers && error.response.headers.location) {
121
+ return error.response.headers.location;
122
+ }
123
+ return null;
124
+ }
125
+ };
126
+ const fetchVideoInfo = async (videoId) => {
127
+ const parsed = parseVideoId(videoId);
128
+ if (!parsed.type || !parsed.id) {
129
+ return null;
130
+ }
131
+ try {
132
+ let url;
133
+ if (parsed.type === 'av') {
134
+ url = `https://api.bilibili.com/x/web-interface/view?aid=${parsed.id.replace('av', '')}`;
135
+ }
136
+ else {
137
+ url = `https://api.bilibili.com/x/web-interface/view?bvid=${parsed.id}`;
138
+ }
139
+ const response = await axios_1.default.get(url, {
140
+ headers: {
141
+ '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',
142
+ },
143
+ });
144
+ if (response.data.code === 0) {
145
+ return response.data;
146
+ }
147
+ return null;
148
+ }
149
+ catch (error) {
150
+ console.error('获取视频信息失败:', error);
151
+ return null;
152
+ }
153
+ };
55
154
  const pollLoginStatus = async (qrcodeKey, userId) => {
56
155
  const session = loginSessions.get(qrcodeKey);
57
156
  if (!session)
@@ -260,7 +359,7 @@ function apply(ctx, config) {
260
359
  return '刷新账号信息失败,请稍后重试';
261
360
  }
262
361
  });
263
- cmd.subcommand('video <bvid>', '获取哔哩哔哩视频信息')
362
+ cmd.subcommand('video <bvid>', '获取B站视频信息')
264
363
  .action(async ({ session }, bvid) => {
265
364
  if (!session) {
266
365
  return '无法获取会话信息';
@@ -269,39 +368,99 @@ function apply(ctx, config) {
269
368
  try {
270
369
  const accounts = await ctx.database.get('bilibili', { userId });
271
370
  if (!accounts || accounts.length === 0) {
272
- return '您还未登录哔哩哔哩账号,请使用 "bilitester login" 进行登录';
371
+ return '您还未登录B站账号,请使用 "bilitester login" 进行登录';
273
372
  }
274
373
  const acc = accounts[0];
275
374
  const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
276
375
  const axiosInstance = createAxiosInstance(cookie);
277
- const response = await axiosInstance.get('https://api.bilibili.com/x/web-interface/view', {
278
- params: { bvid }
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号是否正确';
376
+ const videoInfo = await fetchVideoInfo(bvid);
377
+ if (!videoInfo) {
378
+ return '获取视频信息失败,请检查BV/AV号是否正确';
298
379
  }
380
+ const video = videoInfo.data;
381
+ const duration = formatDuration(video.duration);
382
+ const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
383
+ return `视频信息:\n` +
384
+ `标题: ${video.title}\n` +
385
+ `BV号: ${video.bvid}\n` +
386
+ `AV号: ${video.aid}\n` +
387
+ `UP主: ${video.owner.name}\n` +
388
+ `时长: ${duration}\n` +
389
+ `发布时间: ${pubdate}\n` +
390
+ `播放: ${formatNumber(video.stat.view)} | 弹幕: ${formatNumber(video.stat.danmaku)} | 点赞: ${formatNumber(video.stat.like)}\n` +
391
+ `投币: ${formatNumber(video.stat.coin)} | 收藏: ${formatNumber(video.stat.favorite)} | 分享: ${formatNumber(video.stat.share)}\n` +
392
+ `简介: ${video.desc.substring(0, 100)}${video.desc.length > 100 ? '...' : ''}\n` +
393
+ `${koishi_1.h.image(video.pic)}`;
299
394
  }
300
395
  catch (error) {
301
396
  console.error('获取视频信息失败:', error);
302
397
  return '获取视频信息失败,请稍后重试';
303
398
  }
304
399
  });
400
+ cmd.subcommand('parse <link>', '解析B站视频链接')
401
+ .action(async ({ session }, link) => {
402
+ if (!session) {
403
+ return '无法获取会话信息';
404
+ }
405
+ const userId = session.userId || session.guildId || 'unknown';
406
+ try {
407
+ const accounts = await ctx.database.get('bilibili', { userId });
408
+ if (!accounts || accounts.length === 0) {
409
+ return '您还未登录B站账号,请使用 "bilitester login" 进行登录';
410
+ }
411
+ const acc = accounts[0];
412
+ const cookie = getCookieString(acc.sessdata, acc.biliJct, acc.dedeUserId, acc.dedeUserIdCkMd5);
413
+ const axiosInstance = createAxiosInstance(cookie);
414
+ let videoId = null;
415
+ const parsed = parseVideoId(link);
416
+ if (parsed.type && parsed.id) {
417
+ videoId = parsed.id;
418
+ }
419
+ else {
420
+ const links = parseVideoLinks(link);
421
+ if (links.length > 0) {
422
+ const videoLink = links[0];
423
+ if (videoLink.type === 'Short') {
424
+ const resolvedLink = await resolveShortLink(videoLink.id);
425
+ if (resolvedLink) {
426
+ const resolvedParsed = parseVideoId(resolvedLink);
427
+ if (resolvedParsed.type && resolvedParsed.id) {
428
+ videoId = resolvedParsed.id;
429
+ }
430
+ }
431
+ }
432
+ else {
433
+ videoId = videoLink.id;
434
+ }
435
+ }
436
+ }
437
+ if (!videoId) {
438
+ return '无法解析视频链接,请检查链接格式是否正确';
439
+ }
440
+ const videoInfo = await fetchVideoInfo(videoId);
441
+ if (!videoInfo) {
442
+ return '获取视频信息失败,请检查链接是否正确';
443
+ }
444
+ const video = videoInfo.data;
445
+ const duration = formatDuration(video.duration);
446
+ const pubdate = new Date(video.pubdate * 1000).toLocaleString('zh-CN');
447
+ return `视频信息:\n` +
448
+ `标题: ${video.title}\n` +
449
+ `BV号: ${video.bvid}\n` +
450
+ `AV号: ${video.aid}\n` +
451
+ `UP主: ${video.owner.name}\n` +
452
+ `时长: ${duration}\n` +
453
+ `发布时间: ${pubdate}\n` +
454
+ `播放: ${formatNumber(video.stat.view)} | 弹幕: ${formatNumber(video.stat.danmaku)} | 点赞: ${formatNumber(video.stat.like)}\n` +
455
+ `投币: ${formatNumber(video.stat.coin)} | 收藏: ${formatNumber(video.stat.favorite)} | 分享: ${formatNumber(video.stat.share)}\n` +
456
+ `简介: ${video.desc.substring(0, 100)}${video.desc.length > 100 ? '...' : ''}\n` +
457
+ `${koishi_1.h.image(video.pic)}`;
458
+ }
459
+ catch (error) {
460
+ console.error('解析视频链接失败:', error);
461
+ return '解析视频链接失败,请稍后重试';
462
+ }
463
+ });
305
464
  cmd.subcommand('dynamic', '获取哔哩哔哩动态')
306
465
  .action(async ({ session }) => {
307
466
  if (!session) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-bilitester",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "哔哩哔哩登录和API调用插件,支持二维码登录和Cookie管理",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",