bilibili直播间显示更多信息

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

/// ==UserScript==
// @name         bilibili直播间显示更多信息
// @description  bilibili直播间显示开播时间,直播时长,粉丝数,人气值,分区,在线人数,封面,其他关注直播,pk等信息
// @version      3.12
// @author       wqz
// @match        https://live.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @license      MIT
// @grant        unsafeWindow
// @namespace https://greasyfork.org/users/1060750
// ==/UserScript==

;(function () {
  const isme = false
  let Config = {
    livetime: {
      enable: true, // 显示直播开始时间,持续时间
    },
    fans: {
      enable: true, // 显示粉丝,人气
      updateFrequency: 1000, // 粉丝,人气刷新频率(ms)
      maxWidth: 350, // 显示最大宽度(显示有问题才需要考虑改)
    },
    rank: {
      //(目前b站官方已经显示在线人数了 2024-05-05)
      enable: true, // 显示高能榜(在线人数)
      showmode: 0, // 0:(同1,不做修改) ,1:[总在线人数], 2:[非0值在线人数/总人数], 3:[非0在线人数]
      updateFrequency: 1000, // 在线人数刷新频率(ms)
    },
    cover: {
      enable: true, // 显示直播封面
      location: 'afterbegin', // 封面显示位置(afterbegin,beforeend)
    },
    pk: {
      enable: true, // 显示pk相关信息
      updateFrequency: 5 * 1000, // 刷新频率(ms)
    },
    area: {
      enable: true, // 显示分区信息
      updateFrequency: 10 * 1000, // 刷新频率(ms)
    },
    otherLive: {
      enable: true, // 显示其他关注的直播
      updateFrequency: 60 * 1000, // 刷新频率(ms)
      showTime: true, // 是否在头像下面显示直播时长
      location: 'right', // 显示位置(left,right)
      opacity: 1.0, // 透明度
      maxHeight: 550, // 最大显示高度(px)
      imgSize: 40, // 头像大小
    },
  }

  const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow

  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, // 开播时间时间戳
    rankCount: 0, // 非零在线人数 从api获取
    guardCount: 0, // 舰长数
    rank1: 0, // websocket获取
    rank2: 0, // websocket获取
    aliveList: null, // 其他关注直播
    inpk: false,
    pk: {
      pkInfo: null,
      roomId: null,
      uid: null,
      uname: null,
      liveInfo: null,
      rankCount: 0,
      guardCount: 0,
    },

    async initData() {
      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 && Config.rank.showmode >= 0 && ut.hook_wrapper()
    },

    updateData() {
      const update_liveInfo = async () => {
        this.liveInfo = await api.getLiveInfo(G.roomId)
      }
      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) {
          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.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}`
            )
          }
        } else {
          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.guardCount = 0
        }
      }

      Config.fans.enable && update_liveInfo(),
        setInterval(update_liveInfo, Config.fans.updateFrequency)
      Config.rank.enable && update_rankCount(),
        setInterval(update_rankCount, Config.rank.updateFrequency)
      Config.otherLive.enable && update_aliveList(),
        setInterval(update_aliveList, Config.otherLive.updateFrequency)
      Config.pk.enable && update_pkInfo(), setInterval(update_pkInfo, Config.pk.updateFrequency)
    },
    async run() {
      // 定时更新数据
      await this.initData()
      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.insertAdjacentHTML('beforeend', this.html)
        this.dom.liveStartTime = document.getElementById('liveStartTime')
        this.dom.liveDuration = document.getElementById('liveDuration')
        this.initialized = true
      }
      this.dom.liveStartTime.textContent = `${this.prefix[0]}${Dao.liveInfo.data.live_time}`
    },
    updateUI() {
      if (liveInfo.data.live_status == 0) {
        this.dom.liveStartTime.textContent = `当前状态:未开播`
        return
      }
      if (liveInfo.data.live_status == 2) {
        this.dom.liveStartTime.textContent = `当前状态:轮播中`
        // liveStatus = liveInfo.data.live_status (0:未开播 1:直播中 2:轮播中)`);
        return
      }
      const currentTime = new Date()
      const startTime = new Date(Dao.liveStartTimeStamp * 1000)
      const elapsedSeconds = Math.floor((currentTime - startTime) / 1000)
      let timeText = ut.formatTime(elapsedSeconds, '{h}小时 {mm}分钟 {ss}秒')
      timeText = timeText.startsWith('0小时 ') ? timeText.slice(4) : timeText
      this.dom.liveDuration.textContent = `${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" >
    `,
    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'
      )
      if (container) {
        container.insertAdjacentHTML('beforeend', this.html)
        container.classList.add('fansContainer')
        this.dom.fans = document.getElementById('fans')
        this.initialized = true
      }
    },
    updateUI() {
      // const pkfans = inpk() ? ` / ${pk.liveInfo.data.attention}` : ''
      // const pkonline = inpk() ? ` / ${pk.liveInfo.data.online}` : ''
      // this.dom.fans.textContent = `粉丝:${liveInfo.data.attention}${pkfans}    人气:${liveInfo.data.online}${pkonline}`;
      this.dom.fans.textContent = Dao.inpk
        ? `${this.prefix[0]}${Dao.liveInfo.data.attention} / ${Dao.pk.liveInfo.data.attention}  ${this.prefix[1]}${Dao.liveInfo.data.online} / ${Dao.pk.liveInfo.data.online}`
        : `${this.prefix[0]}${Dao.liveInfo.data.attention}    ${this.prefix[1]}${Dao.liveInfo.data.online}`
    },
    async run() {
      await this.initUI()
      this.updateUI()
      setInterval(() => this.updateUI(), Config.fans.updateFrequency)
    },
  }

  // 高能用户
  let RankModule = {
    dom: {
      rank: null,
      guard: null,
    },
    initialized: false,
    prefix: ['高能用户', '大航海'],
    dirtyfix: false,
    async initUI() {
      let container = await ut.waitForElement('#rank-list-ctnr-box > div.tabs > ul')
      if (container) {
        this.dom.rank = container.firstElementChild
        this.dom.guard = container.lastElementChild
        this.initialized = true
      }
    },
    updateUI() {
      thisguard = ut.getFirstNumber(this.dom.guard.textContent)
      if (Dao.inpk) {
        // todo textContent 会破坏了原来的功能
        this.dom.rank.textContent = `${this.prefix[0]}(${Dao.rankCount}/${Dao.pk.rankCount})`
        this.dom.guard.textContent = `${this.prefix[1]}(${Dao.guardCount}/${Dao.pk.guardCount})`
        this.dirtyfix = true // fix
        return
      } else if (this.dirtyfix) {
        this.dom.rank.textContent = `${this.prefix[0]}(${Dao.rank2})`
        this.dom.guard.textContent = `${this.prefix[1]}(${Dao.guardCount})`
      }
      switch (Config.rank.showmode) {
        case 1:
          this.dom.rank.textContent = `${this.prefix[0]}(${Dao.rank2})`
          break
        case 2:
          this.dom.rank.textContent = `${this.prefix[0]}(${Dao.rank1}/${Dao.rank2})`
          break
        case 3:
          this.dom.rank.textContent = `${this.prefix[0]}(${Dao.rankCount})`
          break
        default:
          break
      }
    },
    async run() {
      await this.initUI()
      this.updateUI()
      setInterval(() => this.updateUI(), Config.rank.updateFrequency)
    },
  }

  // 直播封面
  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="updateButton" data-v-03a54292>2020-9-24 点击刷新</span>
            </p>
        </div>
        <div data-v-03a54292 class="content">
            <img alt="直播封面" id="cover" >
        </div>
    </div>
    `,
    css: `
    #cover{
        width: 100%;
        height: auto;
    }
    `,
    dom: {
      cover: null,
      updateButton: null,
    },
    initialized: false,
    rankPrefix: null,
    async initUI() {
      ut.addCSS(this.css)
      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)
        this.dom.cover = document.getElementById('cover')
        this.dom.updateButton = document.getElementById('updateButton')
        this.dom.cover.addEventListener('click', () => this.updateUI())
        this.dom.updateButton.addEventListener('click', () => this.updateUI())
        this.initialized = true
      }
    },
    updateUI() {
      const timestr = ut.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss')
      this.dom.updateButton.textContent = `${timestr} 更新`
      this.dom.cover.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)
      let body = document.querySelector('body')
      body.insertAdjacentHTML('beforeend', this.html.roomCardContainer)
      this.dom.aliveList = document.getElementById('roomCardContainer')
      if (isme) {
        ut.mapKey('5', () => ut.toggleShow(otherLiveModule.dom.aliveList, 'flex'))
        ut.mapKey('6', () => ut.changeOpacity(otherLiveModule.dom.aliveList, -0.2))
        ut.mapKey('7', () => ut.changeOpacity(otherLiveModule.dom.aliveList, 0.2))
      }
      this.initialized = true
    },
    updateUI() {
      this.dom.aliveList = document.getElementById('roomCardContainer')
      this.dom.aliveList.innerHTML = ''
      let cnt = 0

      if (Dao.inpk) {
        const pkmiliSeconds = new Date() - new Date(Dao.pk.liveInfo.data.live_time)
        const pkSeconds = Math.floor(pkmiliSeconds / 1000)
        let pk_roomLiveTime = ut.formatTime(pkSeconds, '{h}h{mm}m', (Ltrip0 = true))
        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.insertAdjacentHTML('beforeend', pkRoomCard)
        cnt++
      }

      for (const [index, room] of Dao.aliveList.entries()) {
        if (room.room_id == G.roomId) continue
        let roomLiveTime = ut.formatTime(room.live_time, '{h}h{mm}m', (Ltrip0 = true))
        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.insertAdjacentHTML('beforeend', roomCard)
        if (++cnt >= 24) break
      }
    },
    async run() {
      await this.initUI()
      this.updateUI()
      const updateFrequency = Math.min(Config.otherLive.updateFrequency, Config.pk.updateFrequency)
      setInterval(() => this.updateUI(), updateFrequency)
    },
  }

  // 分区信息
  let areaModule = {
    html: {
      area: `
        <span class="live-skin-normal-a-text v-middle myArea"> 分区:
            <a class="areaLink" id="fArea" href="父分区链接">父分区</a> - 
            <a class="areaLink" id="area" href="子分区链接">子分区</a>
        </span> `,
      pk_area: `
        <span class="live-skin-normal-a-text v-middle myArea" id="pk_container"> pk分区:
            <a class="areaLink" id="pk_fArea" href="父分区链接">父分区</a> - 
            <a class="areaLink" id="pk_area" href="子分区链接">子分区</a>
        </span> `,
    },
    css: `
        .myArea{
            font-size: 16px;
            margin-bottom: 20px;
            color: #61666d;
        }
        .areaLink {
            color: black;
        }
        .areaLink:hover {
            color: #ff6699;
        }
        .areaLink:active {
            color: black;
        }
        #pk_container{
            padding-left:50px;
        }
    `,
    dom: {
      area: null,
      fArea: null,
      pk_container: null,
      pk_area: null,
      pk_fArea: null,
    },
    initialized: false,
    async initUI() {
      ut.addCSS(this.css)
      let container = await ut.waitForElement(
        '#sections-vm > div.section-block.f-clear.z-section-blocks > div.left-container > div.room-feed.trends > ul',
        500,
        10000000
      )
      if (container) {
        container.insertAdjacentHTML('beforeend', this.html.area)
        container.insertAdjacentHTML('beforeend', this.html.pk_area)
        this.dom.area = document.getElementById('area')
        console.log('this.dom.area', this.dom.area)
        this.dom.fArea = document.getElementById('fArea')
        this.dom.pk_container = document.getElementById('pk_container')
        this.dom.pk_area = document.getElementById('pk_area')
        this.dom.pk_fArea = document.getElementById('pk_fArea')
        this.initialized = true
      }
    },
    updateUI() {
      // 页面变化可能dom找不到
      this.dom.area = document.getElementById('area')
      this.dom.fArea = document.getElementById('fArea')

      this.dom.area.textContent = Dao.liveInfo.data.area_name
      this.dom.fArea.textContent = Dao.liveInfo.data.parent_area_name
      this.dom.area.href = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${Dao.liveInfo.data.parent_area_id}&areaId=${Dao.liveInfo.data.area_id}`
      this.dom.fArea.href = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${Dao.liveInfo.data.parent_area_id}&areaId=0`

      if (!Dao.inpk) {
        this.dom.pk_container.style.display = 'none'
      } else {
        this.dom.pk_container.style.display = ''
        this.dom.pk_area.textContent = Dao.pk.liveInfo.data.area_name
        this.dom.pk_fArea.textContent = Dao.pk.liveInfo.data.parent_area_name
        this.dom.pk_area.href = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${Dao.pk.liveInfo.data.parent_area_id}&areaId=${Dao.pk.liveInfo.data.area_id}`
        this.dom.pk_fArea.href = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${Dao.pk.liveInfo.data.parent_area_id}&areaId=0`
      }
    },
    async run() {
      await this.initUI()
      this.updateUI()
      setInterval(() => this.updateUI(), 5 * 1000)
    },
  }

  async function main() {
    initSucceed = await G.init()
    if (!initSucceed) return
    console.log(`G.init done`)
    await Dao.run()
    console.log(`Dao.run`)
    tasklist = []
    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())
    Config.area.enable && tasklist.push(areaModule.run())

    await ut.sleep(200) //等数据初始化完成
    await Promise.all(tasklist)
  }
  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 = 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 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))
    },

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

      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)

      Ltrip0 && formattedTime.startsWith('0h') && (formattedTime = formattedTime.slice(2))
      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) {
      return new Promise((resolve) => {
        const checkExist = setInterval(() => {
          const element = document.querySelector(selector)
          if (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() {
      let g_rank_count = 0
      let g_online_count = 0

      function on_online_rank_count(obj) {
        const rank_count = obj.data.count
        const online_count = obj.data.online_count
        let change = false
        if (rank_count && rank_count !== g_rank_count) {
          g_rank_count = rank_count
          change = true
        }
        if (online_count && online_count !== g_online_count) {
          g_online_count = online_count
          change = true
        }
        if (change) {
          Dao.rank1 = g_rank_count
          Dao.rank2 = g_online_count
          // const showers = document.querySelectorAll("#rank-list-ctnr-box > div.tabs > ul > li.item")
          // showers[0].innerText = '高能用户(' + g_rank_count + '/' + g_online_count + ')';
        }
      }

      const cb_map = {
        ONLINE_RANK_COUNT: on_online_rank_count,
      }

      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) {
                  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)
        },
      })
    },

    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()
})()