Skip to content

Commit 4ec1a11

Browse files
committed
fix(bv2av): negative av number
原转换代码针对 2023 年 3 月 28 日 19:45:02 (av99999999) 之后的新视频出现了转换为负数 av 号的 bug,此处使用新的算法来实现: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/bvid_desc.md
1 parent f442d6f commit 4ec1a11

File tree

3 files changed

+93
-85
lines changed

3 files changed

+93
-85
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ desktop.ini
1212
217/217bak/
1313
hotkid/tel/old_ver2
1414
wwwroot/aspnet_client
15-
generator/pyfmt/c/pyfmt.exe
15+
generator/pyfmt/c/pyfmt.exe
16+
__pycache__/
+47-51
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,65 @@
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
33

44
// @ts-check
5-
"use strict";
65

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;
1110
const ERROR = "¿你在想桃子?";
1211

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) {
1615
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();
2723
} catch (cause) {
2824
console.error(new Error(ERROR, { cause }))
2925
return ERROR;
3026
}
3127

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+
}
4440

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}`;
5548
} else {
5649
return ERROR;
5750
};
58-
if (!str.match(/[Bb][Vv][fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{10}/gu)) {
51+
if (!bvid.match(/[Bb][Vv][fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{10}/gu)) {
5952
return ERROR;
6053
};
6154

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'));

math/bv2av/python/bv2av.py

+44-33
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,56 @@
11
#!/usr/bin/env python3
22
import sys
33

4-
table = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'
5-
transform: dict[str, int] = {}
6-
for i in range(58):
7-
transform[table[i]] = i
8-
slots = [11, 10, 3, 8, 4, 6]
9-
xor = 177451812
10-
add = 8728348608
11-
error = '¿你在想桃子?'
12-
13-
def decode(input: str) -> int:
14-
try:
15-
result: int = 0
16-
for i in range(6):
17-
result += transform[input[slots[i]]] * 58 ** i
18-
return (result - add) ^ xor
19-
except:
20-
print(error)
21-
exit()
4+
# 原转换代码针对 2023 年 3 月 28 日 19:45:02 (av99999999) 之后的新视频出现了转换为负数 av 号的 bug,此处使用新的算法来实现:
5+
# https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/bvid_desc.md
6+
7+
XOR_CODE = 23442827791579
8+
MASK_CODE = 2251799813685247
9+
MAX_AID = 1 << 51
10+
ALPHABET = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf"
11+
ENCODE_MAP = 8, 7, 0, 5, 1, 3, 2, 4, 6
12+
DECODE_MAP = tuple(reversed(ENCODE_MAP))
13+
14+
BASE = len(ALPHABET)
15+
PREFIX = "BV1"
16+
PREFIX_LEN = len(PREFIX)
17+
CODE_LEN = len(ENCODE_MAP)
18+
ERROR = "¿你在想桃子?"
2219

23-
def encode(input: int) -> str:
24-
input = (input ^ xor) + add
25-
result = list('BV1 4 1 7 ')
26-
for i in range(6):
27-
result[slots[i]] = table[input // 58 ** i % 58]
28-
return ''.join(result)
20+
def av2bv(aid: int) -> str:
21+
bvid = [""] * 9
22+
tmp = (MAX_AID | aid) ^ XOR_CODE
23+
for i in range(CODE_LEN):
24+
bvid[ENCODE_MAP[i]] = ALPHABET[tmp % BASE]
25+
tmp //= BASE
26+
return PREFIX + "".join(bvid)
2927

30-
if __name__ == '__main__':
28+
def bv2av(bvid: str) -> int:
29+
assert bvid[:3] == PREFIX
30+
31+
bvid = bvid[3:]
32+
tmp = 0
33+
for i in range(CODE_LEN):
34+
idx = ALPHABET.index(bvid[DECODE_MAP[i]])
35+
tmp = tmp * BASE + idx
36+
return (tmp & MASK_CODE) ^ XOR_CODE
37+
38+
if __name__ == "__main__":
3139
if len(sys.argv) < 2:
32-
print('Please enter av/bv number!')
40+
print("Please input av/BV number!")
3341
exit()
3442
original = sys.argv[1].strip()
35-
result = error
36-
if original[:2].lower() == 'bv':
37-
result = 'av' + str(decode(original))
38-
elif original[:2].lower() == 'av':
39-
result = encode(int(original[2:]))
43+
result = ERROR
44+
if original[:2].lower() == "bv":
45+
result = "av" + str(bv2av(original))
46+
elif original[:2].lower() == "av":
47+
result = av2bv(int(original[2:]))
4048
elif original.isdigit():
41-
result = encode(int(original))
49+
result = av2bv(int(original))
4250
else:
43-
print('Please enter a legal av/bv number!')
51+
print("Please input a valid av/BV number!")
4452
exit()
4553
print(result)
54+
55+
# assert av2bv(111298867365120) == "BV1L9Uoa9EUx"
56+
# assert bv2av("BV1L9Uoa9EUx") == 111298867365120

0 commit comments

Comments
 (0)