🏠 Home 

Bilibili Live Tasks Helper

Enhancing the experience of watching Bilibili live streaming.

< 脚本Bilibili Live Tasks Helper的反馈

评价:好评 - 脚本运行良好

Deleted user 663313
§
发表于:2020-08-21
编辑于:2020-08-21

直播流切断经常失效
提供一个大神写的心跳包代码: https://github.com/lzghzr/TampermonkeyJS/tree/master/BiliveClientHeart

// ==UserScript==
// @name        BiliveClientHeart
// @namespace   https://github.com/lzghzr/TampermonkeyJS
// @version     0.1.4
// @author      lzghzr
// @description B站直播客户端心跳
// @include     /^https?:\/\/live\.bilibili\.com\/(?:blanc\/)?\d/
// @connect     passport.bilibili.com
// @connect     api.live.bilibili.com
// @connect     live-trace.bilibili.com
// @require     https://cdn.jsdelivr.net/gh/lzghzr/TampermonkeyJS@55fdb489ce4d3f73c02a4ddec9a6979023b7479c/libBilibiliToken/libBilibiliToken.js
// @require     https://cdn.jsdelivr.net/gh/lzghzr/TampermonkeyJS@fe2340677328762f9d6e9686603e9781d69cd3c9/libWasmHash/libWasmHash.js
// @license     MIT
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @run-at      document-end
// ==/UserScript==
(async () => {
await Sleep(5000);
const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
if (W.BilibiliLive === undefined)
return console.error(GM_info.script.name, '未获取到uid');
const uid = W.BilibiliLive.UID;
const tid = W.BilibiliLive.ANCHOR_UID;
if (uid === 0)
return console.error(GM_info.script.name, '未获取到uid');
const appToken = new BilibiliToken();
const baseQuery = `actionKey=appkey&appkey=${BilibiliToken.appKey}&build=5561000&channel=bili&device=android&mobi_app=android&platform=android&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%225.57.0%22%2C%22abtest%22%3A%22%22%7D`;
let tokenData = JSON.parse(GM_getValue('userToken', '{}'));
const setToken = async () => {
const userToken = await appToken.getToken();
if (userToken === undefined)
return console.error(GM_info.script.name, '未获取到token');
tokenData = userToken;
GM_setValue('userToken', JSON.stringify(tokenData));
return 'OK';
};
const getInfo = () => XHR({
GM: true,
anonymous: true,
method: 'GET',
url: `https://passport.bilibili.com/x/passport-login/oauth2/info?${appToken.signLoginQuery(`access_key=${tokenData.access_token}`)}`,
responseType: 'json',
headers: appToken.headers
});
const mobileOnline = () => XHR({
GM: true,
anonymous: true,
method: 'POST',
url: `https://api.live.bilibili.com/heartbeat/v1/OnLine/mobileOnline?${BilibiliToken.signQuery(`access_key=${tokenData.access_token}&${baseQuery}`)}`,
data: `room_id=${W.BilibiliLive.ROOMID}&scale=xxhdpi`,
responseType: 'json',
headers: appToken.headers
});
const RandomHex = (length) => {
const words = '0123456789abcdef';
let randomID = '';
randomID += words[Math.floor(Math.random() * 15) + 1];
for (let i = 0; i < length - 1; i++)
randomID += words[Math.floor(Math.random() * 16)];
return randomID;
};
const uuid = () => RandomHex(32).replace(/(\w{8})(\w{4})\w(\w{3})\w(\w{3})(\w{12})/, `$1-$2-4$3-${'89ab'[Math.floor(Math.random() * 4)]}$4-$5`);
const mobileHeartBeatJSON = {
platform: 'android',
uuid: uuid(),
buvid: appToken.buvid,
seq_id: '1',
room_id: '{room_id}',
parent_id: '6',
area_id: '283',
timestamp: '{timestamp}',
secret_key: 'axoaadsffcazxksectbbb',
watch_time: '300',
up_id: '{target_id}',
up_level: '40',
jump_from: '30000',
gu_id: RandomHex(43),
play_type: '0',
play_url: '',
s_time: '0',
data_behavior_id: '',
data_source_id: '',
up_session: 'l:one:live:record:{room_id}:{last_wear_time}',
visit_id: RandomHex(32),
watch_status: '%7B%22pk_id%22%3A0%2C%22screen_status%22%3A1%7D',
click_id: uuid(),
session_id: '',
player_type: '0',
client_ts: '{client_ts}'
};
const wasm = new WasmHash();
await wasm.init();
const clientSign = (data) => wasm.hash('BLAKE2b512', wasm.hash('SHA3-384', wasm.hash('SHA384', wasm.hash('SHA3-512', wasm.hash('SHA512', JSON.stringify(data))))));
const getFansMedal = async () => {
const funsMedals = await XHR({
GM: true,
anonymous: true,
method: 'GET',
url: `https://api.live.bilibili.com/fans_medal/v1/FansMedal/get_list_in_room?${BilibiliToken.signQuery(`access_key=${tokenData.access_token}&target_id=${tid}&uid=${uid}&${baseQuery}`)}`,
responseType: 'json',
headers: appToken.headers
});
if (funsMedals !== undefined && funsMedals.response.status === 200)
if (funsMedals.body.code === 0)
if (funsMedals.body.data.length > 0)
return funsMedals.body.data;
};
const getGiftNum = async () => {
let count = 0;
const bagInfo = await XHR({
GM: true,
anonymous: true,
method: 'GET',
url: `https://api.live.bilibili.com/xlive/app-room/v1/gift/bag_list?${BilibiliToken.signQuery(`access_key=${tokenData.access_token}&room_id=${W.BilibiliLive.ROOMID}&${baseQuery}`)}`,
responseType: 'json',
headers: appToken.headers
});
if (bagInfo !== undefined && bagInfo.response.status === 200)
if (bagInfo.body.code === 0)
if (bagInfo.body.data.list.length > 0)
for (const giftData of bagInfo.body.data.list)
if (giftData.gift_id === 30607) {
const expire = (giftData.expire_at - Date.now() / 1000) / 60 / 60 / 24;
if (expire > 6 && expire <= 7)
count += giftData.gift_num;
}
return count;
};
const mobileHeartBeat = async (postJSON) => {
const sign = clientSign(postJSON);
let postData = '';
for (const i in postJSON)
postData += `${i}=${encodeURIComponent(postJSON[i])}&`;
postData += `client_sign=${sign}`;
const mobileHeartBeat = await XHR({
GM: true,
anonymous: true,
method: 'POST',
url: 'https://live-trace.bilibili.com/xlive/data-interface/v1/heartbeat/mobileHeartBeat',
data: BilibiliToken.signQuery(`access_key=${tokenData.access_token}&${postData}&${baseQuery}`),
responseType: 'json',
headers: appToken.headers
});
if (mobileHeartBeat !== undefined && mobileHeartBeat.response.status === 200)
if (mobileHeartBeat.body.code === 0)
return true;
return false;
};
if (tokenData.access_token === undefined && await setToken() === undefined)
return;
else {
const userInfo = await getInfo();
if (userInfo === undefined)
return console.error(GM_info.script.name, '获取用户信息错误');
if (userInfo.body.code !== 0 && await setToken() === undefined)
return;
else if (userInfo.body.data.mid !== uid && await setToken() === undefined)
return;
}
console.log(GM_info.script.name, '开始客户端心跳');
mobileOnline();
setInterval(() => mobileOnline(), 5 * 60 * 1000);
const giftNum = await getGiftNum();
if (giftNum < 24) {
const fansMedal = await getFansMedal();
if (fansMedal !== undefined) {
const control = 24 - giftNum;
const loopNum = Math.ceil(control / fansMedal.length);
let count = 0;
for (let i = 0; i < loopNum; i++) {
for (const funsMedalData of fansMedal) {
if (count >= control)
break;
const postData = Object.assign({}, mobileHeartBeatJSON, {
room_id: funsMedalData.room_id.toString(),
timestamp: (BilibiliToken.TS - 300).toString(),
up_id: funsMedalData.target_id.toString(),
up_session: `l:one:live:record:${funsMedalData.room_id}:${funsMedalData.last_wear_time}`,
client_ts: BilibiliToken.TS.toString()
});
await mobileHeartBeat(postData);
count++;
}
if (count >= control)
break;
else
await Sleep(300 * 1000);
}
}
}
function XHR(XHROptions) {
return new Promise(resolve => {
const onerror = (error) => {
console.error(GM_info.script.name, error);
resolve(undefined);
};
if (XHROptions.GM) {
if (XHROptions.method === 'POST') {
if (XHROptions.headers === undefined)
XHROptions.headers = {};
if (XHROptions.headers['Content-Type'] === undefined)
XHROptions.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
}
XHROptions.timeout = 30 * 1000;
XHROptions.onload = res => resolve({ response: res, body: res.response });
XHROptions.onerror = onerror;
XHROptions.ontimeout = onerror;
GM_xmlhttpRequest(XHROptions);
}
else {
const xhr = new XMLHttpRequest();
xhr.open(XHROptions.method, XHROptions.url);
if (XHROptions.method === 'POST' && xhr.getResponseHeader('Content-Type') === null)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
if (XHROptions.cookie)
xhr.withCredentials = true;
if (XHROptions.responseType !== undefined)
xhr.responseType = XHROptions.responseType;
xhr.timeout = 30 * 1000;
xhr.onload = ev => {
const res = ev.target;
resolve({ response: res, body: res.response });
};
xhr.onerror = onerror;
xhr.ontimeout = onerror;
xhr.send(XHROptions.data);
}
});
}
function Sleep(ms) {
return new Promise(resolve => setTimeout(() => resolve('sleep'), ms));
}
})();
andywang作者
§
发表于:2020-08-22
编辑于:2020-08-22

感谢。等我研究好之后会在下个版本加入这个功能。顺便也想研究一下这位大佬的反挂机功能。
已更新✔

andywang作者
§
发表于:2020-08-22

这作者太牛了,上次双端心跳的代码也是从他那来的。

Deleted user 663313
§
发表于:2020-08-22

作者这更新速度666, 给你点个赞

发表回复

登录以发表回复。