Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/clis/bilibili/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { cli, Strategy } from '../../registry.js';
import { apiGet } from './utils.js';
import { apiGet, resolveBvid } from './utils.js';

cli({
site: 'bilibili',
Expand All @@ -18,7 +18,7 @@ cli({
],
columns: ['rank', 'author', 'text', 'likes', 'replies', 'time'],
func: async (page, kwargs) => {
const bvid = String(kwargs.bvid).trim();
const bvid = await resolveBvid(kwargs.bvid);
const limit = Math.min(Number(kwargs.limit) || 20, 50);

// Resolve bvid → aid (required by reply API)
Expand Down
3 changes: 2 additions & 1 deletion src/clis/bilibili/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { cli, Strategy } from '../../registry.js';
import { checkYtdlp, sanitizeFilename } from '../../download/index.js';
import { downloadMedia } from '../../download/media-download.js';
import { resolveBvid } from './utils.js';

cli({
site: 'bilibili',
Expand All @@ -25,7 +26,7 @@ cli({
],
columns: ['bvid', 'title', 'status', 'size'],
func: async (page, kwargs) => {
const bvid = kwargs.bvid;
const bvid = await resolveBvid(kwargs.bvid);
const output = kwargs.output;
const quality = kwargs.quality;

Expand Down
7 changes: 4 additions & 3 deletions src/clis/bilibili/subtitle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cli, Strategy } from '../../registry.js';
import { AuthRequiredError, CommandExecutionError, EmptyResultError, SelectorError } from '../../errors.js';
import type { IPage } from '../../types.js';
import { apiGet } from './utils.js';
import { apiGet, resolveBvid } from './utils.js';

cli({
site: 'bilibili',
Expand All @@ -15,8 +15,9 @@ cli({
columns: ['index', 'from', 'to', 'content'],
func: async (page: IPage | null, kwargs: any) => {
if (!page) throw new CommandExecutionError('Browser session required for bilibili subtitle');
const bvid = await resolveBvid(kwargs.bvid);
// 1. 先前往视频详情页 (建立有鉴权的 Session,且这里不需要加载完整个视频)
await page.goto(`https://www.bilibili.com/video/${kwargs.bvid}/`);
await page.goto(`https://www.bilibili.com/video/${bvid}/`);

// 2. 利用 __INITIAL_STATE__ 获取基础信息,拿 CID
const cid = await page.evaluate(`(async () => {
Expand All @@ -31,7 +32,7 @@ cli({
// 3. 在 Node 端使用 apiGet 获取带 Wbi 签名的字幕列表
// 之前纯靠 evaluate 里的 fetch 会失败,因为 B 站 /wbi/ 开头的接口强校验 w_rid,未签名直接被风控返回 403 HTML
const payload = await apiGet(page, '/x/player/wbi/v2', {
params: { bvid: kwargs.bvid, cid },
params: { bvid, cid },
signed: true, // 开启 wbi_sign 自动签名
});

Expand Down
21 changes: 21 additions & 0 deletions src/clis/bilibili/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest';
import { resolveBvid } from './utils.js';

describe('resolveBvid', () => {
it('passes through a valid BV ID', async () => {
expect(await resolveBvid('BV1MV9NBtENN')).toBe('BV1MV9NBtENN');
});

it('passes through BV ID with surrounding whitespace', async () => {
expect(await resolveBvid(' BV1MV9NBtENN ')).toBe('BV1MV9NBtENN');
});

it('handles non-string input via String() coercion', async () => {
expect(await resolveBvid('BV123abc' as any)).toBe('BV123abc');
});

it('rejects invalid input that cannot be resolved', async () => {
// A random string that b23.tv won't resolve — should timeout or fail
await expect(resolveBvid('not-a-valid-code-99999')).rejects.toThrow();
});
});
27 changes: 27 additions & 0 deletions src/clis/bilibili/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,36 @@
* Bilibili shared helpers: WBI signing, authenticated fetch, nav data, UID resolution.
*/

import https from 'node:https';
import type { IPage } from '../../types.js';
import { AuthRequiredError, EmptyResultError } from '../../errors.js';

/**
* Resolve Bilibili short URL / short code to BV ID.
* Supports: BV1MV9NBtENN, XYzsqGa, b23.tv/XYzsqGa, https://b23.tv/XYzsqGa
*/
export function resolveBvid(input: unknown): Promise<string> {
const trimmed = String(input).trim();
if (/^BV[A-Za-z0-9]+$/i.test(trimmed)) {
return Promise.resolve(trimmed);
}
const shortCode = trimmed.replace(/^https?:\/\//, '').replace(/^(www\.)?b23\.tv\//, '');
const url = 'https://b23.tv/' + shortCode;
return new Promise((resolve, reject) => {
const req = https.get(url, (res) => {
const location = res.headers.location;
if (location) {
const match = location.match(/\/video\/(BV[A-Za-z0-9]+)/);
if (match) { res.resume(); resolve(match[1]); return; }
}
res.resume();
reject(new Error(`Cannot resolve BV ID from short URL: ${trimmed}`));
});
req.on('error', reject);
req.setTimeout(5000, () => { req.destroy(); reject(new Error(`Timeout resolving short URL: ${trimmed}`)); });
});
}

const MIXIN_KEY_ENC_TAB = [
46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,27,43,5,49,
33,9,42,19,29,28,14,39,12,38,41,13,37,48,7,16,24,55,40,
Expand Down