From 9fff55ebb9d6fae386c2f94f53da10714d27487a Mon Sep 17 00:00:00 2001 From: MotooriKashin <60416767+MotooriKashin@users.noreply.github.com> Date: Tue, 30 Jan 2024 08:23:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99BV=3D>av=E7=AE=97=E6=B3=95=20?= =?UTF-8?q?#525?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chrome/content.ts | 2 +- src/core/comment.ts | 4 +- src/core/url.ts | 10 ++--- src/io/api-webshow-locs.ts | 6 +-- src/page/read.ts | 4 +- src/utils/abv.ts | 54 ---------------------- src/utils/av.ts | 91 ++++++++++++++++++++++++++++++++++++++ src/utils/utils.ts | 8 ++-- 8 files changed, 108 insertions(+), 71 deletions(-) delete mode 100644 src/utils/abv.ts create mode 100644 src/utils/av.ts diff --git a/chrome/content.ts b/chrome/content.ts index eda7d6caa..a0cea130a 100644 --- a/chrome/content.ts +++ b/chrome/content.ts @@ -119,7 +119,7 @@ window.addEventListener("beforeunload", () => { }); }); -documentPictureInPicture?.addEventListener('enter', e => { +self.documentPictureInPicture?.addEventListener('enter', e => { if (documentPictureInPicture.window) { const url = chrome.runtime.getURL('/player/video.css'); const link = document.createElement('link'); diff --git a/src/core/comment.ts b/src/core/comment.ts index b597fc96d..853052be3 100644 --- a/src/core/comment.ts +++ b/src/core/comment.ts @@ -1,5 +1,5 @@ import { apiReply } from "../io/api-reply"; -import { BV2avAll } from "../utils/abv"; +import { AV } from "../utils/av"; import { addCss, addElement, loadScript } from "../utils/element"; import { urlObj } from "../utils/format/url"; import { jsonpHook } from "../utils/hook/node"; @@ -524,7 +524,7 @@ export class Comment { } } - return BV2avAll(str); + return AV.fromStr(str); }; } /** 评论图片 */ diff --git a/src/core/url.ts b/src/core/url.ts index 21979457c..0cc5750ab 100644 --- a/src/core/url.ts +++ b/src/core/url.ts @@ -1,4 +1,4 @@ -import { abv, BV2avAll } from "../utils/abv"; +import { AV } from "../utils/av"; import { URL } from "../utils/format/url"; /** 垃圾参数序列 */ @@ -62,11 +62,11 @@ class UrlCleaner { const params = url.params; // 旧版页面一般不支持bvid,转化为aid if (params.bvid) { - params.aid = abv(params.bvid); + params.aid = AV.fromBV(params.bvid); } // B站偶有发病出现名为aid实为bvid的情况 if (params.aid && !Number(params.aid)) { - params.aid = abv(params.aid); + params.aid = AV.fromBV(params.aid); } // 通杀 paramsSet.forEach(d => { delete params[d]; }); @@ -78,8 +78,8 @@ class UrlCleaner { } } }); - url.base = BV2avAll(url.base); - url.hash && (url.hash = BV2avAll(url.hash)); + url.base = AV.fromStr(url.base); + url.hash && (url.hash = AV.fromStr(url.hash)); return url.toJSON(); } else return str; } diff --git a/src/io/api-webshow-locs.ts b/src/io/api-webshow-locs.ts index ad21140fa..917677590 100644 --- a/src/io/api-webshow-locs.ts +++ b/src/io/api-webshow-locs.ts @@ -1,4 +1,4 @@ -import { BV2avAll } from "../utils/abv"; +import { AV } from "../utils/av"; import { objUrl } from "../utils/format/url"; import { jsonCheck, IApiData } from "./api"; import { URLS } from "./urls"; @@ -9,7 +9,7 @@ export async function apiWebshowLoc(id: number) { id })); const text = await response.text(); - return jsonCheck(BV2avAll(text)).data; + return jsonCheck(AV.fromStr(text)).data; } export async function apiWebshowLocs(data: IApiWebshowLocsData) { const response = await fetch(objUrl(URLS.WEBSHOW_LOCS, { @@ -17,7 +17,7 @@ export async function apiWebshowLocs(data: IApiWebshowLocsData) { ids: data.ids.join(',') })); const text = await response.text(); - return jsonCheck(BV2avAll(text)).data; + return jsonCheck(AV.fromStr(text)).data; } interface IApiWebshowLocsData extends IApiData { diff --git a/src/page/read.ts b/src/page/read.ts index 88368fc31..99b9f68d3 100644 --- a/src/page/read.ts +++ b/src/page/read.ts @@ -4,12 +4,12 @@ import { toast } from '../core/toast'; import html from '../html/read.html'; import { IStat } from "../io/api"; import { IUserInfo } from "../io/api-view-detail"; -import { BV2avAll } from '../utils/abv'; import { debug } from "../utils/debug"; import { addCss, loadScript } from "../utils/element"; import { propertyHook } from "../utils/hook/method"; import { webpackHook } from '../utils/hook/webpack'; import { Page } from "./page"; +import { AV } from '../utils/av'; interface ReadInfo { act_id: number; @@ -158,7 +158,7 @@ export class PageRead extends Page { debug.error(e) } } - return BV2avAll(str); + return AV.fromStr(str); } protected tagContainer() { this.readInfoStr += (this.readInfo.tags || []).reduce((o, d) => { diff --git a/src/utils/abv.ts b/src/utils/abv.ts deleted file mode 100644 index fe6694618..000000000 --- a/src/utils/abv.ts +++ /dev/null @@ -1,54 +0,0 @@ -class Abv { - base58Table = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'; - digitMap = [11, 10, 3, 8, 4, 6]; - xor = 177451812; - add = 8728348608; - bvidTemplate = ['B', 'V', 1, '', '', 4, '', 1, '', 7, '', '']; - table: Record = {}; - constructor() { - for (let i = 0; i < 58; i++) this.table[this.base58Table[i]] = i; - } - /** - * av/BV互转 - * @param input av或BV,可带av/BV前缀 - * @returns 转化结果 - */ - check(input: string | number) { - if (/^[aA][vV][0-9]+$/.test(String(input)) || /^\d+$/.test(String(input))) return this.avToBv(Number((/[0-9]+/.exec(String(input)))[0])); - if (/^1[fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{9}$/.test(String(input))) return this.bvToAv("BV" + input); - if (/^[bB][vV]1[fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{9}$/.test(String(input))) return this.bvToAv(String(input)); - throw input; - } - bvToAv(BV: string) { - let r = 0; - for (let i = 0; i < 6; i++) r += this.table[BV[this.digitMap[i]]] * 58 ** i; - return (r - this.add) ^ this.xor; - } - avToBv(av: number) { - let bv = Array.from(this.bvidTemplate); - av = (av ^ this.xor) + this.add; - for (let i = 0; i < 6; i++) bv[this.digitMap[i]] = this.base58Table[parseInt(String(av / 58 ** i)) % 58]; - return bv.join(""); - } -} -/** - * av <=> BV - * @param input av/BV - * @returns BV/aid - * @example - * abv(170001) // BV17x411w7KC - * abv("av170001") // BV17x411w7KC - * abv("AV170001") // BV17x411w7KC - * abv("BV17x411w7KC") // 170001 - * abv("17x411w7KC") // 170001 - */ -export function abv(input: string | number) { - return new Abv().check(input); -} -/** - * 替换所有BV号为av号 - * @param str 含有BV号的字符串 - */ -export function BV2avAll(str: string) { - return str.replace(/[bB][vV]1[fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{9}/g, (s: string) => "av" + abv(s)); -} \ No newline at end of file diff --git a/src/utils/av.ts b/src/utils/av.ts new file mode 100644 index 000000000..bf29165f9 --- /dev/null +++ b/src/utils/av.ts @@ -0,0 +1,91 @@ +/** + * @file av号工具 + */ + +export namespace AV { + const XOR_CODE = 23442827791579n; + const MASK_CODE = 2251799813685247n; + + const MAX_AID = 1n << 51n; + const MIN_AID = 1n; + + const BASE = 58n; + const BYTES = ['B', 'V', 1, '', '', '', '', '', '', '', '', '']; + const BV_LEN = BYTES.length; + + const ALPHABET = [ + 'F', 'c', 'w', 'A', 'P', 'N', 'K', 'T', 'M', 'u', 'g', '3', 'G', 'V', '5', 'L', + 'j', '7', 'E', 'J', 'n', 'H', 'p', 'W', 's', 'x', '4', 't', '', '8', 'h', 'a', + 'Y', 'e', 'v', 'i', 'q', 'B', 'z', '6', 'r', 'k', 'C', 'y', '1', '2', 'm', 'U', + 'S', 'D', 'Q', 'X', '9', 'R', 'd', 'o', 'Z', 'f' + ]; + const DIGIT_MAP = [0, 1, 2, 9, 7, 5, 6, 4, 8, 3, 10, 11]; + const REG_EXP = new RegExp(`^[bB][vV]1[${ALPHABET.join('')}]{9}$`, 'g'); + const REG_EXP_SHORT = new RegExp(`^1[${ALPHABET.join('')}]{9}$`, 'g'); + const REG_EXP_STR = new RegExp(`[bB][vV]1[${ALPHABET.join('')}]{9}`, 'g'); + + /** + * aid => BV + * + * @example + * toBV(170001) // BV17x411w7KC + */ + export function toBV(avid: bigint | number) { + typeof avid === "bigint" || (avid = BigInt(avid)); + + if (avid < MIN_AID) { + throw new RangeError(`Av ${avid} is smaller than ${MIN_AID}`); + } + if (avid >= MAX_AID) { + throw new RangeError(`Av ${avid} is bigger than ${MAX_AID}`); + } + + const bytes = Array.from(BYTES); + + let bv_idx = BV_LEN - 1; + let tmp = (MAX_AID | avid) ^ XOR_CODE; + while (tmp !== 0n) { + let table_idx = tmp % BASE; + bytes[DIGIT_MAP[Number(bv_idx)]] = ALPHABET[Number(table_idx)]; + tmp /= BASE; + bv_idx -= 1; + } + + return bytes.join(''); + } + + /** + * BV => aid + * + * @example + * fromBV('BV17x411w7KC') // 170001 + * fromBV('17x411w7KC') // 170001 + */ + export function fromBV(bvid: string) { + if (REG_EXP_SHORT.test(bvid)) { + bvid = 'BV' + bvid; + } + if (!REG_EXP.test(bvid)) { + throw new TypeError(`${bvid} is illegal`); + } + + let r = 0n; + for (let i = 3; i < BV_LEN; i++) { + r = r * BASE + BigInt(ALPHABET.indexOf(bvid[DIGIT_MAP[i]])); + } + + return `${(r & MASK_CODE) ^ XOR_CODE}`; + } + + /** + * 替换文本中所有BV号 + * + * @param str 含有BV号的文本 + * @returns 替换为av号的文本 + * @example + * fromStr('***BV17x411w7KC***') // ***av170001*** + */ + export function fromStr(str: string) { + return str.replace(REG_EXP_STR, (s: string) => "av" + fromBV(s)); + } +} \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e7fb76eee..173af776a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import { apiBiliplusView } from "../io/api-biliplus-view"; import { apiPlayerPagelist } from "../io/api-player-pagelist"; import { apiView } from "../io/api-view"; import { apiXView } from "../io/api-x-view"; -import { abv } from "./abv"; +import { AV } from "./av"; import { debug } from "./debug"; import { objUrl, urlObj } from "./format/url"; @@ -55,9 +55,9 @@ export async function urlParam(url: string, redirect = true): Promise let pgc = false; !aid && (aid = obj.avid); !aid && (url.replace(/[aA][vV]\d+/, d => aid = d.substring(2))); - !aid && (url.replace(/[bB][vV]1[fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{9}/, d => aid = abv(d))); - !aid && obj.bvid && (aid = abv(obj.bvid)); - aid && !Number(aid) && (aid = abv(aid)); + !aid && (url.replace(/[bB][vV]1[fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF]{9}/, d => aid = AV.fromBV(d))); + !aid && obj.bvid && (aid = AV.fromBV(obj.bvid)); + aid && !Number(aid) && (aid = AV.fromBV(aid)); p = p || 1; !ssid && (ssid = obj.seasonId); !ssid && (ssid = obj.season_id);