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)
}
}