bilibili直播间显示更多信息

bilibili直播间显示开播时间,直播时长,粉丝数,人气,收益,在线人数,封面,其他关注直播,pk等信息

As of 2025-01-05. See the latest version.

Author
killall
Ratings
0 0 0
Version
3.18
Created
2023-12-30
Updated
2025-01-05
Size
28 KB
License
MIT
Applies to

// ==UserScript== // @name bilibili直播间显示更多信息 // @description bilibili直播间显示开播时间,直播时长,粉丝数,人气,收益,在线人数,封面,其他关注直播,pk等信息 // @version 3.18 // @author killall // @match https://live.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js // @license MIT // @namespace https://greasyforks.org/users/1060750 // ==/UserScript==

; (function () { const $ = jQuery.noConflict(true); let Config = { livetime: { enable: true, // 显示直播开始时间,持续时间 }, fans: { enable: true, // 显示粉丝,人气,收益 maxWidth: 350, // 显示最大宽度(显示有问题才需要考虑改) }, rank: { //(目前b站官方已经显示在线人数了 2024-05-05) //(现在改成显示在聊天框下面 2024-12-13) enable: true, // 显示高能榜(同接) }, cover: { enable: true, // 显示直播封面 location: 'afterbegin', // 封面显示位置(afterbegin,beforeend) }, pk: { enable: true, // 显示pk相关信息 }, otherLive: { enable: true, // 显示其他关注的直播 showTime: true, // 是否在头像下面显示直播时长 location: 'right', // 显示位置(left,right) opacity: 1.0, // 透明度 maxHeight: 550, // 最大显示高度(px) imgSize: 40, // 头像大小 }, }

async function main() { initSucceed = await G.init() if (!initSucceed) return console.log(G.init done) initSucceed = await Dao.init() console.log(Dao.init done) tasklist = [] tasklist.push(Dao.run()) Config.livetime.enable && tasklist.push(LiveTimeModule.run()) Config.fans.enable && tasklist.push(FansModule.run()) Config.rank.enable && tasklist.push(RankModule.run()) Config.cover.enable && tasklist.push(CoverModule.run()) Config.otherLive.enable && tasklist.push(otherLiveModule.run())

await ut.sleep(200) //等数据初始化完成
await Promise.all(tasklist)

}

let G = { roomId_short: 0, // 直播房间号(short,从当前url获取) roomId: 0, // 直播房间号 uid: 0, // 主播uid async init() { this.roomId_short = api.getRoomID() if (!this.roomId_short) return false liveInfo = await api.getLiveInfo(this.roomId_short) this.uid = liveInfo.data.uid this.roomId = liveInfo.data.room_id

  console.log(`roomId(short_id): ${this.roomId_short} `)
  console.log(`roomId: ${this.roomId} `)
  console.log(`uid: ${this.uid}`)
  return true
},

}

let Dao = { liveInfo: null, liveStartTimeStamp: null, // 开播时间时间戳 income: 0, // 高能榜收入 初始化从api统计 之后从hook_wrapper统计收入 // rankCount: 0, // 非零在线人数 从api获取 guardCount: 0, // 舰长数 rank1: 0, // hook_wrapper获取 rank2: 0, // hook_wrapper获取 aliveList: null, // 其他关注直播 inpk: false, pk: { pkInfo: null, roomId: null, uid: null, uname: null, liveInfo: null, rankCount: 0, income: 0, guardCount: 0, },

async init() {
  this.liveInfo = await api.getLiveInfo(G.roomId)
  // console.log('liveInfo', this.liveInfo)
  this.liveStartTimeStamp = await api.getLiveTimeStamp(G.roomId)
  // console.log('liveStartTimeStamp', this.liveStartTimeStamp)
  Config.otherLive.enable && (this.aliveList = await api.getAliveList())
  Config.rank.enable && ut.hook_wrapper()
  this.income = (await api.getGongxian(G.roomId, G.uid)) / 10
},

updateData() {
  const update_liveInfo = async () => {
    this.liveInfo = await api.getLiveInfo(G.roomId)
  }
  // const update_income = async () => {
  // this.income = await api.getIncome(G.roomId, G.uid)
  // }
  const update_rankCount = async () => {
    // this.rankCount = await api.getOnlinePeople(G.roomId, G.uid)
    this.guardCount = await api.getGuardCount(G.roomId, G.uid)
  }
  const update_aliveList = async () => {
    this.aliveList = await api.getAliveList()
  }
  const update_pkInfo = async () => {
    const pkInfo = await api.getPkInfo(G.roomId)

    if (!pkInfo.data.pk_id) {
      this.inpk = false
      this.pk.pkInfo = null
      this.pk.roomId = null
      this.pk.uid = null
      this.pk.liveInfo = null
      this.pk.rankCount = 0
      this.pk.income = 0
      this.pk.guardCount = 0
      return
    }
    newpk = !this.inpk
    this.inpk = true
    this.pk.pkInfo =
      pkInfo.data.init_info.room_id == G.roomId ? pkInfo.data.match_info : pkInfo.data.init_info
    this.pk.roomId = this.pk.pkInfo.room_id
    this.pk.uid = this.pk.pkInfo.uid
    this.pk.uname = this.pk.pkInfo.uname
    this.pk.liveInfo = await api.getLiveInfo(this.pk.roomId)
    this.pk.rankCount = await api.getOnlinePeople(this.pk.roomId, this.pk.uid)
    this.pk.income = (await api.getGongxian(this.pk.roomId, this.pk.uid)) / 10
    this.pk.guardCount = await api.getGuardCount(this.pk.roomId, this.pk.uid)
    if (newpk) {
      console.log(`pk开始(${this.pk.roomId}) ${this.pk.uname} : ${this.pk.liveInfo.data.title}`)
    }
  }
  Config.fans.enable && update_liveInfo(), setInterval(update_liveInfo, 1000)
  Config.rank.enable && update_rankCount(), setInterval(update_rankCount, 1000)
  // Config.fans.enable && update_income(), setInterval(update_income, 10000)
  Config.otherLive.enable && update_aliveList(), setInterval(update_aliveList, 60000)
  Config.pk.enable && update_pkInfo(), setInterval(update_pkInfo, 5000)
},

async run() {
  // 定时更新数据
  this.updateData()
},

}

// 直播时间,开播时长 let LiveTimeModule = { html: <div class="live-skin-normal-a-text livetimeContainer" > <div id="liveStartTime">0000-00-00 00:00:00</div> <div id="liveDuration">0小时0分钟0秒</div> </div>, css: .livetimeContainer { display: flex; margin-left: 10px; user-select: text; flex-direction: column; opacity: 1; } , dom: { liveStartTime: null, // 开播时间 liveDuration: null, // 直播持续时间 }, initialized: false, prefix: ['', ''], // perfix: ['开播时间:', '直播时长:'], async initUI() { ut.addCSS(this.css) let container = await ut.waitForElement('#head-info-vm > div > div > div.upper-row > div.left-ctnr.left-header-area') if (container) { $(container).append(this.html); this.dom.liveStartTime = $('#liveStartTime') this.dom.liveDuration = $('#liveDuration') this.initialized = true } this.dom.liveStartTime.text(${this.prefix[0]}${Dao.liveInfo.data.live_time}) }, updateUI() { if (Dao.liveInfo.data.live_status == 0) { this.dom.liveStartTime.text(当前状态:未开播) return } if (Dao.liveInfo.data.live_status == 2) { this.dom.liveStartTime.text(当前状态:轮播中) // liveStatus = liveInfo.data.live_status (0:未开播 1:直播中 2:轮播中)); return } let timeText = ut.timeFrom(Dao.liveStartTimeStamp, '{h}小时 {mm}分钟 {ss}秒') this.dom.liveDuration.text(${this.prefix[1]}${timeText}`) }, async run() { await this.initUI() this.updateUI(), setInterval(() => this.updateUI(), 1000) }, }

// 粉丝,人气,收益 let FansModule = { html: <div id="fans" class="right-text live-skin-normal-a-text v-middle preserve-space" title="收益的计算方法为先初始化为高能榜的和(这里不计入小于20)的,之后有礼物、sc、上舰等事件加入统计" > , css: #fans{ display: flex; opacity: 1; margin-bottom: 2px; padding-left: 15px; justify-content: flex-end; } .preserve-space{ white-space: pre; } .fansContainer{ display: flex; flex-direction: row; flex-wrap: wrap-reverse; align-content: center; justify-content: right; align-items: center; max-width: ${Config.fans.maxWidth}px; } , dom: { fans: null, }, initialized: false, prefix: ['粉丝:', '人气:', '收益:'], async initUI() { ut.addCSS(this.css) let container = await ut.waitForElement('#head-info-vm > div > div > div.upper-row > div.right-ctnr') // document.querySelector("#head-info-vm > div > div > div.upper-row > div.right-ctnr") // console.log("container", container);

  if (container) {
    $(container).append(this.html)
    $(container).addClass('fansContainer')
    this.dom.fans = $('#fans')
    this.initialized = true
  }
},
updateUI() {
  let fensi = `${this.prefix[0]}${Dao.liveInfo.data.attention}`
  let renqi = `${this.prefix[1]}${Dao.liveInfo.data.online}`
  let gongxian = `${this.prefix[2]}${Dao.income.toFixed(1)}`
  if (Dao.inpk) {
    fensi += `/ ${Dao.pk.liveInfo.data.attention}`
    renqi += `/ ${Dao.pk.liveInfo.data.online}`
    gongxian += `/ ${Dao.pk.income.toFixed(1)}`
  }

  $('#fans').text(`${fensi}  ${renqi}  ${gongxian}`)
  // console.log("`${fensi}  ${renqi}  ${gongxian}`", `${fensi}  ${renqi}  ${gongxian}`);
  // this.dom.fans.text(`${fensi}  ${renqi}  ${gongxian}`)
},
async run() {
  await this.initUI()
  this.updateUI(), setInterval(() => this.updateUI(), 1000)
},

}

let RankModule = { html: <div style="width: 50%; position: relative; color: #666666" id="rank_gurad_div" title="同接/高能(0/0) = 0.00%"> <div id="rank_num" style="width: 100%; margin-bottom: 5px">同接:0</div> <div id="guard_num">舰长:0</div> </div> , dom: { rank_guard_card: null, rank: null, guard: null, }, initialized: false, prefix: ['同接:', '舰长:'], dirtyfix: false, async initUI() { let container = await ut.waitForElement("#control-panel-ctnr-box") // container.insertAdjacentHTML('beforeend', this.html) $(container).append(this.html) if (container) { this.dom.rank_guard_card = $('#rank_gurad_div') this.dom.rank = this.dom.rank_guard_card.children().first(); this.dom.guard = this.dom.rank_guard_card.children().last(); this.initialized = true } }, updateUI() { this.dom.rank.text(this.prefix[0] + (!Dao.inpk ? ${Dao.rank1}/${Dao.rank2} : ${Dao.rank1}/${Dao.pk.rankCount})) this.dom.guard.text(this.prefix[1] + (!Dao.inpk ? ${Dao.guardCount} : ${Dao.guardCount}/${Dao.pk.guardCount})) this.dom.rank_guard_card.attr('title', 同接/高能(${Dao.rank1}/${Dao.rank2}) = ${(Dao.rank1 * 100 / Dao.rank2).toFixed(2)}%); }, async run() { await this.initUI() this.updateUI(), setInterval(() => this.updateUI(), 1000) }, }

// 直播封面 let CoverModule = { html: <div data-v-03a54292 class="announcement-cntr" > <div data-v-03a54292 class="header"> <p data-v-03a54292 style="color:#ff6699">直播封面 <span id="coverTitle" data-v-03a54292>2020-9-24 点击刷新</span> </p> </div> <div data-v-03a54292 class="content"> <img alt="直播封面" id="cover" style="width: 100%; height: auto;"> </div> </div> , dom: { coverTitle: null, cover: null, }, initialized: false, async initUI() { let CoverContainer = await ut.waitForElement('#sections-vm > div.section-block.f-clear.z-section-blocks > div.right-container') if (CoverContainer) { // CoverContainer.insertAdjacentHTML("beforeend", this.html) // CoverContainer.insertAdjacentHTML(Config.cover.location, this.html) Config.cover.location == "beforeend" ? $(CoverContainer).append(this.html) : $(CoverContainer).prepend(this.html) this.dom.cover = $('#cover') this.dom.coverTitle = $('#coverTitle') this.dom.cover.on('click', () => this.updateUI());

    this.initialized = true
  }
},
updateUI() {
  const timestr = ut.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss')
  this.dom.coverTitle.text(`${timestr}更新 点击刷新`)
  this.dom.cover.src = Dao.liveInfo.data.user_cover
  this.dom.cover.attr('src', Dao.liveInfo.data.user_cover)
},
async run() {
  await this.initUI()
  this.updateUI(), setInterval(() => this.updateUI(), 60 * 1000)
},

}

// 其他关注直播 let otherLiveModule = { html: { roomCardContainer: <div id = "roomCardContainer"></div> , roomCard: <div class="roomCard"> <a href="{room.link}" target="_blank"> <img class="roomAvatar" src="{room.face}" alt="{room.title}" title="{room.uname} : {room.title}"> </a> {roomText} </div>, pkRoomCard: <div class="roomCard"> <a href="{room.link}" target="_blank"> <img id="pkAvatar" src="{room.face}" alt="{room.title}" title="{room.uname} : {room.title}"> </a> {roomText} </div>, }, css: ` #roomCardContainer { position: fixed; z-index: 9999; display: flex; flex-direction: column; flex-wrap: ${Config.otherLive.location == 'right' ? 'wrap-reverse' : 'wrap'}; opacity:${Config.otherLive.opacity}; align-content: center; justify-content: flex-end; align-items: center; max-height: ${Config.otherLive.maxHeight}px; ${Config.otherLive.location}: 5px; top: calc(50% + 32px); transform: translateY(-50%); } #roomCardContainer .roomCard{ padding-bottom: 5px; padding-left: 5px; display: flex; justify-content: center; align-items: center; flex-direction: column; align-content: center }

#roomCardContainer .roomCard .roomText{
    /*
    border-radius: 20%;
    border: 1px solid #0095ff;
    */
    min-width: -webkit-fill-available;
    text-align: center;
    background-color: rgba(255, 255, 255, 0.5);
}

#roomCardContainer .roomCard .roomAvatar {
    /* 头像O */
    border: 2px solid #0095ff;
    border-radius: 50%;
    opacity: 1;
    width: ${Config.otherLive.imgSize}px;
    height: ${Config.otherLive.imgSize}px;
}
#roomCardContainer .roomCard #pkAvatar {
    /* 头像O */
    border: 2px solid #ff0000;
    border-radius: 50%;
    opacity: 1;
    width: ${Config.otherLive.imgSize}px;
    height: ${Config.otherLive.imgSize}px;
    }
`,
dom: {
  aliveList: null,
},
initialized: false,
async initUI() {
  ut.addCSS(this.css)
  $('body').append(this.html.roomCardContainer);
  this.dom.aliveList = $('#roomCardContainer')
  this.initialized = true
},
updateUI() {
  this.dom.aliveList = $('#roomCardContainer')
  this.dom.aliveList.html('');
  let cnt = 0

  if (Dao.inpk) {
    let pk_roomLiveTime = ut.timeFrom(Dao.pk.liveInfo.data.live_time, '{h}h{mm}m')
    let pk_roomText = Config.otherLive.showTime
      ? `<div class="roomText"> ${pk_roomLiveTime} </div> `
      : ''
    const pkRoomCard = this.html.pkRoomCard
      .replace('{room.link}', `https://live.bilibili.com/${Dao.pk.roomId}`)
      .replace(/{room.title}/g, Dao.pk.liveInfo.data.title)
      .replace('{room.face}', Dao.pk.pkInfo.face)
      .replace('{room.uname}', Dao.pk.uname)
      .replace('{roomText}', pk_roomText)
    this.dom.aliveList.append(pkRoomCard)
    cnt++
  }

  for (const [index, room] of Dao.aliveList.entries()) {
    if (room.room_id == G.roomId) continue
    let roomLiveTime = ut.formatSeconds(room.live_time, '{h}h{mm}m')
    let roomText = Config.otherLive.showTime
      ? `<div class="roomText"> ${roomLiveTime} </div> `
      : ''

    const roomCard = this.html.roomCard
      .replace('{room.link}', room.link)
      .replace('{room.face}', room.face)
      .replace(/{room.title}/g, room.title)
      .replace('{room.uname}', room.uname)
      .replace('{roomText}', roomText)

    this.dom.aliveList.append(roomCard)
    if (++cnt >= 24) break
  }
},
async run() {
  await this.initUI()
  this.updateUI(), setInterval(() => this.updateUI(), 5000)
},

}

const api = { // ============================== api ============================== // liveInfo = get https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${roomId} // 开播时间 = liveInfo.data.live_time // 主播uid = liveInfo.data.uid // 真roomId = liveInfo.data.room_id // liveStatus = liveInfo.data.live_status (0:未开播 1:直播中 2:轮播中)`); // 粉丝数 = liveInfo.data.attention // 人气 = liveInfo.data.online // 分区 = liveInfo.data.area_name // 父分区 = liveInfo.data.parent_area_name

// pk信息 `https://api.live.bilibili.com/xlive/general-interface/v1/battle/getInfoById?room_id=${roomId}&pk_version=6`;

// getOnlineGoldRank = https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank?ruid=${uid}&roomId=${roomId}&page=1&pageSize=1
// 在线人数 = getOnlineGoldRank.data.onlineNum

// room_init = `https://api.live.bilibili.com/room/v1/Room/room_init?id=${roomId}`;
// 开播时间戳 = room_init.data.live_time

// alive = `https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=1`;
// 其他关注直播间 = alive.data.rooms
// =================================================================

// 获取直播间id
getRoomID() {
  try {
    const urlpathname = window.location.pathname
    // const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow
    // const urlpathname = W.location.pathname
    console.log(`urlpathname: ${urlpathname} `)
    return urlpathname.match(/\d{3,}/)[0]
  } catch (error) {
    console.log(`getRoomID error`)
    return null
  }
},
// 获取直播开始时间时间戳
async getLiveTimeStamp(roomId) {
  // https://api.live.bilibili.com/room/v1/Room/room_init?id=60989
  const url = `https://api.live.bilibili.com/room/v1/Room/room_init?id=${roomId}`
  return (await ut.fetchURL(url)).data.live_time
},
// 获取直播信息数据
async getLiveInfo(roomId) {
  const url = `https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${roomId}`
  return await ut.fetchURL(url)
},
// 获取在线人数
async getOnlinePeople(roomId, uid) {
  // 计算规则 https://ngabbs.com/read.php?tid=29562585
  // https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank?ruid=2978046&roomId=60989&page=1&pageSize=1
  const url = `https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank?ruid=${uid}&roomId=${roomId}&page=1&pageSize=1`
  return (await ut.fetchURL(url)).data.onlineNum
},
// 获取高能榜贡献值
async getGongxian(roomId, uid) {
  let res = 0
  let page = 1
  while (true) {
    // 大概每页需要100ms
    const url = `https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank?ruid=${uid}&roomId=${roomId}&page=${page}&pageSize=50`
    const onlineRankItem = (await ut.fetchURL(url)).data.OnlineRankItem
    page++
    const len = onlineRankItem.length
    // len > 0 && (res += onlineRankItem.reduce((total, item) => total + item.score, 0))
    // 小于20的不算在内
    if (len > 0) {
      res += onlineRankItem.reduce((total, item) => total + (item.score <= 20 ? 0 : item.score), 0)
      if (onlineRankItem[0].score < 20)
        break
    }
    if (len < 50) break
  }
  return res
},
// 获取舰长数量
async getGuardCount(roomId, uid) {
  const url = `https://api.live.bilibili.com/xlive/app-room/v2/guardTab/topList?roomid=${roomId}&page=1&ruid=${uid}&page_size=0`
  return (await ut.fetchURL(url)).data.info.num
},
// 获取关注直播列表
async getAliveList() {
  let roomlist = []
  const url = `https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=1`
  let res = (await ut.fetchURL(url, true)).data
  roomlist = [].concat(res.rooms)
  if (res.count > 10) {
    for (let page = 2; page <= Math.ceil(res.count / 10); page++) {
      const nextPageUrl = `https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page=${page}`
      const nextPageRes = (await ut.fetchURL(nextPageUrl, true)).data
      roomlist = roomlist.concat(nextPageRes.rooms)
    }
  }
  roomlist.sort((a, b) => a.live_time - b.live_time)
  return roomlist
},
// 获取pk信息
async getPkInfo(roomId) {
  const url = `https://api.live.bilibili.com/xlive/general-interface/v1/battle/getInfoById?room_id=${roomId}&pk_version=6`
  return await ut.fetchURL(url)
},

}

const ut = { async fetchURL(url, useCookie = false) { try { const response = await fetch(url, { credentials: useCookie ? 'include' : 'same-origin', }) if (!response.ok) throw new Error(请求${url}错误 response.status : ${response.status}) const data = await response.json() return data } catch (error) { throw new Error(请求${url}错误 error.message: ${error.message}) } },

// 添加CSS
addCSS(css) {
  let myStyle = document.createElement('style')
  myStyle.textContent = css
  let doc = document.head || document.documentElement
  doc.appendChild(myStyle)
},

sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
},

timeFrom(startTime, format, Ltrip0 = true) {
  if (typeof startTime === 'number') {
    startTime = startTime > 9999999999
      ? startTime
      : startTime * 1000
  }
  const elapsedSeconds = Math.floor((new Date() - new Date(startTime)) / 1000)
  return ut.formatSeconds(elapsedSeconds, format, Ltrip0)
},

formatSeconds(seconds, format, Ltrip0 = true) {
  const hours = Math.floor(seconds / 3600)
  const minutes = Math.floor((seconds % 3600) / 60)
  const remainingSeconds = seconds % 60

  Ltrip0 && hours == 0 && (format = format.replace(/.*\{mm\}/, '{mm}').replace(/.*\{m\}/, '{m}'))
  let formattedTime = format
    .replace('{hh}', hours.toString().padStart(2, '0'))
    .replace('{mm}', minutes.toString().padStart(2, '0'))
    .replace('{ss}', remainingSeconds.toString().padStart(2, '0'))
    .replace('{h}', hours)
    .replace('{m}', minutes)
    .replace('{s}', remainingSeconds)
  return formattedTime
},

getFirstNumber(text) {
  // 使用正则表达式匹配第一个数字
  const match = text.match(/\d+/)
  // 如果找到匹配的数字,则返回第一个匹配结果
  if (match) {
    return parseInt(match[0], 10) // 将匹配的字符串转换为整数并返回
  }
  // 如果未找到数字,则返回 null 或其他指定的默认值
  return 0
},

formatDate(date, format) {
  const year = date.getFullYear()
  const month = date.getMonth() + 1 // 月份是从 0 开始的
  const day = date.getDate()
  const hours = date.getHours()
  const minutes = date.getMinutes()
  const seconds = date.getSeconds()
  const formattedDate = format
    .replace('YYYY', year)
    .replace('YY', year % 100)
    .replace('MM', (month < 10 ? '0' : '') + month)
    .replace('DD', (day < 10 ? '0' : '') + day)
    .replace('hh', (hours < 10 ? '0' : '') + hours)
    .replace('mm', (minutes < 10 ? '0' : '') + minutes)
    .replace('ss', (seconds < 10 ? '0' : '') + seconds)

  return formattedDate
},
waitForElement(selector, interval = 200, timeout = 500000) {
  // console.log("waitForElement selector", selector);
  return new Promise((resolve) => {
    const checkExist = setInterval(() => {
      // const element = document.querySelector(selector)
      // if (element) {
      const element = $(selector)
      if (element.length) {
        // console.log("find element", element);
        clearInterval(checkExist)
        clearTimeout(timeoutTimer)
        resolve(element)
      }
    }, interval)

    const timeoutTimer = setTimeout(() => {
      clearInterval(checkExist)
      resolve(null)
    }, timeout)
  })
},
toggleShow(dom, display) {
  dom.style.display = dom.style.display == 'none' ? display : 'none'
},
changeOpacity(dom, dif) {
  let currentOpacity = dom.style.opacity === '' ? 1 : parseFloat(dom.style.opacity)
  dom.style.opacity = Math.max(0, Math.min(parseFloat(currentOpacity) + dif, 1))
  console.log('dom.style.opacity', dom.style.opacity)
},

hook_wrapper() {

  const cb_map = {
    ONLINE_RANK_COUNT: function (obj) {
      Dao.rank1 = obj.data.count;
      Dao.rank2 = obj.data.online_count;
    },
    GUARD_BUY: function (obj) {
      // console.log("GUARD_BUY\n", obj);
      const d = obj.data
      console.log(`GUARD_BUY: ${d.username} 开通 ${d.gift_name} (¥${d.price / 1000})`)
      Dao.income += d.price / 1000 / 2
      // console.log("Dao.income += ", d.price / 1000);

    },
    SUPER_CHAT_MESSAGE: function (obj) {
      const d = obj.data
      console.log(`SUPER_CHAT_MESSAGE: (¥${d.price}) ${d.uinfo.base.name}: ${d.message}`)
      Dao.income += d.price / 2
      // console.log("Dao.income += ", d.price);
    },
    COMBO_SEND: function (obj) {
      const d = obj.data
      console.log(`COMBO_SEND: ${d.uname} ${d.action} ${d.combo_num}个 $(gift_name} (共¥${d.combo_total_coin / 1000})`)
      // Dao.income += d.combo_total_coin / 1000 / 2
      // return obj.data.coin_type === 'gold' ? (obj.data.total_coin / 1000).toFixed(2) : '0.00';
      // console.log("Dao.income += ", d.combo_total_coin / 1000);
    },
    SEND_GIFT: function (obj) {
      // console.log("SEND_GIFT:", obj);
      const d = obj.data
      const price = d.coin_type == 'gold' ? d.total_coin / 1000 : 0
      console.log(`SEND_GIFT: ${d.uname} ${d.action} ${d.num}个 ${d.giftName} (共¥${d.total_coin / 1000})`)
      Dao.income += price / 2
      // console.log("Dao.income += ", price);
    },
  }
  const cmdTracker = new Map();
  Array.prototype.push = new Proxy(Array.prototype.push, {
    apply(target, thisArg, argArray) {
      try {
        if (argArray && argArray.length > 0) {
          for (let i = 0; i < argArray.length; i++) {
            if (argArray[i] && argArray[i].cmd) {
              !cmdTracker.has(argArray[i].cmd) && cmdTracker.set(argArray[i].cmd, argArray[i])
              if (cb_map[argArray[i].cmd]) {
                cb_map[argArray[i].cmd](argArray[i])
              }
            } else {
              break
            }
          }
        }
      } catch (e) {
        console.error(e)
      }
      return Reflect.apply(target, thisArg, argArray)
    },
  })
  // this.mapKey('0', () => { console.log(cmdTracker); })
},

mapKey(key, func) {
  document.addEventListener('keydown', function (event) {
    if (event.key === key) {
      func()
    }
  })
},

} // await Utils.sleep(3000) // if (W.location.pathname != '/p/html/live-web-mng/index.html') main() $(document).ready(function () { console.log("ready document.URL:", document.URL); if (/^https?:\/\/live.bilibili.com\/(blanc\/)?\d+\??.*/.test(document.URL)) { main() } // if (/^\/\d+$/.test(window.location.pathname)) // main() }); })()

长期地址
遇到问题?请前往 GitHub 提 Issues。