Greasy Fork is available in English.

BiliveHeart

B站直播心跳

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/447321/1416383/BiliveHeart.js

// ==UserScript==
// @name        BiliveHeart
// @namespace   https://github.com/lzghzr/TampermonkeyJS
// @version     0.0.7
// @author      lzghzr
// @description B站直播心跳
// @include     /^https?:\/\/live\.bilibili\.com\/(?:blanc\/)?\d/
// @require     https://greasyfork.org/scripts/441505-crypto-js4-1-1/code/crypto-js411.js?version=1028182
// @license     MIT
// @grant       none
// ==/UserScript==
class RoomHeart {
    constructor(t,g,r) {
        this.roomID = t,
        this.Dotime = g,
        this.ruid = r
    }
    areaID;
    parentID;
    seq = 0;
    roomID;
    Dotime;
    get id() {
        return [this.parentID, this.areaID, this.seq, this.roomID]
    }
    buvid = this.getItem("LIVE_BUVID");
    uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (t => {
                const e = 16 * Math.random() | 0;
                return ("x" === t ? e : 3 & e | 8).toString(16)
            }));
    device = [this.buvid, this.uuid];
    get ts() {
        return Date.now()
    }
    _patchData = {};
    get patchData() {
        const t = [];
        for (const[e, i]of Object.entries(this._patchData))
            t.push(i);
        return t
    }
    get isPatch() {
        return 0 === this.patchData.length ? 0 : 1
    }
    W = "undefined" == typeof unsafeWindow ? window : unsafeWindow;
    ua = this.W && this.W.navigator ? this.W.navigator.userAgent : "";
    csrf = this.getItem("bili_jct") || "";
    nextInterval = Math.floor(5) + Math.floor(55 * Math.random());
    heartBeatInterval;
    secretKey;
    secretRule;
    timestamp;
    lastHeartbeatTimestamp = Date.now();
    get watchTimeFromLastReport() {
        const t = Math.ceil(((new Date).getTime() - this.lastHeartbeatTimestamp) / 1e3);
        return t < 0 ? 0 : t > this.heartBeatInterval ? this.heartBeatInterval : t
    }
    start() {
        return this.getInfoByRoom()
    }
    doneFunc = function () {};
    async getInfoByRoom() {
        if (0 === this.roomID)
            return !1;
        const t = await fetch(`//api.live.bilibili.com/room/v1/Room/get_info?room_id=${this.roomID}&from=room`, {
            mode: "cors",
            credentials: "include"
        }).then((t => t.json()));
        return 0 === t.code && (({
                area_id: this.areaID,
                parent_area_id: this.parentID,
                room_id: this.roomID
            } = t.data), 0 !== this.areaID && 0 !== this.parentID && (this.e(), !0))
    }
    async webHeartBeat() {
        if (this.seq > this.Dotime)
            return;
        const t = `${this.nextInterval}|${this.roomID}|1|0`,
        e = CryptoJS.enc.Utf8.parse(t),
        i = CryptoJS.enc.Base64.stringify(e),
        s = await fetch(`//live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb=${encodeURIComponent(i)}&pf=web`, {
            mode: "cors",
            credentials: "include"
        }).then((t => t.json()));
        0 === s.code && (this.nextInterval = s.data.next_interval, setTimeout((() => this.webHeartBeat()), 1e3 * this.nextInterval))
    }
    async savePatchData() {
        if (this.seq > this.Dotime)
            return;
        const t = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ruid:this.ruid,
            ets: this.timestamp,
            benchmark: this.secretKey,
            time: this.watchTimeFromLastReport > this.heartBeatInterval ? this.heartBeatInterval : this.watchTimeFromLastReport,
            ts: this.ts,
            ua: this.ua
        },
        e = this.sypder(JSON.stringify(t), this.secretRule),
        i = Object.assign({
            s: e
        }, t);
        this._patchData[this.roomID] = i,
        setTimeout((() => this.savePatchData()), 15e3)
    }
    async e() {
        const t = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ruid:this.ruid,
            ts: this.ts,
            is_patch: 0,
            heart_beat: "[]",
            ua: this.ua
        },
        e = await fetch("//live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/E", {
            headers: {
                "content-type": "application/x-www-form-urlencoded"
            },
            method: "POST",
            body: `${this.json2str(t)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
            mode: "cors",
            credentials: "include"
        }).then((t => t.json()));
        0 === e.code && (this.seq += 1, ({
                heartbeat_interval: this.heartBeatInterval,
                secret_key: this.secretKey,
                secret_rule: this.secretRule,
                timestamp: this.timestamp
            } = e.data), setTimeout((() => this.x()), 1e3 * this.heartBeatInterval))
    }
    async x() {
        if (this.seq > this.Dotime)
            return this.doneFunc();
        const t = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ruid:this.ruid,
            ets: this.timestamp,
            benchmark: this.secretKey,
            time: this.heartBeatInterval,
            ts: this.ts,
            ua: this.ua
        },
        e = this.sypder(JSON.stringify(t), this.secretRule),
        i = Object.assign({
            s: e
        }, t);
        this._patchData[this.roomID] = i,
        this.lastHeartbeatTimestamp = Date.now();
        const s = await fetch("//live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/X", {
            headers: {
                "content-type": "application/x-www-form-urlencoded"
            },
            method: "POST",
            body: `${this.json2str(i)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
            mode: "cors",
            credentials: "include"
        }).then((t => t.json()));
        0 === s.code && (this.seq += 1, ({
                heartbeat_interval: this.heartBeatInterval,
                secret_key: this.secretKey,
                secret_rule: this.secretRule,
                timestamp: this.timestamp
            } = s.data), setTimeout((() => this.x()), 1e3 * this.heartBeatInterval))
    }
    sypder(t, e) {
        const i = JSON.parse(t),
        [s, a, r, n] = JSON.parse(i.id),
        [o, c] = JSON.parse(i.device),
        h = i.benchmark,
        m = {
            platform: "web",
            parent_id: s,
            area_id: a,
            seq_id: r,
            room_id: n,
            buvid: o,
            uuid: c,
            ets: i.ets,
            time: i.time,
            ts: i.ts
        };
        let d = JSON.stringify(m);
        for (const t of e)
            switch (t) {
            case 0:
                d = CryptoJS.HmacMD5(d, h).toString(CryptoJS.enc.Hex);
                break;
            case 1:
                d = CryptoJS.HmacSHA1(d, h).toString(CryptoJS.enc.Hex);
                break;
            case 2:
                d = CryptoJS.HmacSHA256(d, h).toString(CryptoJS.enc.Hex);
                break;
            case 3:
                d = CryptoJS.HmacSHA224(d, h).toString(CryptoJS.enc.Hex);
                break;
            case 4:
                d = CryptoJS.HmacSHA512(d, h).toString(CryptoJS.enc.Hex);
                break;
            case 5:
                d = CryptoJS.HmacSHA384(d, h).toString(CryptoJS.enc.Hex);
                break;
            default:
                break
            }
        return d
    }
    getItem(t) {
        return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(t).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || ""
    }
    json2str(t) {
        let e = "";
        for (const i in t)
            e += `${i}=${encodeURIComponent(t[i])}&`;
        return e.slice(0, -1)
    }
}