要AV号,不要BV码

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           要AV号,不要BV码
// @name:en        Make AV number, not BV code
// @namespace      https://pilipili.com/bv10492
// @version        0.1
// @description    Make AV number, not BV code. F**k you BiliBili
// @description:en Make AV number, not BV code. F**k you BiliBili
// @author         jk1551
// @require        https://unpkg.com/[email protected]/dist/ajaxhook.min.js
// @match          https://www.bilibili.com/video/*
// @match          http://www.bilibili.com/video/*
// @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

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]] = i;
    }

    const s = [11, 10, 3, 8, 4, 6];
    const xor = 177451812;
    const add = 8728348608;

    /**
     * @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 = 0;

        for (let i = 0; i < 6; i += 1) {
            r += tr[x[s[i]]] * Math.pow(58, i);
        }

        return (r - add) ^ xor;
    }

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

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

        for (let i = 0; i < 6; i += 1) {
            const index = Math.pow((x / 58) | 0, i % 58);
            r[s[i]] = table[index];
        }

        return r.join("");
    }

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

/**
 * @param avNumber {number | string}
 */
function getAvUrl(avNumber) {
    return "//www.bilibili.com/video/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 = "av" + avNumber.toString();
        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 = "av" + avNumber.toString();
        link.href = getAvUrl(avNumber);
        link.style.marginRight = "16px";
    }
}

(() => {
    const bvUrlRE = /www\.bilibili\.com\/video\/(BV[ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz123456789]+)/;
    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;

// TODO: 处理 HTML5 History 相关

const bvidRE = /bvid=(BV[ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz123456789]+)/;
const aidRE = /aid=(\d+)/;

ah.proxy({
    //请求发起前进入
    onRequest: (config, handler) => {
        while (config.url.indexOf("api.bilibili.com/x/web-interface/view") > 0) {
            let 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;
                break;
            }

            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;
                break;
            }

            break;
        }

        handler.next(config);
    },

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

        handler.next(response);
    }
});