|
1 |
| -// 改写自 https://www.zhihu.com/question/381784377/answer/1099438784,并加上一些适当的处理 |
2 |
| -// 我这人虽然是写 JS 的,但是看 Python 不是问题 |
| 1 | +// 原转换代码针对 2023 年 3 月 28 日 19:45:02 (av99999999) 之后的新视频出现了转换为负数 av 号的 bug,此处使用新的算法来实现: |
| 2 | +// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/bvid_desc.md |
3 | 3 |
|
4 | 4 | // @ts-check
|
5 |
| -"use strict"; |
6 | 5 |
|
7 |
| -const table = [..."fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"]; |
8 |
| -const slots = [11, 10, 3, 8, 4, 6]; |
9 |
| -const xor = 177451812; |
10 |
| -const add = 8728348608; |
| 6 | +const XOR_CODE = 23442827791579n; |
| 7 | +const MASK_CODE = 2251799813685247n; |
| 8 | +const MAX_AID = 1n << 51n; |
| 9 | +const BASE = 58n; |
11 | 10 | const ERROR = "¿你在想桃子?";
|
12 | 11 |
|
13 |
| -export const av2bv = (/** @type {string | number | bigint} */ av) => { |
14 |
| - /** @type {number | undefined} */ |
15 |
| - let num; |
| 12 | +const data = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf'; |
| 13 | + |
| 14 | +export function av2bv(/** @type {bigint | number | string} */ aid) { |
16 | 15 | try {
|
17 |
| - if (typeof av === "number") { |
18 |
| - num = Math.trunc(av); |
19 |
| - } else if (typeof av === "string") { |
20 |
| - num = Number(av.replace(/[^0-9]/gu, "")); |
21 |
| - } else if (typeof av === "bigint") { |
22 |
| - num = Number(av); |
23 |
| - }; |
24 |
| - if (num === undefined || !Number.isFinite(num) || num <= 0) { |
25 |
| - throw new Error(); |
26 |
| - }; |
| 16 | + if (typeof aid === "bigint") { } |
| 17 | + else if (typeof aid === "number") { |
| 18 | + aid = BigInt(Math.trunc(aid)); |
| 19 | + } else if (typeof aid === "string") { |
| 20 | + aid = BigInt(aid.replace(/[^0-9]/gu, "")); |
| 21 | + } else throw new Error(); |
| 22 | + if (aid === undefined || aid <= 0) throw new Error(); |
27 | 23 | } catch (cause) {
|
28 | 24 | console.error(new Error(ERROR, { cause }))
|
29 | 25 | return ERROR;
|
30 | 26 | }
|
31 | 27 |
|
32 |
| - num = (num ^ xor) + add; |
33 |
| - let result = [..."BV1 4 1 7 "]; |
34 |
| - let i = 0; |
35 |
| - while (i < 6) { |
36 |
| - // 这里改写差点犯了运算符优先级的坑 |
37 |
| - // 果然 Python 也不是特别熟练 |
38 |
| - // 说起来 ** 按照传统语法应该写成 Math.pow(),但是我个人更喜欢 ** 一些 |
39 |
| - result[slots[i]] = table[Math.floor(num / 58 ** i) % 58]; |
40 |
| - i++; |
41 |
| - }; |
42 |
| - return result.join(""); |
43 |
| -}; |
| 28 | + const bytes = [..."BV1000000000"]; |
| 29 | + let bvIndex = bytes.length - 1; |
| 30 | + let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE; |
| 31 | + while (tmp > 0) { |
| 32 | + bytes[bvIndex] = data[Number(tmp % BigInt(BASE))]; |
| 33 | + tmp = tmp / BASE; |
| 34 | + bvIndex -= 1; |
| 35 | + } |
| 36 | + [bytes[3], bytes[9]] = [bytes[9], bytes[3]]; |
| 37 | + [bytes[4], bytes[7]] = [bytes[7], bytes[4]]; |
| 38 | + return bytes.join(''); |
| 39 | +} |
44 | 40 |
|
45 |
| -export const bv2av = (/** @type {string} */ bv) => { |
46 |
| - let str = ""; |
47 |
| - if (bv.length === 12) { |
48 |
| - str = bv; |
49 |
| - } else if (bv.length === 10) { |
50 |
| - str = `BV${bv}`; |
51 |
| - // 根据官方 API,BV 号开头的 BV1 其实可以省略 |
52 |
| - // 不过单独省略个 B 又不行( |
53 |
| - } else if (bv.length === 9) { |
54 |
| - str = `BV1${bv}`; |
| 41 | +export function bv2av(/** @type {`BV1${string}`} */ bvid) { |
| 42 | + if (bvid.length === 12) { } |
| 43 | + else if (bvid.length === 10) { |
| 44 | + bvid = /** @type {never} */ (`BV${bvid}`); |
| 45 | + // 根据官方 API,BV 号开头的 BV1 其实可以省略,不过单独省略个 B 又不行。 |
| 46 | + } else if (bvid.length === 9) { |
| 47 | + bvid = `BV1${bvid}`; |
55 | 48 | } else {
|
56 | 49 | return ERROR;
|
57 | 50 | };
|
58 |
| - if (!str.match(/[Bb][Vv][fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{10}/gu)) { |
| 51 | + if (!bvid.match(/[Bb][Vv][fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{10}/gu)) { |
59 | 52 | return ERROR;
|
60 | 53 | };
|
61 | 54 |
|
62 |
| - let result = 0; |
63 |
| - let i = 0; |
64 |
| - while (i < 6) { |
65 |
| - result += table.indexOf(str[slots[i]]) * 58 ** i; |
66 |
| - i += 1; |
67 |
| - }; |
68 |
| - return `av${result - add ^ xor}`; |
69 |
| -}; |
| 55 | + const bvidArr = Array.from(bvid); |
| 56 | + [bvidArr[3], bvidArr[9]] = [bvidArr[9], bvidArr[3]]; |
| 57 | + [bvidArr[4], bvidArr[7]] = [bvidArr[7], bvidArr[4]]; |
| 58 | + bvidArr.splice(0, 3); |
| 59 | + const tmp = bvidArr.reduce((pre, bvidChar) => pre * BASE + BigInt(data.indexOf(bvidChar)), 0n); |
| 60 | + const aid = (tmp & MASK_CODE) ^ XOR_CODE; |
| 61 | + return `av${aid}`; |
| 62 | +} |
| 63 | + |
| 64 | +// console.log(av2bv(111298867365120)); |
| 65 | +// console.log(bv2av('BV1L9Uoa9EUx')); |
0 commit comments