Greasy Fork

live-plugin

虎牙、斗鱼,哔哩哔哩 页面简化,屏蔽主播

目前为 2023-07-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         live-plugin
// @namespace    https://github.com/wuxin0011/huya-live
// @version      4.0.1
// @author       wuxin0011
// @description  虎牙、斗鱼,哔哩哔哩 页面简化,屏蔽主播
// @license      MIT
// @icon         https://cdn.staticaly.com/gh/wuxin0011/blog-resource@main/picgo/icon.png
// @source       https://github.com/wuxin0011/huya-live
// @supportURL   https://github.com/wuxin0011/huya-live/issues
// @match        https://*.douyu.*/*
// @match        https://*.huya.*/*
// @match        https://*.bilibili.*/*
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';
    if (typeof window === undefined) {
        return;
    }
    // const
    const huya_address_pattern = /^https:\/\/.*\.huya\.((com)|(cn)).*/
    const doyu_address_pattern = /^https:\/\/.*\.douyu\.((com)|(cn)).*/
    const bilibili_address_pattern = /^https:\/\/.*\.bilibili\..*/
    const localhost = /^http:\/\/127.0.0.1.*|^http:\/\/localhost.*/
    const local_url = window.location.href
    const is_huya = huya_address_pattern.test(local_url) // 是否是虎牙地址
    const is_douyu = doyu_address_pattern.test(local_url) // 是否是斗鱼地址
    const is_bilibili = bilibili_address_pattern.test(local_url) // 是否是bilibili
    const is_localhost = localhost.test(local_url) // 本地环境
    const wd = window.document
    const wls = window.localStorage // 简化存储对象
    const download_plugin_url = 'https://greasyfork.org/zh-CN/scripts/449261-%E8%99%8E%E7%89%99%E7%9B%B4%E6%92%AD' // 下载地址
    const source_code_url = 'https://github.com/wuxin0011/huya-live' // 源码地址
    // common method
    const isImage = (file) => /.*(\.(png|jpg|jpeg|apng|avif|bmp|gif|ico|cur|svg|tiff|webp))$/.test(file)
    const querySelector = (el, sel) => !!el && el instanceof HTMLElement ? el.querySelector(sel) : wd.querySelector(el)
    const querySelectorAll = (el, sel) => !!el && el instanceof HTMLElement ? el.querySelectorAll(sel) : wd.querySelectorAll(el)
    const addEventListener = (el, type, callback) => el && type && callback && el.addEventListener(type, callback, false)
    const createElement = (tag) => !!tag && wd.createElement(tag)
    const appendChild = (el1, el2) => (!!el1 && !!el2 && (el1 instanceof HTMLElement) && (el2 instanceof HTMLElement)) && el1.appendChild(el2)
    const insertChild = (el1, el2) => (!!el1 && !!el2 && (el1 instanceof HTMLElement) && (el2 instanceof HTMLElement)) && el1.insertBefore(el2, el1.firstChild)
    const addStyle = (str) => {
        if (window?.GM_addStyle && typeof window.GM_addStyle == 'function') {
            window.GM_addStyle(str)
        } else {
            let head = querySelector('head')
            let style = createElement('style')
            style.innerText = str
            head.appendChild(style)
        }
    }
    const removeDOM = (element, realRemove = false) => {
        try {
            if (!(element instanceof HTMLElement)) {
                element = querySelector(element)
            }
            if (element instanceof HTMLElement) {
                element.style.display = 'none'
                if (realRemove) {
                    element.remove()
                }
            }
        } catch (e) { } // 防止element没有remove方法而抛出异常
    }
    const s2d = (string) => new DOMParser().parseFromString(string, 'text/html').body.childNodes[0]

    const isArray = (a) => a && a?.length > 0


    const timeoutSelectorAll = (selector, callback, time = 200) => {
        setTimeout(() => {
            const nodes = querySelectorAll(selector)
            if (isArray(nodes) && typeof callback === 'function') {
                callback(nodes)
            }
        }, time)
    }

    const timeoutSelector = (selector, callback, time = 0) => {
        setTimeout(() => {
            const arrayNode = querySelector(selector)
            if (arrayNode && typeof callback === 'function') {
                callback(arrayNode)
            }
        }, time)
    }


    const getLocalStore = (k, type = Array.name, isParse = true) => {
        let obj = wls.getItem(k);
        if (type == Array.name) {
            if (isParse && obj) {
                obj = JSON.parse(obj);
            }
            return Array.isArray(obj) ? obj : [];
        }
        if (type == Object.name) {
            if (isParse && obj) {
                obj = JSON.parse(obj);
            }
            return obj ? obj : {};
        }
        if (type == String.name) {
            return obj ? obj : "";
        }
        if (type == Boolean.name) {
            return obj == "true" || obj == true ? true : false;
        }
        return obj;
    }

    const addLocalStore = (k, v = [], type = Array.name, isParse = true) => (type == Object.name || type == Array.name) && isParse ? wls.setItem(k, JSON.stringify(v)) : wls.setItem(k, v)
    const removeVideo = (selector, time1 = 100, maxCount = 1000) => {
        let count = 0
        let video_timer = setInterval(() => {
            try {
                const video = querySelector(selector)
                if (video && video instanceof HTMLVideoElement) {
                    video.pause()
                }
                removeDOM(video, false)
                if (count >= maxCount) {
                    clearInterval(video_timer)
                }
                count = count + 1
            } catch (e) { }
        }, time1)
    }

    const throttle = (wait, func, ...args) => {
        let pre = Date.now();
        return () => {
            if (Date.now() - pre > wait) {
                func(...args)
                pre = Date.now()
            }
        }
    }

    const intervalRemoveElement = (selectors, time = 160, maxCount = 1000) => {
        if (!isArray(selectors)) {
            return;
        }
        let count = 0
        let timer = setInterval(() => {
            selectors.forEach(sel => {
                removeDOM(sel, true)
            })
            if (count >= maxCount) {
                clearInterval(timer)
                return;
            }
            count = count + 1
        }, time)


    }

    const backgroundNone = (element, selectors = ['.layout-Main'], time = 100, maxCount = 500) => {
        if (!(element instanceof HTMLElement) || !isArray(selectors)) {
            return;
        }
        let count = 0
        let timer = setInterval(() => {
            selectors.forEach(sel => {
                let b = querySelector(element, sel)
                if (!(b instanceof HTMLElement)) {
                    return;
                }
                b.style.backgroundImage = 'none'
            })
            // 结束计时器 减少浏览器性能开销
            if (count >= maxCount) {
                clearInterval(timer)
                return;
            }
            count = count + 1
        }, time)

    }

    const hasVideo = (element, selector = '.layout-Main') => !!querySelector(element, selector)



    /**
     * 页面加载完成
     */
    window.onload = () => {
        setTimeout(() => {
            try {
                let text = is_huya ? '虎牙' : '斗鱼'
                text = '%c欢迎使用直播插件,下载地址%c'
                if (!is_localhost) {
                    console.clear()
                }
                console.log(
                    text
                        .concat(download_plugin_url, ''),
                    'background: rgb(255, 93, 35); padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
                    'border-radius: 0 3px 3px 0; color: #fff')
                console.log(
                    '%c源码地址:%c '
                        .concat(source_code_url, ''),
                    'background: rgb(255, 93, 35); padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
                    'border-radius: 0 3px 3px 0; color: #fff')
                if (querySelector && querySelectorAll) {
                    //插件执行入口
                    if (is_huya) {
                        // 执行虎牙直播插件
                        new TriggerLive()
                    } else if (is_douyu) {
                        // 执行斗鱼直播插件
                        new FishLive()
                    } else if (is_bilibili) {
                        // 执行bilibili直播插件
                        new Bilibili()
                    }
                    else if (is_localhost) {
                        // 本地测试使用
                        new LivePlugin()
                    }
                    else {
                        log('插件地址不适配,请检查匹配地址!!!', 'error')
                    }
                }
                else {
                    log('请使用新版浏览器,该插件不支持!', 'error')
                }

            } catch (e) {
                log(e)
            }

        }, 0)
    }



    /**
     * 日志输出
     */
    const log = (msg, level = 'log') => {
        if (level == 'log') {
            console.log(new Date().toLocaleString(), msg);
        }
        if (level == 'info') {
            console.info(new Date().toLocaleString(), msg);
        }
        if (level == 'warn') {
            console.warn(new Date().toLocaleString(), msg);
        }
        if (level == 'error') {
            console.error(new Date().toLocaleString(), msg);
        }
    }


    /**
     * 主播类
     */
    class HostUser {
        constructor(roomId, name) {
            this.roomId = roomId;
            this.name = name;
        }
    }


    /**
     * 直播插件,要求所有直播插件继承该类,并实现要求重写的方法!
     */
    class LivePlugin {
        constructor() {
            this.key = 'key'  // 存放内容信息
            this.bg_key = 'bg_key' // 存放背景图
            this.bg_show_key = 'bg_show_key'  // 是否显示背景key
            this.menu_show_key = 'menu_show_key' // 是否显示菜单
            this.full_screen_key = 'full_screen_key' // 是否剧场模式
            this.baseUrl = "http://127.0.0.1:8080"  // 直播源
            this.defaultBackgroundImage = 'https://cdn.staticaly.com/gh/wuxin0011/blog-resource@main/picgo/bg5.jpg' // 默认背景图
            this.users = [] // 存放屏蔽主播信息
            this.html = querySelector('html') // html
            this.body = querySelector('body') // body
            this.menu = null  // 菜单
            this.tbody = null // 操作数据
            this.m_container = null   // 操作容器
            this.gift_key = this.key + '_gift' // 礼物
            this.giftTool = null  // 礼物栏
            this.logo_btn = null // button
            this.logo_show_key = this.key + "_logo_show" // logo key
            this.header_logo = 'none' // logo 是否显示
            this.buttonName = '' // button name
            if (is_localhost) { // 本地测试允许加载
                this.init()
            }
        }

        // 初始化操作方法,子类可以继承该类,实现该类中空方法,参考此操作,初始化构造器实调用该方法就可以了。。。
        init() {
            if (!this.removeRoom()) {
                this.detail()
                this.common()
                this.index()
                this.category()
                this.create_container()
                this.isShowLeftMenu()
                this.isShowGift()
            }
            // 设置壁纸
            this.settingBackgroundImage()

        }


        /*********************************建议下面操作方法必须重写的,并且参考此步骤*****************************/

        // 公共部分页面操作
        common() { }
        //首页操作
        index() { }
        // 分类页面操作
        category() { }
        // 详情页操作
        detail() { }
        // 通过点击直播间名称删除直播间
        removeRoomByClickRoomName() { }
        // 通过房间号获取直播间name
        getNameByRoomId(roomId) {
            alert('该操作未实现!');
            return null
        }
        // 通过房间地址获取房间号
        getRoomIdByUrl(url) {
            return null
        }




        /*********************************子类继承无需修改的方法******************************/


        /**
         * 容器,所有操作容器均在此容器中,
         */
        create_container() {
            // 初始化房间号
            let that = this
            if (!that.body || !that.html) {
                that.html = querySelector('html')
                that.body = querySelector('body')
            }
            if (!that.body) {
                that.body = createElement('body')
            }
            that.users = getLocalStore(that.key, Array.name)
            let show1 = getLocalStore(that.bg_show_key, Boolean.name)
            let show2 = getLocalStore(that.menu_show_key, Boolean.name)
            let show3 = getLocalStore(that.full_screen_key, Boolean.name)
            let show4 = getLocalStore(that.gift_key, Boolean.name)
            let show5 = getLocalStore(that.logo_show_key, Boolean.name)
            that.m_container = s2d(`
                             <div class="m-container">
                                <div class="m-container-box">
                                    <div class="operation">
                                        <input type="text" placeholder="房间号或者名称...">
                                        <button class="btn btn-primary add-room" title="复制地址栏房间号,手动添加房间">添加</button>
                                        <button class="btn btn-success clear-room" title="重置表格数据">重置</button>
                                        <button class="btn btn-warning bg-btn" title="上传背景图">背景</button>
                                        <input type="file" id="file">
                                        <input type="checkbox" id="checkbox1" ${show1 ? 'checked' : ''} class="checkbox" title="是否显示背景" />背景
                                        <input type="checkbox" id="checkbox2" ${show2 ? 'checked' : ''} class="checkbox" title="是否显示左侧菜单"/>菜单
                                        <input type="checkbox" id="checkbox3" ${show3 ? 'checked' : ''} class="checkbox" title="自动适应屏幕"/>剧场
                                        <input type="checkbox" id="checkbox4" ${show4 ? 'checked' : ''} class="checkbox" title="是否开启礼物"/>礼物
                                        <input type="checkbox" id="checkbox5" ${show5 ? 'checked' : ''} class="checkbox" title="关闭或者显示插件Logo"/>logo
                                        <a class="m-link" href="https://greasyfork.org/zh-CN/scripts/449261-%E8%99%8E%E7%89%99%E7%9B%B4%E6%92%AD" target="_blank" title="更新、反馈">更新</a>
                                        <button class="btn btn-info btn-close-container" title="关闭" style="float:right;">关闭</button>
                                    </div>
                                    <table>
                                        <thead>
                                            <th>序号</th>
                                            <th>名称</th>
                                            <th>房间号</th>
                                            <th>操作</th>
                                        </thead>
                                        <tbody>
                                        </tbody>
                                    </table>
                                    </div>
                             </div>
         `)



            appendChild(that.body, that.m_container)
            that.tbody = querySelector(that.m_container, '.m-container table tbody')
            // 生成操作按钮
            that.operationDOMButton()
            // 添加直播房间号信息
            that.createRoomItem(that.users)
            // 右侧点击添加button
            that.createButton()

        }


        /**
         * 通过用户列表构建列表
         * @param {Object} arr  用户列表
         */
        createRoomItem(arr) {
            if (!isArray(arr)) {
                return;
            }
            let that = this
            arr.forEach((item, index) => {
                let tr = createElement('tr')
                tr.innerHTML =
                    `<td>${index + 1}</td>
                          <td>${item.name}</td>
                          <td>${item.roomId}</td>
                          <td>
                          <button class="btn btn-danger" room-id="${item.roomId}">删除</button></td>`
                that.tbody.appendChild(tr)
                // 添加删除事件
                const deleteBtn = querySelector(tr, 'button')
                addEventListener(deleteBtn, 'click', function (e) {
                    let roomId = e.target.getAttribute('room-id');
                    that.userDelete(roomId)
                    // 如果是当前主播,需要刷新
                    if (that.getRoomIdByUrl(local_url) == roomId) {
                        window.location.reload()
                    }
                    removeDOM(tr, true)
                })

            })
        }




        /**
         * 绘制表格
         * @param {Object} arr 表格数据
         */
        resetTbody(arr) {
            if (!this.tbody) {
                return;
            }
            querySelectorAll(this.tbody, 'tr').forEach(item => removeDOM(item, true))
            this.createRoomItem(arr)
        }


        /**
         * 操作框容器
         */
        operationDOMButton() {
            let that = this
            if (!that.m_container) {
                return;
            }
            const container = that.m_container
            const inputValue = querySelector(container, '.m-container .operation input')
            addEventListener(inputValue, 'input', () => {
                let arr = that.users.filter(item => (item.roomId && item.roomId.indexOf(inputValue.value) != -1) || (item.name && item.name.indexOf(inputValue.value) != -1))
                that.resetTbody(arr)
            })

            // 添加
            const addRoomBtn = querySelector(container, '.m-container .operation  button.add-room')
            addEventListener(addRoomBtn, 'click', function () {
                const keywords = inputValue.value.trim()
                if (!keywords) {
                    return alert('请输入房间号!')
                }
                if (!that.userIsExist(keywords)) {
                    const name = that.getNameByRoomId(keywords)
                    if (name) {
                        that.addUser(keywords, name)
                        inputValue.value = ''
                    } else {
                        if (confirm(`房间号为${keywords}的主播不存在!确定添加?`)) {
                            that.addUser(keywords, keywords)
                            inputValue.value = ''
                        }
                    }

                } else {
                    alert('该主播已添加!')
                }
            })



            // 清空
            const clearRoomBtn = querySelector(container, '.m-container button.clear-room')
            addEventListener(clearRoomBtn, 'click', function () {
                if (confirm('确认重置?')) {
                    that.users = []
                    for (let item of [that.key, that.bg_key, that.menu_show_key, that.gift_key, that.logo_show_key, that.full_screen_key]) {
                        wls.removeItem(item)
                    }
                    that.resetTbody(that.users)
                    window.location.reload()
                }
            })
            // 文件上传
            const uploadButton = querySelector(container, '.m-container #file')
            addEventListener(uploadButton, 'change', function (e) {
                const file = uploadButton.files[0] || null
                // 图片格式校验
                if (!isImage(file?.name)) {
                    return alert("图片格式不正确!")
                }

                try {
                    let fileReader = new FileReader()
                    // 转码
                    fileReader.readAsDataURL(file)
                    fileReader.onerror = (e) => {
                        return alert('图片解析失败!' + JSON.stringify(e))
                    }
                    fileReader.onload = (e) => {
                        let base64 = e.target.result
                        let str = base64.slice(base64.indexOf(",") + 1);
                        if (atob) {
                            str = atob(str);
                            let bytes = str.length;
                            var size = (bytes / (1024 * 1024)).toFixed(2);
                            if (size > 5) {
                                if (confirm('图片保存失败,浏览器最大只能保存5MB大小图片,确认查看原因?')) {
                                    window.location.href = 'https://developer.mozilla.org/zh-CN/docs/Web/API/File_and_Directory_Entries_API/Introduction'
                                }
                                return;
                            }
                            // 保存到本地
                            addLocalStore(that.bg_key, base64, String.name, false)
                            that.settingBackgroundImage(e.target.result)

                        } else {
                            alert('保存失败,当前浏览器不支持!')
                        }

                    }

                } catch (e) {
                    alert('图片解析失败!')
                }

            })

            // 文件上传
            const upload = querySelector(container, '.m-container .bg-btn')
            addEventListener(upload, 'click', function (e) {
                uploadButton.click()
            })

            // 显示关闭
            const close_container = querySelector(container, '.m-container .btn-close-container')
            addEventListener(close_container, 'click', function (e) {
                that.isShowContainer()
            })
            // 选择背景
            const checkbox = querySelector(container, '.m-container #checkbox1')
            addEventListener(checkbox, 'change', function (e) {
                addLocalStore(that.bg_show_key, e.target.checked, Boolean.name)
                that.settingBackgroundImage()
            })
            // 是否关闭菜单
            const menu = querySelector(container, '.m-container #checkbox2')
            addEventListener(menu, 'change', function (e) {
                that.getLeftMenu(e.target.checked)
            })

            // 剧场模式
            const full_screen_btn = querySelector(container, '.m-container #checkbox3')
            addEventListener(full_screen_btn, 'change', function (e) {
                addLocalStore(that.full_screen_key, e.target.checked, Boolean.name)
            })

            // 礼物模式
            const show_gitf = querySelector(container, '.m-container #checkbox4')
            addEventListener(show_gitf, 'change', function (e) {
                addLocalStore(that.gift_key, e.target.checked, Boolean.name)
                that.isShowGift()
            })

            const show_logo_btn = querySelector(container, '.m-container #checkbox5')
            addEventListener(show_logo_btn, 'change', function (e) {
                e.preventDefault()
                if (!that.logo_btn) {
                    return alert('获取不到logo');
                }
                if (that.logo_btn.style.display === 'block') {
                    if (confirm('确认隐藏Logo?隐藏之后不再显示哦!如需显示logo,点击直播头部Logo即可显示')) {
                        that.logo_btn.style.display = 'none'
                        addLocalStore(that.logo_show_key, false, Boolean.name)
                    }
                } else {
                    that.logo_btn.style.display = 'block'
                    addLocalStore(that.logo_show_key, true, Boolean.name)
                }

            })
        }

        /**
         * 右侧操作按钮
         * @param text 指定按钮文本,默认是小虎牙或者是小鱼丸
         */
        createButton() {
            let that = this
            if (!!that.logo_btn) {
                return;
            }

            let text = this.buttonName
            let backgroundColor = is_bilibili ? '255,102,102' : '255, 93, 35'

            const btn = createElement('button')
            btn.style.cursor = 'pointer'
            btn.style.position = 'fixed'
            btn.style.top = '300px'
            btn.style.right = '0px'
            btn.style.padding = '5px 10px'
            btn.style.backgroundColor = `rgb(${backgroundColor})`
            btn.style.border = 'none'
            btn.style.outline = 'none'
            btn.style.borderRadius = '20px'
            btn.style.fontSize = '12px'
            btn.style.color = '#fff'
            btn.style.zIndex = 999999999999
            btn.textContent = text ? text : (is_huya ? '小虎牙' : (is_douyu ? '小鱼丸' : is_bilibili ? '小B' : '默认'))
            addEventListener(btn, 'click', function () {
                that.isShowContainer()
            })
            addEventListener(btn, 'mouseenter', function () {
                btn.style.backgroundColor = `rgba(${backgroundColor},0.6)`
            })
            //添加拖拽事件
            let flag = false
            let x, y
            const mouse_key = that.key + "_mouse_key"
            // 从浏览器本地中获取位置信息
            let { mouse_left, mouse_top } = getLocalStore(mouse_key, Object.name)
            if (mouse_left || mouse_top) {
                btn.style.left = mouse_left + 'px'
                btn.style.top = mouse_top + 'px'
                btn.style.right = 'auto'
            }
            addEventListener(btn, 'mousedown', (event) => {
                // 鼠标距离顶部距离
                x = event.offsetX
                y = event.offsetY
                flag = true
                addEventListener(wd, 'mousemove', move)
            })

            addEventListener(btn, 'mouseup', () => {
                flag = false
                wd.onmousemove = null
            })

            addEventListener(btn, 'mouseleave', () => {
                flag = false
                btn.style.backgroundColor = `rgba(${backgroundColor},1)`
                wd.onmousemove = null
            })
            function move(e) {
                e.preventDefault()
                if (!flag) {
                    return
                }
                // 计算位置信息
                let btn_top = Math.min(Math.max(0, e.clientY - y), window.innerHeight - btn.offsetHeight)
                let btn_left = Math.min(Math.max(0, e.clientX - x), window.innerWidth - btn.offsetWidth)

                btn.style.left = btn_left + 'px'
                btn.style.top = btn_top + 'px'
                btn.style.right = 'auto'
                addLocalStore(mouse_key, { 'mouse_left': btn_left, 'mouse_top': btn_top }, Object.name)

            }
            btn.style.display = is_bilibili || getLocalStore(that.logo_show_key, Boolean.name) ? 'block' : 'none'
            that.logo_btn = btn
            appendChild(that.body, that.logo_btn)
        }

        /**
         * 该房间是否已改被删除
         * @param url 房间链接地址 默认 window.location.href
         */
        removeRoom(url = local_url) {
            if (!this.isRemove(url)) {
                return false
            }
            this.roomIsNeedRemove();
            return true
        }

        /**
         * 房间已被删除之后操作
         * @param url 房间链接地址 默认 window.location.href
         */
        roomAlreadyRemove() {
            let that = this
            removeDOM(this.body, true)
            // removeDOM(this.body, true)
            this.body = null; //必须设置为空!否则无法设置新的button
            const h2 = createElement('h3')
            let html = querySelector('html')
            let body = querySelector('body')
            if (!body) { // 如果原来的删除了,从新创建一个body存放内容
                body = createElement('body')
            }
            body.style.display = 'flex'
            body.style.flexDirection = 'column'
            body.style.justifyContent = 'center'
            body.style.alignItems = 'center'
            // 获取主播名称
            let name = this.getUser(this.getRoomIdByUrl(local_url)) ? this.getUser(this.getRoomIdByUrl(
                local_url)).name : ''
            const a = createElement('a')
            a.textContent = '点击解锁'
            a.style.display = 'block'
            a.style.cursor = 'pointer'
            a.style.fontSize = '20px'
            a.onclick = (e) => {
                e.preventDefault()
                that.userDelete(that.getRoomIdByUrl(local_url))
                window.location.reload()
            }
            h2.style.fontSize = '36px'
            h2.textContent = `主播【${name}】已被你屏蔽`
            let title = querySelector('title')
            if (!title) {
                title = createElement('title')
            }
            title.textContent = `主播【${name}】已被你屏蔽`
            html.appendChild(body)
            body.appendChild(h2)
            body.appendChild(a)
            let logo_show = getLocalStore(that.logo_show_key, Boolean.name)
            if (logo_show) {
                let logo = createElement('a')
                logo.textContent = '显示logo'
                logo.style.display = 'block'
                logo.style.cursor = 'pointer'
                logo.style.fontSize = '20px'
                logo.onclick = (e) => {
                    e.preventDefault()
                    logo.style.display = 'none'
                    addLocalStore(that.logo_show_key, false, Boolean.name)
                    that.createButton()
                }
                body.appendChild(logo)
            }
            removeDOM(this.m_container, true)
            this.m_container = null
            // 创建操作面板
            this.create_container()
        }

        /**
         * 判断链接是否应该被删除
         * @param href 房间链接地址 默认 window.location.href
         */
        isRemove(href) {
            return this.userIsExist(this.getRoomIdByUrl(href));
        }


        /**
         * 设置背景图
         * @param url 背景图地址 默认 是默认地址
         */
        settingBackgroundImage(url) {
            if (!this.body) {
                return;
            }
            if (getLocalStore(this.bg_show_key, Boolean.name)) {
                url = !!url ? url : (wls.getItem(this.bg_key) ? wls.getItem(this.bg_key) : this.defaultBackgroundImage)
                this.body.style.backgroundSize = "cover"
                this.body.style.backgroundRepeat = 'no-repeat '
                this.body.style.backgroundAttachment = 'fixed'
                this.body.style.backgroundImage = `url(${url})`
            } else {
                this.body.style.backgroundImage = 'none'
            }

        }


        /**
         * 通过房间名称或者id判断房间是否已经保存到本地
         * @param keywords 房间名或者id
         * @param list 本地缓存数据,默认是本地缓存用户数据
         */
        userIsExist(keywords, list = this.users) {
            return this.getUser(keywords, list) ? true : false
        }


        /**
         * 通过房间名称或者id判断房间是否已经保存到本地
         * @param keywords 房间名或者id
         * @param list 本地缓存数据,默认是本地缓存用户数据
         */
        getUser(keywords, list = this.users) {
            if (!keywords) {
                return null
            }
            for (let i = 0; i < list.length; i++) {
                if ((list[i].name && list[i].name == keywords) || (list[i].roomId && list[i].roomId == keywords)) {
                    return list[i]
                }
            }
            return null
        }



        /**
         * 通过房间id或者房间名删除本地缓存的数据
         * @param keywords 房间名或者id
         */
        userDelete(keywords) {
            let that = this
            if (!isArray(that.users)) {
                return;
            }
            that.users.forEach((item, index) => {
                if (keywords == item.name || keywords == item.roomId) {
                    that.users.splice(index, 1)
                }
            })
            addLocalStore(this.key, this.users)
        }


        /**
         * 添加并保存直播间
         * @param id, 房间id
         * @param name 房间名
         */
        addUser(id, name) {
            if (this.userIsExist(id) || this.userIsExist(name)) {
                alert('该房间已存在!')
                return;
            }
            if (!isArray(this.users)) {
                this.users = []
            }
            const newUser = new HostUser(id, name);
            // 添加
            this.users.unshift(newUser)
            // 保存到本地
            addLocalStore(this.key, this.users)
            this.resetTbody(this.users)
            // 如果是当前主播需要屏蔽
            if (id == this.getRoomIdByUrl(local_url)) {
                this.roomIsNeedRemove(local_url);
            }

        }

        /**
         * @param {selector}  = [选择器]
         * @param {selector}  = [是否真的删除,默认删除而不是display = 'none']
         */
        roomIsNeedRemove(selector = querySelector('video')) {
            this.roomAlreadyRemove()
            removeVideo(selector)
            this.settingBackgroundImage()
        }

        /*
         * 操作左侧导航栏,需要传入选择器,和修改值 建议放到公共方法下执行!
         * @param {selector}  = [选择器]
         * @param {value}  = [要修改的值]
         */
        getLeftMenu(value = false) {
            if (!this.menu) {
                return alert('获取不到导航菜单,操作失败!')
            }
            addLocalStore(this.menu_show_key, value, Boolean.name, false)
            this.menu.style.display = value ? 'block' : 'none'
        }

        /*
         * 操作左侧导航栏,需要传入选择器,和修改值 建议放到公共方法下执行!
         */
        isShowLeftMenu() {
            if (this.menu) {
                this.menu.style.display = getLocalStore(this.menu_show_key, Boolean.name, false) ? 'block' : 'none'
            }
        }

        /**
         * 是否显示礼物
         */
        isShowGift() {
            if (this.giftTool) {
                this.giftTool.style.display = getLocalStore(this.gift_key, Boolean.name) ? 'inline-block' : 'none'
            }
        }

        /**
         * 是否显示容器
         */
        isShowContainer() {
            if (this.m_container) {
                this.m_container.style.display = this.m_container.style.display == 'block' ? 'none' : 'block'
            }
        }

        /**
         *  点击 直播平台 Logo 显示 操作面板
         */
        clickLogoShowContainer() {
            if (this.header_logo === 'none') {
                return
            }
            let that = this
            timeoutSelector(that.header_logo, (a) => {
                a.href = 'javascript:;void(0)';
                a.title = '点击Logo,显示插件配置'
                addEventListener(a, 'click', (e) => {
                    that.isShowContainer()
                })
            }, 5000)
        }


        /**
         * 创建一个占位符显示可以操作按钮
         * @param {*} container 房间容器ID
         * @param {*} place 需要添加文本地方
         * @param {*} id 房间号ID
         * @param {*} name 房间名或者up名
         * @returns
         */
        createSpan(container, place, id, name = id, message = '确认屏蔽up主 ', remove = true) {
            if (!container || !place || !id || !name) {
                return;
            }
            const span = createElement('span')
            span.classList = 'm-span-text'
            appendChild(place, span)
            addEventListener(span, 'click', () => {
                if (confirm(message + name + ' ?')) {
                    if (remove) {
                        removeDOM(container, true)
                    }
                    this.addUser(id, name)
                    this.removeRoom(local_url)
                }
            })
        }

    }

    /**
     * 虎牙直播插件
     */
    class TriggerLive extends LivePlugin {
        constructor() {
            super()
            this.key = 'huyazhibo'
            this.bg_key = 'huyazhibo_bg'
            this.bg_show_key = 'huyazhibo_bg_show'
            this.menu_show_key = 'huyazhibo_menu_show_key'
            this.full_screen_key = 'huyazhibo_full_screen_key'
            this.defaultBackgroundImage = 'https://livewebbs2.msstatic.com/huya_1682329462_content.jpg'
            this.baseUrl = "https://www.huya.com/"
            this.users = getLocalStore(this.key, Array.name, true)
            this.html = querySelector('html')
            this.body = querySelector('body')
            this.menu = querySelector('.mod-sidebar')
            this.header_logo = '#duya-header #duya-header-logo a'
            this.giftTool = querySelector('.room-core .player-gift-wrap')
            this.tbody = null
            this.m_container = null
            this.init()
        }



        // 首页操作
        index() {
            // 直播源
            const url = local_url
            if (url == this.baseUrl) {
                // 操作视频
                removeVideo('.mod-index-main video')
                // 触发点击关闭广告
                const banner_close = querySelector('.mod-index-wrap #banner i')
                if (banner_close) {
                    banner_close.click();
                }
                let count = 0;
                // 暂停播放 防止后续加载出现
                let timer1 = setInterval(() => {
                    let pauseBtn = querySelector('.player-pause-btn')
                    if (pauseBtn) {
                        pauseBtn.click()
                    }
                    if (count >= 10) {
                        clearInterval(timer1)
                    }
                    count = count + 1
                }, 300)

            }

        }
        // 分类页操作
        category() {
            if (new RegExp(/^https:\/\/.*\.huya\.((com)|(cn))\/g(\/.*)$/).test(local_url)) {
                let that = this
                const dd = querySelectorAll('.live-list-nav dd')
                if (isArray(dd)) {
                    for (let d of dd) {
                        addEventListener(d, 'click', () => {
                            setTimeout(() => {
                                that.removeRoomByClickRoomName()
                            }, 2000)
                        })

                    }
                }
            }
        }
        // 公共部分操作
        common() {
            // window.onscroll = throttle(500, () => {
            //     this.removeRoomByClickRoomName()
            // })
            this.removeRoomByClickRoomName()
            this.clickLogoShowContainer()
        }
        // 详情操作
        detail() {
            if (new RegExp(/^https:\/\/www\.huya\.com(\/\w+)$/).test(local_url)) {
                let that = this
                // 点击直播间移除直播间操作
                const hostName = querySelector('.host-name')
                hostName.title = `点击屏蔽主播【${hostName.textContent}】`
                addEventListener(hostName, 'click', () => {
                    if (confirm(`确认屏蔽主播 ${hostName.textContent}?`)) {
                        that.addUser(that.getRoomIdByUrl(local_url), hostName.textContent)
                    }
                })

                // 自动剧场模式
                let fullpageBtn = querySelector('#player-fullpage-btn')
                let show3 = getLocalStore(that.full_screen_key, Boolean.name)
                if (fullpageBtn && show3) {
                    setTimeout(() => { fullpageBtn.click() }, 2000)
                }

                let ads = [
                    '.main-wrap .room-mod-ggTop',
                    '#chatRoom .room-gg-chat',
                    '#huya-ab'
                ]

                // 对于恶意广告要彻底清空
                intervalRemoveElement(ads, 500, 20)


                // TODO 特效设置暂时未开启!
            }
        }
        // 通过地址获取房间号
        getRoomIdByUrl = (url = local_url) => {
            let arr = url.split('/')
            let roomId = arr[arr.length - 1]
            return roomId
        }

        // 通过房间号查找名称
        getNameByRoomId(roomId) {
            let that = this
            let hostName = querySelector('.host-name')
            if (!hostName) {
                return ''
            }
            const rooms = querySelectorAll('.game-live-item')
            if (!isArray(rooms)) {
                return ''
            }
            for (let room of rooms) {
                const a = querySelector(room, 'a')
                if (a && a.href) {
                    const id = that.getRoomIdByUrl(a.href)
                    const user = querySelector(room, '.txt i')
                    if (id === roomId) {
                        hostName = user
                    }
                }

            }
            return hostName?.textContent || ''
        }

        // 通过点击直播间名称删除直播间
        removeRoomByClickRoomName() {
            const that = this
            timeoutSelectorAll('.game-live-item', (rooms) => {
                for (let li of rooms) {
                    let isMark = li.getAttribute('mark')
                    if (!isMark) {
                        li.setAttribute('mark', true)
                        const a = querySelector(li, 'a')
                        const url = a.href
                        const user = querySelector(li, '.txt i')
                        const name = user.textContent || ''
                        addEventListener(user, 'click', () => {
                            if (confirm(`确认禁用 ${name}?`)) {
                                that.addUser(that.getRoomIdByUrl(url), name);
                                removeDOM(li);
                            }
                        })
                        if (that.isRemove(url)) {
                            removeDOM(li)
                        }
                    }

                }

            }, 500)

        }

    }

    /**
     * 斗鱼直播插件
     */
    class FishLive extends LivePlugin {
        constructor() {
            super()
            this.key = 'douyuzhibo'
            this.bg_key = 'douyuzhibo_bg'
            this.bg_show_key = 'douyuzhibo_show'
            this.menu_show_key = 'douyuzhibo_menu_show_key'
            this.full_screen_key = 'douyuzhibo_full_screen_key'
            this.baseUrl = "https://www.douyu.com/"
            this.defaultBackgroundImage = 'https://sta-op.douyucdn.cn/dylamr/2022/11/07/1e10382d9a430b4a04245e5427e892c8.jpg'
            this.users = getLocalStore(this.key, Array.name, true)
            this.html = querySelector('html')
            this.body = querySelector('body')
            this.menu = querySelector('#js-aside')
            this.giftTool = querySelector('.layout-Player-main #js-player-toolbar')
            this.header_logo = '#js-header .Header-left .Header-logo'
            this.tbody = null
            this.m_container = null
            setTimeout(() => {
                this.init()
            }, 500)
        }
        // 公共部分页面操作
        common() {
            this.clickLogoShowContainer()
        }
        //首页操作
        index() {
            let that = this
            // 直播源
            if (window.location.href == that.baseUrl || new RegExp(/https:\/\/www\.douyu\.com\/\?.*/).test(local_url)) {
                window.scroll(0, 0)
                // 移除直播
                removeVideo('.layout-Slide-player video')
                // 获取暂停button
                const vbox = querySelector('#room-html5-player');
                if (vbox) {
                    const divs = querySelectorAll(vbox, 'div')
                    if (isArray(divs)) {
                        for (let div of divs) {
                            if (div?.title == '暂停') {
                                div.click()
                            }
                        }
                    }
                }
                that.removeRoomByClickRoomName()
                // 斗鱼直播使用节流方式加载,只有鼠标下滑,下方直播间才会加载,首次加载不会加载所有页面直播间列表
                // 因此,添加滚动事件来添加
                // 另外防止二次或者多次添加点击事件,将之前保存到init_users中来记录是否该添加
                window.onscroll = throttle(500, () => {
                    that.removeRoomByClickRoomName()
                })

                // btn
                let topBtn = querySelector('.layout-Main .ToTopBtn')
                if (topBtn) {
                    topBtn.style.display = 'block'
                }
            }
        }
        // 分类页面操作
        category() {
            let that = this
            // 匹配分类页
            if (new RegExp(/https:\/\/www.douyu.com(\/((directory.*)|(g_.*)))$/).test(local_url)) {
                that.removeRoomByClickRoomName()
                const labels = querySelectorAll('.layout-Module-filter .layout-Module-label')
                if (isArray(labels)) {
                    for (let label of labels) {
                        addEventListener(label, 'click', (e) => {
                            e.preventDefault()
                            // 获取当前地址
                            let to_link = label && label.href ? label.href : null
                            if (to_link) {
                                window.location.href = to_link
                            } else {
                                // 获取全部地址
                                var result = 'https://www.douyu.com/g_' + local_url.match(RegExp(
                                    /subCate\/.*/g))[0].replace('subCate', '').match(new RegExp(
                                        /\w+/g))[0]
                                window.location.href = result
                            }

                        })

                    }
                }


            }


        }


        // 详情页操作
        detail() {
            let that = this
            // 匹配只有在播放直播间才会生效
            if (!new RegExp(/.*douyu.*(\/((.*rid=\d+)|(\d+)).*)$/).test(local_url)) {
                return;
            }
            setTimeout(() => {
                // 点击主播直播间名称进行操作
                const hostName = querySelector('.Title-roomInfo h2.Title-anchorNameH2')
                hostName.title = `点击屏蔽主播【${hostName?.textContent}】`
                addEventListener(hostName, 'click', () => {
                    if (confirm(`确认屏蔽主播【${hostName?.textContent}】?`)) {
                        that.addUser(that.getRoomIdByUrl(local_url), hostName.textContent)
                    }
                })
            }, 4000)

            // 带有轮播图
            if (new RegExp(/.*douyu.*\/topic(\/(.*rid=\d+).*)/).test(local_url)) {
                log('直播间带有录播图 ……')
                let divs = querySelectorAll('#root>div')
                let backgroundNones = ['.wm-general-wrapper.bc-wrapper.bc-wrapper-player', '.wm-general-bgblur']
                if (isArray(divs)) {
                    for (let element of divs) {
                        if (hasVideo(element, '.layout-Main')) {
                            backgroundNone(element, backgroundNones)
                        } else {
                            removeDOM(element, true)
                        }

                    }
                }


            }
            // 不带有轮播图
            if (new RegExp(/.*douyu.*(\/(\d+)).*/).test(local_url)) {
                log('直播间不带有录播图 ……')
                let count = 20;
                let timer = setInterval(() => {
                    const closeBtn = querySelector('.roomSmallPlayerFloatLayout-closeBtn')
                    if (closeBtn) {
                        closeBtn.click()
                    }
                    count = count - 1
                    if (count == 0) {
                        clearInterval(timer)
                    }

                }, 200)


                // 对于恶意广告要彻底清除!!!
                let ads = [
                    "#player-above-controller+div"
                ]
                //intervalRemoveElement(ads, 500, 20)
                removeDOM('.layout-Main .ToTopBtn', true)

            }
        }
        // 通过点击直播间名称删除直播间
        removeRoomByClickRoomName() {
            let that = this

            // 首页
            if (this.baseUrl == local_url || new RegExp(/https:\/\/www\.douyu\.com\/\?.*/).test(local_url)) {
                timeoutSelectorAll('.layout-List-item', (rooms) => {
                    for (let li of rooms) {
                        setTimeout(() => {
                            let isMark = li.getAttribute('mark')
                            if (!isMark) {
                                try {
                                    li.setAttribute('mark', true);
                                    const a = querySelector(li, '.DyCover')
                                    const url = a.href || ''
                                    const user = querySelector(li, '.DyCover-user')
                                    const name = user?.textContent || ''
                                    a.setAttribute('href', 'javascript:;void(0)');
                                    addEventListener(user, 'click', (e) => {
                                        if (confirm(`确认禁用 ${name}`)) {
                                            that.addUser(that.getRoomIdByUrl(url), name);
                                            removeDOM(li);
                                        }
                                    })
                                    // 是否应该删除
                                    if (that.isRemove(url) || that.userIsExist(name)) {
                                        removeDOM(li)
                                    }
                                } catch (e) { }
                            }

                        }, 100)



                    }
                }, 100)
            }

            if (new RegExp(/https:\/\/www.douyu.com(\/((directory)|(g_)).*)/).test(local_url)) {
                timeoutSelectorAll('.layout-Cover-item', (rooms) => {
                    for (let li of rooms) {
                        try {
                            let isMark = li.getAttribute('mark')
                            if (!isMark) {
                                li.setAttribute('mark', true);
                                const link = querySelector(li, 'a.DyListCover-wrap')
                                if (link) {
                                    const url = link?.href || ''
                                    link.setAttribute('href', 'javascript:;void(0)');
                                    const user = querySelector(link, 'div.DyListCover-userName')
                                    const name = user.textContent || ''
                                    if (that.isRemove(url) || that.userIsExist(name)) {
                                        removeDOM(li, true)
                                    } else {
                                        addEventListener(user, 'click', (e) => {
                                            if (confirm(`确认禁用 ${name}?`)) {
                                                const id = that.getRoomIdByUrl(url);
                                                that.addUser(id, name);
                                                removeDOM(li);
                                            }
                                            e.preventDefault()
                                        })
                                        // 监听鼠标移入事件
                                        addEventListener(li, 'mouseenter', (e) => {
                                            const a = querySelector(e.target, 'a.DyListCover-wrap.is-hover')
                                            const url = a.href
                                            a.setAttribute('href', 'javascript:;void(0)');
                                            const user = querySelector(a, '.DyListCover-userName')
                                            const name = user.textContent || ''
                                            addEventListener(user, 'click', (a) => {
                                                if (confirm(`确认禁用 ${name}?`)) {
                                                    const id = that.getRoomIdByUrl(url);
                                                    that.addUser(id, name);
                                                    removeDOM(li);
                                                }
                                            })

                                        })
                                    }
                                }


                            }

                        } catch (e) { }
                    }
                }, 0)

            }

        }


        // 通过房间号获取直播间name
        getNameByRoomId(keywords) {
            let that = this
            // 从详情页获取
            let hostName = querySelector('.Title-blockInline .Title-anchorName h2')
            let rooms = null;
            if (!hostName) {
                rooms = querySelectorAll('.layout-List-item')
                // index
                if (isArray(rooms)) {
                    for (let room of rooms) {
                        const id = that.getRoomIdByUrl(querySelector(room, 'a').href)
                        const user = querySelector(room, '.DyCover-user')
                        if (id == keywords) {
                            hostName = user
                        }
                    }
                }
                // 如果还是获取不到从分类页面获取
                if (!hostName) {
                    rooms = querySelectorAll('.layout-Cover-item')
                    if (isArray(rooms)) {
                        for (let room of rooms) {
                            const id = that.getRoomIdByUrl(querySelector(room, 'a').href)
                            const user = querySelector(room, '.DyListCover-userName')
                            if (id == keywords) {
                                hostName = user
                            }
                        }
                    }
                }


            }
            return hostName?.textContent || ''
        }


        // 通过房间地址获取房间号
        getRoomIdByUrl(url = local_url) {
            try {
                if (new RegExp(/.*rid=(\d+).*/).test(url)) {
                    return url.match(new RegExp(/rid=(\d+)/))[1]
                }
                if (/https:\/\/www\.douyu\.com\/(\d+).*/.test(url)) {
                    return url.match(new RegExp(/https:\/\/www\.douyu\.com\/(\d+)/))[1]
                }
                return null

            } catch (e) {
                return null
            }
        }



    }


    /**
     * bilibili插件 阿B还是良心 广告很少 首页有一些原神和不想看到的直播间播放详细页有一个小姐姐 ……
     */
    class Bilibili extends LivePlugin {

        constructor() {
            super()
            this.init()
        }

        /**
         * 重写 button
         * @returns
         */
        createButton() {
            let that = this
            if (!!that.logo_btn) {
                return;
            }
            let buttonBoxs = querySelector('.palette-button-wrap .storage-box .storable-items')
            let btn = createElement('button')
            btn.className = 'primary-btn'
            btn.style.fontSize = '16px'

            if (!buttonBoxs) {
                buttonBoxs = querySelector('div.fixed-sidenav-storage')
                if (!buttonBoxs) {
                    console.log('暂不支持...');
                    return;
                }
                btn = createElement('div')
                btn.style.display = 'none'
                btn.className = 'm-bilibili-btn'
                window.onscroll = () => {
                    if (window.scrollY >= 500) {
                        btn.style.display = 'block'
                    } else {
                        btn.style.display = 'none'
                    }
                }
            }

            btn.title = '点击显示'
            btn.innerHTML = `<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2753" width="24" height="24"><path d="M306.005333 117.632L444.330667 256h135.296l138.368-138.325333a42.666667 42.666667 0 0 1 60.373333 60.373333L700.330667 256H789.333333A149.333333 149.333333 0 0 1 938.666667 405.333333v341.333334a149.333333 149.333333 0 0 1-149.333334 149.333333h-554.666666A149.333333 149.333333 0 0 1 85.333333 746.666667v-341.333334A149.333333 149.333333 0 0 1 234.666667 256h88.96L245.632 177.962667a42.666667 42.666667 0 0 1 60.373333-60.373334zM789.333333 341.333333h-554.666666a64 64 0 0 0-63.701334 57.856L170.666667 405.333333v341.333334a64 64 0 0 0 57.856 63.701333L234.666667 810.666667h554.666666a64 64 0 0 0 63.701334-57.856L853.333333 746.666667v-341.333334A64 64 0 0 0 789.333333 341.333333zM341.333333 469.333333a42.666667 42.666667 0 0 1 42.666667 42.666667v85.333333a42.666667 42.666667 0 0 1-85.333333 0v-85.333333a42.666667 42.666667 0 0 1 42.666666-42.666667z m341.333334 0a42.666667 42.666667 0 0 1 42.666666 42.666667v85.333333a42.666667 42.666667 0 0 1-85.333333 0v-85.333333a42.666667 42.666667 0 0 1 42.666667-42.666667z" p-id="2754" fill="currentColor"></path></svg>`


            that.logo_btn = btn
            addEventListener(btn, 'click', function () {
                that.isShowContainer()
            })

            insertChild(buttonBoxs, that.logo_btn)


        }




        getRoomIdByUrl(href) {
            try {
                if (/https:\/\/www.bilibili.com\/video\/.*/.test(href)) {
                    let url = querySelector('.up-info-container .up-info--left .up-avatar-wrap>a').href
                    return this.getBilibiliRoomId(url)
                }
                if (/https:\/\/space\.bilibili\.com\/(\d+).*/.test(href)) {
                    return href.match(/https:\/\/space\.bilibili\.com\/(\d+)/)[1]
                }
            } catch (error) {

            }
            return this.getBilibiliRoomId(href)
        }

        getBilibiliRoomId(href) {
            return !!href && href.replace(/https:\/\/.*\.bilibili.com\/(.*?)/, '$1').replace(/\//ig, '')
        }


        // 添加删除按钮
        addDeleteRoomButton(time = 1000) {
            timeoutSelectorAll('.feed-card', (divs) => {
                for (let feed of divs) {
                    const isMark = !!querySelector(feed, '.m-span-text')
                    if (!isMark) {
                        let item = querySelector(feed, 'div.bili-video-card__info--bottom')
                        const name = querySelector(item, 'span.bili-video-card__info--author')?.textContent
                        const href = querySelector(item, '.bili-video-card__info--owner')?.href
                        const id = this.getBilibiliRoomId(href)
                        if (this.userIsExist(id) || this.userIsExist(name)) {
                            removeDOM(feed, true)
                        } else if (id && name) {
                            this.createSpan(feed, item, id, name)
                        }
                    }

                }

            }, time)

            window.onscroll = throttle(500, () => {
                timeoutSelectorAll('.bili-video-card', (divs) => {
                    for (let feed of divs) {
                        const isMark = !!querySelector(feed, '.m-span-text')
                        if (!isMark) {
                            let item = querySelector(feed, 'div.bili-video-card__info--bottom')
                            let isLive = false;
                            if (!item) {
                                isLive = true;
                                item = querySelector(feed, '.bili-live-card__info--text')
                            }

                            const name = !isLive ? querySelector(item, 'span.bili-video-card__info--author')?.textContent : querySelector(item, 'a.bili-live-card__info--uname span')?.textContent
                            const href = !isLive ? querySelector(item, '.bili-video-card__info--owner')?.href : querySelector(item, 'a.bili-live-card__info--uname')?.href
                            const id = this.getBilibiliRoomId(href)

                            if (this.userIsExist(name) || this.userIsExist(id)) {
                                removeDOM(feed, true)
                            } else if (id && name) {
                                this.createSpan(feed, item, id, name)
                            }
                        }

                    }

                }, time)
            })

        }



        common() {
            let that = this
            that.addDeleteRoomButton(1000)

            // 切换时候需要重新执行
            setTimeout(() => {
                const refreshButton = querySelector('.feed-roll-btn .primary-btn')
                addEventListener(refreshButton, 'click', () => {
                    that.addDeleteRoomButton(200)
                })
            }, 3000)




            //
        }

        index() {
            // TODO index
        }

        detail() {

            if (/https:\/\/www.bilibili.com\/video\/.*/.test(local_url) && false) { // 该功能暂时有bug 不需要 直接 false吧
                const userContainer = querySelector('.right-container-inner .up-info-container')
                const place = querySelector(userContainer, '.up-detail-top')
                const link = querySelector(userContainer, '.up-detail-top>a')
                const name = link.textContent
                const id = this.getRoomIdByUrl(link.href)
                const span = createElement('span')
                span.classList = 'm-span-text'
                appendChild(place, span)
                addEventListener(span, 'click', () => {
                    if (confirm('确认屏蔽up主' + name + ' ?')) {
                        this.addUser(id, name)
                    }
                })
            }
            // TODO more

        }


        detailLeftVideoList(time = 1000, sel = '.video-page-card-small') {

            timeoutSelectorAll(sel, (videoList) => {
                for (let v of videoList) {
                    const isMark = !!v.getAttribute('mark')
                    // 添加标记 下次不用添加了
                    v.setAttribute('mark', true)
                    const playinfo = querySelector(v, '.playinfo')
                    const link = querySelector(v, '.upname a')
                    const id = !!link && link?.href && this.getBilibiliRoomId(link.href)
                    const name = querySelector(v, '.upname .name')?.textContent
                    if (this.userIsExist(id) || this.userIsExist(name)) {
                        removeDOM(v, true)
                    } else if (!isMark && id && name) {
                        const span = createElement('span')
                        span.classList = 'm-span-text'
                        addEventListener(span, 'click', () => {
                            if (confirm('确认删除up主 ' + name + ' ?')) {
                                removeDOM(v, true)
                                this.addUser(id, name)
                                // 遍历一遍 删除所有相关视频
                                this.detailLeftVideoList(0)
                            }
                        })
                        appendChild(playinfo, span)

                    }


                }
            }, time)



        }


        // video 播放详情页
        detail() {
            // 播放详情页
            if (new RegExp(/https:\/\/www\.bilibili\.com\/video\/(.*)/).test(local_url)) {
                this.detailLeftVideoList(100, '.video-page-operator-card-small')
                this.detailLeftVideoList()
                const nextBtn = querySelector('.rec-footer')
                addEventListener(nextBtn, 'click', () => {
                    this.detailLeftVideoList(0)
                })
            }

        }

    }


    // 样式部分
    addStyle(`
    .m-container,
        .m-container .btn,
        .m-container table,
        .m-container table tbody,
        .m-container table thead,
        .m-container table tr {
            margin: 0 !important;
            padding: 0 !important;
            border: none;
            outline: none;
        }

        .m-container {
            box-sizing: border-box !important;
            position: fixed !important;
            display: none;
            flex-direction: column !important;
            width: 650px !important;
            height: 400px !important;
            top: 100px !important;
            left: 50% !important;
            border-radius: 10px !important;
            overflow: hidden !important;
            background-color: #fff !important;
            transform: translateX(-50%) !important;
            z-index: 999999 !important;
            padding: 15px !important;
            transition: display linear 1s !important;
            box-shadow: 20px 20px 10px rgba(0, 0, 0, 0.1),
                -1px -2px 18px rgba(0, 0, 0, 0.1) !important;
        }

        .m-container-box {
            display: flex !important;
            flex-direction: column !important;
            width: 100% !important;
            height: 100% !important;
        }

        .m-container .operation {
            box-sizing: border-box !important;
            height: auto !important;
        }

        .m-container .operation input[type="text"] {
            width: 150px !important;
            box-sizing: border-box !important;
            outline: 1px solid rgba(8, 235, 46, 0.6) !important;
            border: none !important;
            padding: 5px 10px !important;
            border-radius: 20px !important;
        }

        .m-container .operation input[type="text"]:focus {
            outline: 1px solid rgba(8, 235, 46, 1) !important;
        }

        .m-container .operation input[type="checkbox"] {
            display: inline !important;
        }

        .m-container .operation input[type="file"] {
            display: none !important;
        }


        .m-container table {
            position: relative !important;
            box-sizing: border-box !important;
            overflow: hidden !important;
            text-align: left !important;
            flex: 1 !important;
            display: flex !important;
            flex-direction: column !important;
        }

        .m-container table tr {
            margin: 5px 0 !important;
            display: flex !important;
            border-bottom: 1px solid rgba(0, 0, 0, 0.4) !important;
            justify-content: space-between;
        }

        .m-container table tr td:nth-child(1),
        .m-container table thead th:nth-child(1) {
            width: 50px;
            text-align: center !important;
        }

        .m-container table tr td:nth-child(2),
        .m-container table tr td:nth-child(3),
        .m-container table tr td:nth-child(4),
        .m-container table thead th:nth-child(2),
        .m-container table thead th:nth-child(3),
        .m-container table thead th:nth-child(4) {
            flex: 1 !important;
            text-align: center !important;
            white-space: nowrap !important;
            overflow: hidden !important;
            text-overflow: ellipsis !important;

        }
        .m-container table tbody {
            flex: 1 !important;
            overflow: hidden auto  !important;
        }

        .m-container table th,
        .m-container table td {
            padding: 10px !important;
        }

        .m-container table tbody tr:nth-child(1) {
            border-bottom: 1px solid rgba(0, 0, 0, 0.4) !important;
        }

        .m-container .m-link,
        .m-container .m-link:visited {
            color: blnk !important;
        }

        .m-container .m-link:hover {
            color: blue !important;
            text-decoration: underline !important;
        }

        .m-container .btn {
            cursor: pointer !important;
            padding: 5px 8px !important;
            border: none !important;
            color: #fff !important;
            font-size: 1rem !important;
            border-radius: 20px !important;
            max-width: 50px !important;
            margin: 0 0 !important;
            background-color: rgba(166, 169, 173, 1) !important;
            z-index: 1000 !important;
            outline: none !important;
            box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4), 0px 0px 1px rgba(0, 0, 0, 0.4) !important;
        }

        .m-container .btn:hover {
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1) !important;
        }

        .m-container .btn:hover {
            background-color: rgba(166, 169, 173, 0.6) !important;
        }

        .m-container .btn-primary {
            cursor: pointer !important;
            background-color: rgba(64, 158, 255, 1) !important;
        }

        .m-container .btn-primary:hover {
            background-color: rgba(64, 158, 255, 0.6) !important;
        }

        .m-container .btn-success {
            cursor: pointer !important;
            background-color: rgba(103, 194, 58, 1) !important;
        }

        .m-container .btn-success:hover {
            background-color: rgba(103, 194, 58, 0.6) !important;
        }

        .m-container .btn-info {
            cursor: pointer !important;
            background-color: rgba(119, 119, 119, 1) !important;
        }

        .m-container .btn-info:hover {
            background-color: rgba(119, 119, 119, 0.6) !important;
        }

        .m-container .btn-warning {
            cursor: pointer !important;
            background-color: rgba(230, 162, 60, 1) !important;
        }

        .m-container .btn-warning:hover {
            background-color: rgba(230, 162, 60, 0.6) !important;
        }

        .m-container .btn-danger {
            cursor: pointer !important;
            background-color: rgba(245, 108, 108, 1) !important;
        }

        .m-container .btn-danger:hover {
            background-color: rgba(245, 108, 108, 0.6) !important;
        }

        .m-span-text {
            transition: all 0.3s ease;
            cursor: pointer !important;
            opacity: 0;
            float:right;
            display:inline-block;
            margin:0 10px;
            transform: scale(0.5);
            font-size:20px;
            position:relative;
        }

        .m-span-text::before{
            content:"🧹";
            cursor: pointer !important;
        }


       /***************************************************斗鱼直播***************************************************************************/
       .game-live-item i,.host-name {
           cursor:pointer;
       }
       .game-live-item .txt i:hover,.host-name:hover {
           color:rgb(255, 135, 0);
       }
       .layout-List-item .DyCover-content .DyCover-user,.layout-Cover-item .DyListCover-userName,.Title-blockInline .Title-anchorName h2{
           cursor:pointer !important;
       }
       .layout-List-item .DyCover-content .DyCover-user:hover,.layout-Cover-item .DyListCover-userName:hover,.Title-blockInline .Title-anchorName h2:hover {
           color:rgb(255, 135, 0) !important;
        }

       .layout-Section.layout-Slide .layout-Slide-player,
      .layout-Slide-bannerInner,
       #lazyModule3,
       #lazyModule4,
       #lazyModule5,
       #lazyModule6,
       #lazyModule7,
       #lazyModule8,
       #lazyModule23,
       #lazyModule24,
       #js-room-activity,
       #js-right-nav,
       #js-bottom,
       #js-header .Header .HeaderNav,
       #js-header .Header .HeaderGif-left,
       #js-header .Header .HeaderGif-right,
       .Header-download-wrap,
       .AnchorInterToolsUser,
       .RechangeJulyPopups,
       #js-room-activity,
       #js-right-nav,
       #js-bottom,
       li.Header-menu-link,
       .layout-Main .layout-Customize,
       .HeaderCell-label-wrap,
       .Title-AnchorLevel,.RoomVipSysTitle,
       .Aside-nav .Aside-nav-item,
       .Title-roomInfo .Title-row,
       #player-marvel-controller+div,
       .layout-Player-main .GuessGameMiniPanelB-wrapper,
       #js-player-asideMain #layout-Player-aside .FirePower,
       .layout-Player-video .layout-Player-videoAbove .ChargeTask-closeBg,
        #bc4-bgblur,
       .Baby-image.is-achievement,
       .multiBitRate-da4b60{
           display:none !important;
       }


        li.Header-menu-link:nth-child(1),
        li.Header-menu-link:nth-child(2),
        li.Header-menu-link:nth-child(3),
        .Aside-nav .Aside-nav-item:nth-child(1)
       {
           display:inline-block !important;
       }

       .layout-Player-aside .layout-Player-chat,.layout-Player-aside .layout-Player-chat .ChatToolBar {
         display:block !important;
       }


       .Barrage-main  .UserLevel,
       .Barrage-main  .js-user-level,
       .Barrage-main  .Barrage-icon,
       .Barrage-main  .Motor,
       .Barrage-main  .Motor-flag,
       .Barrage-main  .Barrage-hiIcon,
       .Barrage-main  .UserGameDataMedal,
       .Barrage-main  .ChatAchievement,
       .Barrage-main  .Barrage-notice,
       .layout-Player .layout-Player-announce,
       .layout-Player .layout-Player-rank,
       .MatchSystemTeamMedal,
        #js-player-video .ScreenBannerAd,
      .layout-Main #layout-Player-aside .BarrageSuspendedBallAd,
      .layout-Main #layout-Player-aside .SignBarrage,
       #js-player-video-case .VRTips~div,
       .layout-Main .Title-roomInfo .Title-row:nth-child(2) .Title-col.is-normal:last-child,
       .layout-Main .ToTopBtn,
       .Header-right .public-DropMenu-drop .DropPane-ad,
       .Header-right .CloudGameLink,
       .Header-menu-wrap .DropMenuList-ad,
       .public-DropMenu-drop-main div.Header-UserPane-top~div,
       #js-player-dialog .LiveRoomLoopVideo,
       .Header-search-wrap .Search  label,
       .Barrage .Barrage-userEnter{
         display:none !important;
       }

       /* 一般禁用模式 */
       .layout-Player-main #js-player-toolbar{
         display:block;
       }
       #root div.layout-Main{
           margin-top:70px !important;
           display:block !important;
           width:auto !important;
           max-width:auto !important;
       }
       #root>div,
       #root>div .wm-general-bgblur
       {
         background-image:none !important;
       }

        .Title-roomInfo .Title-row:nth-child(1),
        .Title-roomInfo .Title-row:nth-child(2) {
          display:block !important;
        }


       .Barrage-main .Barrage-content {
        color:#333 !important;
       }
       .Barrage-main .Barrage-nickName{
        color:#2b94ff !important;
       }
       .Barrage-listItem{
         color: #333 !important;
         background-color: #f2f5f6 !important;
       }
       .layout-Player-barrage{
           position: absolute !important;
           top: 0 !important;
        }

        .Header-search-wrap input#header-search-input::placeholder {
            color: transparent !important;
            opacity:0 !important;
        }


     /***************************************************虎牙直播***************************************************************************/
       .helperbar-root--12hgWk_4zOxrdJ73vtf1YI,
       .mod-index-wrap .mod-index-main .main-bd,
       .mod-index-wrap .mod-index-main .main-hd,
       .mod-index-wrap #js-main,
       .mod-index-wrap #banner,
       .mod-index-wrap .mod-game-type,
       .mod-index-wrap .mod-actlist,
       .mod-index-wrap .mod-news-section,
       .mod-index-wrap .mod-index-list .live-box #J_adBnM,
       .mod-index-wrap .mod-index-recommend,
       .mod-index-wrap .mod-news-section,
       .mod-index-wrap .recommend-wrap,
       #huya-ab-fixed,
       #huya-ab,
       .liveList-header-r,
       .room-footer,
       .J_roomSideHd,
        #J_roomSideHd,
        #match-cms-content,
        #matchComponent2,
       .hy-nav-item,
       .list-adx,
       .layout-Banner,
        #J_duyaHeaderRight>div>div>div,
        .nav-expand-list .third-clickstat,
        #main_col .special-bg,
        .player-recommend.recommend-ab-mode .end-ab-wrap,
        .chat-wrap-panel.wrap-income,
        .match-room .match-nav,
        .host-detail.J_roomHdDetail span,
        .host-detail.J_roomHdDetail .host-video,
        .room-hd-r .jump-to-phone,
        .room-hd-r #share-entrance,
        .room-hd-r #J_illegalReport,
        .room-hd-r .gamePromote.J_gamePromote,
        .main-wrap .room-mod-ggTop,
        #chatRoom .room-gg-chat,
        .room-core .room-business-game,
        .room-backToTop.j_room-backToTop,
       .room-weeklyRankList{
           display:none !important;
        }

        .ssr-wrapper .mod-sidebar, .room-core #player-gift-wrap, {
          display:none;
        }

        .hy-nav-item:nth-child(1),
        .hy-nav-item:nth-child(2),
        .hy-nav-item:nth-child(3),
        #J_duyaHeaderRight>div>div>div:nth-child(3){
          display:inline-block !important;
        }
        .mod-index-wrap .mod-index-list{
          margin-top:80px !important;
        }
        .duya-header{
          background: hsla(0,0%,100%,.95)  !important;
          border-bottom: 1px solid #e2e2e2 !important;
          box-shadow: 0 0 6px rgb(0 0 0 / 6%) !important;
        }
        .duya-header a,.duya-header i{
         color:#000 !important;
        }
        /*******直播间样式*****/
       .chat-room__list .msg-normal,.chat-room__list .msg-bubble,#J_mainRoom{
          background:none !important;
        }
        #wrap-ext,
       .chat-room__list .msg-normal-decorationPrefix,
       .chat-room__list .msg-normal-decorationSuffix,
       .chat-room__list .msg-bubble-decorationPrefix,
       .chat-room__list img,
       .chat-room__list .msg-noble,
       .chat-room__list .msg-sys,
       .chat-room__list .msg-auditorSys,
       .J_box_msgOfKing,
       .chat-room__list .msg-onTVLottery{
           display: none !important;
        }
       .chat-room__list .msg-bubble span.msg{
           color: #333 !important;
           background:none!important;
        }
       .chat-room__list .msg-bubble .colon,
       .chat-room__list .msg-bubble .msg,
       .chat-room__list .name{
           color: #3c9cfe !important;
           background:none!important;
         }

         #search-bar-input::placeholder{
            color: transparent !important;
            opacity:0 !important;
         }



         /********************************Bilibili********************************** */
         div#i_cecream .floor-single-card,
         div#i_cecream .bili-live-card.is-rcmd,
         div#i_cecream .adblock-tips,
         div.video-container-v1 div.pop-live-small-mode.part-undefined,
         .recommended-swipe.grid-anchor,
         .video-page-special-card-small
         {
            display:none !important;
         }

        /* 输入框*/
        .nav-search-content>input::placeholder {
            color: transparent;
            opacity:0 !important;
        }

        .m-bilibili-btn {
            cursor: pointer !important;
            background: #FFFFFF !important;
            background: var(--bg1_float) !important;
            border: 1px solid #E3E5E7 !important;
            border: 1px solid var(--line_regular) !important;
            border-radius: 8px !important;
            box-sizing: border-box !important;
            padding: 6px !important;
            margin-bottom: 6px !important;
            color: #18191C !important;
            color: var(--text1) !important;
            line-height: 14px;
            font-size: 12px;
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 40px;
        }

        .bili-video-card__info--bottom:hover .m-span-text,
        .video-page-card-small:hover .m-span-text,
        .up-info-container:hover .m-span-text,
        .video-page-operator-card-small:hover .m-span-text
         {
            opacity: 1;
            transform: scale(1.1);
            color:orange;
        }
 `)

})()