您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
无分区的直播间不进行挂机
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/447940/1096591/BiliveHeartWithTimeParam.js
// ==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); } };