Greasy Fork is available in English.
B站直播心跳
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @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)
}
}