要AV号,不要BV码

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

目前為 2020-03-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }
});