// ==UserScript== // @name 哔哩哔哩粉丝勋章勋章升级 // @version 1.02 // @description 模拟观看直播获取粉丝勋章亲密度:每日每观看五分钟可获得300亲密度,最多可获得1500亲密度 // @author 无夏不春风orz // @iconURL https://www.bilibili.com/favicon.ico // @match https://live.bilibili.com/* // @match https://www.bilibili.com/blackboard/live/* // @connect bilibili.com // @require https://unpkg.com/jquery@3.7.1/dist/jquery.js // @require https://unpkg.com/crypto-js@4.2.0/crypto-js.js // @grant unsafeWindow // @grant GM_xmlhttpRequest // @run-at document-idle // @license MIT // @namespace http://tampermonkey.net/ // @downloadURL https://update.greasyfork.icu/scripts/537954/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E7%B2%89%E4%B8%9D%E5%8B%8B%E7%AB%A0%E5%8B%8B%E7%AB%A0%E5%8D%87%E7%BA%A7.user.js // @updateURL https://update.greasyfork.icu/scripts/537954/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E7%B2%89%E4%B8%9D%E5%8B%8B%E7%AB%A0%E5%8B%8B%E7%AB%A0%E5%8D%87%E7%BA%A7.meta.js // ==/UserScript== (function () { var NAME var BAPI var Live_info = { coin: undefined, room_id: undefined, uid: undefined, csrf_token: undefined, rnd: undefined, ruid: undefined, uname: undefined, user_level: undefined, Blever: undefined, vipType: undefined, face_url: undefined, vipTypetext: undefined, vipStatus: undefined, cost: undefined, regtime: undefined, identification: undefined, img_key:undefined, sub_key:undefined, buvid3:undefined, }; const ts_ms = () => Date.now(); const ts_s = () => Math.round(ts_ms() / 1000); const hour = () => new Date(ts_ms()).getHours(); const minute = () => new Date(ts_ms()).getMinutes(); function sleep(ms) { return new Promise(resolve => setTimeout(() => resolve('sleep'), ms)); } String.prototype.replaceAll = function (oldSubStr, newSubStr) { return this.replace(new RegExp(oldSubStr, 'gm'), newSubStr) } const newWindow = { init: () => { return newWindow.Toast.init(); }, Toast: { init: () => { try { const list = []; window.toast = (msg, type = 'info', timeout = 5e3) => { console.log(msg) switch (type){ case 'success': case 'info': case 'error': break; default: type = 'info'; } const a = $(``)[0]; document.body.appendChild(a); a.style.top = (document.body.scrollTop + list.length * 40 + 10) + 'px'; a.style.left = 10 + 'px'; list.push(a); setTimeout(() => { a.className += ' out'; setTimeout(() => { list.shift(); list.forEach((v) => { v.style.top = (parseInt(v.style.top, 10) - 40) + 'px'; }); $(a).remove(); }, 200); }, timeout); }; return $.Deferred().resolve(); } catch (err){ return $.Deferred().reject(); } } } } const getCookie = (name) => { let arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)")); if (arr != null) return unescape(arr[2]); return false; } $(function () { //DOM完毕,等待弹幕加载完成 if((typeof BilibiliLive) == "undefined"){ BilibiliLive = undefined; } let loadInfo = (delay) => { setTimeout(async function () { GM_xmlhttpRequest({ method: "GET", url: "https://api.bilibili.com/x/web-interface/nav", onload: async function(response) { let data = JSON.parse(response.response); if (!data.data.isLogin) { loadInfo(5000); window.toast('无账号登陆信息,请先登录或检查网络','error',8000); } else { if(BilibiliLive == undefined || BilibiliLive.ROOMID == undefined) return loadInfo(5000); Live_info.room_id = BilibiliLive.ROOMID; Live_info.ruid = BilibiliLive.ANCHOR_UID Live_info.uid = data.data.mid Live_info.coin = parseInt(data.data.money) Live_info.Blever = data.data.level_info.current_level Live_info.vipType = data.data.vipType Live_info.vipStatus = data.data.vipStatus Live_info.uname = data.data.uname Live_info.face_url = data.data.face Live_info.vipTypetext = data.data.vip_label.text if(Live_info.vipTypetext=='')Live_info.vipTypetext = '普通用户' let img_url = data.data.wbi_img.img_url let sub_url = data.data.wbi_img.sub_url let img_key = img_url.slice(img_url.lastIndexOf('/') + 1,img_url.lastIndexOf('.')) let sub_key = sub_url.slice(sub_url.lastIndexOf('/') + 1,sub_url.lastIndexOf('.')) Live_info.img_key = img_key Live_info.sub_key = sub_key NAME = 'BILI' + Live_info.uid Live_info.csrf_token = getCookie('bili_jct'); Live_info.buvid3 = getCookie('buvid3'); window.toast('登陆信息获取成功','success'); init(); } }, onerror : function(err){ loadInfo(5000); window.toast('无账号登陆信息,请先登录或检查网络','error',8000); } }); }, delay); }; newWindow.init(); loadInfo(5000); }); async function init() { //API初始化 const right_ctnr = $('.right-ctnr'); const share = right_ctnr.find('.v-middle.icon-font.icon-share').parent(); const remove_button = $( `
去掉直播水印
` ); remove_button.click(() => { $('.web-player-icon-roomStatus').remove() }); const blanc_button = $( `
回到默认界面
` ); blanc_button.click(() => { window.top.location.href = 'https://live.bilibili.com/blanc/'+ Live_info.room_id }) if ($('.right-ctnr').length !== 0){ right_ctnr[0].insertBefore(remove_button[0], share[0]); right_ctnr[0].insertBefore(blanc_button[0], remove_button[0], share[0]); } BAPI = BilibiliAPI; const MY_API = { CONFIG_DEFAULT: { auto_medal_task: true, medal_level_pass: true, medal_pass_level: 21, js_running_mark:0, medal_pass_uid:[], sort:true, hide_Toast:true, medal_first_uid:[], auto_coin_add: true, }, CONFIG: {}, loadConfig: async function () { let p = $.Deferred(); try { let config = JSON.parse(localStorage.getItem(`${NAME}_CONFIG`)); $.extend(true, MY_API.CONFIG, MY_API.CONFIG_DEFAULT); for (let item in MY_API.CONFIG) { if (!MY_API.CONFIG.hasOwnProperty(item)) continue; if (config[item] !== undefined && config[item] !== null) MY_API.CONFIG[item] = config[item]; } p.resolve() } catch (e) { console.log('API载入配置失败,加载默认配置', e); MY_API.CONFIG = MY_API.CONFIG_DEFAULT MY_API.saveConfig() p.reject() } return p }, saveConfig: function () { try { localStorage.setItem(`${NAME}_CONFIG`, JSON.stringify(MY_API.CONFIG)); return true } catch (e) { console.log('API保存出错', e); return false } }, creatSetBox: function () { // 创建设置框 // 获取最大宽度和高度 const widthmax = $('.web-player-ending-panel').width() - 50; const heightmax = $('.chat-history-panel').height(); // 创建主容器 const div = $("
"); // 设置容器样式 div.css({ 'width': '400px', 'min-width': '400px', 'height': '560px', 'max-height': `${heightmax}px`, 'position': 'absolute', 'top': '10px', 'right': '10px', 'background': 'rgba(255,255,255,.95)', 'padding': '10px', 'z-index': '99', 'border-radius': '12px', 'box-shadow': '0 4px 12px rgba(0,0,0,0.15)', 'transition': 'all .3s ease', 'overflow': 'auto', 'line-height': '1.2', 'font-family': 'system-ui, -apple-system, sans-serif' }); // 创建HTML内容 div.append(`

勋章升级设置

用户信息
勋章升级参数设置
跳过 级及以上勋章
不执行观看任务主播UID [逗号隔开]
优先执行观看任务主播UID [逗号隔开]

    注意事项:

  • 同一时间仅一个勋章可获得亲密度,故每日观看总亲密度有上限,可适当排序或过滤。部分设置需要刷新后生效。20级以上默认不执行观看任务。
`); // 添加CSS样式 $('head').append(` `); // 添加到DOM $('.player-section.p-relative.border-box.none-select.z-player-section').append(div); // 隐藏/显示按钮事件 div.find('div[data-toggle="ui_hide"] [data-action="save"]').click(function () { $('.xzsjzsdiv').toggle(); $(this).text($('.xzsjzsdiv').is(':visible') ? '隐藏设置面板' : '显示设置面板'); }); // 初始化复选框状态 const initCheckbox = (toggle, configKey) => { const checkbox = div.find(`div[data-toggle="${toggle}"] input`); if (MY_API.CONFIG[configKey]) { checkbox.prop('checked', true); } checkbox.change(function () { MY_API.CONFIG[configKey] = $(this).prop('checked'); MY_API.saveConfig(); window.toast(`${configKey.replace(/_/g, ' ')} 设置: ${MY_API.CONFIG[configKey]}`); if (configKey === 'auto_medal_task' && MY_API.CONFIG[configKey]) { window.location.reload(); } }); }; // 初始化所有复选框 initCheckbox('auto_medal_task', 'auto_medal_task'); initCheckbox('auto_coin_add', 'auto_coin_add'); initCheckbox('medal_level_pass', 'medal_level_pass'); initCheckbox('sort', 'sort'); initCheckbox('hide_Toast', 'hide_Toast'); // 初始化跳过等级输入 div.find('div[data-toggle="medal_level_pass_num"] .num').val(parseInt(MY_API.CONFIG.medal_pass_level.toString())); div.find('div[data-toggle="medal_level_pass_num"] [data-action="save"]').click(function () { const level = parseInt(div.find('div[data-toggle="medal_level_pass_num"] .num').val()); if (isNaN(level) || level < 1) { window.toast('请输入1以上的有效数字'); return; } MY_API.CONFIG.medal_pass_level = level; MY_API.saveConfig(); window.toast(`勋章跳过等级已设置为: ${MY_API.CONFIG.medal_pass_level}`); }); // 处理UID列表保存 const handleUidSave = (toggle, configKey, message) => { const input = div.find(`div[data-toggle="${toggle}"] .keyword`); input.val(MY_API.CONFIG[configKey].join(', ')); div.find(`div[data-toggle="${toggle}"] [data-action="save"]`).click(function () { let val = input.val().trim(); if (!val) { MY_API.CONFIG[configKey] = []; MY_API.saveConfig(); window.toast(`${message} 已清空`); return; } val = val.replaceAll(',', ','); const uids = val.split(",") .map(uid => uid.trim()) .filter(uid => uid !== '') .map(uid => Number(uid)) .filter(uid => !isNaN(uid)); const uniqueUids = [...new Set(uids)]; MY_API.CONFIG[configKey] = uniqueUids; MY_API.saveConfig(); window.toast(`${message} 已设置: ${uniqueUids.join(', ')}`); }); }; // 初始化UID列表 handleUidSave('medal_pass_uid', 'medal_pass_uid', '【观看跳过】主播UID'); handleUidSave('medal_first_uid', 'medal_first_uid', '【观看优先】主播UID'); }, getMedalList:async() => { let medal_list_now = []; let break_mark = false for(let page=1;page<999;page++){ if(break_mark)break await BAPI.fansMedal_panel(page).then(async (data) => { medal_list_now = medal_list_now.concat(data.data.list); if(data.data.special_list.length)medal_list_now = medal_list_now.concat(data.data.special_list); window.toast(`【勋章升级】正在获取勋章数据:已获取${medal_list_now.length}个`,'success'); if (data.data.page_info.current_page >= data.data.page_info.total_page){ break_mark = true; }else{ await sleep(2000) } }, () => { break_mark = true; }); } return medal_list_now }, auto_heartbert:async() => { window.toast(`【勋章升级】开始获取勋章数据`); let medal_list_now = await MY_API.getMedalList() if(MY_API.CONFIG.sort && medal_list_now.length)medal_list_now.sort(function(a, b) { return a.medal.level - b.medal.level;}); if(MY_API.CONFIG.medal_first_uid.length && medal_list_now.length){ let new_medal_list_now = [] let first_medal_list = [] for(let i=0;i -1){ first_medal_list.push(medal_list_now[i]) }else{ new_medal_list_now.push(medal_list_now[i]) } } medal_list_now = [].concat(first_medal_list).concat(new_medal_list_now) } let start_ts = ts_s() let dotime = 26 if(medal_list_now.length){ for(let i=0;i -1){ if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] 不执行名单跳过`) continue } if(MY_API.CONFIG.medal_level_pass && medal_list_now[i].medal.level >= 21){ if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] ${medal_list_now[i].medal.level}级勋章跳过`) continue } if(MY_API.CONFIG.medal_level_pass && medal_list_now[i].medal.level >= MY_API.CONFIG.medal_pass_level){ if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] ${medal_list_now[i].medal.level}级勋章跳过`) continue } if(medal_list_now[i].medal.today_feed != undefined && medal_list_now[i].medal.today_feed >= 1500){ if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] 今日经验已满`) continue } if(medal_list_now[i].medal.today_feed != undefined){ if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] 今日已获经验${medal_list_now[i].medal.today_feed}`) dotime = ((1500 - medal_list_now[i].medal.today_feed)/300)*5 } for(let t=0;t { if(MY_API.CONFIG.hide_Toast)window.toast(`【观看任务】[${medal_list_now[i].anchor_info.nick_name}][${medal_list_now[i].medal.target_id}] [${medal_list_now[i].medal.medal_name}] [${medal_list_now[i].medal.level}] [${medal_list_now[i].room_info.room_id}] 进度${t}/${dotime}`,'info',60000) },t* 60 * 1000) } let roomHeart = new RoomHeart(medal_list_now[i].room_info.room_id,dotime,medal_list_now[i].medal.target_id) await roomHeart.start() await sleep(dotime*60*1000) } } if(ts_s() - start_ts < 10*60){ window.toast(`【观看任务】将在10分钟后重新运行...`) await sleep(10*60*1000) } return MY_API.auto_heartbert() }, experience_add:async() => {//主站经验大会员加速包 if(Live_info.vipType >= 1 && Live_info.vipStatus == 1){ await BAPI.experience_add(Live_info.csrf_token).then(async(re) => { if(re.code == 0){ window.toast(`【大会员经验】主站经验大会员加速包领取成功`, 'success'); }else if(re.code == 69801){ window.toast(`【大会员经验】主站经验大会员加速包已领取`, 'error'); }else{ window.toast(`【大会员经验】主站经验大会员加速包:${re.message}`, 'error'); } }); window.toast(`【大会员经验】将在12小时后重新运行...`) await sleep(12*3600*1000) return MY_API.experience_add() } }, score_task_sign:async() => {//大会员自动签到 if(Live_info.vipType >= 1 && Live_info.vipStatus == 1){ await BAPI.score_task_sign().then(async(re) => { if(re.code == 0 && re.message == "success"){ window.toast(`【大会员签到】大会员签到成功`, 'success'); await BAPI.vip_point_task_combine().then((r) => { if(r.code == 0){ window.toast(`【大会员签到】大会员积分:${r.data.point_info.point}`, 'success'); } }) } }); window.toast(`【大会员签到】将在12小时后重新运行...`) await sleep(12*3600*1000) return MY_API.score_task_sign() } }, watch_share:async() => { await BAPI.top_rcmd().then(async (response) => { if(response.code === 0 && response.data.item){ const obj =(response.data.item[0]) await BAPI.watch_heartbeat(obj.id, obj.cid, Live_info.uid, Live_info.csrf_token).then((response) => { //console.log('每日观看', response) if(response.code === 0){ window.toast(`【视频观看】视频观看完成(av=${obj.id})`, 'success'); }else{ window.toast(`【视频观看】视频观看${response.message}`, 'error'); } }) await BAPI.aid_share(obj.id, Live_info.csrf_token).then((response) => { //console.log('每日分享', response) if(response.code === 0){ window.toast(`【视频分享】视频分享完成(av=${obj.id})`, 'success'); }else{ window.toast(`【视频分享】视频分享${response.message}`, 'error'); } }) }else{ window.toast(`【观看分享】获取视频失败:${response.msg}`, 'error'); } }) window.toast(`【观看分享】将在12小时后重新运行...`) await sleep(12*3600*1000) return MY_API.watch_share() }, coin_add:async() => { await BAPI.exp().then(async (response) => { //console.log('今日投币已获经验exp',response.data) if(response.code === 0){ let need_coin = (50 - response.data)/10 if(need_coin){ await BAPI.article_recommends().then(async(response) => { let oidlist = response.data let break_mark = false for(let i = 0;i { //console.log('每日专栏投币', response) if(response.code === 0){ window.toast(`【投币任务】投币1个成功(av=${oidlist[i].id})`); }else if(response.code == -104){ break_mark=true window.toast(`【投币任务】投币${response.message}`); }else{ break_mark=true window.toast(`【投币任务】投币${response.message}`); } }) await sleep(5000) } }) }else{ window.toast(`【投币任务】投币任务已完成`); } } }) window.toast(`【投币任务】将在12小时后重新运行...`) await sleep(12*3600*1000) return MY_API.coin_add() }, }; MY_API.loadConfig() try { const promiseInit = $.Deferred(); const uniqueCheck = () => { const t = Date.now(); if(t - MY_API.CONFIG.js_running_mark >= 0 && t - MY_API.CONFIG.js_running_mark <= 10e3){ // 其他脚本正在运行 window.toast('检测到脚本已经运行'); return promiseInit.reject(); } // 没有其他脚本正在运行 return promiseInit.resolve(); }; uniqueCheck().then(() => { let timer_unique; const uniqueMark = () => { timer_unique = setTimeout(uniqueMark, 2e3); MY_API.CONFIG.js_running_mark = Date.now(); try { localStorage.setItem(`${NAME}_CONFIG`, JSON.stringify(MY_API.CONFIG)); return true } catch (e){ console.log('API保存出错', e); return false }; }; window.addEventListener('unload', () => { if(timer_unique){ clearTimeout(timer_unique); MY_API.CONFIG.js_running_mark = 0; try { localStorage.setItem(`${NAME}_CONFIG`, JSON.stringify(MY_API.CONFIG)); return true } catch (e){ console.log('API保存出错', e); return false }; } }); uniqueMark(); StartPlunder(MY_API); }) } catch (e){ console.error('重复运行检测错误', e); } } async function StartPlunder(API) { await BAPI.room_init(Live_info.room_id).then(async function(data){ if(data.code == 0 && data.data.live_status == 1){ window.toast('当前直播间正在直播,由于B站限制仅能生效一个观看亲密度(包括各端),可能不能正常获取亲密度,请挂在未开播直播间','error',60000); } }) let icon_pic = 'https://s1.hdslb.com/bfs/live/e051dfd4557678f8edcac4993ed00a0935cbd9cc.png' let ui_button = $(``) $('html').append(ui_button); ui_button.click(function () { $('.xzsjzsdiv').toggle() }); API.creatSetBox(); //创建设置框 $('.xzsjzsdiv').hide() let get_cost = () => { return BAPI.cost().then((re) => { if(re.code == 0){ let list = re.data.info for(let i=0;i
昵称:${Live_info.uname}
UID:${Live_info.uid}
直播消费:${Live_info.cost}
会员等级:${Live_info.vipTypetext}
主站等级:Lv${Live_info.Blever}
硬币数量:${Live_info.coin}
` } showinfo() setTimeout(async() => { if (API.CONFIG.auto_medal_task) { API.auto_heartbert() } if (API.CONFIG.auto_coin_add) { API.watch_share() API.coin_add() API.score_task_sign() API.experience_add() } }, 5 * 1000); } })(); const BilibiliAPI = { runUntilSucceed: (callback, delay = 0, period = 50) => { setTimeout(() => { if (!callback()) BilibiliAPI.runUntilSucceed(callback, period, period); }, delay); }, processing: 0, ajax: (settings) => { if (settings.xhrFields === undefined) settings.xhrFields = {}; settings.xhrFields.withCredentials = true; jQuery.extend(settings, { url: settings.url, method: settings.method || 'GET', crossDomain: true, dataType: settings.dataType || 'json' }); const p = jQuery.Deferred(); BilibiliAPI.runUntilSucceed(() => { if (BilibiliAPI.processing > 8) return false; ++BilibiliAPI.processing; return jQuery.ajax(settings).then((arg1, arg2, arg3) => { --BilibiliAPI.processing; p.resolve(arg1, arg2, arg3); return true; }, (arg1, arg2, arg3) => { --BilibiliAPI.processing; p.reject(arg1, arg2, arg3); return true; }); }); return p; }, fansMedal_panel: (page,pageSize=50) => {//获取全部勋章数据 return BilibiliAPI.ajax({ url: "https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel", method: "GET", data:{ page:page, page_size:pageSize } }) }, cost: () => {//花费适用于10w以下 return BilibiliAPI.ajax({ url: "https://api.live.bilibili.com/xlive/web-ucenter/v1/achievement/list?type=normal&status=0&category=all&keywords=&page=1&pageSize=100", method: "GET", }) }, room_init: (roomid) => {//花费适用于10w以下 return BilibiliAPI.ajax({ url: "https://api.live.bilibili.com/room/v1/Room/room_init?id="+roomid, method: "GET", }) }, top_rcmd: () => {//首页视频推荐 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/x/web-interface/index/top/rcmd", method: "GET", }) }, watch_heartbeat: (i, e, a, csrf) => {//视频观看 return BilibiliAPI.ajax({ method: "POST", url: "https://api.bilibili.com/x/report/web/heartbeat", data: { aid: i, cid: e, mid: a, start_ts: Math.round(Date.now()/1000), played_time: 0, realtime: 0, type: 3, play_type: 1, dt: 2, csrf:csrf } }) }, aid_share: (i, csrf) => {//视频分享 return BilibiliAPI.ajax({ method: "POST", url: "https://api.bilibili.com/x/web-interface/share/add", data: { aid: i, csrf: csrf, jsonp: "jsonp" } }) }, exp: () => {//投币经验 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/x/web-interface/coin/today/exp", }) }, article_recommends: () => {//获取最新专栏信息 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/x/article/recommends", method: "GET", data:{ aid:'', cid:3, pn:1, ps:20, jsonp:'jsonp', sort:1 } }) }, article_coin_add: (oid, upid, csrf) => {//专栏投币 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/x/web-interface/coin/add", method: "POST", data:{ aid:oid, upid: upid, multiply: 1, avtype: 2, csrf: csrf } }) }, experience_add: (csrf) => {//大会员经验包 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/x/vip/experience/add", method: "POST", data:{ csrf: csrf } }) }, score_task_sign:() => {//大会员积分签到 return BilibiliAPI.ajax({ url: "https://api.bilibili.com/pgc/activity/score/task/sign", method: "POST", }) }, } //https://github.com/lzghzr/TampermonkeyJS 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) } }