bilibili直播净化

增强直播屏蔽功能, 提高直播观看体验

// ==UserScript==
// @name                bilibili直播净化
// @namespace           https://github.com/lzghzr/GreasemonkeyJS
// @version             4.3.1
// @author              lzghzr
// @description         增强直播屏蔽功能, 提高直播观看体验
// @icon                data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIHN0cm9rZT0iIzAwYWVlYyIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+PHRleHQgZm9udC1mYW1pbHk9Ik5vdG8gU2FucyBDSksgU0MiIGZvbnQtc2l6ZT0iMjIiIHg9IjUiIHk9IjIzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMCIgZmlsbD0iIzAwYWVlYyI+5ruaPC90ZXh0Pjwvc3ZnPg==
// @supportURL          https://github.com/lzghzr/GreasemonkeyJS/issues
// @match               https://live.bilibili.com/*
// @match               https://www.bilibili.com/blackboard/*
// @license             MIT
// @require             https://unpkg.com/ajax-hook@3.0.3/dist/ajaxhook.min.js
// @require             https://unpkg.com/crypto-js@4.2.0/crypto-js.js
// @require             https://unpkg.com/crc-32@1.2.2/crc32.js
// @compatible          chrome 基础功能需要 88 以上支持 :not() 伪类,高级功能需要 105 及以上支持 :has() 伪类
// @compatible          edge 基础功能需要 88 以上支持 :not() 伪类,高级功能需要 105 及以上支持 :has() 伪类
// @compatible          firefox 基础功能需要 84 以上支持 :not() 伪类,高级功能需要 121 及以上支持 :has() 伪类
// @grant               GM_addStyle
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               unsafeWindow
// @run-at              document-start
// ==/UserScript==
const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
class DB {
  dbName;
  objectStoreName;
  keyPath;
  db;
  constructor(dbName, objectStoreName, keyPath) {
    this.dbName = dbName;
    this.objectStoreName = objectStoreName;
    this.keyPath = keyPath;
  }
  open(store) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName);
      request.onerror = () => {
        reject(request.error);
      };
      request.onsuccess = () => {
        this.db = request.result;
        resolve(request.result);
      };
      request.onupgradeneeded = () => {
        this.db = request.result;
        if (!this.db.objectStoreNames.contains(this.objectStoreName)) {
          const objectStore = this.db.createObjectStore(this.objectStoreName, { keyPath: this.keyPath });
          store.forEach(vaule => {
            objectStore.createIndex(vaule[0], vaule[0], { unique: vaule[1] });
          });
        }
      };
    });
  }
  putData(data) {
    return new Promise((resolve, reject) => {
      const store = this.db.transaction([this.objectStoreName], 'readwrite').objectStore(this.objectStoreName);
      const request = store.put(data);
      request.onerror = () => {
        reject(request.error);
      };
      request.onsuccess = () => {
        resolve();
      };
    });
  }
  getData(key) {
    return new Promise((resolve, reject) => {
      const store = this.db.transaction([this.objectStoreName], 'readonly').objectStore(this.objectStoreName);
      const request = store.get(key);
      request.onerror = () => {
        reject(request.error);
      };
      request.onsuccess = () => {
        resolve(request.result);
      };
    });
  }
}
class Tools {
  static str2Fn(str) {
    const fnReg = str.match(/([^\{]*)\{(.*)\}$/s);
    if (fnReg !== null) {
      const [, head, body] = fnReg;
      const args = head.replaceAll(/function[^\(]*|[\s()=>]/g, '').split(',');
      return new Function(...args, body);
    }
  }
  static isAllBitsSet(value) {
    if (value === 0) {
      return false;
    }
    return (value & (value + 1)) === 0;
  }
  static scriptName(name) {
    return [
      `%c${GM_info.script.name}%c ${name}`,
      "font-weight: bold; color: white; background-color: #FF6699; padding: 1px 4px; border-radius: 4px;",
      "font-weight: bold; color: #FF6699;"
    ];
  }
  static sleep(ms) {
    return new Promise(resolve => setTimeout(() => resolve('sleep'), ms));
  }
  static crc32(num) {
    return (CRC32.str(num.toString()) >>> 0).toString(16);
  }
  static md5(str) {
    return CryptoJS.MD5(str).toString(CryptoJS.enc.Hex);
  }
}
class NoVIP {
  elmStyleCSS;
  chatObserver;
  danmakuObserver;
  defaultConfig = {
    version: 1734189210657,
    menu: {
      noGiftMsg: {
        name: '屏蔽礼物相关',
        replace: '屏蔽全部礼物及广播',
        enable: false
      },
      noSystemMsg: {
        name: '屏蔽系统消息',
        replace: '屏蔽进场信息',
        enable: false
      },
      noSuperChat: {
        name: '屏蔽醒目留言',
        replace: '屏蔽醒目留言',
        enable: false
      },
      noEmoticons: {
        name: '屏蔽表情聊天',
        replace: '屏蔽表情动画(右下角)',
        enable: false
      },
      noEmotDanmaku: {
        name: '屏蔽表情弹幕',
        replace: '屏蔽表情弹幕',
        enable: false
      },
      noLikeBtn: {
        name: '屏蔽点赞按钮',
        enable: false
      },
      noGiftControl: {
        name: '屏蔽活动控件',
        enable: false
      },
      noGuardIcon: {
        name: '屏蔽舰队标识',
        enable: false
      },
      noWealthMedalIcon: {
        name: '屏蔽荣耀勋章',
        enable: false
      },
      noFansMedalIcon: {
        name: '屏蔽粉丝勋章',
        enable: false
      },
      noLiveTitleIcon: {
        name: '屏蔽成就头衔',
        enable: false
      },
      noRaffle: {
        name: '屏蔽抽奖橱窗',
        enable: false
      },
      noDanmakuColor: {
        name: '屏蔽弹幕颜色',
        enable: false
      },
      noGameId: {
        name: '屏蔽互动游戏',
        enable: false
      },
      noBBChat: {
        name: '屏蔽刷屏聊天',
        enable: false
      },
      noBBDanmaku: {
        name: '屏蔽刷屏弹幕',
        enable: false
      },
      noRoomSkin: {
        name: '屏蔽房间皮肤',
        enable: false
      },
      noActivityPlat: {
        name: '屏蔽活动皮肤',
        enable: false
      },
      noRoundPlay: {
        name: '屏蔽视频轮播',
        enable: false
      },
      noSleep: {
        name: '屏蔽挂机检测',
        enable: false
      },
      rankInvisible: {
        name: '在线榜单隐身',
        enable: false
      },
      invisible: {
        name: '进场隐身观看',
        enable: false
      }
    }
  };
  config;
  replaceMenu = new Set();
  rankInvisible = true;
  userInfoDB;
  message = [];
  constructor() {
    const userConfig = GM_getValue('blnvConfig', null) === null ? this.defaultConfig : JSON.parse(decodeURI(GM_getValue('blnvConfig')));
    if (userConfig.version === undefined || userConfig.version < this.defaultConfig.version) {
      for (const x in this.defaultConfig.menu) {
        try {
          this.defaultConfig.menu[x].enable = userConfig.menu[x].enable;
        }
        catch (error) {
          console.error(...Tools.scriptName('载入配置失效'), error);
        }
      }
      this.config = this.defaultConfig;
    }
    else {
      this.config = userConfig;
    }
    for (const x in this.config.menu) {
      this.replaceMenu.add(this.config.menu[x].replace);
    }
  }
  init() {
    W.getComputedStyle = new Proxy(W.getComputedStyle, {
      apply: function (target, _this, args) {
        if (args !== undefined && args[0] instanceof HTMLElement) {
          let htmlEle = Reflect.apply(target, _this, args);
          htmlEle = new Proxy(htmlEle, {
            get: function (_target, propertyKey) {
              if (propertyKey === 'display' && _target[propertyKey] === 'none') {
                return 'block';
              }
              return Reflect.get(_target, propertyKey);
            }
          });
          return htmlEle;
        }
        return Reflect.apply(target, _this, args);
      }
    });
    Object.defineProperty(W, '__NEPTUNE_IS_MY_WAIFU__', {});
    this.replaceFunction();
  }
  replaceFunction() {
    const that = this;
    let push = 1 << 5;
    W.webpackChunklive_room = W.webpackChunklive_room || [];
    W.webpackChunklive_room.push = new Proxy(W.webpackChunklive_room.push, {
      apply: function (target, _this, args) {
        for (const [name, fn] of Object.entries(args[0][1])) {
          let fnStr = fn.toString();
          if (fnStr.includes('staticClass:"block-effect-icon-root"')) {
            const regexp = /(?<left>staticClass:"block-effect-icon-root"\},\[)"on"===(?<mut_t>\w+)\.blockEffectStatus\?(?<svg>(?<mut_n>\w+)\("svg".*?)\[\k<mut_n>\("path".*?blockEffectIconColor\}\}\)\]/s;
            const match = fnStr.match(regexp);
            if (match !== null) {
              fnStr = fnStr.replace(regexp, '$<left>$<svg>\[\
$<mut_n>("circle",{attrs:{cx:"12",cy:"12",r:"10",stroke:$<mut_t>.blockEffectIconColor,"stroke-width":"1.5",fill:"none"}}),\
$<mut_t>._v(" "),\
$<mut_n>("text",{attrs:{"font-family":"Noto Sans CJK SC","font-size":"14",x:"5",y:"17",fill:$<mut_t>.blockEffectIconColor}},[$<mut_t>._v("滚")])\
]');
              console.info(...Tools.scriptName('脚本 icon 已加载'));
            }
            else {
              console.error(...Tools.scriptName('插入脚本 icon 失效'), fnStr);
            }
            push |= 1 << 0;
          }
          if (fnStr.includes('return this.chatList.children.length')) {
            const regexp = /(?<left>return )this\.chatList\.children\.length/s;
            const match = fnStr.match(regexp);
            if (match !== null) {
              fnStr = fnStr.replace(regexp, '$<left>this.chatList.querySelectorAll(".danmaku-item:not(.NoVIP_hide)").length');
              console.info(...Tools.scriptName('增强聊天显示 已加载'));
            }
            else {
              console.error(...Tools.scriptName('增强聊天显示失效'), fnStr);
            }
            push |= 1 << 1;
          }
          if (that.config.menu.noRoundPlay.enable) {
            if (fnStr.includes('case"PREPARING":')) {
              const regexp = /(?<left>case"PREPARING":)(?<right>[^;]+\((?<mut>\w+)\);break;)/s;
              const match = fnStr.match(regexp);
              if (match !== null) {
                fnStr = fnStr.replace(regexp, '$<left>$<mut>.round=0;$<right>');
                console.info(...Tools.scriptName('屏蔽下播轮播 已加载'));
              }
              else {
                console.error(...Tools.scriptName('屏蔽下播轮播失效'), fnStr);
              }
              push |= 1 << 2;
            }
          }
          else {
            push |= 1 << 2;
          }
          if (that.config.menu.noSleep.enable) {
            if (fnStr.includes('prototype.sleep=function(')) {
              const regexp = /(?<left>prototype\.sleep=function\(\w*\){)/;
              const match = fnStr.match(regexp);
              if (match !== null) {
                fnStr = fnStr.replace(regexp, '$<left>return;');
                console.info(...Tools.scriptName('屏蔽挂机检测 已加载'));
              }
              else {
                console.error(...Tools.scriptName('屏蔽挂机检测失效'), fnStr);
              }
              push |= 1 << 3;
            }
          }
          else {
            push |= 1 << 3;
          }
          if (that.config.menu.rankInvisible.enable) {
            if (fnStr.includes('this.enterRoomTracker=new ')) {
              const regexp = /(?<left>this\.enterRoomTracker=new \w+),/s;
              const match = fnStr.match(regexp);
              if (match !== null) {
                fnStr = fnStr.replace(regexp, '$<left>,this.enterRoomTracker.report=()=>{},');
                console.info(...Tools.scriptName('在线榜单隐身 已加载'));
              }
              else {
                console.error(...Tools.scriptName('在线榜单隐身失效'), fnStr);
              }
              push |= 1 << 4;
            }
          }
          else {
            push |= 1 << 4;
          }
          if (fn.toString() !== fnStr) {
            args[0][1][name] = Tools.str2Fn(fnStr);
          }
          if (Tools.isAllBitsSet(push)) {
            W.webpackChunklive_room.push = target;
            break;
          }
        }
        return Reflect.apply(target, _this, args);
      }
    });
    if (this.config.menu.rankInvisible.enable
      || this.config.menu.noRoundPlay.enable) {
      Array.prototype.concat = new Proxy(Array.prototype.concat, {
        apply: function (target, _this, args) {
          if (args[0] && args[0] instanceof Object && args[0].cmd) {
            const command = args[0];
            if (that.config.menu.rankInvisible.enable) {
              if (command.cmd.startsWith('DANMU_MSG')) {
                const user = command.info[0][15].user;
                if (user.uid !== 0) {
                  that.addUserInfo([{ uid: user.uid, name: user.base.name }]);
                }
                else if (that.userInfoDB !== undefined) {
                  args[0] = [];
                  that.userInfoDB.getData(command.info[0][7]).then(userInfo => {
                    if (userInfo !== undefined) {
                      command.info[2][0] = userInfo.uid;
                      command.info[2][1] = userInfo.name;
                      user.uid = userInfo.uid;
                      user.base.name = userInfo.name;
                    }
                    that.message.push(command);
                  });
                }
              }
              else if (command?.data?.uinfo?.uid !== 0 && command?.data?.uinfo?.base?.name) {
                that.addUserInfo([{ uid: command.data.uinfo.uid, name: command.data.uinfo.base.name }]);
              }
              if (that.message.length !== 0) {
                args.push(that.message);
                that.message = [];
              }
            }
            if (that.config.menu.noRoundPlay.enable) {
              if (command.cmd === 'PREPARING') {
                command.round = 0;
              }
            }
          }
          return Reflect.apply(target, _this, args);
        }
      });
    }
    if (this.config.menu.rankInvisible.enable) {
      JSON.stringify = new Proxy(JSON.stringify, {
        apply: function (target, _this, args) {
          if (args[0] && args[0] instanceof Object) {
            const value = args[0];
            if (that.config.menu.rankInvisible.enable && that.rankInvisible) {
              if (value.uid && value.roomid && value.protover == 3) {
                value.uid = 0;
              }
            }
          }
          return Reflect.apply(target, _this, args);
        }
      });
    }
    if (this.config.menu.rankInvisible.enable
      || this.config.menu.invisible.enable
      || this.config.menu.noRoomSkin.enable
      || this.config.menu.noRoundPlay.enable) {
      ah.proxy({
        onRequest: (XHRconfig, handler) => {
          if (this.config.menu.rankInvisible.enable && this.rankInvisible) {
            if (XHRconfig.url.includes('/xlive/web-room/v1/index/getDanmuInfo')) {
              XHRconfig.withCredentials = false;
              console.info(...Tools.scriptName('在线榜单隐身 已拦截'));
            }
          }
          if (this.config.menu.invisible.enable) {
            if (XHRconfig.url.includes('/xlive/web-room/v1/index/getInfoByUser')) {
              XHRconfig.url = XHRconfig.url.replace('not_mock_enter_effect=0', 'not_mock_enter_effect=1');
              console.info(...Tools.scriptName('隐藏进场信息 已拦截'));
            }
          }
          handler.next(XHRconfig);
        },
        onResponse: async (XHRresponse, handler) => {
          if (this.config.menu.noRoomSkin.enable) {
            if (XHRresponse.config.url.includes('/xlive/app-room/v2/guardTab/topList')) {
              XHRresponse.response = XHRresponse.response.replace(/"anchor_guard_achieve_level":\d+/, '"anchor_guard_achieve_level":0');
              console.info(...Tools.scriptName('屏蔽大航海榜单背景图 已拦截'));
            }
          }
          if (that.config.menu.noRoundPlay.enable || that.config.menu.rankInvisible.enable) {
            if (XHRresponse.config.url.includes('/xlive/web-room/v2/index/getRoomPlayInfo')) {
              const body = JSON.parse(XHRresponse.response);
              if (that.config.menu.noRoundPlay.enable) {
                if (body.data.live_status == 2) {
                  body.data.live_status = 0;
                }
                console.info(...Tools.scriptName('屏蔽视频轮播 已拦截'));
              }
              if (that.config.menu.rankInvisible.enable) {
                await that.getRank(body.data.room_id, body.data.uid);
                console.info(...Tools.scriptName('在线榜单隐身 已添加'));
              }
              XHRresponse.response = JSON.stringify(body);
            }
          }
          if (this.config.menu.noRoundPlay.enable) {
            if (XHRresponse.config.url.includes('/live/getRoundPlayVideo')) {
              XHRresponse.status = 403;
              console.info(...Tools.scriptName('屏蔽视频轮播 已拦截'));
            }
          }
          handler.next(XHRresponse);
        }
      }, W);
      const checkHookFetchAlive = async () => {
        this.hookFetch();
        for (let i = 0; i < 50; i++) {
          await W.fetch('//blnv_test_fetch_hook_alive/').catch(() => { this.hookFetch(); });
          await Tools.sleep(100);
        }
      };
      checkHookFetchAlive();
    }
  }
  hookFetch() {
    const that = this;
    W.fetch = new Proxy(W.fetch, {
      apply: async function (target, _this, args) {
        const resource = args[0];
        let url = (resource instanceof Request) ? resource.url : resource;
        if (that.config.menu.rankInvisible.enable && that.rankInvisible) {
          if (url.includes('/xlive/web-room/v1/index/getDanmuInfo')) {
            args[1] ? args[1].credentials = 'same-origin' : args[1] = { credentials: 'same-origin' };
            console.info(...Tools.scriptName('在线榜单隐身 已拦截'));
          }
        }
        if (that.config.menu.invisible.enable) {
          if (url.includes('/xlive/web-room/v1/index/getInfoByUser')) {
            url = url.replace('not_mock_enter_effect=0', 'not_mock_enter_effect=1');
            args[0] = (resource instanceof Request) ? new Request(url, resource) : url;
            console.info(...Tools.scriptName('隐藏进场信息 已拦截'));
          }
        }
        if (that.config.menu.noRoomSkin.enable) {
          if (url.includes('/xlive/app-room/v2/guardTab/topList')) {
            const response = await Reflect.apply(target, _this, args);
            const body = await response.json();
            body.data.info.anchor_guard_achieve_level = 0;
            const newResponse = new Response(JSON.stringify(body));
            console.info(...Tools.scriptName('屏蔽大航海榜单背景图 已拦截'));
            return newResponse;
          }
        }
        if (that.config.menu.noRoundPlay.enable || that.config.menu.rankInvisible.enable) {
          if (url.includes('/xlive/web-room/v2/index/getRoomPlayInfo')) {
            const response = await Reflect.apply(target, _this, args);
            const body = await response.json();
            if (that.config.menu.noRoundPlay.enable) {
              if (body.data.live_status == 2) {
                body.data.live_status = 0;
              }
              console.info(...Tools.scriptName('屏蔽视频轮播 已拦截'));
            }
            if (that.config.menu.rankInvisible.enable) {
              await that.getRank(body.data.room_id, body.data.uid);
              console.info(...Tools.scriptName('在线榜单隐身 已添加'));
            }
            const newResponse = new Response(JSON.stringify(body));
            return newResponse;
          }
        }
        if (that.config.menu.noRoundPlay.enable) {
          if (url.includes('/live/getRoundPlayVideo')) {
            const response = await Reflect.apply(target, _this, args);
            const newResponse = new Response(response.body, {
              status: 403,
              statusText: 'Forbidden',
              headers: response.headers
            });
            console.info(...Tools.scriptName('屏蔽视频轮播 已拦截'));
            return newResponse;
          }
        }
        if (url.includes('//blnv_test_fetch_hook_alive/')) {
          return new Response('success');
        }
        return Reflect.apply(target, _this, args);
      }
    });
  }
  start() {
    this.elmStyleCSS = GM_addStyle('');
    this.addCSS();
    const chatMessage = new Map();
    this.chatObserver = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        mutation.addedNodes.forEach(addedNode => {
          if (addedNode instanceof HTMLDivElement && addedNode.classList.contains('danmaku-item')) {
            const chatNode = addedNode.querySelector('.danmaku-item-right');
            if (chatNode !== null) {
              const chatText = chatNode.innerText;
              const dateNow = Date.now();
              if (chatMessage.has(chatText) && dateNow - chatMessage.get(chatText) < 10_000) {
                addedNode.classList.add('NoVIP_chat_hide');
              }
              else {
                chatMessage.set(chatText, dateNow);
              }
            }
          }
        });
      });
    });
    const elmDivChatList = document.querySelector('#chat-items');
    if (elmDivChatList !== null) {
      this.chatObserver.observe(elmDivChatList, { childList: true });
    }
    const danmakuMessage = new Map();
    this.danmakuObserver = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        mutation.addedNodes.forEach(addedNode => {
          const danmakuNode = addedNode instanceof Text ? addedNode.parentElement : addedNode;
          if (danmakuNode?.classList?.contains('danmaku-item-container')) {
            this.danmakuObserver.disconnect();
            this.danmakuObserver.observe(danmakuNode, { childList: true });
          }
          else if (danmakuNode?.classList?.contains('bili-danmaku-x-dm')) {
            danmakuNode.addEventListener('animationstart', () => {
              const danmakuText = danmakuNode.innerText.split(/ ?[x×]\d+$/);
              const dateNow = Date.now();
              if (danmakuMessage.has(danmakuText[0]) && dateNow - danmakuMessage.get(danmakuText[0]) < 10_000) {
                danmakuNode.classList.add('NoVIP_danmaku_hide');
              }
              else if (danmakuText[1] !== undefined) {
                danmakuNode.classList.add('NoVIP_danmaku_hide');
              }
              else {
                danmakuMessage.set(danmakuText[0], dateNow);
              }
            });
          }
        });
      });
    });
    const elmDivDanmaku = document.querySelector('#live-player');
    if (elmDivDanmaku !== null) {
      this.danmakuObserver.observe(elmDivDanmaku, { childList: true, subtree: true });
    }
    setInterval(() => {
      const dateNow = Date.now();
      chatMessage.forEach((value, key) => {
        if (dateNow - value > 60_000) {
          chatMessage.delete(key);
        }
      });
      danmakuMessage.forEach((value, key) => {
        if (dateNow - value > 60_000) {
          danmakuMessage.delete(key);
        }
      });
    }, 60_000);
    const docObserver = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        mutation.addedNodes.forEach(addedNode => {
          if (addedNode instanceof HTMLDivElement) {
            if (addedNode.classList.contains('dialog-ctnr')) {
              const blockEffectCtnr = addedNode.querySelector('.block-effect-ctnr');
              if (blockEffectCtnr !== null) {
                this.addUI(blockEffectCtnr);
              }
            }
          }
        });
      });
    });
    docObserver.observe(document, { childList: true, subtree: true });
    const blcok = localStorage.getItem('LIVE_BLCOK_EFFECT_STATE');
    if (blcok !== null) {
      const block = blcok.split(',').filter(item => item === '2' || item === '9');
      localStorage.setItem('LIVE_BLCOK_EFFECT_STATE', block.join(','));
    }
    this.changeCSS();
  }
  noRoomSkin() {
    if (this.config.menu.noRoomSkin.enable) {
      W.roomBuffService.__NORoomSkin = true;
      W.roomBuffService.unmount();
    }
    else {
      W.roomBuffService.__NORoomSkin = false;
      W.roomBuffService.mount(W.roomBuffService.__NORoomSkin_skin);
    }
  }
  changeCSS() {
    let height = 62;
    let cssText = `
/* 统一用户名颜色 */
.chat-item .user-name {
  color: var(--brand_blue) !important;
}`;
    if (this.config.menu.noGuardIcon.enable) {
      cssText += `
/* 聊天背景 */
.chat-item.chat-colorful-bubble {
  background-color: unset !important;
  border-image-source: unset !important;
  border-radius: unset !important;
  display: block !important;
  margin: unset !important;
}
/* 聊天背景 */
.chat-item.chat-colorful-bubble div:has(div[style*="border-image-source"]),
/* 欢迎提示条 */
#welcome-area-bottom-vm,
/* 粉丝勋章内标识 */
.chat-item .fans-medal-item-ctnr .medal-guard,
/* 舰队指挥官标识 */
.chat-item .pilot-icon,
.chat-item .pilot-icon ~ br,
/* 订阅舰长 */
.chat-item.guard-buy {
  display: none !important;
}
/* 兼容chrome 105以下版本 */
@supports not selector(:has(a, b)) {
  .chat-item.chat-colorful-bubble div[style*="border-image-source"] {
  display: none !important;
  }
}`;
    }
    if (this.config.menu.noWealthMedalIcon.enable) {
      cssText += `
/* 聊天背景, 存疑 */
.chat-item.wealth-bubble {
  border-image-source: unset !important;
}
/* 聊天背景, 存疑 */
.chat-item.has-bubble {
  border-image-source: unset !important;
  border-image-slice: unset !important;
  border-image-width: unset !important;
  box-sizing: unset !important;
  display: block !important;
  margin: unset !important;
}
.chat-item.has-bubble .danmaku-item-left > br,
/* 欢迎提示条 */
#welcome-area-bottom-vm,
/* 弹幕 */
.bili-danmaku-x-dm > .bili-icon,
/* 聊天 */
.chat-item .wealth-medal-ctnr {
  display: none !important;
}`;
    }
    if (this.config.menu.noGiftMsg.enable) {
      height -= 32;
      cssText += `
/* 底部小礼物, 调整高度 */
.chat-history-list.with-penury-gift {
  height: 100% !important;
}
/* 热门流量推荐 */
.chat-item.hot-rank-msg,
/* VIP标识 */
#activity-welcome-area-vm,
.chat-item .vip-icon,
.chat-item.welcome-msg,
/* 高能标识 */
.chat-item.top3-notice,
.chat-item .rank-icon,
/* 分享直播间 */
.chat-item.important-prompt-item,
/* 礼物栏 */
.gift-control-panel > *:not(.left-part-ctnr),
#web-player__bottom-bar__container,
/* 礼物按钮 */
#web-player-controller-wrap-el .web-live-player-gift-icon-wrap,
/* 主播心愿 */
.gift-wish-card-root,

#chat-gift-bubble-vm,
#penury-gift-msg,
#gift-screen-animation-vm,
#my-dear-haruna-vm .super-gift-bubbles,
.chat-item.gift-item,
.chat-item.system-msg,

.web-player-inject-wrap .announcement-wrapper,
.bilibili-live-player-video-operable-container>div:first-child>div:last-child,
.bilibili-live-player-video-gift,
.bilibili-live-player-danmaku-gift {
  display: none !important;
}`;
    }
    if (this.config.menu.noSystemMsg.enable) {
      height -= 30;
      cssText += `
.chat-history-list.with-brush-prompt {
  height: 100% !important;
}
/* 目前只看到冲榜提示 */
.chat-history-panel #all-guide-cards,
/* 聊天下方滚动消息,进场、点赞之类的 */
#brush-prompt,
/* 初始系统提示 */
.chat-item.convention-msg,
/* 各种野生消息 */
.chat-item.common-danmuku-msg,
/* 各种野生消息 x2 */
.chat-item.misc-msg,
/* 各种野生消息 x3 (Toasts) */
.link-toast,
/* pk */
.chat-item.new-video-pk-item-dm {
  display: none !important;
}`;
    }
    if (this.config.menu.noSuperChat.enable) {
      cssText += `
/* 调整 SuperChat 聊天框 */
.chat-history-list {
  padding-top: 5px !important;
}
.chat-item.superChat-card-detail {
  margin-left: unset !important;
  margin-right: unset !important;
  min-height: unset !important;
}
.chat-item .card-item-middle-top {
  background-color: unset !important;
  background-image: unset !important;
  border: unset !important;
  display: inline !important;
  padding: unset !important;
}
.chat-item .card-item-middle-top-right {
  display: unset !important;
}
.chat-item .superChat-base {
  display: unset !important;
  height: unset !important;
  line-height: unset !important;
  vertical-align: unset !important;
  width: unset !important;
}
.chat-item .superChat-base .fans-medal-item-ctnr {
  margin-right: 4px !important;
}
.chat-item .name,
.chat-item .card-item-name {
  display: unset !important;
  font-size: unset !important;
  font-weight: unset !important;
  height: unset !important;
  line-height: 20px !important;
  margin-left: unset !important;
  opacity: unset !important;
  overflow: unset !important;
  text-overflow: unset !important;
  vertical-align: unset !important;
  white-space: unset !important;
  width: unset !important;
}
.chat-item .card-item-name>span {
  color: var(--brand_blue) !important;
}
/* 为 SuperChat 用户名添加 : */
.chat-item.superChat-card-detail .name:after,
.chat-item.superChat-card-detail .card-item-name>span:after {
  content: ' : ';
}
.chat-item .card-item-middle-bottom {
  background-color: unset !important;
  display: unset !important;
  padding: unset !important;
}
.chat-item .input-contain {
  display: unset !important;
}
.chat-item .text {
  color: var(--text2) !important;
}
/* SuperChat 提示条 */
#chat-msg-bubble-vm,
/* SuperChat 保留条 */
#pay-note-panel-vm,
.chat-item .bottom-background,
/* SuperChat 聊天条 右上角电池 */
.chat-item .card-item-top-right,
/* SuperChat 按钮 */
#chat-control-panel-vm .super-chat {
  display: none !important;
}`;
    }
    if (this.config.menu.noEmoticons.enable) {
      cssText += `
#chat-control-panel-vm .emoticons-panel,
.chat-item.chat-emoticon {
  display: none !important;
}`;
    }
    if (this.config.menu.noEmotDanmaku.enable) {
      cssText += `
.bili-danmaku-x-dm > img:not(.bili-icon) {
  display: none !important;
}`;
    }
    if (this.config.menu.noLikeBtn.enable) {
      cssText += `
/* 点赞按钮 */
#chat-control-panel-vm .like-btn,
/* 点赞消息 */
.chat-item[data-type="6"],
/* 点赞数 */
#head-info-vm .icon-ctnr:has(.like-icon) {
  display: none !important;
}
/* 兼容chrome 105以下版本 */
@supports not selector(:has(a, b)) {
  #head-info-vm .like-icon,
  #head-info-vm .like-text {
  display: none !important;
  }
}`;
    }
    if (this.config.menu.noGiftControl.enable) {
      cssText += `
/* 排行榜 */
.rank-list-section .gift-rank-cntr .top3-cntr .default,
.rank-list-section .guard-rank-cntr:not(.open) .guard-empty {
  height: 42px !important;
}
.rank-list-section .guard-rank-cntr:not(.open) .guard-empty {
  background-size: contain !important;
  background-position: center !important;
  background-repeat: no-repeat !important;
}
.rank-list-section .gift-rank-cntr .top3-cntr .default-msg {
  bottom: -12px !important;
}
.rank-list-section,
.rank-list-section.new .rank-list-ctnr[style*="height: 178px;"] {
  height: 98px !important;
}
.rank-list-section .tab-content,
.rank-list-section .tab-content-pilot,
.rank-list-section.new .guard-rank-cntr .rank-list-cntr {
  min-height: unset !important;
}
.rank-list-section .tab-content[style*="height: 9"],
.rank-list-section .tab-content-pilot[style*="height: 9"],
.rank-list-section .gift-rank-cntr .top3-cntr {
  height: 64px !important;
}
.rank-list-section .guard-rank-cntr .top3-cntr > span {
  height: 32px !important;
}
.rank-list-section.new .gift-rank-cntr .top3-cntr,
.rank-list-section.new .guard-rank-cntr {
  height: unset !important;
}
.rank-list-section.new .gift-rank-cntr .top3-cntr {
  padding-top: 5px !important;
}
.rank-list-section.new .guard-rank-cntr .top3-cntr {
  top: 15px !important;
}
/* 调整聊天区 */
.chat-history-panel {
  height: calc(100% - 145px) !important;
  padding-bottom: 0px !important;
}
/* 有些直播间没有排行榜 */
.rank-list-section~.chat-history-panel {
  height: calc(100% - 98px - 145px) !important;
}
/* 有些直播间 .chat-history-panel 没有 .new */
#aside-area-vm:has(.control-panel-ctnr-new) .chat-history-panel {
  height: calc(100% - 114px) !important;
}
#aside-area-vm:has(.control-panel-ctnr-new) .rank-list-section~.chat-history-panel {
  height: calc(100% - 98px - 114px) !important;
}
.player-full-win #aside-area-vm:has(.control-panel-ctnr-new) .chat-history-panel {
  height: calc(100% - 104px) !important;
}
#aside-area-vm:has(.control-panel-ctnr-new) #chat-control-panel-vm {
  height: 114px !important;
}
#chat-control-panel-vm .control-panel-ctnr-new {
  padding-top: 5px !important;
}
#chat-control-panel-vm .chat-input-ctnr-new {
  margin-top: 5px !important;
}
#chat-control-panel-vm .control-panel-ctnr-new .danmakuPreference,
#chat-control-panel-vm .control-panel-ctnr-new .blockSetting,
#chat-control-panel-vm .control-panel-ctnr-new .effectBlock {
  bottom: 114px !important;
}
/* 直播分区 */
.live-area {
  display: flex !important;
}
/* 排行榜 */
.rank-list-section.new .gift-rank-cntr .top3 > div ~ div,
.rank-list-section.new .guard-rank-cntr .top3-cntr > span ~ span,
.rank-list-section.new .pilot,
/* 人气榜 */
#head-info-vm .popular-and-hot-rank,
#head-info-vm #LiveRoomHotrankEntries,
/* 礼物星球 */
#head-info-vm .gift-planet-entry,
/* 活动榜 */
#head-info-vm .activity-entry,
/* 粉丝团  */
#head-info-vm .follow-ctnr,
/* 头像框 */
.blive-avatar-pendant,
/* 主播城市 */
.anchor-location,
/* 水印 */
.web-player-icon-roomStatus,
.blur-edges-ctnr,
/* 遮罩 */
#web-player-module-area-mask-panel {
  display: none !important;
}
/* 兼容chrome 105以下版本 */
@supports not selector(:has(a, b)) {
  .chat-history-panel.new {
  height: calc(100% - 114px) !important;
  }
  .rank-list-section~.chat-history-panel.new {
  height: calc(100% - 98px - 114px) !important;
  }
  .chat-history-panel.new~#chat-control-panel-vm {
  height: 114px !important;
  }
  .player-full-win #aside-area-vm .chat-history-panel.new {
  height: calc(100% - 104px) !important;
  }
}`;
    }
    if (this.config.menu.noFansMedalIcon.enable) {
      cssText += `
/* 团体勋章 */
.chat-item .group-medal-ctnr,
/* 团体勋章 底部提示条 */
#brush-prompt .group-medal-ctnr,
/* 粉丝勋章 聊天 */
.chat-item .fans-medal-item-ctnr,
/* 粉丝勋章 底部提示条 */
#brush-prompt .fans-medal-item-ctnr {
  display: none !important;
}`;
    }
    if (this.config.menu.noLiveTitleIcon.enable) {
      cssText += `
.chat-item .title-label {
  display: none !important;
}`;
    }
    if (this.config.menu.noRaffle.enable) {
      cssText += `
body:not(.player-full-win):has(iframe[src*="live-lottery"])[style*="overflow: hidden;"] {
  overflow-y: overlay !important;
}
#shop-popover-vm,
#anchor-guest-box-id,
#player-effect-vm,
#chat-draw-area-vm,
.m-nobar__popup-container:has(iframe[src*="live-lottery"]),
/* 天选之类的 */
.gift-control-panel .left-part-ctnr,
.anchor-lottery-entry,
.popular-main .lottery {
  display: none !important;
}
/* 兼容chrome 105以下版本 */
@supports not selector(:has(a, b)) {
  body:not(.player-full-win)[style*="overflow: hidden;"] {
  overflow-y: overlay !important;
  }
  .m-nobar__popup-container {
  display: none !important;
  }
}`;
    }
    if (this.config.menu.noDanmakuColor.enable) {
      cssText += `
.bili-danmaku-x-dm {
  color: #ffffff !important;
}`;
    }
    if (this.config.menu.noGameId.enable) {
      cssText += `
/* 总容器 */
.web-player-inject-wrap,
/* PK */
/* #pk-vm, */
/* #awesome-pk-vm, */
/* #chaos-pk-vm, */
/* 多人连麦 */
/* #multi-voice-index, */
/* #multi-player, */
/* 互动游戏 */
#game-id,
/* 连麦 */
#chat-control-panel-vm .voice-rtc,
/* 帮玩 */
#chat-control-panel-vm .play-together-service-card-container,
/* 一起玩 */
#chat-control-panel-vm .play-together-entry,
/* 神秘人 */
.chat-item .common-nickname-medal {
  display: none !important;
}`;
    }
    if (this.config.menu.rankInvisible.enable) {
      cssText += `
#aside-area-vm .privacy-dialog {
  display: none !important;
}`;
    }
    if (this.config.menu.noBBChat.enable) {
      cssText += `
/* 官方 */
#aside-area-vm #combo-card,
#aside-area-vm #combo-danmaku-vm,
#aside-area-vm .vote-card,
/* 自定义 */
.chat-item.NoVIP_chat_hide {
  display: none !important;
}`;
    }
    if (this.config.menu.noBBDanmaku.enable) {
      cssText += `
/* 官方 */
.danmaku-item-container .bilibili-combo-danmaku-container,
.danmaku-item-container .combo {
  display: none !important;
}
/* 自定义 */
.bili-danmaku-x-dm.NoVIP_danmaku_hide,
/* 官方 */
.danmaku-item-container .mode-adv {
  opacity: 0 !important;
}`;
    }
    cssText += `
.chat-history-list.with-penury-gift.with-brush-prompt {
  height: calc(100% - ${height}px) !important;
}`;
    this.noRoomSkin();
    this.elmStyleCSS.innerHTML = cssText;
  }
  addUI(addedNode) {
    const elmUList = addedNode.firstElementChild;
    elmUList.childNodes.forEach(child => {
      if (child instanceof Comment) {
        child.remove();
      }
    });
    const listLength = elmUList.childElementCount;
    if (listLength > 10) {
      return;
    }
    const changeListener = (itemHTML, x) => {
      const itemSpan = itemHTML.querySelector('span');
      const itemInput = itemHTML.querySelector('input');
      itemInput.checked = this.config.menu[x].enable;
      itemInput.checked ? selectedCheckBox(itemSpan) : defaultCheckBox(itemSpan);
      itemInput.addEventListener('change', ev => {
        const evt = ev.target;
        evt.checked ? selectedCheckBox(itemSpan) : defaultCheckBox(itemSpan);
        this.config.menu[x].enable = evt.checked;
        GM_setValue('blnvConfig', encodeURI(JSON.stringify(this.config)));
        this.changeCSS();
      });
    };
    const selectedCheckBox = (spanClone) => {
      spanClone.classList.remove('checkbox-default');
      spanClone.classList.add('checkbox-selected');
    };
    const defaultCheckBox = (spanClone) => {
      spanClone.classList.remove('checkbox-selected');
      spanClone.classList.add('checkbox-default');
    };
    const itemHTML = elmUList.firstElementChild.cloneNode(true);
    const itemInput = itemHTML.querySelector('input');
    const itemLabel = itemHTML.querySelector('label');
    itemInput.id = itemInput.id.replace(/\d/, '');
    itemLabel.htmlFor = itemLabel.htmlFor.replace(/\d/, '');
    const listNodes = elmUList.childNodes;
    const replaceChild = [];
    for (const child of listNodes) {
      if (this.replaceMenu.has(child.innerText)) {
        replaceChild.push(child);
      }
    }
    replaceChild.forEach(child => child.remove());
    let i = listLength + 10;
    const itemFragment = document.createDocumentFragment();
    for (const x in this.config.menu) {
      const itemHTMLClone = itemHTML.cloneNode(true);
      const itemInputClone = itemHTMLClone.querySelector('input');
      const itemLabelClone = itemHTMLClone.querySelector('label');
      itemInputClone.id += i;
      itemLabelClone.htmlFor += i;
      i++;
      itemLabelClone.innerText = this.config.menu[x].name;
      changeListener(itemHTMLClone, x);
      itemFragment.appendChild(itemHTMLClone);
    }
    elmUList.appendChild(itemFragment);
  }
  addCSS() {
    GM_addStyle(`
/* 多行菜单 */
#chat-control-panel-vm .effectBlock[style*="width: 200px;"] {
  width: 270px !important;
}
#chat-control-panel-vm .control-panel-ctnr-new .effectBlock .arrow {
  left: 245px !important;
}
.block-effect-ctnr .item {
  float: left;
}
.block-effect-ctnr .item .cb-icon {
  left: unset !important;
  margin-left: -6px;
}
.block-effect-ctnr .item label {
  width: 84px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 隐藏网页全屏榜单 */
.player-full-win .rank-list-section {
  display: none !important;
}
.player-full-win .chat-history-panel {
  height: calc(100% - 135px) !important;
}`);
  }
  async getRank(room_id, ruid) {
    const queryContributionRank = await fetch(this.queryRank(room_id, ruid, 'online_rank', 'contribution_rank'));
    const rank = await queryContributionRank.json();
    if (this.rankInvisible && rank?.data?.count > 150) {
      this.rankInvisible = false;
    }
    const item = rank?.data?.item;
    this.addUserInfo(item);
    this.addRank(room_id, ruid);
  }
  async addRank(room_id, ruid) {
    const types = [['online_rank', 'entry_time_rank'],
      ['daily_rank', 'today_rank'], ['daily_rank', 'yesterday_rank'],
      ['weekly_rank', 'current_week_rank'], ['weekly_rank', 'last_week_rank'],
      ['monthly_rank', 'current_month_rank'], ['monthly_rank', 'last_month_rank']];
    for (const type of types) {
      await Tools.sleep(1000);
      const queryContributionRank = await fetch(this.queryRank(room_id, ruid, type[0], type[1]));
      const rank = await queryContributionRank.json();
      const item = rank?.data?.item;
      this.addUserInfo(item);
    }
  }
  queryRank(room_id, ruid, type, switch_) {
    const wts = Date.now();
    const salt = 'ea1db124af3c7062474693fa704f4ff8';
    const wrid = Tools.md5(`page=1&page_size=100&platform=web&room_id=${room_id}&ruid=${ruid}&switch=${switch_}&type=${type}&web_location=444.8&wts=${wts}${salt}`);
    return `//api.live.bilibili.com/xlive/general-interface/v1/rank/queryContributionRank?\
ruid=${ruid}&room_id=${room_id}&page=1&page_size=100&type=${type}&switch=${switch_}&platform=web&web_location=444.8&w_rid=${wrid}&wts=${wts}`;
  }
  async addUserInfo(item) {
    if (this.userInfoDB === undefined) {
      this.userInfoDB = new DB('blnvUserInfo', 'userInfo', 'crc32');
      await this.userInfoDB.open([["uid", true], ["name", false]]);
    }
    item?.forEach(userInfo => {
      this.userInfoDB.putData({ crc32: Tools.crc32(userInfo.uid), uid: userInfo.uid, name: userInfo.name });
    });
  }
}
const noVIP = new NoVIP();
if (location.href.match(/^https:\/\/live\.bilibili\.com\/(?:blanc\/)?\d/) && document.documentElement.hasAttribute('lab-style')) {
  if (noVIP.config.menu.noActivityPlat.enable) {
    if (self === top) {
      if (location.pathname.startsWith('/blanc')) {
        history.replaceState(null, '', location.href.replace(`${location.origin}/blanc`, location.origin));
      }
      else {
        location.href = location.href.replace(location.origin, `${location.origin}/blanc`);
      }
    }
    else {
      top?.postMessage(location.origin + location.pathname, 'https://live.bilibili.com');
      top?.postMessage(location.origin + location.pathname, 'https://www.bilibili.com');
    }
  }
  noVIP.init();
  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'interactive') {
      if (W.roomBuffService.mount !== undefined) {
        W.roomBuffService.mount = new Proxy(W.roomBuffService.mount, {
          apply: function (target, _this, args) {
            if (args[0] !== undefined) {
              _this.__NORoomSkin_skin = args[0];
              if (args[0].id !== 0) {
                _this.__NORoomSkin_skin_id = args[0].id;
              }
              if (_this.__NORoomSkin) {
                args[0].id = 0;
                args[0] = {};
              }
              else if (args[0].id === 0 && args[0].start_time !== 0) {
                args[0].id = _this.__NORoomSkin_skin_id || 0;
              }
            }
            return Reflect.apply(target, _this, args);
          }
        });
        W.roomBuffService.unmount = new Proxy(W.roomBuffService.unmount, {
          apply: function (target, _this, args) {
            if (_this.__NORoomSkin_skin !== undefined) {
              _this.__NORoomSkin_skin.id = 0;
            }
            return Reflect.apply(target, _this, args);
          }
        });
      }
    }
    if (document.readyState === 'complete') {
      noVIP.start();
    }
  });
}
else {
  if (noVIP.config.menu.noActivityPlat.enable) {
    W.addEventListener("message", msg => {
      if (msg.origin === 'https://live.bilibili.com' && msg.data.startsWith('https://live.bilibili.com/blanc/')) {
        location.href = msg.data;
      }
    });
  }
}