// ==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);
}
};