BiliveHeartWithTimeParam

无分区的直播间不进行挂机

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/447940/1096591/BiliveHeartWithTimeParam.js

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name        BiliveHeart
// @namespace   https://github.com/lzghzr/TampermonkeyJS
// @version     0.0.5.4
// @author      lzghzr
// @description 在0.0.5的基础上修改了一下用于挂机经验
// @include     /^https?:\/\/live\.bilibili\.com\/(?:blanc\/)?\d/
// @require     https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/crypto-js/4.1.1/crypto-js.min.js
// @license     MIT
// @grant       none
// @run-at      document-end
// ==/UserScript==
class RoomHeart {
    constructor(roomID, timeLimit = 70) {
        this.roomID = roomID;
        this.timeLimit = timeLimit;
    }
    areaID;
    parentID;
    seq = 0;
    roomID;
    timeLimit;
    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 list = [];
        for (const [_, data] of Object.entries(this._patchData))
            list.push(data);
        return list;
    }
    get isPatch() { return this.patchData.length === 0 ? 0 : 1; }
    W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
    ua = this.W && this.W.navigator ? this.W.navigator.userAgent : '';
    csrf = this.getItem("bili_jct") || '';
    nextInterval = Math.floor(5) + Math.floor(Math.random() * (60 - 5));
    heartBeatInterval;
    secretKey;
    secretRule;
    timestamp;
    lastHeartbeatTimestamp = Date.now();
    get watchTimeFromLastReport() {
        const t = Math.ceil(((new Date).getTime() - this.lastHeartbeatTimestamp) / 1000);
        return t < 0 ? 0 : t > this.heartBeatInterval ? this.heartBeatInterval : t;
    }
    start() {
        return this.getInfoByRoom();
    }
    doneFunc = function () { }
    errorFunc = function () { }
    async getInfoByRoom() {
        if (this.roomID === 0)
            return false;
        const getInfoByRoom = await fetch(`//api.live.bilibili.com/room/v1/Room/get_info?room_id=${this.roomID}&from=room`, {
            mode: 'cors',
            credentials: 'include',
        }).then(res => res.json());
        if (getInfoByRoom.code === 0) {
            ;
            ({ area_id: this.areaID, parent_area_id: this.parentID, room_id: this.roomID } = getInfoByRoom.data);
            if (this.areaID === 0 || this.parentID === 0)
                return false;
            this.e();
            return true;
        }
        else {
            console.error(GM_info.script.name, `未获取到房间 ${this.roomID} 信息`);
            return false;
        }
    }
    async webHeartBeat() {
        if (this.seq > 30)
            return;
        const arg = `${this.nextInterval}|${this.roomID}|1|0`;
        const argUtf8 = CryptoJS.enc.Utf8.parse(arg);
        const argBase64 = CryptoJS.enc.Base64.stringify(argUtf8);
        const webHeartBeat = await fetch(`//live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb=${encodeURIComponent(argBase64)}&pf=web`, {
            mode: 'cors',
            credentials: 'include',
        }).then(res => res.json());
        if (webHeartBeat.code === 0) {
            this.nextInterval = webHeartBeat.data.next_interval;
            setTimeout(() => this.webHeartBeat(), this.nextInterval * 1000);
        }
        else
            console.error(GM_info.script.name, `房间 ${this.roomID} 心跳失败`);
    }
    async savePatchData() {
        if (this.seq > 30)
            return;
        const sypderData = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ets: this.timestamp,
            benchmark: this.secretKey,
            time: this.watchTimeFromLastReport > this.heartBeatInterval ? this.heartBeatInterval : this.watchTimeFromLastReport,
            ts: this.ts,
            ua: this.ua,
        };
        const s = this.sypder(JSON.stringify(sypderData), this.secretRule);
        const arg = Object.assign({ s }, sypderData);
        this._patchData[this.roomID] = arg;
        setTimeout(() => this.savePatchData(), 15 * 1000);
    }
    async e() {
        const arg = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ts: this.ts,
            is_patch: 0,
            heart_beat: '[]',
            ua: this.ua,
        };
        const 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(arg)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
            mode: 'cors',
            credentials: 'include',
        }).then(res => res.json());
        if (e.code === 0) {
            this.seq += 1;
            ({ heartbeat_interval: this.heartBeatInterval, secret_key: this.secretKey, secret_rule: this.secretRule, timestamp: this.timestamp } = e.data);
            setTimeout(() => {
                try {
                    this.x();
                } catch (error) {
                    this.errorFunc(error);
                    throw error;
                }
            }, this.heartBeatInterval * 1000);
        }
        else
            console.error(GM_info.script.name, `房间 ${this.roomID} 获取小心心失败`);
    }
    async x() {
        if (this.seq > this.timeLimit)
            return this.doneFunc();
        const sypderData = {
            id: JSON.stringify(this.id),
            device: JSON.stringify(this.device),
            ets: this.timestamp,
            benchmark: this.secretKey,
            time: this.heartBeatInterval,
            ts: this.ts,
            ua: this.ua,
        };
        const s = this.sypder(JSON.stringify(sypderData), this.secretRule);
        const arg = Object.assign({ s }, sypderData);
        this._patchData[this.roomID] = arg;
        this.lastHeartbeatTimestamp = Date.now();
        const x = 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(arg)}&csrf_token=${this.csrf}&csrf=${this.csrf}&visit_id=`,
            mode: 'cors',
            credentials: 'include',
        }).then(res => res.json());
        if (x.code === 0) {
            this.seq += 1;
            ({ heartbeat_interval: this.heartBeatInterval, secret_key: this.secretKey, secret_rule: this.secretRule, timestamp: this.timestamp } = x.data);
            setTimeout(() => this.x(), this.heartBeatInterval * 1000);
        } else {
            console.error(GM_info.script.name, `房间 ${this.roomID} 小心心 心跳失败`);
            this.errorFunc(x);
        }
    }
    sypder(str, rule) {
        const data = JSON.parse(str);
        const [parent_id, area_id, seq_id, room_id] = JSON.parse(data.id);
        const [buvid, uuid] = JSON.parse(data.device);
        const key = data.benchmark;
        const newData = {
            platform: 'web',
            parent_id,
            area_id,
            seq_id,
            room_id,
            buvid,
            uuid,
            ets: data.ets,
            time: data.time,
            ts: data.ts,
        };
        let s = JSON.stringify(newData);
        for (const r of rule) {
            switch (r) {
                case 0:
                    s = CryptoJS.HmacMD5(s, key).toString(CryptoJS.enc.Hex);
                    break;
                case 1:
                    s = CryptoJS.HmacSHA1(s, key).toString(CryptoJS.enc.Hex);
                    break;
                case 2:
                    s = CryptoJS.HmacSHA256(s, key).toString(CryptoJS.enc.Hex);
                    break;
                case 3:
                    s = CryptoJS.HmacSHA224(s, key).toString(CryptoJS.enc.Hex);
                    break;
                case 4:
                    s = CryptoJS.HmacSHA512(s, key).toString(CryptoJS.enc.Hex);
                    break;
                case 5:
                    s = CryptoJS.HmacSHA384(s, key).toString(CryptoJS.enc.Hex);
                    break;
                default:
                    break;
            }
        }
        return s;
    }
    getItem(t) {
        return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(t).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || '';
    }
    json2str(arg) {
        let str = '';
        for (const name in arg)
            str += `${name}=${encodeURIComponent(arg[name])}&`;
        return str.slice(0, -1);
    }
};