Make AV number, not BV code

Make AV number, not BV code. F**k you BiliBili

当前为 2020-03-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name              Make AV number, not BV code
// @name:en           Make AV number, not BV code
// @name:zh-CN        要AV号,不要BV码
// @namespace         https://pilipili.com/bv10492
// @version           0.1.3
// @description       Make AV number, not BV code. F**k you BiliBili
// @description:en    Make AV number, not BV code. F**k you BiliBili
// @description:zh-CN 要AV号,不要BV码,屑站飞了
// @author            jk1551
// @require           https://unpkg.com/[email protected]/dist/ajaxhook.min.js
// @nocompat          Chrome
// @nocompat          Firefox
// @nocompat          Opera
// @nocompat          Edge
// @match             https://www.bilibili.com/video/*
// @match             http://www.bilibili.com/video/*
// @match             https://www.bilibili.com/bangumi/play/*
// @match             http://www.bilibili.com/bangumi/play/*
// @match             https://acg.tv/*
// @match             http://acg.tv/*
// @match             https://b23.tv/*
// @match             http://b23.tv/*
// @grant             none
// ==/UserScript==

/* jshint esversion: 6 */

const CONFIG = {
    autoJump: false
};

// GM代码构思来自 https://greasyfork.org/zh-CN/scripts/398526-give-me-av-not-bv

(() => {
    if (typeof BigInt !== "function") {
        console.warn("Your browser does not support BigInt. AV/BV conversion is disabled.");
        return;
    }

    const bv2av = (() => {
        // https://www.zhihu.com/question/381784377/answer/1099438784
        const table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF";
        const tr = Object.create(null);

        for (let i = 0; i < 58; i += 1) {
            tr[table[i]] = BigInt(i);
        }

        const s = [11, 10, 3, 8, 4, 6];
        const xor = BigInt(177451812);
        const add = BigInt(8728348608);
        const pow58 = [];
        
        // 58进制下2^32的最大位数(=6),也就是在此之前变动的最多位数,也就是s表的长度
        const highestDigit = 6;
        
        for (let i = 0; i < highestDigit; i += 1) {
            pow58.push(BigInt(Math.pow(58, i)));
        }

        /**
         * @param s {string}
         */
        function list(s) {
            return s.split("");
        }

        /**
         * @param x {string} BV string; including the beginning "BV" (e.g. "BV17x411w7KC")
         */
        function dec(x) {
            let r = BigInt(0);

            for (let i = 0; i < highestDigit; i += 1) {
                r += tr[x[s[i]]] * pow58[i];
            }

            return Number((r - add) ^ xor);
        }

        /**
         * @param x {number} Exsiting AV number
         */
        function enc(x) {
            x = BigInt(x);
            x = (x ^ xor) + add;

            const r = list("BV1  4 1 7  ");

            for (let i = 0; i < highestDigit; i += 1) {
                const index = (x / pow58[i]) % BigInt(58);
                r[s[i]] = table[index];
            }

            return r.join("");
        }

        return {
            enc: enc,
            dec: dec
        };
    })();

    /**
     * @param avNumber {number | string | bigint}
     */
    function getAvUrl(avNumber) {
        return "//www.bilibili.com/video/av" + avNumber.toString();
    }

    /**
     * @param avNumber {number | string | bigint}
     */
    function getAvText(avNumber) {
        return "av" + avNumber.toString();
    }

    /**
     * @param avNumber {number | string}
     */
    function appendOrUpdateAvNumberLink(avNumber) {
        const avInfoLabels = document.getElementsByClassName("video-data");
        // 分区和日期
        const infoLabel1 = avInfoLabels[0];
        // 直接塞到和infoLabel1同级会导致谜之排版错误
        const info1 = infoLabel1.children[0];
        let link;

        if (info1.children.length === 2) {
            // 原始状态(只有分区和日期两个span)
            const span = document.createElement("span");
            link = document.createElement("a");
            link.textContent = getAvText(avNumber);
            link.href = getAvUrl(avNumber);
            info1.appendChild(span);
        } else if (info1.children.length > 2) {
            // pilipili的谜之DOM更新,又不像是VDOM
            // 本来添加了一个span和a,但是刷新之后反而变成了a是info1的第一个子元素
            link = info1.children[0];
            link.textContent = getAvText(avNumber);
            link.href = getAvUrl(avNumber);
            link.style.marginRight = "16px";
        }
    }

    /**
     * @param avNumber {number | string}
     */
    function modifyBangumiAvNumberLink(avNumber) {
        const pubs = document.getElementsByClassName("pub-wrapper");

        if (!pubs || pubs.length === 0) {
            return;
        }

        const avLink = pubs[0].querySelector("a.av-link");

        if (!avLink) {
            return;
        }

        avLink.textContent = getAvText(avNumber);
        avLink.href = getAvUrl(avNumber);
    }

    (() => {
        const bvUrlRE = /www\.bilibili\.com\/video\/(BV[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)/;
        const bvmatch = bvUrlRE.exec(window.location.href);

        if (bvmatch) {
            console.log("F**k You BV Number!");

            if (CONFIG.autoJump) {
                const bvstr = bvmatch[1];
                const avNumber = bv2av.dec(bvstr);
                const url = getAvUrl(avNumber);
                window.location.href = url;
            }
        }
    })();

    let isContentRefreshed = false;
    let isAvNumberWritten = false;
    let avNumber = Number.NaN;
    let pageType = Number.NaN;

    const PageType = {
        video: 0,
        bangumi: 1
    };

    // TODO: 处理 HTML5 History 相关

    const bvidRE = /bvid=(BV[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)/;
    const aidRE = /aid=(\d+)/;
    const avidRE = /avid=(\d+)/;

    ah.proxy({
        //请求发起前进入
        onRequest: (config, handler) => {
            let match;

            if (config.url.indexOf("api.bilibili.com/x/web-interface/view") > 0) {
                match = bvidRE.exec(config.url);

                if (match) {
                    // https://api.bilibili.com/x/web-interface/view?cid=167028524&bvid=BV1TE411A7VJ
                    avNumber = bv2av.dec(match[1]);
                    isContentRefreshed = true;
                    pageType = PageType.video;
                } else {
                    match = aidRE.exec(config.url);

                    if (match) {
                        // https://api.bilibili.com/x/web-interface/view?cid=167028524&aid=97836354
                        avNumber = Number.parseInt(match[1]);
                        isContentRefreshed = true;
                        pageType = PageType.video;
                    }
                }
            } else if (config.url.indexOf("api.bilibili.com/pgc/player/web/playurl") > 0) {
                match = avidRE.exec(config.url);

                if (match) {
                    // https://api.bilibili.com/pgc/player/web/playurl?cid=122062626&qn=0&type=&otype=json&avid=70455036&ep_id=286039&fourk=1&fnver=0&fnval=16&session=xxx
                    avNumber = Number.parseInt(match[1]);
                    isContentRefreshed = true;
                    pageType = PageType.bangumi;
                }
            }

            handler.next(config);
        },

        //请求成功后进入
        onResponse: (response, handler) => {
            if (isContentRefreshed) {
                switch (pageType) {
                    case PageType.video: {
                        if (isAvNumberWritten) {
                            if (!Number.isNaN(avNumber)) {
                                setTimeout(() => appendOrUpdateAvNumberLink(avNumber));
                                isAvNumberWritten = true;
                            }
                        } else {
                            if (!Number.isNaN(avNumber)) {
                                setTimeout(() => appendOrUpdateAvNumberLink(avNumber));
                            }
                        }

                        break;
                    }
                    case PageType.bangumi: {
                        if (!Number.isNaN(avNumber)) {
                            setTimeout(() => modifyBangumiAvNumberLink(avNumber));
                        }

                        break;
                    }
                    default:
                        break;
                }
            }

            handler.next(response);
        }
    });
})();