Make AV number, not BV code

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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴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.4
// @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 "/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 infoBlock1 = avInfoLabels[0];

        // 投稿日期span
        // 直接塞到和infoBlock1同级会导致谜之排版错误,只好借壳咯(其实是Vue的锅
        const info1 = infoBlock1.children[1];

        // 我放弃创建新的元素了……干脆直接黑魔法换个a
        const avreg = /av\d+/;
        const oldContent = info1.textContent;
        
        if (!avreg.test(oldContent)) {
            const avUrl = getAvUrl(avNumber);
            const avText = getAvText(avNumber);
            info1.outerHTML = `<a href="${avUrl}" title="${avText}" target="_blank">${oldContent}\u00a0${avText}</a>`;
            // 以下两行本来是调整样式,让其显示跟原来差不多的,但是Vue似乎劫持了样式设定,所以没出效果也就算了
            info1.style.color = "#999";
            info1.style.height = "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 = /\/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 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 (response.config.url.indexOf("api.bilibili.com") > 0) {
                if (isContentRefreshed) {
                    switch (pageType) {
                        case PageType.video: {
                            if (!Number.isNaN(avNumber)) {
                                setTimeout(() => appendOrUpdateAvNumberLink(avNumber));
                            }

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

                            break;
                        }
                        default:
                            break;
                    }
                }
            }

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