Bilibili直播间助手

提供同传弹幕过滤,快速切换牌子,自动切换直播清晰度等功能

// ==UserScript==
// @name         Bilibili直播间助手
// @namespace    http://tampermonkey.net/
// @version      1.0.9.3
// @description  提供同传弹幕过滤,快速切换牌子,自动切换直播清晰度等功能
// @author       QingMu_
// @match        *://live.bilibili.com/*
// @require      https://greasyfork.org/scripts/417560-bliveproxy/code/bliveproxy.js?version=984333
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js
// @grant        none
// @license      GPL License
// ==/UserScript==


(function () {
    'use strict';
    function main() {
        initCss();
        waitDoucment(MedalComponent);
        waitDoucment(FilterSettingComponent);
        waitDoucment(FilterBoxComponent)

        // 防休眠
        setInterval(() => {
            setTimeout(() => {
                document.body.dispatchEvent(
                    new Event("mousemove", { bubbles: true })
                );
            }, Math.random() * 2000);
        }, 10000);
    }
    const toast = {
        line: 0,
        withConsole: false,
        init(msg) {
            let box = document.createElement("div");
            box.innerText = msg;
            box.style.setProperty("position", "fixed");
            box.style.setProperty("padding", "12px 24px");
            box.style.setProperty("font-size", "14px");
            box.style.setProperty("border-radius", "10px");
            box.style.setProperty("color", "#fff");
            box.style.setProperty("z-index", "999999");
            box.style.setProperty("max-width", "70%");
            box.style.setProperty("word-wrap", "break-word");
            setTimeout(function () {
                let num = document.querySelectorAll(".animate-toast").length;
                if (num <= 1) {
                    toast.line = 0;
                }
                document.body.removeChild(box);
            }, 3000);
            document.body.appendChild(box);
            return box;
        },
        success(msg, x, y) {
            let box = this.init(msg);
            let px = x ? x + "px" : (document.body.clientWidth - box.clientWidth - 10) + "px";
            let py = y ? y + "px" : this.line + 10 + "px";
            this.line += box.clientHeight + 5;
            box.className = "animate-toast";
            box.style.setProperty("background-color", "#47D279");
            box.style.setProperty(
                "box-shadow",
                "0.1em 0.1em .1em rgba(71, 210, 121, .2)"
            );
            box.style.setProperty("left", px);
            box.style.setProperty("top", py);
            if (toast.withConsole) console.log(msg);
        },
        error(msg, x, y) {
            let box = this.init(msg);
            let px = x ? x + "px" : (document.body.clientWidth - box.clientWidth - 10) + "px";
            let py = y ? y + "px" : this.line + 10 + "px";
            this.line += box.clientHeight + 5;
            box.className = "animate-toast";
            box.style.setProperty("background-color", "#F04742");
            box.style.setProperty(
                "box-shadow",
                "0.1em 0.1em .1em rgba(240, 71, 66, .2)"
            );
            box.style.setProperty("left", px);
            box.style.setProperty("top", py);
            if (toast.withConsole) console.error(msg);
        },
        warning(msg, x, y) {
            let box = this.init(msg);
            let px = x ? x + "px" : (document.body.clientWidth - box.clientWidth - 10) + "px";
            let py = y ? y + "px" : this.line + 10 + "px";
            this.line += box.clientHeight + 5;
            box.className = "animate-toast";
            box.style.setProperty("background-color", "#EFA957");
            box.style.setProperty(
                "box-shadow",
                "0.1em 0.1em .1em rgba(239, 169, 87, .2)"
            );
            box.style.setProperty("left", px);
            box.style.setProperty("top", py);
            if (toast.withConsole) console.warn(msg);
        },
        info(msg, x, y) {
            let box = this.init(msg);
            let px = x ? x + "px" : (document.body.clientWidth - box.clientWidth - 10) + "px";
            let py = y ? y + "px" : this.line + 10 + "px";
            this.line += box.clientHeight + 5;
            box.className = "animate-toast";
            box.style.setProperty("background-color", "#48bbf8");
            box.style.setProperty(
                "box-shadow",
                "0.1em 0.1em .1em rgba(72, 187, 248, .2)"
            );
            box.style.setProperty("left", px);
            box.style.setProperty("top", py);
            if (toast.withConsole) console.log(msg);
        },
    };

    function waitDoucment(domOperate, timeout = 1 * 1000) {
        function wait() {
            try {
                domOperate();
            } catch (e) {
                setTimeout(wait, timeout);
            }
        }
        wait();
    }

    function initCss() {
        let css = `
    /* 屏蔽原来的按钮与弹窗 */
    .medal-section {
      display: none !important;
    }
    .dialog-ctnr.medal {
      display: none !important;
    }
    #shop-popover-vm {
      display: none !important;
    }

    /* toast 动画 */
    .animate-toast {
      animation: dropIn 0.5s, hidden 3.1s;
    }
    @keyframes dropIn {
      from {
        transform: translateY(20px);
      }
      to {
        transform: translateY(0px);
      }
    }
    @keyframes hidden {
      0%,
      75% {
        opacity: 1;
      }
      100% {
        opacity: 0;
      }
    }
    @keyframes scale-in-ease {
      0% {
        transform: scale(0);
      }
      50% {
        transform: scale(1.1);
      }
      100% {
        transform: scale(1);
      }
    }
    @keyframes scale-out {
      from {
        opacity: 1;
        transform: scale(1);
      }
      to {
        opacity: 0;
        transform: scale(0.8);
      }
    }

    /* 按钮样式 */
    .bilitools-button {
      position: relative;
      box-sizing: border-box;
      line-height: 1;
      margin: 0;
      padding: 6px 12px;
      border: 0;
      background-color: transparent;
      cursor: pointer;
      outline: 0;
      overflow: hidden;
      display: inline-flex;
      justify-content: center;
      align-items: center;
      background-color: #23ade5;
      color: #fff;
      border-radius: 4px;
    }
    .bilitools-button.bleak {
      background-color: #909399;
    }
    .bilitools-button.disable {
      cursor: not-allowed;
      background-color: #e9eaec;
      color: #b4b4b4;
    }
    .bilitools-medal-button:hover {
      background-color: #39b5e7;
    }
    .bilitools-medal-button:active {
      background-color: #21a4d9;
    }

    /* 复选框样式 */
    .bilitools-switch {
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;
      text-align: center;
      position: relative;
      width: 26px;
      height: 14px;
      border: 1px solid #dfdfdf;
      outline: 0;
      border-radius: 16px;
      box-sizing: border-box;
      background: #cccccc;
      cursor: pointer;
      margin: 0 5px;
    }

    .bilitools-switch:active,
    .bilitools-switch:visited {
      outline: none;
    }

    .bilitools-switch:before {
      content: attr(data-off);
      position: absolute;
      top: 0;
      left: 0;
      padding-left: 24px;
      border-radius: 15px;
      background-color: #cccccc;
      color: #fff;
    }

    .bilitools-switch:after {
      content: " ";
      position: absolute;
      top: -3px;
      left: -3px;
      width: 17px;
      height: 17px;
      border-radius: 15px;
      background-color: #ffffff;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
      transition: transform 0.3s;
    }

    .bilitools-switch:checked {
      border-color: #23ade5;
      background-color: #23ade5;
    }

    .bilitools-switch:checked:before {
      content: attr(data-on);
      padding-left: 0px;
      background-color: #23ade5;
      color: #fff;
      width: 31px;
    }

    .bilitools-switch:checked::after {
      transform: translateX(12px);
    }

    /* 模态框 */
    .bilitools-medal-modal-bg {
      width: 100%;
      height: 100%;
      position: fixed;
      overflow: hidden;
      z-index: 9999999;
      top: 0;
      left: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: rgba(0, 0, 0, 0.06);
    }
    .bilitools-medal-modal-box {
      width: 45%;
      height: 60%;
      min-width: 700px;
      padding: 1rem;
      background-color: #fff;
      color: rgb(75, 85, 99);
      border-radius: 0.25rem;
      box-shadow: 0 6px 12px 0 rgba(106, 115, 133, 0.22);
    }
    .bilitools-medal-modal-box-header {
      display: flex;
      justify-content: space-between;
    }
    .bilitools-medal-modal-box-header span {
      font-size: 18px;
      line-height: 24px;
    }
    .bilitools-close {
      color: #cfcfcf;
      font-size: 18px;
      line-height: 24px;
      cursor: pointer;
      outline: none;
      border: none;
      background-color: #fff;
    }
    .bilitools-close:hover {
      color: #23ade5;
    }
    .bilitools-medal-modal-box-body {
      display: block;
      height: calc(100% - 24px);
    }

    /* 牌子相关 布局 */
    .bilitools-medal-setting {
      display: flex;
      flex-wrap: wrap;
      text-align: left;
      font-size: 12px;
    }
    .setting-full {
      width: 100%;
      margin: 2px 0;
    }
    .setting-half {
      margin: 2px 0;
      width: 50%;
    }
    #bilitools-havent {
      background-color: #fff;
      border: 2px solid #e9ebef;
      color: #77787c;
      border-radius: 4px;
      padding: 4px;
      font-size: 14px;
    }
    #bilitools-search {
      border: 2px solid #e9ebef;
      color: #77787c;
      border-radius: 4px;
      padding: 5px;
      font-size: 14px;
      outline: none;
      width: 70%;
    }
    #bilitools-havent option {
      color: #000;
      font-size: 12px;
    }
    #bilitools-havent:hover,
    #bilitools-search:hover {
      border: 2px solid #c7cbd2;
    }
    #bilitools-havent:focus,
    #bilitools-search:focus {
      border: 2px solid #87c2ff;
    }

    /* 展示区域 */
    .bilitools-medal-table {
      margin-top: 10px;
      font-size: 14px;
    }
    .bilitools-medal-table table {
      border-collapse: separate;
      border-spacing: 0px 5px;
      width: 100%;
      height: 100%;
    }
    .bilitools-medal-table-body {
      display: block;
      overflow: hidden auto;
      height: 100px;
    }
    .bilitools-medal-table-row,
    .bilitools-medal-table-header {
      display: table;
      table-layout: fixed;
      width: 100%;
    }

    .bilitools-medal-table-row:hover {
      background-color: #f5f7fa;
    }
    .bilitools-liver-name a {
      color: #409eff;
      text-decoration: none;
    }
    .bilitools-liver-name,
    .bilitools-medal-name,
    .bilitools-medal-level,
    .bilitools-medal-exp,
    .bilitools-medal-exp-limit,
    .bilitools-medal-operate {
      text-align: left;
      padding: 10px;
      user-select: none;
    }
    .bilitools-liver-name.sort::after,
    .bilitools-medal-name.sort::after,
    .bilitools-medal-level.sort::after,
    .bilitools-medal-exp.sort::after,
    .bilitools-medal-exp-limit.sort::after {
      content: ">";
      display: inline-block;
      color: inherit;
      margin-left: 5px;
      font-weight: 900;
      transform: rotate(90deg);
    }

    .active.positive::after,
    .active.positive::after,
    .active.positive::after,
    .active.positive::after,
    .active.positive::after {
      content: ">";
      display: inline-block;
      color: #409eff;
      margin-left: 5px;
      font-weight: 900;
      transform: rotate(90deg);
    }
    .active.reverse::after,
    .active.reverse::after,
    .active.reverse::after,
    .active.reverse::after,
    .active.limit.reverse::after {
      content: ">";
      display: inline-block;
      color: #409eff;
      margin-left: 5px;
      font-weight: 900;
      transform: rotate(-90deg);
    }

    .bilitools-medal-operate::after {
      content: none !important;
    }
    .bilitools-medal-status {
      display: inline-block;
      border: 1px solid;
      border-radius: 4px;
      padding: 4px;
      color: #fff;
    }

    /* 弹幕过滤相关 */
    .bilitools-icons {
      display: inline-block;
      position: relative;
      vertical-align: top;
      fill: #c8c8c8;
      margin: 0 5px;
      font-size: 0;
      color: #c8c8c8;
    }
    .bilitools-icons svg {
      width: 22px;
      height: 22px;
    }
    .bilitools-icons:hover {
      color: #23ade5;
      fill: #23ade5;
    }
    .bilitools-icons.active {
      color: #23ade5;
      fill: #23ade5;
    }
    .bilitools-arrow::after {
      content: "";
      position: absolute;
      display: inline-block;
      z-index: 9999;
      width: 0;
      height: 0;
      top: 100%;
      left: 20%;
      border-left: 4px solid transparent;
      border-right: 4px solid transparent;
      border-top: 8px solid #fff;
    }
    .bilitools-dialog {
      position: absolute;
      z-index: 699;
      padding: 16px;
      font-size: 14px;
      box-sizing: border-box;
      background: #fff;
      border: 1px solid #e9eaec;
      border-radius: 8px;
      box-shadow: 0 6px 12px 0 rgba(106, 115, 133, 0.22);
      color: rgb(75, 85, 99);
      word-break: keep-all;
      left: -210%;
    }
    .animate-scale-in-ease {
      animation: scale-in-ease cubic-bezier(0.22, 0.58, 0.12, 0.98) 0.4s;
    }
    .animate-scale-out {
      animation: scale-out cubic-bezier(0.22, 0.58, 0.12, 0.98) 0.4s;
    }
    .bilitools-dialog-title {
      font-weight: 400;
      font-size: 18px;
      color: #23ade5;
    }
    .bilitools-dialog input[type="text"] {
      border: 2px solid #e9ebef;
      color: #77787c;
      border-radius: 4px;
      padding: 3px;
      font-size: 14px;
      outline: none;
      width: 100px;
      margin: 0 5px;
    }
    .bilitools-dialog input[type="text"]:hover {
      border: 2px solid #c7cbd2;
    }
    .bilitools-dialog input[type="text"]:focus {
      border: 2px solid #87c2ff;
    }
    .bilitools-dialog-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 8px;
      position: relative;
      flex-wrap: wrap;
    }
    .bliltools-scroll {
      width: 100%;
      height: 100px;
      overflow: hidden auto;
    }
    .bilitools-dragable {
      position: absolute;
      user-select: none;
      cursor: move;
      z-index:9999;
      pointer-events: all;
    }
    .bilitools-filterBox {
      height: 2rem;
      max-width: 60rem;
      background-color: rgba(0, 0, 0, 0.6);
      border-radius: 4px;
      padding: 10px;
      overflow: hidden scroll;
      line-height: 2rem;
      color: #fff;
      scrollbar-width: none;
      word-break: keep-all;
    }
    .bilitools-filterBox::-webkit-scrollbar {
      display: none;
    }
    .bilitools-filterBox p {
      margin: 0 5px;
      font-size: 18px;
    }
    .bilitools-filterBox.multiline {
      height: 6rem;
      text-align: left;
    }
    /* 修复模态框层级问题 */
    #aside-area-vm{
      z-index:999;
    }
    `;
        let styleElement = document.createElement("style");
        styleElement.innerText = css;
        document.head.appendChild(styleElement);
    }

    function MedalComponent() {
        window.loadMedalConfig = function () {
            return {
                medalConfig: {
                    wore: "",
                    searchText: "",
                    searchList: [],
                    autoWear: {
                        enable: false,
                        remember: 0
                    },
                    sortType: {
                        sorted: 0,
                        uname: true,
                        medalName: true,
                        level: true,
                        exp: true,
                        limit: true
                    },
                    list: [],
                    modalAnimate: false
                },
                medalBox: false,
                openMedalBox() {
                    this.getWore().then(res => {
                        this.medalConfig.wore = res.length == 0 ? "" : res.medal_name;
                    })
                    this.updateMedalList();
                    this.modalAnimate = true;
                    this.medalBox = true;
                },
                closeMedalBox() {
                    this.modalAnimate = false;
                    setTimeout(() => {
                        this.medalConfig.sortType.sorted = 0;
                        this.medalConfig.searchText = "";
                        this.medalConfig.searchList = [];
                        this.medalConfig.list = [];
                        this.medalBox = false;
                    }, 400)
                },
                initComponent() {
                    this.readSetting().then((config)=>{
                        if (config.enable){
                            document.querySelector(".chat-input-ctnr textarea").addEventListener("click",(e)=>{
                                this.autoWear()
                            })
                        }
                    });

                    // 初始化检查牌子
                    this.getWore().then(res => {
                        this.medalConfig.wore = res.length == 0 ? "" : res.medal_name;
                    })

                },
                // 太菜了 css解决不了表格溢出 先这么用着
                computedHeight() {
                    let bodyBox = this.$refs.bodyBox;
                    let setting = this.$refs.setting;
                    let tableHeader = this.$refs.tableHeader;
                    return bodyBox.offsetHeight - setting.offsetHeight - tableHeader.offsetHeight;
                },
                search() {
                    this.medalConfig.searchList = [];
                    for (let item of this.medalConfig.list) {
                        if (
                            item.uname
                            .toLocaleLowerCase()
                            .indexOf(this.medalConfig.searchText.trim().toLocaleLowerCase()) !==
                            -1 ||
                            item.medal_name
                            .toLocaleLowerCase()
                            .indexOf(this.medalConfig.searchText.trim().toLocaleLowerCase()) !== -1
                        ) {
                            this.medalConfig.searchList.push(item);
                        }
                    }
                },
                sort(col) {
                    this.medalConfig.sortType.sorted = col;
                    switch (col) {
                        case 1: {
                            if (this.medalConfig.sortType.uname) {
                                this.medalConfig.list.sort(function (a, b) {
                                    return a.uname.localeCompare(b.uname, "zh");
                                });
                            } else {
                                this.medalConfig.list.reverse();
                            }
                            this.medalConfig.sortType.medalName = true;
                            this.medalConfig.sortType.level = true;
                            this.medalConfig.sortType.exp = true;
                            this.medalConfig.sortType.limit = true;
                            this.medalConfig.sortType.uname = !this.medalConfig.sortType.uname;

                            break;
                        }
                        case 2: {
                            if (this.medalConfig.sortType.medalName) {
                                this.medalConfig.list.sort(function (a, b) {
                                    return a.medalName.localeCompare(b.medalName, "zh");
                                });
                            } else {
                                this.medalConfig.list.reverse();
                            }
                            this.medalConfig.sortType.uname = true;
                            this.medalConfig.sortType.level = true;
                            this.medalConfig.sortType.exp = true;
                            this.medalConfig.sortType.limit = true;
                            this.medalConfig.sortType.medalName = !this.medalConfig.sortType.medalName;
                            break;
                        }
                        case 3: {
                            if (this.medalConfig.sortType.level) {
                                this.medalConfig.list.sort(function (a, b) {
                                    return a.level - b.level;
                                });
                            } else {
                                this.medalConfig.list.reverse();
                            }
                            this.medalConfig.sortType.uname = true;
                            this.medalConfig.sortType.medalName = true;
                            this.medalConfig.sortType.exp = true;
                            this.medalConfig.sortType.limit = true;
                            this.medalConfig.sortType.level = !this.medalConfig.sortType.level;
                            break;
                        }
                        case 4: {
                            if (this.medalConfig.sortType.exp) {
                                this.medalConfig.list.sort(function (a, b) {
                                    return a.intimacy - b.intimacy;
                                });
                            } else {
                                this.medalConfig.list.reverse();
                            }
                            this.medalConfig.sortType.uname = true;
                            this.medalConfig.sortType.medalName = true;
                            this.medalConfig.sortType.level = true;
                            this.medalConfig.sortType.limit = true;
                            this.medalConfig.sortType.exp = !this.medalConfig.sortType.exp;
                            break;
                        }
                        case 5: {
                            if (this.medalConfig.sortType.limit) {
                                this.medalConfig.list.sort(function (a, b) {
                                    return a.today_intimacy - b.today_intimacy;
                                });
                            } else {
                                this.medalConfig.list.reverse();
                            }
                            this.medalConfig.sortType.uname = true;
                            this.medalConfig.sortType.medalName = true;
                            this.medalConfig.sortType.level = true;
                            this.medalConfig.sortType.exp = true;
                            this.medalConfig.sortType.limit = !this.medalConfig.sortType.limit;
                            break;
                        }
                    }
                },
                topping(arrays) {
                    let wore = [];
                    let room_medal = [];
                    let medals = [];
                    for (let item of arrays) {
                        if (item.medal_name == this.medalConfig.wore) {
                            wore.push(item)
                        } else if (item.roomid == window.BilibiliLive.SHORT_ROOMID || item.roomid == window.BilibiliLive.ROOMID) {
                            room_medal.push(item)
                        } else {
                            medals.push(item);
                        }
                    }
                    medals.sort((a, b) => b.level - a.level);
                    medals.sort((a, b) => b.is_lighted - a.is_lighted);
                    return [...wore, ...room_medal, ...medals];
                },
                sortedStyle(col) {
                    let isActive = this.medalConfig.sortType.sorted == col ? 'active ' : ' ';
                    let sortType = "";
                    switch (col) {
                        case 1: {
                            sortType = this.medalConfig.sortType.uname ? 'positive' : 'reverse';
                            break;
                        }
                        case 2: {
                            sortType = this.medalConfig.sortType.medalName ? 'positive' : 'reverse';
                            break;
                        }
                        case 3: {
                            sortType = this.medalConfig.sortType.level ? 'positive' : 'reverse';
                            break;
                        }
                        case 4: {
                            sortType = this.medalConfig.sortType.exp ? 'positive' : 'reverse';
                            break;
                        }
                        case 5: {
                            sortType = this.medalConfig.sortType.limit ? 'positive' : 'reverse';
                            break;
                        }
                    }
                    return (isActive + sortType).trim();
                },
                updateMedalList() {
                    let tasks = [];
                    let tmp_array = [];
                    if (this.medalConfig.list.length == 0) {
                        this.getMedalPage(1).then(res => {
                            tmp_array = res.data.items;
                            console.log(res.data)
                            for (let page = 2; page <= res.data.page_info.total_page; page++) {
                                tasks.push(this.getMedalPage(page))
                            }
                            Promise.all(tasks).then(res => {
                                for (const item of res) {
                                    Array.prototype.push.apply(tmp_array, item.data.items);
                                }
                            }).then(() => {
                                this.medalConfig.list = this.topping(tmp_array);
                            })
                        })
                    }
                },
                saveSetting() {
                    window.localStorage.setItem("bilitools-autowear", JSON.stringify(this.medalConfig.autoWear))
                },
                async readSetting() {
                    let config = await window.localStorage.getItem("bilitools-autowear");
                    if (!(config === "undefined" || config == null)) {
                        this.medalConfig.autoWear = JSON.parse(config);
                    }
                    return this.medalConfig.autoWear;
                },
                getToken() {
                    return document.cookie.match(/bili_jct=(.+?);/)[1];
                },
                autoWear() {
                    this.getWore().then(res => {
                        this.medalConfig.wore = res.length == 0 ? "" : res.medal_name;
                    }).then(() => {
                        if (window.__NEPTUNE_IS_MY_WAIFU__ && this.medalConfig.autoWear.enable) {
                            let medalInfo = window.__NEPTUNE_IS_MY_WAIFU__.roomInfoRes.data.anchor_info.medal_info;
                            if (medalInfo && medalInfo.medal_name !== this.medalConfig.wore) {
                                this.wear(medalInfo.medal_id).catch(e => {
                                    setTimeout(() => {
                                        if (this.medalConfig.autoWear.remember !== 0) {
                                            this.wear(this.medalConfig.autoWear.remember).catch(e => { });
                                        } else {
                                            this.takeOff();
                                        }
                                    }, 1500);
                                });
                            }else if (this.checkContained(medalInfo.medal_name)) {
                                if (this.medalConfig.autoWear.remember !== 0) {
                                    this.wear(this.medalConfig.autoWear.remember).catch(e => { });
                                } else {
                                    this.takeOff();
                                }
                            }
                        }
                    })
                },
                checkContained(name){
                    for(let item of this.medalConfig.list){
                        if(item.medal_name === name) return true;
                    }
                    return false;
                },
                refreshMedalCache() {
                    let originalMedalButton = document.querySelector('.dp-i-block.medal-item-margin')
                    if (originalMedalButton === null) {
                        return
                    }
                    originalMedalButton.click()
                    setTimeout(() => originalMedalButton.click(), 0)
                },
                async getMedalPage(page) {
                    const res = await fetch("https://api.live.bilibili.com/xlive/app-ucenter/v1/user/GetMyMedals?page_size=10&page=" + page, {
                        method: "GET",
                        credentials: "include"
                    });
                    const res_1 = await res.json();
                    if (res_1.code !== 0) {
                        toast.error(`获取第 ${page} 页粉丝牌失败 ${res_1.message}`);
                        return [];
                    } else {
                        return res_1;
                    }
                },
                async getWore() {
                    const res = await fetch("https://api.live.bilibili.com/live_user/v1/UserInfo/get_weared_medal", {
                        method: "GET",
                        credentials: "include"
                    });
                    const res_1 = await res.json();
                    if (res_1.code !== 0) {
                        toast.error(`获取当前粉丝牌失败 ${res_1.message}`);
                        return "";
                    } else {
                        return res_1.data;
                    }
                },
                async takeOff() {
                    let token = this.getToken();
                    let formdate = new FormData();
                    formdate.append("csrf_token", token);
                    formdate.append("csrf", token);
                    const res = await fetch("https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/take_off", {
                        method: "POST",
                        credentials: "include",
                        body: formdate
                    });
                    const res_1 = await res.json();
                    if (res_1.code !== 0) {
                        toast.error(`取消佩戴失败 ${res_1.message}`);
                    } else {
                        toast.success(`取消佩戴成功`);
                        this.medalConfig.wore = "";
                    }
                },
                async wear(medal_id) {
                    let token = this.getToken();
                    let formdate = new FormData();
                    formdate.append("csrf_token", token);
                    formdate.append("csrf", token);
                    formdate.append("medal_id", medal_id);
                    const res = await fetch("https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/wear", {
                        method: "POST",
                        credentials: "include",
                        body: formdate
                    });
                    const res_1 = await res.json();
                    if (res_1.code !== 0) {
                        throw res_1.message;
                    } else {
                        this.getWore().then(res => {
                            this.medalConfig.wore = res.medal_name;
                            toast.success(`你现在是 ${res.medal_name}`)
                        }).then(() => {
                            this.refreshMedalCache()
                        })
                    }
                }
            }
        }
        let myMedal = document.createElement("div");
        myMedal.setAttribute("x-data", "loadMedalConfig()");
        myMedal.setAttribute("x-init", "initComponent()");
        myMedal.innerHTML = `
    <button
      class="bilitools-button"
      x-text="medalConfig.wore == ''?'未佩戴':medalConfig.wore"
      @click="openMedalBox"
    ></button>
    <template x-if="medalBox" x-transport="body">
      <div class="bilitools-medal-modal-bg" @click.self="closeMedalBox">
        <div class="bilitools-medal-modal-box" :class="modalAnimate?'animate-scale-in-ease':'animate-scale-out'">
          <div class="bilitools-medal-modal-box-header">
            <span>我持有的粉丝牌</span>
            <button class="bilitools-close" @click="closeMedalBox">×</button>
          </div>
          <div class="bilitools-medal-modal-box-body" x-ref="bodyBox">
            <div class="bilitools-medal-setting" x-ref="setting">
              <div class="setting-full">
                <label for="bilitools-autoMeadl">
                  发言时自动佩戴对应粉丝牌(刷新后生效)
                  <input
                    type="checkbox"
                    class="bilitools-switch"
                    id="bilitools-autoMeadl"
                    name="autoMeadl"
                    @change="saveSetting"
                    x-model="medalConfig.autoWear.enable"
                  />
                </label>
              </div>
              <div class="setting-half">
                <label>
                  未持有牌子的直播间佩戴
                  <select id="bilitools-havent" @change="saveSetting" x-model = "medalConfig.autoWear.remember">
                    <option value="0">不佩戴</option>
                    <template
                      x-for="(item,index) in medalConfig.list"
                      :key="index"
                    >
                      <option
                        x-text="item.target_name + '/' + item.medal_name"
                        :value="item.medal_id"
                        :selected="item.medal_id == medalConfig.autoWear.remember ? true : false"
                      ></option>
                    </template>
                  </select>
                </label>
              </div>
              <div class="setting-half">
                <input
                  type="text"
                  id="bilitools-search"
                  placeholder="搜索"
                  x-model="medalConfig.searchText"
                  @input.debounce="search"
                />
              </div>
            </div>
            <div class="bilitools-medal-table">
              <table>
                <thead
                  class="bilitools-medal-table-header"
                  x-ref="tableHeader"
                >
                  <th
                    class="bilitools-liver-name sort"
                    style="cursor: pointer"
                    :class="sortedStyle(1)"
                    @click="sort(1)"
                  >
                    主播
                  </th>
                  <th
                    class="bilitools-medal-name sort"
                    style="cursor: pointer"
                    :class="sortedStyle(2)"
                    @click="sort(2)"
                  >
                    粉丝牌
                  </th>
                  <th
                    class="bilitools-medal-level sort"
                    style="cursor: pointer"
                    :class="sortedStyle(3)"
                    @click="sort(3)"
                  >
                    等级
                  </th>
                  <th
                    class="bilitools-medal-exp sort"
                    style="cursor: pointer"
                    :class="sortedStyle(4)"
                    @click="sort(4)"
                  >
                    粉丝牌经验
                  </th>
                  <th
                    class="bilitools-medal-exp-limit sort"
                    style="cursor: pointer"
                    :class="sortedStyle(5)"
                    @click="sort(5)"
                  >
                    今日经验
                  </th>
                  <th class="bilitools-medal-operate">操作</th>
                </thead>
                <tbody
                  class="bilitools-medal-table-body"
                  :style="'height: ' + (computedHeight() - 16)+ 'px;'"
                >
                  <template
                    x-for="(item,index) in (medalConfig.searchText == ''?medalConfig.list:medalConfig.searchList)"
                    :key="item.medal_id"
                  >
                    <tr class="bilitools-medal-table-row">
                      <td class="bilitools-liver-name">
                        <a
                          :href="'https://live.bilibili.com/'+item.roomid"
                          target="_blank"
                          x-text="item.target_name"
                        ></a>
                      </td>
                      <td class="bilitools-medal-name">
                        <span
                          class="bilitools-medal-status"
                          x-text="item.medal_name"
                          :style="'border-color: #' + item.medal_color_border.toString(16) + ';background-image: linear-gradient(45deg,#' + item.medal_color_start.toString(16) + ',#'+ item.medal_color_end.toString(16)+')'"
                        >
                        </span>
                      </td>
                  <td
                        class="bilitools-medal-level"
                        x-text="item.level"
                      ></td>
                      <td
                        class="bilitools-medal-exp"
                        x-text="item.intimacy + '/' + item.next_intimacy"
                      ></td>
                      <td
                        class="bilitools-medal-exp-limit"
                        x-text="item.today_feed + '/' + item.day_limit"
                      ></td>
                      <td class="bilitools-medal-operate">
                        <button
                          class="bilitools-button"
                          x-text="item.medal_name == medalConfig.wore? '取消佩戴': '佩戴'"
                          :class="item.medal_name == medalConfig.wore?'bleak':''"
                          @click="item.medal_name == medalConfig.wore?takeOff():wear(item.medal_id)"
                        ></button>
                      </td>
                    </tr>
                  </template>
                </tbody>
              </table>
            </div>
          </div>
        </div>
      </div>
    </template>
    `;
        document.querySelector("#control-panel-ctnr-box").appendChild(myMedal);
    }

    function FilterSettingComponent() {
        window.loadDanmuConfig = function () {
            return {
                danmuConfig: {
                    regexp:
                    "(?<who>[^〈{『〖[〔「【]*)[〈{『〖[〔「【](?<text>[^〉}『〗]〕」】]*)[$〉}『〗]〕」】]?",
                    liveRoomSwitch: [],
                    translatorList: [],
                    autoHigh: true,
                    filteBoxStyle: false,
                },
                filterEnable: false,
                settingBox: false,
                translatorText: "",
                dialogAnimate: false,
                roomid: 0,
                dialog: {
                    ["@click.stop"]() {
                        this.openSettingBox();
                    },
                    ["@click.document"]() {
                        this.closeSettingBox();
                    },
                    [":class"]() {
                        return this.dialogAnimate ? "active" : "";
                    },
                },
                initComponent() {
                    this.readSetting();
                    if (this.danmuConfig.autoHigh) {
                        this.autoHigh();
                    }
                },
                saveSetting() {
                    window.localStorage.setItem(
                        "bilitools-danmuFilter",
                        JSON.stringify(this.danmuConfig)
                    );
                },
                readSetting() {
                    let config = window.localStorage.getItem("bilitools-danmuFilter");
                    let room = window.BilibiliLive;
                    if (!(config === "undefined" || config == null)) {
                        this.danmuConfig = JSON.parse(config);
                    }
                    if (typeof room === "object") {
                        this.roomid = room.SHORT_ROOMID || room.ROOMID;
                        this.filterEnable = this.danmuConfig.liveRoomSwitch.includes(
                            this.roomid
                        );
                    }
                },
                openSettingBox() {
                    this.dialogAnimate = true;
                    this.settingBox = true;
                },
                closeSettingBox() {
                    this.dialogAnimate = false;
                    setTimeout(() => {
                        this.settingBox = false;
                    }, 400);
                },
                computedDialogHeight() {
                    let height = 0;
                    height += this.$refs.dialog.offsetHeight;
                    return -height - 8 + "px";
                },
                saveRegexp($dispatch) {
                    this.danmuConfig.regexp = this.danmuConfig.regexp.trim();
                    this.saveSetting();
                    $dispatch("regexp-update",{regexp:this.danmuConfig.regexp})
                    toast.success("保存成功");
                },
                saveTranselator($dispatch) {
                    this.danmuConfig.translatorList.push(this.translatorText.trim());
                    this.saveSetting();
                    $dispatch("translator-update",{regexp:this.danmuConfig.translatorList})
                    toast.success("保存成功");
                },
                removeTranselator(index,$dispatch) {
                    this.danmuConfig.translatorList.splice(index, 1);
                    this.saveSetting();
                    $dispatch("translator-update",{list:this.danmuConfig.translatorList})
                },
                changeFilterStatus($dispatch) {
                    this.filterEnable = !this.filterEnable;
                    if (this.filterEnable) {
                        this.danmuConfig.liveRoomSwitch.push(this.roomid);
                    } else {
                        let index = this.danmuConfig.liveRoomSwitch.indexOf(this.roomid);
                        if (index >= 0) {
                            this.danmuConfig.liveRoomSwitch.splice(index, 1);
                        }
                    }
                    $dispatch("filter-update",{ mode: this.filterEnable })
                    this.saveSetting();
                },
                changeFilteBoxStyle($dispatch) {
                    this.danmuConfig.filteBoxStyle = !this.danmuConfig.filteBoxStyle;
                    $dispatch("style-update", { mode: this.danmuConfig.filteBoxStyle });
                    this.saveSetting();
                },
                saveAutoHigh() {
                    this.danmuConfig.autoHigh = !this.danmuConfig.autoHigh;
                    this.saveSetting();
                },
                autoHigh() {
                    let time1 = setInterval(function() {
                        try {
                            let videoDom = document.querySelector("#live-player");
                            videoDom.dispatchEvent(new Event("mousemove"));
                            let quality = document.querySelector(".quality-wrap");
                            quality.dispatchEvent(new Event("mouseenter"));
                            if (document.querySelector(".list-it.selected").innerText != "原画") {
                                let list = document.querySelectorAll(".list-it");
                                for(let item of list){
                                   if(item.innerText == "原画") {
                                        console.log(item)
                                        item.click()
                                        // item.dispatchEvent(new Event("click"));
                                        break;
                                    }
                                }
                                quality.dispatchEvent(new Event("mouseleave"));
                            }
                            clearInterval(time1)
                        } catch {}
                    }, 1000);
                },
            };
        };
        let myFilterSetting = document.createElement("span");
        myFilterSetting.setAttribute("x-data", "loadDanmuConfig()");
        myFilterSetting.setAttribute("x-init", "initComponent()");
        myFilterSetting.setAttribute("x-bind", "dialog");
        myFilterSetting.className = "bilitools-icons live-skin-main-text"
        myFilterSetting.innerHTML = `
    <svg
    id="图层_1"
    data-name="图层 1"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 40 40"
  >
    <title>过滤设置</title>
    <path
      id="Combined-Shape"
      d="M6.73,35.66a6.74,6.74,0,0,1-6.5-7V11.32a6.76,6.76,0,0,1,6.5-7h22.8a6.74,6.74,0,0,1,6.5,7v.85a2,2,0,0,1-4,0h0v-.54A3.22,3.22,0,0,0,29,8.26H7.3a3.24,3.24,0,0,0-3.09,3.37V28.37A3.23,3.23,0,0,0,7.3,31.74h8.84a2,2,0,1,1,0,3.92H6.73Z"
    ></path>
    <path
      id="形状结合"
      d="M15.16,24.86a1.87,1.87,0,0,1,0,3.73h-4a1.87,1.87,0,0,1,0-3.73Zm-1.57-6.72a1.86,1.86,0,1,1,0,3.72h-4a1.86,1.86,0,0,1,0-3.72Zm6.09-6.73a1.87,1.87,0,0,1,0,3.73H13.44a1.87,1.87,0,0,1,0-3.73Z"
      style="fill-rule: evenodd"
    ></path>
    <path
      id="图标1"
      d="m29.026,22.86956c-1.36082,0 -2.46349,1.19207 -2.46349,2.66323c0,1.47048 1.10267,2.66323 2.46349,2.66323c0.3118,0 0.60719,-0.0696 0.88176,-0.18424l0.52767,0.87683l0.85777,-0.60388l-0.52451,-0.87069c0.44498,-0.48174 0.72081,-1.14704 0.72081,-1.88125c0,-1.47116 -1.10267,-2.66323 -2.46349,-2.66323zm-0.00947,-7.2084c-5.06647,0 -9.17355,4.44009 -9.17355,9.91735c0,5.47727 4.10708,9.91735 9.17355,9.91735c5.06647,0 9.17355,-4.44009 9.17355,-9.91735c0,-5.47727 -4.10708,-9.91735 -9.17355,-9.91735zm6.42161,13.1988c-0.15211,0.34391 -0.32379,0.67553 -0.52135,0.98942l-1.99326,-0.46332c-0.3036,0.35892 -0.64948,0.67826 -1.03135,0.94506l0.07511,2.1849c-0.32001,0.15694 -0.64885,0.28727 -0.99032,0.38894l-1.23395,-1.71544c-0.2348,0.0348 -0.47338,0.05868 -0.71702,0.05868c-0.2247,0 -0.44561,-0.01979 -0.66337,-0.05049l-1.22764,1.70589c-0.34147,-0.10304 -0.67221,-0.23337 -0.99095,-0.38826l0.07385,-2.14669c-0.40017,-0.27158 -0.76246,-0.60047 -1.08058,-0.97372l-1.94024,0.44899c-0.19756,-0.31115 -0.36798,-0.64414 -0.52135,-0.98805l1.3179,-1.57556c-0.14265,-0.4797 -0.22344,-0.9901 -0.243,-1.51688l-1.7471,-0.99897c0.03471,-0.38417 0.10414,-0.75537 0.18998,-1.12111l1.94656,-0.29137c0.18304,-0.47901 0.42605,-0.92254 0.71639,-1.32513l-0.74605,-1.97951c0.25184,-0.26544 0.52388,-0.50767 0.8098,-0.73012l1.68083,1.1409c0.41027,-0.22995 0.85146,-0.40464 1.31601,-0.51177l0.61098,-2.04775c0.17547,-0.01433 0.3503,-0.02866 0.5283,-0.02866c0.17799,0 0.35346,0.01501 0.5283,0.02866l0.61603,2.06617c0.45445,0.11122 0.88428,0.28386 1.28508,0.51177l1.70671,-1.15796c0.28719,0.22245 0.55986,0.46468 0.81106,0.73012l-0.76751,2.03751c0.27014,0.38485 0.50052,0.80586 0.6741,1.25895l2.0103,0.30092c0.08521,0.36506 0.1559,0.73763 0.19062,1.12111l-1.80959,1.03445c-0.02146,0.49539 -0.09468,0.97577 -0.22407,1.43295l1.36082,1.62537z"
      style="fill-rule: evenodd"
    />
  </svg>
  <template x-if="settingBox">
    <div
      class="bilitools-dialog bilitools-arrow"
      :class="dialogAnimate?'animate-scale-in-ease':'animate-scale-out'"
      x-ref="dialog"
      :style="'top: ' + computedDialogHeight()"
      @mouseleave="closeSettingBox"
    >
      <div class="bilitools-dialog-title">当前房间设置</div>
      <div class="bilitools-dialog-body">
        <div class="bilitools-dialog-row" style="justify-content: left">
          <label for="bilitools-filter-switch"> 过滤开关 </label>
          <input
            type="checkbox"
            class="bilitools-switch"
            name="filterSwitch"
            id="bilitools-filter-switch"
            x-model="filterEnable"
            @click="changeFilterStatus($dispatch)"
          />
        </div>
        <div class="bilitools-dialog-row" style="justify-content: left">
          <label for="bilitools-filter-switch"> 过滤框 单行/多行 </label>
          <input
            type="checkbox"
            class="bilitools-switch"
            name="filterSwitch"
            id="bilitools-filter-switch"
            x-model="danmuConfig.filteBoxStyle"
            @click="changeFilteBoxStyle($dispatch)"
          />
        </div>
        <div class="bilitools-dialog-row" style="justify-content: left">
          <label for="bilitools-video-switch"> 自动选择最高画质 </label>
          <input
            type="checkbox"
            class="bilitools-switch"
            name="videoSwitch"
            id="bilitools-video-switch"
            x-model="danmuConfig.autoHigh"
            @click="saveAutoHigh"
          />
        </div>
        <div class="bilitools-dialog-row" style="flex-wrap: nowrap">
          <label for="bilitools-filter-regexp"> 过滤正则 </label>
          <input
            type="text"
            name="filterRegexp"
            id="bilitools-filter-regexp"
            x-model="danmuConfig.regexp"
          />
          <button
            class="bilitools-button"
            :disabled="danmuConfig.regexp == '' ? true : false"
            :class="danmuConfig.regexp == '' ? 'disable' : ''"
            @click="saveRegexp($dispatch)"
          >
            确定
          </button>
        </div>
        <div class="bilitools-dialog-row" style="flex-wrap: nowrap">
          <label for="bilitools-filter-translator"> 添加译者 </label>
          <input
            type="text"
            name="translator"
            id="bilitools-filter-translator"
            x-model="translatorText"
          />
          <button
            class="bilitools-button"
            :disabled="translatorText == '' ? true : false"
            :class="translatorText == '' ? 'disable' : ''"
            @click="saveTranselator($dispatch)"
          >
            确定
          </button>
        </div>
        <div class="bilitools-dialog-row">
          <span
            style="
              display: block;
              width: 100%;
              flex-shrink: 0;
              font-weight: 600;
            "
            >译者列表</span
          >
          <div class="bliltools-scroll">
            <template
              x-for="(item,index) in danmuConfig.translatorList"
              :key="index"
            >
              <div class="bilitools-dialog-row" style="margin: 0 4px">
                <span x-text="item"></span>
                <button
                  class="bilitools-close"
                  @click="removeTranselator(index,$dispatch)"
                >
                  ×
                </button>
              </div>
            </template>
          </div>
        </div>
      </div>
    </div>
  </template>
`;
        document.querySelector(".icon-left-part").appendChild(myFilterSetting);
    }

    function FilterBoxComponent() {
        window.loadFilterBoxSetting = function () {
            return {
                filter: {
                    regexp: null,
                    translatorList: [],
                },
                filteBoxStatus:false,
                filteBoxStyle: false,
                filtedList: [],
                dragData: {
                    moveX: 0,
                    moveY: 0,
                    maxX:0,
                    maxY:0
                },
                status:{
                    ["@filter-update.document"]($dispatch){
                        this.updateStatus($dispatch)
                    },
                    ["@regexp-update.document"]($dispatch){
                        this.updateRegexp($dispatch)
                    },
                    ["@translator-update.document"]($dispatch){
                        this.updateTranslator($dispatch)
                    }
                },
                initComponent() {
                    this.readSetting();
                    this.dragData.moveX = this.$el.offsetWidth * 0.45
                    this.dragData.moveY = this.$el.offsetHeight * 0.8
                    this.websocketHook();
                },
                readSetting() {
                    let config = window.localStorage.getItem("bilitools-danmuFilter");
                    if (config === "undefined" || config == null) {
                        this.filter.regexp = new RegExp(
                            "(?<who>[^〈{『〖[〔「【]*)[〈{『〖[〔「【](?<text>[^〉}『〗]〕」】]*)[$〉}『〗]〕」】]?"
                        );
                    } else {
                        let setting = JSON.parse(config);
                        this.filter.regexp = new RegExp(setting.regexp);
                        this.filter.translatorList = setting.translatorList;
                        this.filteBoxStyle = setting.filteBoxStyle;
                        try{
                            this.filteBoxStatus = setting.liveRoomSwitch.includes(window.BilibiliLive.SHORT_ROOMID) || setting.liveRoomSwitch.includes(window.BilibiliLive.ROOMID);
                        }catch {}
                    }
                },
                updateStyle(e) {
                    this.filteBoxStyle = e.detail.mode;
                },
                updateStatus(e) {
                    this.filteBoxStatus = e.detail.mode;
                },
                updateRegexp(e) {
                    this.filter.regexp = new RegExp(e.detail.regexp);
                },
                updateTranslator(e) {
                    this.filter.translatorList = e.detail.list;
                },
                updateSetting(setting) {
                    if (typeof setting === "object") {
                        this.filter.regexp = new RegExp(setting.regexp);
                        this.filter.translatorList = setting.translatorList;
                    }
                },
                websocketHook() {
                    bliveproxy.addCommandHandler("DANMU_MSG", (cmd)=>{
                        if(this.filteBoxStatus){
                            if (this.filter.regexp.test(cmd.info[1]) || this.filter.translatorList.includes(cmd.info[2][1])){
                                this.filtedList.unshift(cmd.info[1])
                            }
                        }
                    });
                },
                drag(el) {
                    this.dragData.maxX = el.offsetWidth;
                    this.dragData.maxY = el.offsetHeight;
                    document.onmousemove = (e) => {
                        let x = this.dragData.moveX + e.movementX;
                        let y = this.dragData.moveY + e.movementY;
                        this.dragData.moveX = (x > 0 && x + this.$refs.dragable.offsetWidth < this.dragData.maxX)?x:this.dragData.moveX;
                        this.dragData.moveY = (y > 0 && y + this.$refs.dragable.offsetHeight < this.dragData.maxY)?y:this.dragData.moveY;
                    };
                    document.onmouseup = (e) => {
                        document.onmousemove = null;
                        document.onmouseup = null;
                    };
                },
            };
        }
        let myFilterBox = document.createElement("div");
        myFilterBox.setAttribute("x-data", "loadFilterBoxSetting()");
        myFilterBox.setAttribute("x-init", "initComponent()");
        myFilterBox.setAttribute("x-spread","status");
        myFilterBox.style="height: 100%;width: 100%;position: absolute;z-index: 100;top: 0;left: 0;pointer-events: none;";
        myFilterBox.innerHTML = `
    <template x-if="filteBoxStatus && filtedList.length > 0">
    <div
      class="bilitools-filterBox bilitools-dragable"
      @style-update.window="updateStyle"
@mousedown="drag($el)"
      :style="'top: ' + dragData.moveY + 'px;left: ' + dragData.moveX + 'px'"
      :class="filteBoxStyle?'multiline':''"
x-ref="dragable"
>
    <template x-if="filteBoxStyle" x-for="(item,index) in filtedList">
        <p x-text="item"></p>
</template>
<template x-if="!filteBoxStyle">
    <p style="font-weight: 600" x-text="filtedList[0]"></p>
</template>
</div>
</template>
`
    document.querySelector("#live-player").append(myFilterBox);
    }
    main();
})();