yamibo优化摸鱼体验

yamibo论坛显示优化,参考https://github.com/kisshang1993/NGA-BBS-Script

// ==UserScript==
// @name         yamibo优化摸鱼体验
// @namespace    https://github.com/FujinomiyaNeko981213/Yamibo-bbs-Script
// @version      1.0.0
// @author       ZAIYANGNANYUE
// @description  yamibo论坛显示优化,参考https://github.com/kisshang1993/NGA-BBS-Script
// @license      MIT
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/jquery/3.4.0/jquery.min.js#sha512=Pa4Jto+LuCGBHy2/POQEbTh0reuoiEXQWXGn8S7aRlhcwpVkO8+4uoZVSOqUjdCsE+77oygfu2Tl+7qGHGIWsw==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/spectrum/1.8.0/spectrum.min.js#sha512=Bx3FZ9S4XKYq5P1Yxfqp36JifotqAAAl5eotNaGWE1zSSLifBZlbKExLh2NKHA4CTlqHap7xdFzo39W+CTKrWQ==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/localforage/1.10.0/localforage.min.js#sha512=+BMamP0e7wn39JGL8nKAZ3yAQT2dL5oaXWr4ZYlTGkKOaoXM/Yj7c4oy50Ngz5yoUutAG17flueD4F6QpTlPng==
// @require      https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/echarts/5.3.0/echarts.min.js#sha512=dvHO84j/D1YX7AWkAPC/qwRTfEgWRHhI3n7J5EAqMwm4r426sTkcOs6OmqCtmkg0QXNKtiFa67Tp77JWCRRINg==
// @require      https://gf.qytechs.cn/scripts/424901-nga-script-resource/code/NGA-Script-Resource.js?version=1268947
// @icon         https://bbs.yamibo.com/uc_server/data/avatar/000/69/12/22_avatar_middle.jpg?ts=1752408353
// @match        *://bbs.yamibo.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        unsafeWindow
// @inject-into  content
// ==/UserScript==

(function () {
    'use strict';

    /**
     * yamibo摸鱼主脚本
     * @class yamiboBBSScript
     * @constructor
     */
    class yamiboBBSScript {
        constructor() {
            // 配置
            this.setting = {
                original: [],
                normal: {},
                advanced: {}
            }
            // 模块
            this.modules = []
            // 样式
            this.style = ''
            // 数据存储
            this.store = {}
            // 引用库
            this.libs = {$, echarts, localforage}
        }
        /**
         * 获取模块对象
         * @method getModule
         * @param {String} name 模块name
         * @return {Object} 模块对象
         */
        getModule(name) {
            for (const m of this.modules) {
                if (m.name && m.name === name) {
                    return m
                }
            }
            return null
        }
        /**
         * 全程渲染函数
         * @method renderAlways
         */
        renderAlways() {
            for (const module of this.modules) {
                try {
                    module.renderAlwaysFunc && module.renderAlwaysFunc(this)
                } catch (error) {
                    this.printLog(`[${module.name}]模块在[renderAlwaysFunc()]中运行失败!`)
                    console.log(error)
                }
            }
        }
        /**
         * 列表页渲染函数
         * @method renderThreads
         */
        renderThreads() {
            $('#threadlist tbody tr[hld-threads-render!=ok]').each((index, dom) => {
                const $el = $(dom)
                for (const module of this.modules) {
                    try {
                        module.renderThreadsFunc && module.renderThreadsFunc($el, this)
                    } catch (error) {
                        this.printLog(`[${module.name}]模块在[renderThreadsFunc()]中运行失败!`)
                        console.log(error)
                    }
                }
                $el.attr('hld-threads-render', 'ok')
            })
        }
        /**
         * 详情页渲染函数
         * @method renderForms
         */
        renderForms() {
            $('#postlist table.plhin > tbody:first-of-type > tr:first-child').each((index, dom) => {
                const $el = $(dom)
                // 等待yamibo页面渲染完成
                for (const module of this.modules) {
                    try {
                        module.renderFormsFunc && module.renderFormsFunc($el, this)
                    } catch (error) {
                        this.printLog(`[${module.name}]模块在[renderFormsFunc()]中运行失败!`)
                        console.log(error)
                    }
                }
                $el.attr('hld-forms-render', 'ok')
            })
        }
        /**
         * 添加模块
         * @method addModule
         * @param {Object} module 模块对象
         * @param {Boolean} plugin 是否为插件
         */
        addModule(module) {
            // 组件预处理函数
            if (module.preProcFunc) {
                try {
                    module.preProcFunc(this)
                } catch (error) {
                    this.printLog(`[${module.name}]模块在[preProcFunc()]中运行失败!`)
                    console.log(error)
                }
            }
            // 添加设置
            const addSetting = setting => {
                // 标准模块配置
                if (setting.shortCutCode && this.setting.normal.shortcutKeys) {
                    this.setting.normal.shortcutKeys.push(setting.shortCutCode)
                }
                if (setting.key) {
                    this.setting[setting.type || 'normal'][setting.key] = setting.default ?? ''
                    this.setting.original.push(setting)
                }
            }
            // 功能板块
            if (module.setting && !Array.isArray(module.setting)) {
                addSetting(module.setting)
            }
            if (module.settings && Array.isArray(module.settings)) {
                for (const setting of module.settings) {
                    addSetting(setting)
                }
            }
            // 添加样式
            if (module.style) {
                this.style += module.style
            }
            this.modules.push(module)
        }
        /**
         * 判断当前页面是否为列表页
         * @method isThreads
         * @return {Boolean} 判断状态
         */
        isThreads() {
            return $('#threadlist').length > 0
        }
        /**
         * 判断当前页面是否为详情页
         * @method isForms
         * @return {Boolean} 判断状态
         */
        isForms() {
            return $('#postlist').length > 0
        }
        /**
         * 抛出异常
         * @method throwError
         * @param {String} msg 异常信息
         */
        throwError(msg) {
            alert(msg)
            throw(msg)
        }
        /**
         * 初始化
         * @method init
         */
        init() {
            // 开始初始化
            this.printLog('初始化...')
            localforage.config({name: 'yamibo BBS Script DB'})
            const startInitTime = new Date().getTime()
            const modulesTable = []
            //同步配置
            this.loadSetting()
            // 组件初始化函数
            for (const module of this.modules) {
                if (module.initFunc) {
                    try {
                        module.initFunc(this)
                    } catch (error) {
                        this.printLog(`[${module.name}]模块在[initFunc()]中运行失败!`)
                        console.log(error)
                    }
                }
            }
            // 组件后处理函数
            for (const module of this.modules) {
                if (module.postProcFunc) {
                    try {
                        module.postProcFunc(this)
                    } catch (error) {
                        this.printLog(`[${module.name}]模块在[postProcFunc()]中运行失败!`)
                        console.log(error)
                    }
                }
            }
            // 动态样式
            for (const module of this.modules) {
                if (module.asyncStyle) {
                    try {
                        this.style += module.asyncStyle(this)
                    } catch (error) {
                        this.printLog(`[${module.name}]模块在[asyncStyle()]中运行失败!`)
                        console.log(error)
                    }
                }
                modulesTable.push({
                    name: module.title || module.name || 'UNKNOW',
                    type: module.type == 'plugin' ? '插件' : '标准模块',
                    version: module.version || '-'
                })
            }
            // 插入样式
            const style = document.createElement("style")
            style.appendChild(document.createTextNode(this.style))
            document.getElementsByTagName('head')[0].appendChild(style)
            // 初始化完成
            const endInitTime = new Date().getTime()
            console.table(modulesTable)
            this.printLog(`[v${this.getInfo().version}] 初始化完成: 共加载${this.modules.length}个模块,总耗时${endInitTime-startInitTime}ms`)
            console.log('%c反馈问题请前往: https://github.com/FujinomiyaNeko981213/Yamibo-bbs-Script/issues', 'color:orangered;font-weight:bolder')
        }
        /**
         * 通知弹框
         * @method popNotification
         * @param {String} msg 消息内容
         * @param {Number} duration 显示时长(ms)
         */
        popNotification(msg, duration=1000) {
            $('#noti_container').length == 0 && $('body').append('<div id="noti_container"></div>')
            let $msgBox = $(`<div class="noti-msg">${msg}</div>`)
            $('#noti_container').append($msgBox)
            $msgBox.slideDown(100)
            setTimeout(() => { $msgBox.fadeOut(500) }, duration)
            setTimeout(() => { $msgBox.remove() }, duration + 500)
        }
        /**
         * 消息弹框
         * @method popMsg
         * @param {String} msg 消息内容
         * @param {String} type 消息类型 [ok, err, warn]
         */
        popMsg(msg, type='ok') {
            $('.msg').length > 0 && $('.msg').remove()
            let $msg = $(`<div class="msg msg-${type}">${msg}</div>`)
            $('body').append($msg)
            $msg.slideDown(200)
            setTimeout(() => { $msg.fadeOut(500) }, type == 'ok' ? 2000 : 5000)
            setTimeout(() => { $msg.remove() }, type == 'ok' ? 2500 : 5500)
        }
        /**
         * 打印控制台消息
         * @method printLog
         * @param {String} msg 消息内容
         */
        printLog(msg) {
            console.log(`%cyamibo%cScript%c ${msg}`,
                'background: #6E2B19;color: #fff;font-weight:bold;padding:2px 2px 2px 4px;border-radius:4px 0 0 4px;',
                'background: #DBC38C;color: #6E2B19;font-weight:bold;padding:2px 4px 2px 2px;border-radius:0px 4px 4px 0px;',
                'background:none;color:#000;'
            )
        }
        /**
         * 读取值
         * @method getValue
         * @param {String} key
         */
        getValue(key) {
            try {
                return GM_getValue(key) || window.localStorage.getItem(key)
            } catch {
                // 兼容性代码: 计划将在5.0之后废弃
                return window.localStorage.getItem(key)
            }
        }
        /**
         * 写入值
         * @method setValue
         * @param {String} key
         * @param {String} value
         */
        setValue(key, value) {
            try {
                GM_setValue(key, value)
            } catch {}
        }
        /**
         * 删除值
         * @method deleteValue
         * @param {String} key
         */
        deleteValue(key) {
            try {
                GM_deleteValue(key)
            } catch {}
            // 兼容性代码: 计划将在5.0之后飞起
            window.localStorage.removeItem(key)
        }
        /**
         * 保存配置到本地
         * @method saveBasicSetting
         * @param {String} msg 自定义消息信息
         */
        saveBasicSetting(msg='保存配置成功,刷新页面生效') {
            // 基础设置
            for (let k in this.setting.normal) {
                $('input#cb_' + k).length > 0 && (this.setting.normal[k] = $('input#cb_' + k)[0].checked)
            }
            script.setValue('yamibo_setting', JSON.stringify(this.setting.normal))
            // 高级设置
            for (let k in this.setting.advanced) {
                if ($('#adv_' + k).length > 0) {
                    const originalSetting = this.setting.original.find(s => s.type == 'advanced' && s.key == k)
                    const valueType = typeof originalSetting.default
                    const inputType = $('#adv_' + k)[0].nodeName
                    if (inputType == 'SELECT') {
                        this.setting.advanced[k] = $('#adv_' + k).val()
                    } else {
                        if (valueType == 'boolean') {
                            this.setting.advanced[k] = $('#adv_' + k)[0].checked
                        }
                        if (valueType == 'number') {
                            this.setting.advanced[k] = +$('#adv_' + k).val()
                        }
                        if (valueType == 'string') {
                            this.setting.advanced[k] = $('#adv_' + k).val()
                        }
                    }
                }
            }
            script.setValue('yamibo_advanced_setting', JSON.stringify(this.setting.advanced))
            msg && this.popMsg(msg)
        }
        /**
         * 从本地读取配置
         * @method loadSetting
         */
        loadSetting() {
            // 基础设置
            try {
                const settingStr = script.getValue('yamibo_setting')
                console.log("yamibo_setting",settingStr);
                if (settingStr) {
                    let localSetting = JSON.parse(settingStr)
                    for (let k in this.setting.normal) {
                        !localSetting.hasOwnProperty(k) && (localSetting[k] = this.setting.normal[k])
                        if (k == 'shortcutKeys') {
                            if (localSetting[k].length < this.setting.normal[k].length) {
                                const offset_count = this.setting.normal[k].length - localSetting[k].length
                                localSetting[k] = localSetting[k].concat(this.setting.normal[k].slice(-offset_count))
                            }
                            // 更改默认按键
                            let index = 0
                            for (const module of this.modules) {
                                if (module.setting && module.setting.shortCutCode) {
                                    if (localSetting[k][index] != module.setting.shortCutCode) {
                                        module.setting.rewriteShortCutCode = localSetting[k][index]
                                    }
                                    index += 1
                                }else if (module.settings) {
                                    for (const setting of module.settings) {
                                        if (setting.shortCutCode) {
                                            if (localSetting[k][index] != setting.shortCutCode) {
                                                setting.rewriteShortCutCode = localSetting[k][index]
                                            }
                                            index += 1
                                        }
                                    }
                                }
                            }
                        }
                    }
                    for (let k in localSetting) {
                        !this.setting.normal.hasOwnProperty(k) && delete localSetting[k]
                    }
                    this.setting.normal = localSetting
                }
                // 高级设置
                const advancedSettingStr = script.getValue('yamibo_advanced_setting')
                if (advancedSettingStr) {
                    let localAdvancedSetting = JSON.parse(advancedSettingStr)
                    for (let k in this.setting.advanced) {
                        !localAdvancedSetting.hasOwnProperty(k) && (localAdvancedSetting[k] = this.setting.advanced[k])
                    }
                    for (let k in localAdvancedSetting) {
                        !this.setting.advanced.hasOwnProperty(k) && delete localAdvancedSetting[k]
                    }
                    this.setting.advanced = localAdvancedSetting
                }
            } catch(e) {
                script.throwError(`【yamibo-Script】读取配置文件出现错误,无法加载配置文件!\n错误问题: ${e}\n\n请尝试使用【修复脚本】来修复此问题`)
            }

        }
        /**
         * 检查是否更新
         * @method checkUpdate
         */
        checkUpdate() {
            // 字符串版本转数字
            const vstr2num = str => {
                let num = 0
                str.split('.').forEach((n, i) => num += i < 2 ? +n * 1000 / Math.pow(10, i) : +n)
                return num
            }
            // 字符串中版本截取
            const vstr2mid = str => {
                return str.substring(0, str.lastIndexOf('.'))
            }
            //检查更新
            const cver = script.getValue('yamibo_version')
            script.setValue('yamibo_version', GM_info.script.version)
        }
        /**
         * 创建储存对象实例
         * @param {String} instanceName 实例名称
         */
        createStorageInstance(instanceName) {
            if (!instanceName || Object.keys(this.store).includes(instanceName)) {
                this.throwError('【yamibo-Script】创建储存对象实例失败,实例名称不能为空或实例名称已存在')
            }
            const lfInstance = localforage.createInstance({name: instanceName})
            this.store[instanceName] = lfInstance
            return lfInstance
        }
        /**
         * 运行脚本
         * @method run
         */
        run() {
            this.checkUpdate()
            this.init()
            this.renderAlways()
            this.isThreads() && this.renderThreads()
            this.isForms() && this.renderForms()
            const moOpts = { childList: true, subtree: true, attributes: false, characterData: false };

            // 只在一帧内处理一次,避免突发大量节点导致回调风暴
            let scheduled = false;
            // 防止重入
            let isRendering = false;

            // 仅当真正影响到你关心的区域时才处理
            function isUsefulMutation(records) {
            for (const r of records) {
                if (!r.addedNodes || r.addedNodes.length === 0) continue;
                for (const n of r.addedNodes) {
                // 只关心元素节点;跳过 style/script/text 等
                if (n.nodeType !== 1) continue;
                // 命中你关心的容器或其后代(按实际结构改)
                if (
                    n.id === 'threadlist' || n.id === 'postlist' ||
                    (n.closest && (n.closest('#threadlist') || n.closest('#postlist')))
                ) {
                    return true;
                }
                }
            }
            return false;
            }

            const process = () => {
            scheduled = false;
            if (isRendering) return;
            isRendering = true;
            try {
                // 关键:渲染前暂时断开,避免自己改 DOM 又触发回调形成死循环
                mo.disconnect();

                // === 你的渲染 ===
                this.renderAlways();
                if (document.getElementById('threadlist')) this.renderThreads();
                if (document.getElementById('postlist')) this.renderForms();
                // === 你的渲染 ===
            } finally {
                // 渲染完再恢复监听
                mo.observe(document.documentElement, moOpts);
                isRendering = false;
            }
            };

            const mo = new MutationObserver((records) => {
            if (!isUsefulMutation(records)) return;
            // 合并同一批次/同一帧的多次变更,只调一次渲染
            if (!scheduled) {
                scheduled = true;
                // 用 rAF 合并到浏览器下一帧,体感“零延迟”
                (window.requestAnimationFrame || setTimeout)(process, 0);
            }
            });

            // 初次观测(尽量缩小范围:若能直接指向具体容器更好)
            mo.observe(document.documentElement, moOpts);

            // 首屏先跑一遍,不等 Mutation 到来
            this.renderAlways();
            if (document.getElementById('threadlist')) this.renderThreads();
            if (document.getElementById('postlist')) this.renderForms();
        }
        /**
         * 获取脚本信息
         * @method getInfo
         * @return {Object} 脚本信息对象
         */
        getInfo() {
            return {
                version: GM_info.script.version,
                author: 'ZAIYANGNANYUE',
                github: 'https://github.com/FujinomiyaNeko981213/Yamibo-bbs-Script',
                update: 'https://gf.qytechs.cn/zh-CN/scripts/393991-nga%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C'
            }
        }
    }

    /* 注册(不可用)菜单按钮 */
    try {
        // 设置面板
        GM_registerMenuCommand('设置面板', function () {
            $('#setting_cover').css('display', 'block')
            $('html, body').animate({scrollTop: 0}, 500)
        })
        // 清理缓存
        GM_registerMenuCommand('清理缓存', function () {
            if (window.confirm('此操作为清理Local Storage与IndexedDB部分缓存内容,不会清理配置\n\n继续请点击【确定】')) {
                script.deleteValue('yamibo_post_author')
                localforage.clear()
                alert('操作成功,请刷新页面重试')
            }
        })
        // 修复脚本
        GM_registerMenuCommand('修复脚本', function () {
            if (window.confirm('如脚本运行失败或无效,尝试修复脚本,这会清除脚本的所有数据\n* 数据包含配置,各种名单等\n* 此操作不可逆转,请谨慎操作\n\n继续请点击【确定】')) {
                try {
                    GM_listValues().forEach(key => GM_deleteValue(key))
                } catch {}
                // 兼容性代码: 计划将在5.0之后废弃
                window.localStorage.clear()
                alert('操作成功,请刷新页面重试')
            }
        })
        // 反馈问题
        GM_registerMenuCommand('反馈问题', function () {
            if (window.confirm('如脚本运行失败而且修复后也无法运行,请反馈问题报告\n* 问题报告请包含使用的: [浏览器],[脚本管理器],[脚本版本]\n* 描述问题最好以图文并茂的形式\n* 如脚本运行失败,建议提供F12控制台的红色错误输出以辅助排查\n\n默认打开的为Greasy Fork镜像的反馈页面,有能力最好去Github Issue反馈问题,可以获得优先处理\n\n即将打开反馈页面,继续请点击【确定】')) {
                window.open('https://gf.qytechs.cn/zh-CN/scripts/553252-yamibo%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C/feedback')
            }
        })
    } catch (e) {
        // 不支持此命令
        console.warn(`【yamibo Script】警告: 此脚本管理器不支持菜单按钮,可能会导致新特性无法正常使用,建议更改脚本管理器为
        Tampermonkey[https://www.tampermonkey.net/] 或 Violentmonkey[https://violentmonkey.github.io/]`)
    }

    /* 标准模块 */
    /**
     * 设置模块
     * @name SettingPanel
     * @description 提供脚本的设置面板,提供配置修改,保存等基础功能
     */
    const SettingPanel = {
        name: 'SettingPanel',
        title: '设置模块',
        initFunc() {
            //设置面板
            let $panelDom = $(`
            <div id="setting_cover" class="animated zoomIn">
                <div id="setting_panel">
                    <a href="javascript:void(0)" id="setting_close" class="setting-close" close-type="hide">×</a>
                    <p class="sp-title"><a title="更新地址" href="https://gf.qytechs.cn/zh-CN/scripts/553252-yamibo%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C" target="_blank">yamibo优化摸鱼体验<span class="script-info">v${script.getInfo().version}</span></a></p>
                    <div class="field">
                        <p class="sp-section">显示优化</p>
                        <div id="normal_left"></div>
                    </div>
                    <div class="field">
                        <p class="sp-section">功能增强</p>
                        <div id="normal_right"></div>
                    </div>
                    <div style="clear:both"></div>
                    <div class="advanced-setting">
                        <button id="advanced_button">+</button><span>高级设置</span>
                        <div class="advanced-setting-panel">
                            <p><svg t="1590560820184" class="icon" viewBox="0 0 1040 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2738" width="200" height="200"><path d="M896.355855 975.884143 127.652332 975.884143c-51.575656 0-92.993974-19.771299-113.653503-54.238298-20.708648-34.515095-18.194384-79.5815 6.9022-123.5632L408.803663 117.885897c25.244964-44.376697 62.767556-69.77004 102.953813-69.77004 40.136116 0 77.658707 25.393343 103.002932 69.671803L1003.006873 798.131763c25.097608 44.030819 27.711132 89.049129 6.952342 123.514081C989.348806 956.159916 947.881368 975.884143 896.355855 975.884143L896.355855 975.884143 896.355855 975.884143 896.355855 975.884143 896.355855 975.884143zM511.805572 119.511931c-12.769838 0-27.414373 12.376888-39.298028 33.134655L84.656075 832.892451c-12.130272 21.350261-14.989389 40.530089-7.741311 52.611242 7.297197 12.08013 25.787316 19.033495 50.737568 19.033495l768.703523 0c24.997324 0 43.439348-6.903224 50.736545-19.033495 7.197936-12.031011 4.387937-31.210839-7.791453-52.5611L551.055504 152.646586C539.220968 131.888819 524.527314 119.511931 511.805572 119.511931L511.805572 119.511931 511.805572 119.511931 511.805572 119.511931 511.805572 119.511931zM512.004093 653.807726c-20.1182 0-36.488029-15.975856-36.488029-35.69906L475.516064 296.773124c0-19.723204 16.369829-35.698037 36.488029-35.698037 20.117177 0 36.485983 15.975856 36.485983 35.698037l0 321.335543C548.490076 637.832893 532.12127 653.807726 512.004093 653.807726L512.004093 653.807726 512.004093 653.807726 512.004093 653.807726zM511.757476 828.308039c31.359218 0 56.851822-24.950252 56.851822-55.717999s-25.491581-55.716976-56.851822-55.716976c-31.408337 0-56.851822 24.949228-56.851822 55.716976S480.349139 828.308039 511.757476 828.308039L511.757476 828.308039 511.757476 828.308039 511.757476 828.308039z" p-id="2739"></path></svg> 鼠标停留在<span class="help" title="详细描述">选项文字</span>上可以显示详细描述,设置有误可能会导致插件异常或者无效!</p>
                            <table id="advanced_left"></table>
                            <table id="advanced_right"></table>
                        </div>
                    </div>
                    <div class="buttons">
                        <span id="hld_setting_panel_buttons"></span>
                        <span>
                            <button class="btn" id="save__data">保存设置</button>
                        </span>
                    </div>
                </div>
            </div>
            `)
            const insertDom = setting => {
                if (setting.type === 'normal') {
                    $panelDom.find(`#normal_${setting.menu || 'left'}`).append(`
                    <p><label ${setting.desc ? 'class="help" help="'+setting.desc+'"' : ''}><input type="checkbox" id="cb_${setting.key}"> ${setting.title || setting.key}${setting.shortCutCode ? '(快捷键切换[<b>'+script.getModule('ShortCutKeys').getCodeName(setting.rewriteShortCutCode || setting.shortCutCode)+'</b>])' : ''}</label></p>
                    `)
                    if (setting.extra) {
                        $panelDom.find(`#cb_${setting.key}`).attr('enable', `${setting.key}_${setting.extra.mode || 'fold'}`)
                        $panelDom.find(`#normal_${setting.menu || 'left'}`).append(`
                        <div class="sp-${setting.extra.mode || 'fold'}" id="${setting.key}_${setting.extra.mode || 'fold'}" data-id="${setting.key}">
                            <p><button id="${setting.extra.id}">${setting.extra.label}</button></p>
                        </div>
                        `)
                    }
                }
                if (setting.type === 'advanced') {
                    let formItem = ''
                    const valueType = typeof setting.default
                    if (valueType === 'boolean') {
                        formItem = `<input type="checkbox" id="adv_${setting.key}">`
                    }
                    if (valueType === 'number') {
                        formItem = `<input type="number" id="adv_${setting.key}">`
                    }
                    if (valueType === 'string') {
                        if (setting.options) {
                            let t = ''
                            for (const option of setting.options) {
                                t += `<option value="${option.value}">${option.label}</option>`
                            }
                            formItem = `<select id="adv_${setting.key}">${t}</select>`
                        } else {
                            formItem = `<input type="text" id="adv_${setting.key}">`
                        }
                    }
                    $panelDom.find(`#advanced_${setting.menu || 'left'}`).append(`
                    <tr>
                        <td><span class="help" help="${setting.desc || ''}">${setting.title || setting.key}</span></td>
                        <td>${formItem}</td>
                    </tr>`)
                }
            }
            for (const module of script.modules) {
                if (module.setting && module.setting.key) {
                    insertDom(module.setting)
                }
                if (module.settings) {
                    for (const setting of module.settings) {
                        setting.key && insertDom(setting)
                    }
                }
            }
            /**
             * Bind:Mouseover Mouseout
             * 提示信息Tips
             */
            $('body').on('mouseover', '.help', function(e){
                if (!$(this).attr('help')) return
                const $help = $(`<div class="help-tips">${$(this).attr('help').replace(/\n/g, '<br>')}</div>`)
                $help.css({
                    top: ($(this).offset().top + $(this).height() + 5) + 'px',
                    left: $(this).offset().left + 'px'
                })
                $('body').append($help)
            }).on('mouseout', '.help', ()=>$('.help-tips').remove())
            $('body').append($panelDom)
            //本地恢复设置
            //基础设置
            for (let k in script.setting.normal) {
                if ($('#cb_' + k).length > 0) {
                    $('#cb_' + k)[0].checked = script.setting.normal[k]
                    const enableDomID = $('#cb_' + k).attr('enable')
                    if (enableDomID) {
                        script.setting.normal[k] ? $('#' + enableDomID).show() : $('#' + enableDomID).hide()
                        $('#' + enableDomID).find('input').each(function () {
                            $(this).val() == script.setting.normal[$(this).attr('name').substring(8)] && ($(this)[0].checked = true)
                        })
                        $('#cb_' + k).on('click', function () {
                            $(this)[0].checked ? $('#' + enableDomID).slideDown() : $('#' + enableDomID).slideUp()
                        })
                    }
                }
            }
            //高级设置
            for (let k in script.setting.advanced) {
                if ($('#adv_' + k).length > 0) {
                    const valueType = typeof script.setting.advanced[k]
                    if (valueType == 'boolean') {
                        $('#adv_' + k)[0].checked = script.setting.advanced[k]
                    }
                    if (valueType == 'number' || valueType == 'string') {
                        $('#adv_' + k).val(script.setting.advanced[k])
                    }
                }
            }
            /**
             * Bind:Click
             * 设置面板-展开切换高级设置
             */
            $('body').on('click', '#advanced_button', function () {
                if ($('.advanced-setting-panel').is(':hidden')) {
                    $('.advanced-setting-panel').css('display', 'flex')
                    $(this).text('-')
                } else {
                    $('.advanced-setting-panel').css('display', 'none')
                    $(this).text('+')
                }
            })
            /**
             * Bind:Click
             * 关闭面板(通用)
             */
            $('body').on('click', '.list-panel .setting-close', function () {
                if ($(this).attr('close-type') == 'hide') {
                    $(this).parent().hide()
                } else {
                    $(this).parent().remove()
                }
            })
            /**
             * Bind:Click
             * 保存配置
             */
            $('body').on('click', '#save__data', () => {
                script.saveBasicSetting()
                $('#setting_cover').fadeOut(200)
            })
        },
        renderAlwaysFunc() {
            if($('.setting-box').length == 0) {
                $('#startmenu > tbody > tr > td.last').append('<div><div class="item setting-box"></div></div>')
                let $entry = $('<a id="setting" title="打开yamibo优化摸鱼插件设置面板">yamibo优化摸鱼插件设置</a>')
                $entry.click(()=>{
                    $('#setting_cover').css('display', 'block')
                    $('html, body').animate({scrollTop: 0}, 500)
                })
                $('#setting_close').click(()=>$('#setting_cover').fadeOut(200))
                $('.setting-box').append($entry)
            }
        },
        addButton(button) {
            const $button = $(`<button class="btn" id="${button.id}" title="${button.desc}">${button.title}</button>`)
            if (typeof button.click == 'function') {
                $button.on('click', function() {
                    button.click($(this))
                })
            }
            $('#hld_setting_panel_buttons').append($button)
        },
        style: `
        .animated {animation-duration:.3s;animation-fill-mode:both;}
        .animated-1s {animation-duration:1s;animation-fill-mode:both;}
        .zoomIn {animation-name:zoomIn;}
        .bounce {-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom;}
        .fadeInUp {-webkit-animation-name:fadeInUp;animation-name:fadeInUp;}
        #loader {display:none;position:absolute;top:50%;left:50%;margin-top:-10px;margin-left:-10px;width:20px;height:20px;border:6px dotted #FFF;border-radius:50%;-webkit-animation:1s loader linear infinite;animation:1s loader linear infinite;}
        @keyframes loader {0% {-webkit-transform:rotate(0deg);transform:rotate(0deg);}100% {-webkit-transform:rotate(360deg);transform:rotate(360deg);}}
        @keyframes zoomIn {from {opacity:0;-webkit-transform:scale3d(0.3,0.3,0.3);transform:scale3d(0.3,0.3,0.3);}50% {opacity:1;}}
        @keyframes bounce {from,20%,53%,80%,to {-webkit-animation-timing-function:cubic-bezier(0.215,0.61,0.355,1);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%,43% {-webkit-animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}
        @keyframes fadeInUp {from {opacity:0;-webkit-transform:translate3d(-50%,100%,0);transform:translate3d(-50%,100%,0);}to {opacity:1;-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0);}}
        .msg{display:none;position:fixed;top:10px;left:50%;transform:translateX(-50%);color:#fff;text-align:center;z-index:99996;padding:10px 30px 10px 45px;font-size:16px;border-radius:10px;background-image:url("${SVG_ICON_MSG}");background-size:25px;background-repeat:no-repeat;background-position:15px}
        .msg a{color:#fff;text-decoration: underline;}
        .msg-ok{background:#4bcc4b}
        .msg-err{background:#c33}
        .msg-warn{background:#FF9900}
        .flex{display:flex;}
        .float-left{float: left;}
        .clearfix {clear: both;}
        #noti_container {position:fixed;top:10px;left:10px;z-index:99;}
        .noti-msg {display:none;padding:10px 20px;font-size:14px;font-weight:bold;color:#fff;margin-bottom:10px;background:rgba(0,0,0,0.6);border-radius:10px;cursor:pointer;}
        .btn-groups {display:flex;justify-content:center !important;margin-top:10px;}
        button.btn {padding:3px 8px;border:1px solid #591804;background:#fff8e7;color:#591804;}
        button.btn:hover {background:#591804;color:#fff0cd;}
        button.btn[disabled] {opacity:.5;}
        #updated {position:fixed;top:20px;right:20px;width:230px;padding:10px;border-radius:5px;box-shadow:0 0 15px #666;border:1px solid #591804;background:#fff8e7;z-index: 9999;}
        #updated .readme {text-decoration:underline;color:#591804;}
        .script-info {margin-left:4px;font-size:70%;color:#666;}
        #setting {color:#6666CC;cursor:pointer;}
        #setting_cover {display:none;padding-top: 70px;position:absolute;top:0;left:0;right:0;bottom:0;z-index:999;}
        #setting_panel {position:relative;background:#fff8e7;width:600px;left: 50%;transform: translateX(-50%);padding:15px 20px;border-radius:10px;box-shadow:0 0 10px #666;border:1px solid #591804;}
        #setting_panel > div.field {float:left;width:50%;}
        #setting_panel p {margin-bottom:10px;}
        #setting_panel .sp-title {font-size:15px;font-weight:bold;text-align:center;}
        #setting_panel .sp-section {font-weight:bold;margin-top:20px;}
        .setting-close {position:absolute;top:5px;right:5px;padding:3px 6px;background:#fff0cd;color:#591804;transition:all .2s ease;cursor:pointer;border-radius:4px;text-decoration:none;z-index:9999;}
        .setting-close:hover {background:#591804;color:#fff0cd;text-decoration:none;}
        #setting_panel button {transition:all .2s ease;cursor:pointer;}
        .advanced-setting {border-top: 1px solid #e0c19e;border-bottom: 1px solid #e0c19e;padding: 3px 0;margin-top:25px;}
        .advanced-setting >span {font-weight:bold}
        .advanced-setting >button {padding: 0px;margin-right:5px;width: 18px;text-align: center;}
        .advanced-setting-panel {display:none;padding:5px 0;flex-wrap: wrap;}
        .advanced-setting-panel>p {width:100%;}
        .advanced-setting-panel>table {width:50%;}
        .advanced-setting-panel>p {margin: 7px 0 !important;font-weight:bold;}
        .advanced-setting-panel>p svg {height:16px;width:16px;vertical-align: top;margin-right:3px;}
        .advanced-setting-panel>table td {padding-right:10px}
        .advanced-setting-panel input[type=text],.advanced-setting-panel input[type=number] {width:80px}
        .advanced-setting-panel input[type=number] {border: 1px solid #e6c3a8;box-shadow: 0 0 2px 0 #7c766d inset;border-radius: 0.25em;}
        .help {cursor:help;text-decoration: underline;}
        .buttons {clear:both;display:flex;justify-content:space-between;padding-top:15px;}
        button.btn {padding:3px 8px;border:1px solid #591804;background:#fff8e7;color:#591804;}
        button.btn:hover {background:#591804;color:#fff0cd;}
        .sp-fold {padding-left:23px;}
        .sp-fold .f-title {font-weight:bold;}
        .help-tips {position: absolute;padding: 5px 10px;background: rgba(0,0,0,.8);color: #FFF;border-radius: 5px;z-index: 9999;}
        `
    }
    /**
     * 快捷键模块
     * @name ShortCutKeys
     * @description 为模块提供快捷键切换的能力,提供修改,保存快捷键等
    */
    const ShortCutKeys = {
        name: 'ShortCutKeys',
        title: '快捷键支持',
        setting: {
            type: 'advanced',
            key: 'dynamicEnable',
            default: true,
            title: '动态功能启用',
            desc: '此配置表示部分可以快捷键切换的功能默认行为策略\n选中时: 关闭功能(如隐藏头像)也可以通过快捷键切换显示/隐藏\n取消时: 关闭功能(如隐藏头像)将彻底关闭功能,快捷键会失效',
            menu: 'left'
        },
        preProcFunc() {
            script.setting.normal.shortcutKeys = []
        },
        initFunc() {
            const _this = this
            // 添加到配置面板的设置入口
            script.getModule('SettingPanel').addButton({
                id: 'shortcut_manage',
                title: '编辑快捷键',
                desc: '编辑快捷键'
            })
            /**
             * Bind:keyup
             * 注册(不可用)监听按键
             */
            $('body').keyup(event => {
                if (/textarea|select|input/i.test(event.target.nodeName)
                    || /text|password|number|email|url|range|date|month/i.test(event.target.type)) {
                    return
                }
                if (event.ctrlKey || event.altKey || event.shiftKey) return
                for (const keyCode of script.setting.normal.shortcutKeys) {
                    if (event.keyCode === keyCode) {
                        for (const module of script.modules) {
                            if (module.setting && module.shortcutFunc) {
                                if (module.setting.rewriteShortCutCode) {
                                    if (module.setting.rewriteShortCutCode === event.keyCode) {
                                        module.shortcutFunc[module.setting.key].call(module)
                                    }
                                } else if (module.setting.shortCutCode === event.keyCode) {
                                    module.shortcutFunc[module.setting.key].call(module)
                                }
                            }
                            if (module.settings) {
                                for (const setting of module.settings) {
                                    if (module.shortcutFunc) {
                                        if (setting.rewriteShortCutCode) {
                                            if (setting.rewriteShortCutCode === event.keyCode) {
                                                module.shortcutFunc[setting.key].call(module)
                                            }
                                        } else if (setting.shortCutCode === event.keyCode) {
                                            module.shortcutFunc[setting.key].call(module)
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            })
            /**
             * Bind:Click
             * 快捷键编辑面板
             */
            $('body').on('click', '#shortcut_manage', () => {
                if($('#shortcut_panel').length > 0) return
                let $shortcutPanel = $(`<div id="shortcut_panel" class="list-panel animated fadeInUp">
                <a href="javascript:void(0)" class="setting-close">×</a>
                <div>
                <div><p><b>编辑快捷键</b></p><div class="float-left"><table class="table table-keyword"><thead><tr><td>功能</td><td width="60">快捷键</td></tr></thead>
                <tbody></tbody></table></div><div class="float-left shortcut-desc"><p><b>支持的快捷键范围</b></p><p>键盘 <code>A</code>~<code>Z</code></p><p>左箭头 <code>LEFT</code></p><p>右箭头 <code>RIGHT</code></p><p>上箭头 <code>UP</code></p><p>下箭头 <code>DOWN</code></p><p><i>* 留空则取消快捷键</i></p><br><p>如按键异常请尝试重置按键</p>
                </div>
                <div class="clearfix"></div></div>
                </div>
                <div class="btn-groups">
                <button class="btn" id="reset_shortcut">重置按键</button>
                <button class="btn" id="save_shortcut">保存快捷键</button>
                </div>
                </div>`)
                const insertDom = setting => $shortcutPanel.find('.table tbody').append(`<tr><td>${setting.title || setting.key}</td><td><input type="text" value="${this.getCodeName(setting.rewriteShortCutCode || setting.shortCutCode)}"></td></tr>`)
                for (const module of script.modules) {
                    if (module.setting && module.setting.shortCutCode) {
                        insertDom(module.setting)
                    }
                    if (module.settings) {
                        for (const setting of module.settings) {
                            if (setting.shortCutCode) {
                                insertDom(setting)
                            }
                        }
                    }
                }
                $('#setting_cover').append($shortcutPanel)
            })
            /**
             * Bind:Click
             * 重置快捷键
             */
            $('body').on('click', '#reset_shortcut', () => {
                const defaultShortcut = []
                for (const module of script.modules) {
                    if (module.setting && module.setting.shortCutCode) {
                        defaultShortcut.push(module.setting.shortCutCode)
                    }
                    if (module.settings) {
                        for (const setting of module.settings) {
                            setting.shortCutCode && defaultShortcut.push(setting.shortCutCode)
                        }
                    }
                }
                script.setting.normal.shortcutKeys = defaultShortcut
                script.saveBasicSetting('重置按键成功,刷新页面生效')
                $('#shortcut_panel').remove()
            })
            /**
             * Bind:Click
             * 保存快捷键
             */
            $('body').on('click', '#save_shortcut', () => {
                const _this = this
                let shortcutKeys = []
                $('.table tbody>tr').each(function () {
                    const v = $(this).find('input').val().trim().toUpperCase()
                    if (v == '') {
                        shortcutKeys.push(-1)
                    } else {
                        const code = _this.getCodeName(v, 'name')
                        if (code > 0) shortcutKeys.push(code)
                        else script.popMsg(`${v}是个无效的快捷键`, 'err')
                    }
                })
                if (shortcutKeys.length != script.setting.normal.shortcutKeys.length) return
                script.setting.normal.shortcutKeys = shortcutKeys
                script.saveBasicSetting('保存按键成功,刷新页面生效')
                $('#shortcut_panel').remove()
            })
        },
        getCodeName(val, valType='code') {
            const shortcutCode = {
                'A': 65, 'B': 66, 'C': 67, 'D': 68, 'E': 69, 'F': 70, 'G': 71,
                'H': 72, 'I': 73, 'J': 74, 'K': 75, 'L': 76, 'M': 77, 'N': 78,
                'O': 79, 'P': 80, 'Q': 81, 'R': 82, 'S': 83, 'T': 84,
                'U': 85, 'V': 86, 'W': 87, 'X': 88, 'Y': 89, 'Z': 90,
                '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57,
                'LEFT': 37, 'RIGHT': 39, 'UP': 38, 'DOWN': 40, '': 0, '': -1
            }
            if (valType == 'code') {
                let keyname = ''
                for (let [n, c] of Object.entries(shortcutCode)) {
                    c == val && (keyname = n)
                }
                return keyname
            } else {
                let code = -1
                for (let [n, c] of Object.entries(shortcutCode)) {
                    n == val && (code = c)
                }
                return code
            }
        },
        style: `
        code {padding:2px 4px;font-size:90%;font-weight:bold;color:#c7254e;background-color:#f9f2f4;border-radius:4px;}
        .list-panel {position:absolute;top: 100px;left: 50%;background:#fff8e7;padding:15px 20px;border-radius:10px;box-shadow:0 0 10px #666;border:1px solid #591804;z-index:9999;}
        .list-panel .list-c {width:45%;}
        .list-panel .list-c textarea {box-sizing:border-box;padding:0;margin:0;height:200px;width:100%;resize:none;}
        .list-panel .list-desc {margin-top:5px;font-size:9px;color:#666;cursor:help;text-decoration: underline;}
        .list-panel .list-c > p:first-child {font-weight:bold;font-size:14px;margin-bottom:10px;}
        .table-keyword {margin-top:10px;width:200px;}
        .table-keyword tr td:last-child {text-align:center;}
        .table-keyword input[type=text] {width:48px;text-transform:uppercase;text-align:center;}
        .table{table-layout:fixed;border-top:1px solid #ead5bc;border-left:1px solid #ead5bc}
        .table-banlist-buttons{margin-top:10px}
        .table thead{background:#591804;border:1px solid #591804;color:#fff}
        .table td,.table th{padding:3px 5px;border-bottom:1px solid #ead5bc;border-right:1px solid #ead5bc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
        .shortcut-desc {width:120px;margin-left:20px;padding-top:6px}
        .shortcut-desc p {margin-bottom:5px;}

        `
    }
    /**
     * 配置备份模块
     * @name BackupModule
     * @description 提供配置的导入,导出功能
     */
    const BackupModule = {
        name: 'BackupModule',
        title: '配置备份',
        backupItems: [],
        initFunc() {
            /**
             * 导入导出设置面板
             */
            const _this = this
            // 在设置面板上添加按钮
            script.getModule('SettingPanel').addButton({
                id: 'backup_panel',
                title: '导入/导出',
                desc: '导入/导出配置字符串,包含设置,黑名单,标记名单等等'
            })
            /**
             * Bind:Click
             * 导入导出面板
             */
            $('body').on('click', '#backup_panel', function () {
                if($('#export_panel').length > 0) return
                $('#setting_cover').append(`
                    <div id="export_panel" class="list-panel animated fadeInUp">
                        <a href="javascript:void(0)" class="setting-close">×</a>
                        <div class="ep-container">
                            <div>
                                <p><b>选择导出的设置</b></p>
                                <div id="export_panel_cb">
                                    <p><label><input type="checkbox" id="cb_export_setting" checked="checked"> 配置</label></p>
                                </div>
                                <br>
                                <p><button id="export__data">导出</button> <button id="import__data">导入</button></p>
                            </div>
                            <div>
                                <p>
                                    <b class="help" help="【导出】\n选择要导出的内容,点击导出,复制以下字符串用于备份,分享等\n【导入】\n将字符串复制到以下输入框中,点击导入,将会自动导入字符串中包含的内容">字符串</b>
                                    <label><input type="checkbox" id="cb_export_encode" checked="checked"> Base64编码</label>
                                </p>
                                <textarea id="export_str" rows="9"></textarea>
                                <p><a href="https://gf.qytechs.cn/zh-CN/scripts?q=NGA%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C%E6%8F%92%E4%BB%B6" target="_blank">使用WebDAV进行配置同步</a></p>
                            </div>
                        </div>
                        <div><p id="export_msg"></p></div>
                    </div>
                `)
                // 加载其他模组备份项
                for (const item of _this.backupItems) {
                    $('#export_panel_cb').append(`
                    <p><label><input type="checkbox" id="cb_export_${item.writeKey}" checked="checked"> ${item.title}</label></p>
                    `)
                }
                /**
                 * Bind:Click
                 * 导出配置
                 */
                $('#export__data').click(function(){
                    let exportItems = []
                    // 基础配置
                    if ($('#cb_export_setting').prop('checked')) {
                        exportItems.push('setting')
                    }
                    // 其他模组备份项
                    for (const item of _this.backupItems) {
                        const $c = $(`#cb_export_${item.writeKey}`)
                        if ($c.length > 0 && $c.prop('checked')) {
                            exportItems.push(item.writeKey)
                        }
                    }
                    if (Object.keys(exportItems).length == 0) {
                        $('#export_msg').html('<span style="color:#CC0000">没有选择任何项目可供导出!</span>')
                        return
                    }
                    const backupB64 = _this.export(exportItems, $('#cb_export_encode').prop('checked'))
                    $('#export_str').val(backupB64)
                    $('#export_msg').html(`<span style="color:#009900">导出成功(${_this.calculateSize(backupB64.length)}),请复制右侧字符串以备份</span>`)
                })
                /**
                 * Bind:Click
                 * 导入配置
                 */
                $('#import__data').click(function(){
                    const dataStr = $('#export_str').val()
                    if (dataStr) {
                        try {
                            const importStatus = _this.import(dataStr, $('#cb_export_encode').prop('checked'))
                            importStatus && $('#export_msg').html('<span style="color:#009900">导入成功,刷新浏览器以生效</span>')
                        } catch (err){
                            script.printLog(`JSON解析失败: ${err}`)
                            $('#export_msg').html('<span style="color:#CC0000">字符串有误,解析失败!</span>')
                        }
                    }
                })
            })
        },
        addItem(item) {
            this.backupItems.push(item)
        },
        // 字符串版本转数字
        vstr2num(str) {
            let num = 0
            str.split('.').forEach((n, i) => num += i < 2 ? +n * 1000 / Math.pow(10, i) : +n)
            return num
        },
        calculateSize(num) {
            if (num == 0) return '0 B'
            let k = 1024
            let sizeStr = ['B','KB','MB','GB']
            let i = 0
            for(let l=0;l<8;l++){
                if(num / Math.pow(k, l) < 1) break
                i = l
            }
            return (num / Math.pow(k, i)).toFixed(2) + ' ' + sizeStr[i]
        },
        export(items, encode=true) {
            const exportData = {
                name: 'yamibo-BBS-SCRIPT',
                ver: script.getInfo().version,
                exportDate: new Date().toLocaleString(),
                timestamp: new Date().getTime()
            }
            Array.isArray(items) || (items = [items])
            // 基础配置
            if (items.includes('setting') || items.includes('*')) {
                exportData['setting'] = script.setting.normal
                exportData['advanced_setting'] = script.setting.advanced
            }
            // 其他模组备份项
            for (const item of this.backupItems) {
                if (items.includes(item.writeKey) || items.includes('*')) {
                    exportData[item.writeKey] = item.module[item.valueKey]
                }
            }
            const exportDataStr = JSON.stringify(exportData)
            return encode ? this.Base64.encode(exportDataStr) : exportDataStr
        },
        import(dataStr, isEncode=true) {
            dataStr = isEncode ? this.Base64.decode(dataStr) : dataStr
            let obj = JSON.parse(dataStr)
            const unsupported = '3.3.0'
            const currentVer = script.getInfo().version
            const objVer = this.vstr2num(obj.ver)
            if (objVer != 0 && objVer > this.vstr2num(currentVer)) {
                script.popMsg(`此配置是由更高版本(v${obj.ver})的脚本导出,请升级您的脚本 <a title="更新地址" href="https://gf.qytechs.cn/zh-CN/scripts/553252-yamibo%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C" target="_blank">[脚本地址]</a>`, 'warn')
                return
            }
            if (objVer != 0 && objVer < this.vstr2num(unsupported)) {
                script.popMsg(`此配置是由低版本(v${obj.ver})的脚本导出,当前版本(v${currentVer})已不支持!`, 'err')
                return
            }
            let confirm = window.confirm('此操作会覆盖你的配置,确认吗?')
            if (!confirm) return
            if (Object.keys(obj).includes('setting')) {
                obj.setting && (script.setting.normal = obj.setting)
                obj.advanced_setting && (script.setting.advanced = obj.advanced_setting)
                script.setValue('yamibo_setting', JSON.stringify(script.setting.normal))
                script.setValue('yamibo_advanced_setting', JSON.stringify(script.setting.advanced))
            }
            // 其他模组备份项
            for (const item of this.backupItems) {
                if (Object.keys(obj).includes(item.writeKey)) {
                    item.module[item.valueKey] = obj[item.writeKey]
                    script.setValue(`yamibo_${item.writeKey}`, JSON.stringify(obj[item.writeKey]))
                }
            }
            script.popMsg('导入成功,刷新页面生效')
            return true
        },
        /**
         * Base64互转
         */
        Base64: {
            encode: (str) => {
                return window.btoa(unescape(encodeURIComponent(str)))
            },
            decode: (str) => {
                return decodeURIComponent(escape(window.atob(str)))
            }
        },
        style: `
        .ep-container{display:flex;width:300px;margin-bottom: 7px;}
        .ep-container p {margin-bottom:10px;}
        .ep-container >div{width:50%;}
        .ep-container textarea {width: 100%;padding:0;margin:0;resize:none;}
        `
    }
    /**
     * 赏面板
     * @name RewardPanel
     * @description 别问,好活当赏
     */
    const RewardPanel = {
        name: 'RewardPanel',
        title: '赏面板',
        initFunc() {
            /**
             * 打赏
             */
            script.getModule('SettingPanel').addButton({
                id: 'reward',
                title: '<span style="margin-right:3px">¥</span>赏',
                desc: '好活当赏'
            })
            /**
             * Bind:Click
             * 打赏面板
             */
            $('body').on('click', '#reward', function () {
                $('#setting_cover').append(`
                <div class="list-panel reward-panel animated fadeInUp">
                    <a href="javascript:void(0)" class="setting-close">×</a>
                    <div class="reward-info">
                        <p><b>喜欢此脚本请可以去作者<a href="${script.getInfo().github}" target="_blank"><b>Github</b></a>点个⭐️</p>
                        <p>如果觉得脚本好用<span class="delete-line">摸到鱼了</span>,也可以请作者喝杯☕意思意思,打多少零看缘分😎</p>
                        <p>如若有功能需求或者建议,欢迎在社区进行反馈</p>
                    </div>
                    <div class="flex">
                        <div class="list-c"><img src="${IMG_REWARD_ALIPAY}"></div>
                        <div class="list-c"><img src="${IMG_REWARD_WXPAY}"></div>
                    </div>
                    <div class="source">
                        <a href="${script.getInfo().github}" target="_blank"><img alt="Mozilla Add-on" src="https://img.shields.io/github/stars/kisshang1993/NGA-BBS-Script?label=Star&style=social"></a>
                        <a href="${script.getInfo().update}" target="_blank"><img alt="Mozilla Add-on" src="https://img.shields.io/badge/Greasy%20Fork-NGA优化摸鱼体验-brightgreen"></a>
                    </div>
                </div>
            `)
            })
        },
        style: `
        .reward-panel {width:500px;}
        .reward-panel .reward-info {display:block;font-size:15px;margin-bottom:20px;line-height:20px;}
        .reward-panel .reward-info p {margin-bottom:5px;}
        .delete-line {text-decoration:line-through;color:#666;}
        .reward-panel .list-c {width:50%;}
        .reward-panel .list-c:first-child {margin-right:15px;}
        .reward-panel .list-c>img {width:100%;height:auto;}
        .reward-panel .source {margin-top:15px;}
        .reward-panel .source > a {margin-right:10px;}
        `
    }
    /**
     * 隐藏头像模块
     * @name HideAvatar
     * @description 此模块提供了可以快捷键切换显示隐藏头像
     */
    const HideAvatar = {
        name: 'HideAvatar',
        title: '隐藏头像',
        setting: {
            shortCutCode: 81, // Q
            type: 'normal',
            key: 'hideAvatar',
            default: true,
            title: '隐藏头像',
            menu: 'left'
        },
        apply(on = !!script?.setting?.normal?.hideAvatar) {
            $('body').toggleClass('hld-hide-avatar', on);
        },
        renderFormsFunc($el) {
            try {
                this.apply();
            } catch (e) {
                console.error('[HideAvatar] renderFormsFunc error:', e);
            }
        },
        shortcutFunc: {
             hideAvatar() {
                try {
                    script.setting.normal.hideAvatar = !script.setting.normal.hideAvatar;
                    HideAvatar.apply(script.setting.normal.hideAvatar);
                    script.popNotification(`${script.setting.normal.hideAvatar ? '隐藏' : '显示'}头像`);
                } catch (e) {
                    console.error('[HideAvatar] shortcut error:', e);
                }
            }
        },
        // 异步样式:基于 body 类,天然适配后续插入的节点
        asyncStyle() {
            // 不再依赖内联切换 display,而是通过类控制;这样更稳
            return `
        /* 开关:给 <body> 加上 .hld-hide-avatar 即可隐藏 */
        .hld-hide-avatar .avatar,
        .hld-hide-avatar .avtm img,
        .hld-hide-avatar img.user_avatar {
        display: none !important;
        }
        .hld-hide-avatar .c1 {
        background-image: none !important;
        }

        /* 可选:避免头像占位高度过大时出现空白(按需打开)
        .hld-hide-avatar .avatar { height: 0 !important; overflow: hidden !important; }
        */
            `;
        }
    }
    /**
     * 隐藏表情模块
     * @name HideSmile
     * @description 此模块提供了可以快捷键切换显示隐藏表情
     *              其中隐藏的表情会用文字来替代
     */
    const HideSmile = {
        name: 'HideSmile',
        title: '隐藏表情',
        setting: {
            shortCutCode: 87, // W
            type: 'normal',
            key: 'hideSmile',
            default: false,
            title: '隐藏表情',
            menu: 'left'
        },
        renderFormsFunc($el) {
            $el.find('.common img').each(function () {
                const classs = $(this).attr('class')
                if (classs && classs.includes('smile') && !$(this).is(':hidden')) {
                    const alt = $(this).attr('alt')
                    const $alt = $('<span class="smile_alt_text">[' + alt + ']</span>')
                    script.setting.normal.hideSmile ? $(this).hide() : $alt.hide()
                    $(this).after($alt)
                }
            })
        },
        shortcutFunc: {
            hideSmile() {
                if (script.setting.normal.hideSmile || script.setting.advanced.dynamicEnable) {
                    $('.common img').each(function () {
                        const classs = $(this).attr('class');
                        if (classs && classs.includes('smile')) $(this).toggle()
                    })
                    $('.smile_alt_text').toggle()
                    script.popNotification(`${$('.smile_alt_text:hidden').length > 0 ? '显示' : '隐藏'}表情`)
                }
            }
        }
    }
    /**
     * 贴内图片缩放模块
     * @name ImgResize
     * @description 此模块提供了可以调整贴内图片的尺寸
     */
    const ImgResize = {
        name: 'ImgResize',
        title: '贴内图片缩放',
        settings: [{
            type: 'normal',
            key: 'imgResize',
            title: '贴内图片缩放',
            default: true,
            menu: 'left'
        }, {
            type: 'advanced',
            key: 'imgResizeWidth',
            default: 200,
            title: '图片缩放宽度',
            desc: '贴内图片缩放的宽度,高度自适应,单位px',
            menu: 'left'
        }],
        renderFormsFunc($el) {
            $el.find('.common img').each(function () {
                const classs = $(this).attr('class')
                if ((!classs || !classs.includes('smile')) && script.setting.normal.imgResize) {
                    $(this).addClass('img-resize').attr('hld-img-resize', 'ok').attr('title', '点击大图显示')
                }
            })
        },
        asyncStyle: () => {
            return `
            .img-resize {outline:none !important;outline-offset:'';cursor:alias;min-width:auto !important;min-height:auto !important;max-width:${script.setting.advanced.imgResizeWidth || 200}px !important;max-height:none !important;margin:5px;}
            `
        }
    }
    /**
     * 隐藏图片模块
     * @name HideImage
     * @description 此模块提供了可以快捷键切换显示隐藏图片
     *              其中隐藏的图片会用一个按钮来替代
     */
    const HideImage = {
        name: 'HideImage',
        title: '隐藏图片',
        setting: {
            shortCutCode: 69, // E
            type: 'normal',
            key: 'hideImage',
            default: false,
            title: '隐藏贴内图片',
            menu: 'left'
        },
        renderFormsFunc($el) {
        try {
            // 在帖子正文区域内筛图:兼容 .common / .t_f / .pcb 等容器
            const $container = $el; // 这里 $el 就是一行/一个帖子块
            $container.find('.common img, .t_f img, .pcb img').each(function () {
            const $img = $(this);

            // 1) 跳过:表情图、已隐藏过、已处理过或 display:none 的非目标
            const cls = $img.attr('class') || '';
            if (cls.includes('smile') || $img.attr('hld-hideimg') === 'ok') return;

            // 2) 取原图地址:优先 zoomfile/file,其次去掉 .medium.jpg
            const srcNow   = $img.attr('src') || '';
            const zoomfile = $img.attr('zoomfile');
            const fileAttr = $img.attr('file');
            const fullSrc  = zoomfile || fileAttr || srcNow.replace('.medium.jpg', '');

            // 某些主题会给图设置 pointer-events:none;不改它
            // 但我们在初次处理时替换成原图
            if (fullSrc && srcNow !== fullSrc) {
                $img.attr('src', fullSrc);
            }

            // 标记已处理,避免二次重复
            $img.addClass('img-postimg').attr('hld-hideimg', 'ok');

            // 3) 找对应的 tip menu(Discuz zoom 菜单)
            //    规则:id="aimg_1443855" -> 菜单 id="aimg_1443855_menu"
            const imgId = $img.attr('id');
            const $tip  = imgId ? $('#' + imgId + '_menu') : $();

            // 4) 创建切换按钮
            let $btn = $('<button class="switch-img" type="button" style="display:none;margin:4px 0 0 0;">图</button>');

            // 点击时同时切换图片与 tip 可见性
            $btn.on('click', function () {
                const hidden = $img.is(':hidden');
                $img.toggle(!hidden);   // 如果原来 hidden,就显示
                if ($tip && $tip.length) $tip.toggle(!hidden);
                $(this).text(hidden ? '隐藏' : '图');
            });

            // 5) 根据设置决定初始状态
            if (script.setting?.normal?.hideImage) {
                $img.hide();
                if ($tip && $tip.length) $tip.hide();
                $btn.text('图').show(); // 默认隐藏 -> 按钮显示
            } else {
                $btn.text('隐藏').hide(); // 默认显示 -> 按钮可按需隐藏(你也可以改成显示)
            }

            // 6) 放在图片后面(紧邻),确保操作目标就是这张图
            $img.after($btn);

            // 7) 日志
            // console.log('[hideImage] processed:', { id: imgId, src: fullSrc });
            });
        } catch (e) {
            console.error('[hideImage] renderFormsFunc error:', e);
        }
        },
        shortcutFunc: {
            hideImage() {
                if (!script.setting.advanced.dynamicEnable) return
                if ($('.img-postimg:hidden').length < $('.switch-img').length) {
                    $('.img-postimg').hide()
                    $('.switch-img').text('图').show()
                    script.popNotification(`隐藏图片`)
                    return
                }
                $('.img-postimg').each(function () {
                    $(this).toggle()
                    $(this).is(':hidden') ? $(this).next('button.switch-img').show() : $(this).next('button.switch-img').hide()
                })
                script.popNotification(`${$('.switch-img:hidden').length > 0 ? '显示' : '隐藏'}图片`)
            }
        }
    }
    /**
     * 隐藏签名模块
     * @name HideSign
     * @description 此模块提供了可以配置默认隐藏签名
     */
    const HideSign = {
        name: 'HideSign',
        title: '隐藏签名',
        setting: {
            type: 'normal',
            key: 'hideSign',
            default: true,
            title: '隐藏签名',
            menu: 'left'
        },
        renderFormsFunc($el) {
            script.setting.normal.hideSign && $el.find('.sign, .sigline').css('display', 'none')
        }
    }
    /**
     * 隐藏版头模块
     * @name HideHeader
     * @description 此模块提供了可以配置默认隐藏版头
     *              以及一个高级配置可选一起隐藏顶部背景
     */
    const HideHeader = {
        name: 'HideHeader',
        title: '隐藏版头',
        settings: [{
            type: 'normal',
            key: 'hideHeader',
            default: true,
            title: '隐藏版头/版规/子版入口',
            menu: 'left'
        }, {
            type: 'advanced',
            key: 'hideCustomBg',
            default: true,
            title: '隐藏背景图片',
            desc: '选中时: 隐藏版头的同时顶部背景图片\n取消时: 无操作',
            menu: 'right'
        }],
        renderAlwaysFunc($el) {
            //隐藏版头
            if (script.setting.normal.hideHeader && $('#switch_header').length == 0) {
                $('#toppedtopic').hide()
                $('#toppedtopic').length > 0 && $('#sub_forums').hide()
                let $toggleHeaderBtn = $('<button style="position: absolute;right: 16px;" id="switch_header">切换显示版头</button>')
                $toggleHeaderBtn.click(() => $('#toppedtopic, #sub_forums').toggle())
                $('#toptopics > div > h3').append($toggleHeaderBtn)
            }
            if(script.setting.normal.hideHeader && script.setting.advanced.hideCustomBg) {
                $('#custombg').hide()
                $('#mainmenu').css('margin', '0px')
            }
        },
        style: `
        #threadlist .toptopicsRight {float:none;width:auto;}
        .topicrowsLeftC {margin-right:0;}
        `
    }
    /**
     * Excel模块
     * @name ExcelMode
     * @description 此模块提供了可以快捷键切换Excel模式
     *              以及一个高级配置可选更改Excel左侧序号的类型
     */
    const ExcelMode = {
        name: 'ExcelMode',
        title: 'Excel模式',
        settings: [{
            shortCutCode: 82, // R
            type: 'normal',
            key: 'excelMode',
            default: false,
            title: 'Excel模式',
            menu: 'left'
        }, {
            type: 'advanced',
            key: 'excelTheme',
            default: 'tencent',
            options: [{
                label: '腾讯文档',
                value: 'tencent'
            }, {
                label: 'WPS',
                value: 'wps'
            }, {
                label: 'Office',
                value: 'office'
            }],
            title: 'Excel皮肤',
            desc: 'Excel的皮肤\n腾云文档是矢量图形绘制,适应各种分辨率,不会失真,推荐优先使用\nWPS与Office为图片拼接而成,分辨率为1080P,高于此分辨率可能会失真',
            menu: 'left'
        }, {
            type: 'advanced',
            key: 'excelNoMode',
            default: false,
            title: 'Excel左列序号',
            desc: 'Excel最左列的显示序号,此策略为尽可能的更像Excel\n选中时: Excel最左栏为从1开始往下,逐行+1\n取消时: Excel最左栏为原始的回帖数\n*此功能仅在贴列表有效',
            menu: 'left'
        }, {
            type: 'advanced',
            key: 'excelTitle',
            default: '工作簿1',
            title: 'Excel覆盖标题',
            desc: 'Excel模式下标签栏的名称, 如留空, 则显示原始标题',
            menu: 'left'
        }],
        beforeUrl: window.location.href,
        initFunc() {
            // 生成列标题字母列表
            const columnLetters = () => {
                let capital = []
                let columnLetters = []
                for (let i=65;i<91;i++) capital.push(String.fromCharCode(i))
                Array('', 'A', 'B', 'C').forEach(n => capital.forEach(c => columnLetters.push(`${n}${c}`)))
                return columnLetters
            }
            if (script.setting.advanced.excelTheme == 'tencent') {
                // 腾讯文档元素
                // 插入Excel头部
                $('body').append(`
                <div class="excel-div excel-header">
                    <div class="excel-titlebar">
                        <div class="excel-titlebar-content excel-icon24" style="margin:2px 2px 2px 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_1')});"></div>
                        <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div style="height: 24px;border-right: 1px solid rgb(0, 0, 0);opacity: 0.06;margin: 0 12px;vertical-align: middle;"></div>
                        <div class="excel-titlebar-title"></div>
                        <div class="excel-titlebar-content excel-icon16" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_3')});"></div>
                        <div class="excel-titlebar-content excel-icon16" style="margin-left: 12px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_4')});"></div>
                        <div class="excel-titlebar-content excel-icon16" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_5')});"></div>
                        <div style="margin-left: 5px;font-size: 12px;line-height: 20px;height: 18px;;color: #000;opacity: 0.48;font-weight:400;">上次修改是在2小时前进行的</div>
                        <div style="flex-grow: 1;"></div>
                        <div style="height: 24px;border-right: 1px solid rgb(0, 0, 0);opacity: 0.06;margin: 0 12px;vertical-align: middle;"></div>
                        <div style="width:28px;height:28px;border-radius: 4px;background: #e9e9e9;text-align: center;line-height: 32px;">🐟︎</div>
                    </div>
                    <div class="excel-toolbar">
                        ${Array.from({length: 4}, (_, i) => '<div class="excel-titlebar-content excel-icon20" style="margin:0 6px;background-image:url(' + getExcelTheme(script.setting.advanced.excelTheme, "icon_"+(10+i)) + ');"></div>').join('')}
                        <div style="height: 16px;border-right: 1px solid rgb(0, 0, 0);opacity: 0.06;margin: 0 4px;vertical-align: middle;"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 8px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_14')});"></div>
                        <div style="padding: 0 2px;">插入</div>
                        <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div style="height: 16px;border-right: 1px solid rgb(0, 0, 0);opacity: 0.06;margin: 0 8px;vertical-align: middle;"></div>
                        <div style="padding: 0 30px 0 4px;">常规</div>
                        <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 12px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_15')});"></div>
                        <div style="margin-left: 1px;">
                            <div class="excel-titlebar-content excel-icon12" style="transform: rotate(180deg);background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                            <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        </div>
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.06;margin: 0 4px;vertical-align: middle;"></div>
                        <div style="padding: 0 4px 0 16px;">默认字体</div>
                        <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div style="padding: 0 4px 0 13px;">10</div>
                        <div class="excel-titlebar-content excel-icon12" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_16')});"></div>
                        <div class="excel-titlebar-pick">
                            <div class="excel-titlebar-content excel-icon20" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_17')});"></div>
                            <div class="excel-titlebar-indication" style="background-color: #000;"></div>
                        </div>
                        <div class="excel-titlebar-content excel-icon12" style="margin-left: 4px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div class="excel-titlebar-pick">
                            <div class="excel-titlebar-content excel-icon20" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_18')});"></div>
                            <div class="excel-titlebar-indication" style="background-color: #8cddfa;"></div>
                        </div>
                        <div class="excel-titlebar-content excel-icon12" style="margin-left: 4px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_19')});"></div>
                        <div class="excel-titlebar-content excel-icon12" style="margin-left: 2px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_20')});"></div>
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.06;margin: 0 10px;vertical-align: middle;"></div>
                        ${Array.from({length: 4}, (_, i) => '<div class="excel-titlebar-content excel-icon20" style="background-image:url(' + getExcelTheme(script.setting.advanced.excelTheme, "icon_"+(21+i)) + ');"></div><div class="excel-titlebar-content excel-icon12" style="margin-left: 2px;margin-right: '+ (i==3?'0':'10') +'px;background-image:url(' + getExcelTheme(script.setting.advanced.excelTheme, "icon_2") + ');"></div>').join('')}
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.06;margin: 0 10px;vertical-align: middle;"></div>
                        <div class="excel-titlebar-content excel-icon20" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_25')});"></div>
                        <div class="excel-titlebar-content excel-icon12" style="margin-left: 4px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.06;margin: 0 10px;vertical-align: middle;"></div>
                        ${Array.from({length: 4}, (_, i) => '<div class="excel-titlebar-content excel-icon20" style="background-image:url(' + getExcelTheme(script.setting.advanced.excelTheme, "icon_"+(26+i)) + ');"></div><div class="excel-titlebar-content excel-icon12" style="margin-left: 2px;margin-right: '+ (i==3?'0':'10') +'px;background-image:url(' + getExcelTheme(script.setting.advanced.excelTheme, "icon_2") + ');"></div>').join('')}
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.06;margin: 0 10px;vertical-align: middle;"></div>
                        <div class="excel-titlebar-content excel-icon20" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_20')});"></div>
                        <div style="flex-grow: 1;"></div>
                    </div>
                    <div class="excel-formulabar">
                        <div style="border-right: 1px solid #e0e2e4;color: #777;text-align: center;width: 50px;font-size: 12px;height: 25px;line-height: 25px;font-weight:400;">A1</div>
                    </div>
                    <div class="excel-h4">
                        <div class="excel-sub"><div></div></div>
                        ${(columnLetters().map(c => '<div class="excel-column">'+c+'</div>')).join('')}
                    </div>
                </div>
                `)
                // 插入Excel尾部
                $('body').append(`
                    <div class="excel-div excel-footer">
                        <div class="excel-icon24" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_33')});"></div>
                        <div class="excel-icon24" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_34')});"></div>
                        <div class="excel-sheet-tab">
                            <div class="excel-sheet-name">
                                <div>工作表1</div>
                                <div class="excel-icon12" style="margin-left: 4px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                            </div>
                            <div class="excel-sheet-underblock"></div>
                        </div>
                        <div style="flex-grow: 1;"></div>
                        <div class="excel-icon24" style="margin-left: 10px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_35')});"></div>
                        <div class="excel-icon12" style="margin-left: 2px;background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_2')});"></div>
                        <div style="height: 16px;border-right: 1px solid #000;opacity: 0.12;margin: 0 10px;vertical-align: middle;"></div>
                        <div class="excel-icon24" style="background-image:url(${getExcelTheme(script.setting.advanced.excelTheme, 'icon_36')});"></div>
                        <div class="excel-footer-item" style="font-size: 20px;margin-left:20px;">-</div>
                        <div class="excel-footer-item" style="font-weight: 400">100%</div>
                        <div class="excel-footer-item" style="font-size: 20px;">+</div>
                        <div style="width:10px;"></div>
                    </div>
                `)
            } else {
                // WPS与Office元素
                // 插入Excel头部
                $('body').append(`
                    <div class="excel-div excel-header">
                        <div class="excel-h1">
                            <div class="excel-title">${script.setting.advanced.excelTitle || document.title} - Excel</div>
                            <img class="excel-img-h1-l1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_L_1')}">
                            <img class="excel-img-h1-r1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_R_1')}">
                        </div>
                        <div class="excel-h2">
                            <img class="excel-img-h2-l1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_L_2')}">
                            <img class="excel-img-h2-r1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_R_2')}">
                        </div>
                        <div class="excel-h3">
                            <img class="excel-img-h3-l1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_L_3')}">
                            <img class="excel-img-h3-r1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'H_R_3')}">
                            <div class="excel-fx"></div>
                        </div>
                        <div class="excel-h4">
                            <div class="excel-sub"><div></div></div>
                            ${(columnLetters().map(c => '<div class="excel-column">'+c+'</div>')).join('')}
                        </div>
                    </div>
                `)
                // 插入Excel尾部
                $('body').append(`
                    <div class="excel-div excel-footer">
                        <div class="excel-f1">
                            <img class="excel-img-f1-l1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'F_L_1')}">
                            <img class="excel-img-f1-r1" src="${getExcelTheme(script.setting.advanced.excelTheme, 'F_R_1')}">
                        </div>
                        <div class="excel-f2">
                        <img class="excel-img-fl2" src="${getExcelTheme(script.setting.advanced.excelTheme, 'F_L_2')}">
                        <img class="excel-img-fr2" src="${getExcelTheme(script.setting.advanced.excelTheme, 'F_R_2')}">
                        </div>
                    </div>
                `)
            }

            $('#excel_setting').click(()=>$('#setting_cover').css('display', 'block'))
            $('#mainmenu .half').parent().append($('#mainmenu .half').clone(true).addClass('half-clone').text($('#mainmenu .half').text().replace('你好', '')))
            if(script.setting.normal.excelMode) {
                this.switchExcelMode()
            }
        },
        renderAlwaysFunc($el) {
            $('.excel-theme-' + script.setting.advanced.excelTheme).length == 0 && $('body').addClass('excel-theme-' + script.setting.advanced.excelTheme)
            if(script.setting.normal.excelMode && window.location.href != this.beforeUrl) {
                this.beforeUrl = window.location.href
                if(this.beforeUrl.includes('thread.php') || this.beforeUrl.includes('read.php')) {
                    $('.excel-body').length == 0 && $('body').addClass('excel-body')
                }else {
                    $('.excel-body').length > 0 && $('body').removeClass('excel-body')
                }
                $('body').toggleClass('excel-original-no', !script.setting.advanced.excelNoMode)
            }
            // Excel Title
            if ($('.excel-body').length > 0) {
                const excelTitle = script.setting.advanced.excelTitle
                if (excelTitle) {
                    $(document).attr('title') != excelTitle && $(document).attr('title', excelTitle)
                }
                $('.excel-titlebar-title').html(excelTitle || $(document).attr('title'))
                $('#excel_icon').length == 0 && $('head').append(`<link id= "excel_icon" rel="shortcut icon" type="image/png" href="${IMG_EXCEL_ICON}" />`)
            }
        },
        renderThreadsFunc($el){
            try {
                const $scope = $el;
                const $tr = $scope;
                // 避免重复插入:如果第一格已经是 c0 就跳过
                const already = $tr.children(':first').is('td.c0');
                if (!already) {
                    $tr.prepend('<td class="c0"></td>');
                }
            } catch (e) {
                console.error('[plhin] renderFormsFunc error:', e);
            }
        },
        renderFormsFunc($el) {
            try {
            const $tr = $el; // 传进来的就是当前行

            // 1) 行首补一个 <td class="c0">
            const already = $tr.children(':first').is('td.c0');
            if (!already) {
                $tr.prepend('<td class="c0"></td>');
            }

            // 2) 定位到 td.pls(注意第二行可能没有,因为上面的 td 用了 rowspan=2)
            const $plsTd = $tr.children('td.pls').first();
            if ($plsTd.length === 0) {
                console.log('[threads] no td.pls on this row (likely the 2nd row of a rowspan)');
                return;
            }

            // 3) 在 td.pls 内找 div.pls.cl.favatar
            const $favatar = $plsTd.find('div.pls.cl.favatar').first();
            if ($favatar.length === 0) {
                console.warn('[threads] div.pls.cl.favatar not found inside td.pls');
                return;
            }

            // 4) 给它的“直属子元素”中,除第一个外都加类(避免重复加)
            const $children = $favatar.children();
            const $targets = $children.not(':first').not('.displaynoneInExcel');
            $targets.addClass('displaynoneInExcel');
            } catch (e) {
            }

        },
        shortcutFunc: {
            excelMode() {
                if (script.setting.normal.excelMode || script.setting.advanced.dynamicEnable) {
                    this.switchExcelMode()
                    script.popNotification($('.excel-body').length > 0 ? 'Excel模式' : '普通模式')
                }
            }
        },
        /**
         * 切换Excel模式
         * @method switchExcelMode
         */
        switchExcelMode: () => {
            $('body').toggleClass('excel-body')
            !script.setting.advanced.excelNoMode && $('body').addClass('excel-original-no')
            script.setting.normal.darkMode && script.popMsg('Excel模式与暗黑模式不兼容, 请勿重合使用', 'warn')
        },
        style: `
        /* WPS风格 */
        .c0{display:none;}
        #nv_forum.excel-body, #nv_home.excel-body, .excel-body #pt a{color:#1a3959;}
        .excel-body i.pstatus{color:#7797bd;}
        .excel-body .c0{display:table-cell;}
        .excel-body .pil.cl, .excel-body .pls.cl.favatar i, .excel-body .tbox.theatlevel, .excel-body .sign, .excel-body .cm, .excel-body .rate, .excel-body .psth.xs1, .excel-body .plc.plm, .excel-body .plc .po.hin, .excel-body .tns.xg2, .excel-body .pti, .excel-body .displaynoneInExcel{display:none;}
        .excel-body .tl .bm_c tr:hover th,.excel-body .tl .bm_c tr:hover td{background-color:#fff}
        #nv_forum.excel-body.excel-theme-tencent, #nv_home.excel-body.excel-theme-tencent {margin-top: 145px;}
        .excel-body .tl th {border-bottom: 1px solid #ebebeb;border-right: 1px solid #ebebeb;}
        .excel-header, .excel-footer, .excel-setting, .half-clone {display: none;}
        .excel-header>div, .excel-footer>div {position: relative;box-sizing: border-box;}
        .excel-header img, .excel-footer img {position: absolute;}
        .excel-header {border-bottom:1px solid #bbbbbb;}
        .excel-title {display:none;}
        .excel-h1 {height:30px;background:#f3f5f8;border-bottom:1px solid #c5cbd6;}
        .excel-h2 {height:102px;background:#f4f4f4;}
        .excel-img-h1-l1, .excel-img-h2-l1, .excel-img-f1-l1, .excel-img-fl2 {top:0;left:0;}
        .excel-img-h1-r1, .excel-img-h2-r1, .excel-img-f1-r1, .excel-img-fr2 {top:0;right:0;}
        .excel-h3 {height:44px;background:#e8e8e8;box-shadow: inset 0 3px 5px #d9d9d9;}
        .excel-img-h3-l1 {top:12px;left:0;}
        .excel-img-h3-r1 {toP:8px;right:0;}
        .excel-fx {position: absolute;top:12px;left:253px;right:45px;height:24px;box-sizing: border-box;border:1px solid #cccccc;border-radius:4px;background:#ffffff;}
        .excel-h4 {height:21px;display:flex;overflow: hidden;}
        .excel-h4 > div {height:21px;border-right:1px solid #c8c8c8;box-sizing:border-box;flex-shrink: 0;}
        .excel-sub {width:34px;position: relative;}
        .excel-sub > div {position: absolute;right:4px;bottom:4px;width: 0px;height: 0px;border-top: 6px solid transparent;border-left: 6px solid transparent;border-right: 6px solid #b8b8b8;border-bottom: 6px solid #b8b8b8;}
        .excel-column {width: 72px;line-height:21px;text-align:center;color:#444444;font-family: sans-serif;font-weight:100;font-size:14px;}
        .excel-f1 {height:22px;background:#e8e8e8;}
        .excel-f2 {height:28px;background:#f4f4f4;}
        .excel-body {background:#fff !important;}
        .excel-body .bm {background:#fff !important;}
        .excel-body #wp {width: 100%;}
        .excel-body .oyheader {display:none;}
        .excel-body #hd {display:none;}
        .excel-body #ft {display:none;}
        .excel-body .by em {display:none;}
        .excel-body tr .num {display:none;}
        .excel-body #mainmenu {position: fixed;top: 5px;right: 75px;width: 425px;z-index: 98;}
        .excel-body #mainmenu .right {float:none;}
        .excel-body #mainmenu .stdbtn {background:none;box-shadow:none;}
        .excel-body #mainmenu .half {display:none;}
        .excel-body .icn {display:none;}
        .excel-body #mainmenu .half-clone {display:block;width: 150px;text-align: right;overflow: hidden;text-overflow:ellipsis;white-space: nowrap;}
        .excel-body #mainmenu .half {color:#f4f4f4 !important;}
        .excel-body #mainmenu .stdbtn a:hover {background:none;text-decoration:underline;color:#2c5787 !important;}
        .excel-body #mainmenu .mmdefault.cell input {padding:0;margin:0;background:#ededed;border:1px solid #c9d0dc;border-radius:10px;box-shadow:none;font-size:13px !important;}
        .excel-body #mainmenu, .excel-body #mainmenu .half, .excel-body #mainmenu td a, .excel-body #mainmenu .stdbtn .innerbg, .excel-body #mainmenu, .excel-body #mainmenu .stdbtn a, .excel-body #mainmenu .stdbtn .td {height: 20px !important;line-height: 20px !important;padding: 0 5px !important;background:none;color:#424242 !important;}
        .excel-body #mainmenu .innerbg > div:nth-child(2) > div:first-child {display:none;}
        .excel-body .single_ttip2 {position: fixed !important;z-index:999 !important;top:30px !important;border-color:#888;}
        .excel-body #threadlist .th,.excel-body .excel-body #mainmenu, .excel-body .catenew,.excel-body #toptopics,.excel-body #f_pst,.excel-body #pgt,.excel-body .bm.bw0.pgs.cl,.excel-body #m_fopts,.excel-body #b_nav,.excel-body #fast_post_c,.excel-body #custombg,.excel-body #postlist th,.excel-body .r_container,.excel-body #footer,.excel-body .clickextend ,.excel-body #thread_types,.excel-body .bm.bml.pbn {display:none !important;}
        .excel-body #mmc {margin-top:195px;margin-bottom:35px;}
        .excel-body .postBtnPos > div, .excel-body .postBtnPos .stdbtn a {background:#fff !important;border-color:#bbb;}
        .excel-body .excel-div,.excel-body .excel-setting {display:block;}
        .excel-body .excel-setting {position:fixed;width:60px;height:20px;top:5px;right:95px;background:#f2f4f7;z-index:999;}
        .excel-body .excel-setting img {width:20px;height:auto;vertical-align:middle;}
        .excel-body .excel-setting a {margin-left:5px;vertical-align:middle;}
        .excel-body .excel-header {position:fixed;top:0;left:0;height:196px;}
        .excel-body .excel-footer {position:fixed;bottom:0;left:0;height:50px;}
        .excel-body .excel-header, .excel-body .excel-footer {width: 100%;text-align: center;font-size: 16px;font-weight: bold;background:#e8e8e8;color:#337ab7;line-height: 45px;}
        .excel-body .excel-header>img, .excel-body .excel-footer>img{position:absolute;top:0;left:0}
        .excel-body #pt {position:fixed;top:136px;left:261px;margin:0;padding:0;z-index:99;width: 9999px;}
        .excel-body #pt .bm {display:block;border:0;border-radius:0;padding:0;box-shadow:none;background:none;margin-top: 18px;margin-left: 10px;}
        .excel-body #pt .nav_spr span {color:#000;font-size:16px;vertical-align:unset;font-weight:normal;}
        .excel-body #pt .nav_root,.excel-body #pt {background:none;border:none;box-shadow:none;padding:0;color:#000;border-radius:0;font-weight:normal;}
        .excel-body .bm.cl {font-size:14px !important;}
        .excel-body #mainmenu .stdbtn a {font-size:13px !important;}
        .excel-body #threadlist {margin:0;}
        .excel-body .postBtnPos > div {z-index:9991;}
        .excel-body #threadlist {border:none;box-shadow:none;border-radius:0;margin:0;background-color:#fff;counter-reset:num;border-spacing:0;}
        .excel-body #threadlist tbody {border-spacing:0;background-color:#fff;}
        .excel-body .topicrow {border-spacing:0;}
        .excel-body #threadlist td {background:#fff;padding:5px 0;margin:0;border:none;border-right:1px solid #bbbbbb;border-bottom:1px solid #bbbbbb;margin-right:-1px;}
        .excel-body .tl .c0 {width:33px;background:#e8e8e8 !important;}
        .excel-body .tl .c0 a {display:none;color: #777777 !important;font-size: 16px !important;font-family: auto;}
        .excel-body #separatorline {display:none;}
        .excel-body.excel-original-no .tl .c0 a {display:inline-block;}
        .excel-body.excel-original-no .tl .c0 img {width:20px;}
        .excel-body .tl .c0:before {content:counter(num);counter-increment:num;color:#777777;font-size:16px;}
        .excel-body tr .common {padding-left:5px !important;}
        .excel-body tr .common em,.excel-body tr .common i,.excel-body tr .common span,.excel-body tr .common img {display:none;}
        .excel-body .topicrow .c3 {color:#1a3959 !important;}
        .excel-body .topicrow .c3 > div, .excel-body .topicrow .c4 > div {background:#FFF !important;}
        .excel-body .topicrow .c3 > div a, .excel-body .topicrow .c4 > div a {color:#888 !important;}
        .excel-body .block_txt {background:#fff !important;color:#1a3959 !important;border-radius:0;padding:0 !important;min-width:0 !important;font-weight:normal;}
        .excel-body .quote {background:#fff !important;}
        .excel-body #postlist .block_txt {font-weight:bold;}
        .excel-body .topicrow .postdate,.excel-body .topicrow .replydate {display:inline;margin:10px;}
        .excel-body #autopbn {margin:0;border-bottom:1px solid #bbbbbb;}
        .excel-body .country-flag {border:.5px solid rgba(0,0,0,.2);}
        .excel-body #pagebbtm,.excel-body #autopbn .right_ {margin:0;}
        .excel-body #pagebbtm:before {display:block;line-height:35px;width:33px;float:left;content:"#";border-right:1px solid #bbbbbb;color:#777;font-size:16px;background:#e8e8e8;}
        .excel-body #autopbn {line-height:35px;padding:0 5px;}
        .excel-body #autopbn .stdbtn {box-shadow:none;border:none !important;padding:0;padding-left:5px;background:#fff;border-radius:0;font-size:13px !important;}
        .excel-body #autopbn .stdbtn .invert {color:#591804;}
        .excel-body #autopbn {background:#fff;padding:0;border:0;}
        .excel-body #postlist .comment_c .comment_c_1 {border-top-color:#bbbbbb;}
        .excel-body #postlist .comment_c .comment_c_2 {border-color:#bbbbbb;}
        .excel-body #postlist {border:0;box-shadow:none;padding-bottom:0;margin:0;counter-reset:num;}
        .excel-body #postlist td {background:#fff;border-top:1px solid #bbbbbb;border-right:1px solid #bbbbbb;border-bottom:1px solid #bbbbbb;}
        .excel-body #postlist .c0 {width:32px;color:#777;font-size:16px;background:#e8e8e8;text-align:center;}
        .excel-body #postlist .c0:before {content:counter(num);counter-increment:num;}
        .excel-body #postlist .vertmod {background:#fff !important;color:#ccc;}
        .excel-body #postlist a[name="uid"]:before {content:"UID:"}
        .excel-body #postlist .white,.excel-body #postlist .block_txt_c2,.excel-body #postlist .block_txt_c0 {background:#fff !important;color:#777777;}
        .excel-body #postlist .quote {background:#fff;border-color:#bbbbbb;}
        .excel-body #postlist .postrow .postinfob .iconfont,.excel-body #postlist .ogoodbtn a:hover .iconfont {fill: #10273f;}
        .excel-body #postlist .postInfo svg {fill:#10273f !important;}
        .excel-body #postlist .recommendvalue {color:#10273f !important;}
        .excel-body #postlist button {background:#eee;}
        .excel-body #postlist button:active {outline-color:#bbbbbb;}
        .excel-body #postlist .postbox {border:none !important;}
        .excel-body .posterInfoLine {background: #FFF !important;border-bottom-color: #FFF !important;}
        .excel-body.reply-fixed #postbbtm {position:fixed;right:30px;top:75px;z-index:999;border-radius: 10px;overflow: hidden;}
        .excel-body .row2 .comment_c .comment_c_1_1 {border-top-color: #FFF;}
        .excel-body #postlist .comment_c .comment_c_1 {border-color: #FFF;border-top-color: #BBB;}
        /* Office风格 */
        .excel-body.excel-theme-office .excel-header {height:221px;}
        .excel-body.excel-theme-office .excel-h1 {height:59px;background:#227447;display:flex;justify-content: center;}
        .excel-body.excel-theme-office .excel-title {display: block;color:#FFF;font-size: 12px;font-weight: 400;font-family: sans-serif;line-height:30px;}
        .excel-body.excel-theme-office .excel-h2 {height:95px;background:#f1f1f1;border-bottom:1px solid #d5d5d5;}
        .excel-body.excel-theme-office .excel-h3 {height:48px;background:#e6e6e6;box-shadow:none;}
        .excel-body.excel-theme-office .excel-fx {left:250px;right: 5px;border-color:#c6c6c6;border-radius:0;height:28px;}
        .excel-body.excel-theme-office .excel-h4 {height:20px;}
        .excel-body.excel-theme-office .excel-f1 {height:29px;}
        .excel-body.excel-theme-office .excel-f2 {height:21px;}
        .excel-body.excel-theme-office .excel-f1 {border-top:1px solid #999999;border-bottom:1px solid #bfbfbf;}
        .excel-body.excel-theme-office .excel-img-f1-l1, .excel-body.excel-theme-office .excel-img-f1-r1 {top:-1px;}
        .excel-body.excel-theme-office #mmc {margin-top:221px;}
        .excel-body.excel-theme-office #pt {top:160px;}
        .excel-body.excel-theme-office #postlist .c0,
        .excel-body.excel-theme-office .tl .c0 {width:32px;}
        .excel-body.excel-theme-office #pagebbtm:before,
        .excel-body.excel-theme-office .tl .c0 a {width:28px;}
        .excel-body.excel-theme-office .excel-setting {top: 36px;background:none;text-align: center;}
        .excel-body.excel-theme-office .excel-setting a {color:#FFFFFF;}
        .excel-body.excel-theme-office .excel-setting img {display:none;}
        .excel-body.excel-theme-office.reply-fixed #postbbtm {top: 162px;}
        .excel-body.excel-theme-office #autopbn td a,
        .excel-body.excel-theme-office #autopbn .stdbtn {background: none;}
        .excel-body.excel-theme-office #mainmenu {top:35px;right:45px;}
        .excel-body.excel-theme-office #mainmenu .mmdefault.cell input {border-radius:0;}
        .excel-body.excel-theme-office #mainmenu .stdbtn a, .excel-body.excel-theme-office #mainmenu .half-clone {color:#FFF !important;}
        .excel-body.excel-theme-office .single_ttip2 {top:59px !important;}
        /* 腾讯文档风格 */
        .excel-body.excel-theme-tencent {font-family: -apple-system, Helvetica Neue, Helvetica, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif !important;}
        .excel-body.excel-theme-tencent .excel-header {height:125px;background:#FFF;}
        .excel-body.excel-theme-tencent .excel-titlebar-title {height: 36px;line-height: 36px;font-size: 18px;font-weight: 500;color: #000;opacity: 0.88;margin: 0 9px;max-width: 30%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
        .excel-body.excel-theme-tencent #nv_forum {margin-top: 145px;}
        .excel-body.excel-theme-tencent #pt {top: 94px;left: 65px;}
        .excel-body.excel-theme-tencent .excel-sub {width: 51px;}
        .excel-body.excel-theme-tencent .excel-titlebar {height:56px;display: flex;align-items: center;flex-shrink: 0;padding: 0 4px;border-bottom:1px solid #ebebeb;}
        .excel-body.excel-theme-tencent .excel-toolbar {height:44px;display: flex;align-items: center;flex-shrink: 0;padding: 0 12px;border-bottom:1px solid #ebebeb;line-height: 24px;font-size: 12px;color:rgba(0, 0, 0, 0.88);font-weight:400;}
        .excel-body.excel-theme-tencent .excel-toolbar > div {flex-shrink: 0;}
        .excel-body.excel-theme-tencent .excel-titlebar-pick {margin-left: 12px;margin-top: -2px;}
        .excel-body.excel-theme-tencent .excel-titlebar-pick .excel-titlebar-content {width:17px;height:17px}
        .excel-body.excel-theme-tencent .excel-titlebar-pick .excel-titlebar-indication {height: 3px;width: 14px;margin-left: 2px;margin-top: -2px;}
        .excel-body.excel-theme-tencent .excel-formulabar {height:25px;}
        .excel-body.excel-theme-tencent .excel-icon24 {width:24px;height:24px;background-size: 100% 100%;}
        .excel-body.excel-theme-tencent .excel-icon20 {width:20px;height:20px;background-size: 100% 100%;}
        .excel-body.excel-theme-tencent .excel-icon16 {width:16px;height:16px;background-size: 100% 100%;}
        .excel-body.excel-theme-tencent .excel-icon12 {width:12px;height:12px;background-size: 100% 100%;}
        .excel-body.excel-theme-tencent .excel-h4 > div {background-color:#f9fafb;border-bottom: 1px solid #ebebeb;border-top: 1px solid #ebebeb;border-color:#ebebeb;}
        .excel-body.excel-theme-tencent #postlist .c0, .excel-body.excel-theme-tencent .tl .c0, .excel-body.excel-theme-tencent #pagebbtm:before {width:50px;background-color:#f9fafb !important;}
        .excel-body.excel-theme-tencent #threadlist td, .excel-body.excel-theme-tencent #postlist td {border-color:#ebebeb;}
        .excel-body.excel-theme-tencent .excel-footer {height:32px;background:#FFF;display:flex;align-items: center;border-top: 1px solid #e0e0e0;padding: 0 10px;}
        .excel-body.excel-theme-tencent .excel-sheet-tab {margin-left: 8px;width:104px;border: 1px solid #e0e0e0;border-top: 1px solid #fff;text-align:center;height: 30px;}
        .excel-body.excel-theme-tencent .excel-sheet-tab .excel-sheet-name {font-size: 14px;color: rgba(0,0,0,.88);font-weight: 400;height: 26px;line-height: 26px;border-bottom:2px solid #1e6fff;display:flex;justify-content: center;align-items: center;}
        .excel-body.excel-theme-tencent .excel-footer-item {color:#464d5a;font-size:14px;margin:0 4px;height: 32px;line-height: 32px;}
        .excel-body.excel-theme-tencent #mainmenu {top: 18px;right: 20px;}
        .excel-body.excel-theme-tencent #postbbtm {top: 60px;right: 5px;}
        .excel-body.excel-theme-tencent #autopbn .stdbtn, .excel-body.excel-theme-tencent #autopbn .stdbtn a {background: none;font-weight:400;}
        .excel-body.excel-theme-tencent #autopbn .uitxt1 span {font-size: 1em !important;color: #10273f;}
        .excel-body.excel-theme-tencent #mainmenu .mmdefault.cell input {background: #FFF;}
        `
    }
    /**
     * 折叠引用模块
     * @name FoldQuote
     * @description 此模块提供了可以选择配置自动折叠过长引用
     *              提供一个高级配置可以设置折叠的阈值
     */
    const FoldQuote = {
        name: 'FoldQuote',
        title: '折叠引用',
        settings: [{
            type: 'normal',
            key: 'foldQuote',
            default: true,
            title: '折叠过长引用与附件',
            menu: 'left'
        },{
            type: 'advanced',
            key: 'foldQuoteHeight',
            default: 300,
            title: '自动折叠引用高度',
            desc: '自动折叠引用的高度阈值,单位为像素(px)',
            menu: 'right'
        }],
        renderFormsFunc($el) {
            if (script.setting.normal.foldQuote) {
                // 自动折叠过长引用
                $el.find('.postcontent .quote').each(function() {
                    const $quote = $(this)
                    if ($quote.height() > (script.setting.advanced.foldQuoteHeight || 300)) {
                        const originalHeight = $quote.height()
                        $quote.addClass('quote-fold')
                        const foldHeight = $quote.height()
                        const $openBtn = $(`<div class="quote-box"><button>查看全部 (剩余${100-parseInt(foldHeight/originalHeight*100)}%)</button></div>`)
                        $openBtn.on('click', 'button', function(){
                            $(this).parent().remove()
                            $quote.removeClass('quote-fold')
                        })
                        $quote.append($openBtn)
                    }
                })
                // 折叠附件
                if ($el.find('h4.silver.subtitle').length > 0) {
                    $el.find('h4.silver.subtitle').each(function (){
                        if ($(this).html() === '附件' && $(this).next().attr('id').includes('postattach')) {
                            const $attach = $(this).next()
                            $attach.hide()
                            const $openBtn = $(`<button>显示附件</button>`)
                            $openBtn.on('click', function(){
                                $(this).remove()
                                $attach.show()
                            })
                            $(this).next().after($openBtn)
                        }
                    })
                }
            }
        },
        style: `
        .quote-fold{height:150px;overflow:hidden;position: relative;}
        .quote-box{padding:10px;position: absolute;left:0;right:0;bottom:0;background:#f2eddf;}
        .excel-body .quote-box{background:#FFF;}
        `
    }
    /**
     * 新页面打开模块
     * @name LinkTargetBlank
     * @description 此模块提供了可以选择配置在新页面打开链接
     */
    const LinkTargetBlank = {
        name: 'LinkTargetBlank',
        title: '新页面打开',
        setting: {
            type: 'normal',
            key: 'linkTargetBlank',
            default: false,
            title: '论坛列表新窗口打开',
            menu: 'right'
        },
        renderThreadsFunc($el) {
            if (script.setting.normal.linkTargetBlank) {
                let $link = $el.find('.topic')
                $link.data('href', $link.attr('href')).attr('href', 'javascript:void(0)')
                $link.click(() => {
                    window.open($link.data('href'))
                    return false
                })
            }
        }
    }
    /**
     * 链接直接跳转
     * @name DirectLinkJump
     * @description 此模块提供了超链接等直接跳转无须弹窗确认
     */
    const DirectLinkJump = {
        name: 'DirectLinkJump',
        title: '链接直接跳转',
        setting: {
            type: 'normal',
            key: 'directLinkJump',
            default: true,
            title: '链接直接跳转',
            menu: 'right'
        },
        renderFormsFunc($el) {
            if (script.setting.normal.directLinkJump) {
                $el.find('a[onclick]').each(function(){
                    if ($(this).attr('onclick').includes('showUrlAlert')) {
                        $(this).removeAttr('onclick onmouseover onmouseout')
                    }
                })
            }
        }
    }
    /**
     * 图片增强模块
     * @name ImgEnhance
     * @description 此模块提供了图片增强功能,使用一个独立的图层打开图片
     *              可以快速切换,缩放,旋转等
     */
    const ImgEnhance = {
        name: 'ImgEnhance',
        title: '图片增强',
        settings: [{
            type: 'normal',
            key: 'imgEnhance',
            default: true,
            title: '贴内图片功能增强',
            menu: 'right'
        }, {
            shortCutCode: 37, // LEFT
            key: 'imgEnhancePrev',
            title: '楼内上一张图'
        }, {
            shortCutCode: 39, // RIGHT
            key: 'imgEnhanceNext',
            title: '楼内上一张图'
        }],
        renderFormsFunc($el) {
            $el.find('img').each(function () {
                const classs = $(this).attr('class')
                if (!classs || (classs && !classs.includes('smile'))) {
                    $(this).attr('imglist', 'ready').removeAttr('onload').removeAttr('onclick')
                }
            })
            //图片增强
            if (script.setting.normal.imgEnhance) {
                const _this = this
                $('#mc').on('click', '.postcontent img[imglist=ready]', function () {
                    _this.resizeImg($(this))
                    return false
                })
            }
        },
        resizeImg(el) {
            if ($('#img_full').length > 0) return
            let urlList = []
            let currentIndex = el.parent().find('[imglist=ready]').index(el)
            el.parent().find('[imglist=ready]').each(function () {
                if ($(this).attr('src') != 'about:blank') {
                    urlList.push($(this).data('srcorg') || $(this).data('srclazy') || $(this).attr('src'))
                }
            })
            let $imgBox = $('<div id="img_full" title="点击背景关闭"><div id="loader"></div></div>')
            let $imgContainer = $('<div class="img_container zoom-target"></div>')
            let $img = $('<img title="鼠标滚轮放大/缩小\n左键拖动移动" class="img zoom-target">')

            const renderImg = (index) => {
                let timer = null
                $('#loader').show()
                $imgContainer.css({
                    'top': $(window).height() * 0.03 + 'px',
                    'left': (($(window).width() - ($(window).height()) * 0.85) / 2) + 'px',
                    'width': $(window).height() * 0.85 + 'px',
                    'height': $(window).height() * 0.85 + 'px'
                })
                $img.css({ 'width': '', 'height': '' }).attr('src', urlList[index]).hide()
                timer = setInterval(() => {
                    const w = $img.width()
                    const h = $img.height()
                    if (w > 0) {
                        w > h ? $img.css({ 'width': '100%', 'height': 'auto' }) : $img.css({ 'height': '100%', 'width': 'auto' })
                        $img.show()
                        $('#loader').hide()
                        clearInterval(timer)
                    }
                }, 1)
            }
            //当前图片
            renderImg(currentIndex)
            $img.mousedown(function (e) {
                let endx = 0;
                let endy = 0;
                let left = parseInt($imgContainer.css("left"))
                let top = parseInt($imgContainer.css("top"))
                let downx = e.pageX
                let downy = e.pageY
                e.preventDefault()
                $(document).on("mousemove", function (es) {
                    endx = es.pageX - downx + left
                    endy = es.pageY - downy + top
                    $imgContainer.css("left", endx + "px").css("top", endy + "px")
                    return false
                });
            })
            $img.mouseup(function () { $(document).unbind("mousemove") })
            $imgContainer.append($img)
            $imgBox.append($imgContainer)
            $imgBox.click(function (e) { !$(e.target).hasClass('img') && $(this).remove() })
            $imgBox.append(`
                <div class="if_control">
                <div class="change prev-img" title="本楼内上一张"><div></div></div>
                <div class="change rotate-right" title="逆时针旋转90°"><div></div></div>
                <div class="change rotate-left" title="顺时针旋转90°"><div></div></div>
                <div class="change next-img" title="本楼内下一张"><div></div></div>
                </div>
            `)
            /**
             * Bind:Click
             * 切换图片
             */
            $imgBox.on('click', '.change', function () {
                if ($(this).hasClass('prev-img') && currentIndex - 1 >= 0)
                    renderImg(--currentIndex)

                if ($(this).hasClass('next-img') && currentIndex + 1 < urlList.length)
                    renderImg(++currentIndex)

                if ($(this).hasClass('rotate-right') || $(this).hasClass('rotate-left')) {
                    let deg = ($img.data('rotate-deg') || 0) - ($(this).hasClass('rotate-right') ? 90 : -90)
                    if (deg >= 360 || deg <= -360) deg = 0
                    $img.css('transform', `rotate(${deg}deg)`)
                    $img.data('rotate-deg', deg)
                } else {
                    $img.css('transform', '')
                    $img.data('rotate-deg', 0)
                }
                window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty()
                return false;
            })
            /**
             * Bind:MouseWheel
             * 大图鼠标滚动缩放
             */
            $imgBox.on("mousewheel DOMMouseScroll", function (e) {
                const delta = (e.originalEvent.wheelDelta && (e.originalEvent.wheelDelta > 0 ? 1 : -1)) ||
                    (e.originalEvent.detail && (e.originalEvent.detail > 0 ? -1 : 1));

                if ($imgContainer.width() > 50 || delta > 0) {
                    const offsetY = $imgContainer.height() * 0.2
                    const offsetX = $imgContainer.width() * 0.2
                    let offsetTop = offsetY / 2
                    let offsetLeft = offsetX / 2

                    if ($(e.target).hasClass('zoom-target')) {
                        const targetOffsetX = Math.round(e.clientX - $imgContainer.position().left)
                        const targetOffsetY = Math.round(e.clientY - $imgContainer.position().top)
                        offsetLeft = (targetOffsetX / ($imgContainer.height() / 2)) * offsetLeft
                        offsetTop = (targetOffsetY / ($imgContainer.height() / 2)) * offsetTop
                    }

                    if (delta > 0) {
                        $imgContainer.css({
                            'width': ($imgContainer.height() + offsetY) + 'px',
                            'height': ($imgContainer.height() + offsetY) + 'px',
                            'top': ($imgContainer.position().top - offsetTop) + 'px',
                            'left': ($imgContainer.position().left - offsetLeft) + 'px'
                        })
                    }
                    if (delta < 0) {
                        $imgContainer.css({
                            'width': ($imgContainer.height() - offsetY) + 'px',
                            'height': ($imgContainer.height() - offsetY) + 'px',
                            'top': ($imgContainer.position().top + offsetTop) + 'px',
                            'left': ($imgContainer.position().left + offsetLeft) + 'px'
                        })
                    }
                }
                e.stopPropagation()
                return false
            })
            /**
             * Bind:Keyup
             * Esc关闭大图
             */
            $('body').keyup(event => (event.keyCode == 27 && $('#img_full').length > 0) && $('#img_full').remove())
            $('body').append($imgBox)
        },
        shortcutFunc: {
            imgEnhancePrev() {
                if ($('#img_full').length > 0) {
                    $('#img_full .prev-img').click()
                }
            },
            imgEnhanceNext() {
                if ($('#img_full').length > 0) {
                    $('#img_full .next-img').click()
                }
            }
        },
        style: `
        .img_container {position:absolute;display:flex;justify-content:center;align-items:center;}
        .if_control {position:absolute;display:flex;left:50%;bottom:15px;width:160px;margin-left:-80px;height:40px;background:rgba(0,0,0,0.6);z-index:9999999;}
        .postcontent img {margin:0 5px 5px 0 !important;box-shadow:none !important;outline:none !important;max-height: none !important;}
        #img_full {position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:99999;}
        #img_full img {cursor:move;transition:transform .2s ease;}
        #img_full .imgcenter {top:50%;left:50%;transform:translate(-50%,-50%);}
        #img_full .change {width:40px;height:40px;cursor:pointer;}
        #img_full .rotate-right,#img_full .rotate-left {background:url(${IMG_ICON_REFRESH}) center no-repeat;background-size:25px;}
        #img_full .rotate-right {transform:rotateY(180deg);}
        #img_full .rotate-left:hover {transform:scale(1.2);}
        #img_full .rotate-right:hover {transform:scale(1.2) rotateY(180deg);}
        #img_full .next-img:hover {transform:scale(1.2) rotate(180deg);}
        #img_full .prev-img,#img_full .next-img {background:url(${IMG_ICON_LEFT}) center no-repeat;}
        #img_full .next-img {transform:rotate(180deg);}
        #img_full .prev-img:hover {transform:scale(1.2);}
        #img_full .next-img:hover {transform:scale(1.2) rotate(180deg);}
        `
    }
    /**
     * 标记楼主模块
     * @name AuthorMark
     * @requires https://cdn.staticfile.org/spectrum/1.8.0/spectrum.js
     * @description 此模块提供了自动标记楼主,使其更醒目
     *              提供了高级设置可选标记楼主的颜色
     *              以及为其他模块提供一个spectrum的配置文件
     */
    const AuthorMark = {
        name: 'AuthorMark',
        title: '标记楼主',
        settings: [{
            type: 'normal',
            key: 'authorMark',
            default: true,
            title: '高亮楼主',
            menu: 'right'
        }, {
            type: 'advanced',
            key: 'authorMarkColor',
            default: '#F00',
            title: '标记楼主颜色',
            desc: '标记楼主中的[楼主]的背景颜色,单位为16进制颜色代码',
            menu: 'left'
        }],
        // spectrum配置对象
        colorPickerConfig: {
            type: 'color',
            preferredFormat: 'hex',
            showPaletteOnly: 'true',
            togglePaletteOnly: 'true',
            hideAfterPaletteSelect: 'true',
            showAlpha: 'false',
            togglePaletteMoreText: '更多选项',
            togglePaletteLessText: '隐藏',
            palette: [
                ['#000000','#444444','#5b5b5b','#999999','#bcbcbc','#eeeeee','#f3f6f4','#ffffff'],
                ['#f44336','#744700','#ce7e00','#8fce00','#2986cc','#16537e','#6a329f','#c90076'],
                ['#f4cccc','#fce5cd','#fff2cc','#d9ead3','#d0e0e3','#cfe2f3','#d9d2e9','#ead1dc'],
                ['#ea9999','#f9cb9c','#ffe599','#b6d7a8','#a2c4c9','#9fc5e8','#b4a7d6','#d5a6bd'],
                ['#e06666','#f6b26b','#ffd966','#93c47d','#76a5af','#6fa8dc','#8e7cc3','#c27ba0'],
                ['#cc0000','#e69138','#f1c232','#6aa84f','#45818e','#3d85c6','#674ea7','#a64d79'],
                ['#990000','#b45f06','#bf9000','#38761d','#134f5c','#0b5394','#351c75','#741b47'],
                ['#660000','#783f04','#7f6000','#274e13','#0c343d','#073763','#20124d','#4c1130']
            ]
        },
        postAuthor: [],
        initFunc() {
            const localPostAuthor = script.getValue('yamibo_post_author')
            localPostAuthor && (this.postAuthor = localPostAuthor.split(','))
            // 初始化颜色选择器
            this.initSpectrum('#setting_cover #adv_authorMarkColor')
        },
        renderFormsFunc($el) {
            const _this = this
            if (script.setting.normal.authorMark) {
                const author = $('#postauthor0').text().replace('楼主', '')
                const tid = this.getQueryString('tid')
                const authorStr = `${tid}:${author}`
                if (author && !this.postAuthor.includes(authorStr) && ['authorid=', 'pid='].every(k => !window.location.href.includes(k))) {
                    this.postAuthor.unshift(authorStr) > 10 && this.postAuthor.pop()
                    script.setValue('yamibo_post_author', this.postAuthor.join(','))
                }
                $el.find('a.userlink').each(function () {
                    const name = $(this).attr('hld-mark-before-name') || $(this).text().replace('[', '').replace(']', '')
                    if (name && _this.postAuthor.includes(`${tid}:${name}`)) {
                        $(this).append('<span class="post-author">楼主</span>')
                    }
                })
            }
        },
        /**
         * 获取URL参数
         * @method getQueryString
         * @param {String} name key
         * @param {String} url 要解析的URL
         * @return {String|null} value
         */
        getQueryString(name, url='') {
            url ||= decodeURIComponent(window.location.href.replace(/&amp;/g, "&"))
            let reg = new RegExp("(?:\\?|&)" + name + "=([^&]*)(&|$)")
            let r = url.substring(1).match(reg)
            if (r != null) return encodeURIComponent(r[1])
            return null
        },
        /**
        * 初始化颜色选择器
        * @method initSpectrum
        * @param {str} selector 元素选择器
        */
        initSpectrum(selector) {
            if (selector instanceof jQuery) {
                selector.spectrum(this.colorPickerConfig)
            } else {
                $(selector).spectrum(this.colorPickerConfig)
            }
        },
        asyncStyle() {
            return `
            .post-author {background:${script.setting.advanced.authorMarkColor || '#F00'};color: #FFF;display: inline-block;padding:0 5px;margin-left: 5px;border-radius: 5px;font-weight:bold;line-height: 1.4em;padding-top: 0.1em;padding-bottom: 0;}
            `
        },
        style: `
        .cp-color-picker{z-index:99997}
        .sp-container{position:absolute;top:0;left:0;display:inline-block;z-index:9999994;overflow:hidden}
        .sp-original-input-container{position:relative;display:inline-flex}
        .sp-original-input-container input{margin:0!important}
        .sp-original-input-container .sp-add-on{width:40px;border-top-right-radius:0!important;border-bottom-right-radius:0!important}
        input.spectrum.with-add-on{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}
        .sp-original-input-container .sp-add-on .sp-colorize{height:100%;width:100%;border-radius:inherit}
        .sp-colorize-container{background-image:url(${IMG_ICON_ALPHA})}
        .sp-container.sp-flat{position:relative}
        .sp-container,.sp-container *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}
        .sp-top{position:relative;width:100%;display:inline-block}
        .sp-top-inner{position:absolute;top:0;left:0;bottom:0;right:0}
        .sp-color{position:absolute;top:0;left:0;bottom:0;right:20px!important}
        .sp-hue{position:absolute;top:0;right:0;bottom:0;width:12px;height:100%;left:initial!important}
        .sp-clear-enabled .sp-hue{top:15%;height:85%}
        .sp-fill{padding-top:80%}
        .sp-sat,.sp-val{position:absolute;top:0;left:0;right:0;bottom:0}
        .sp-alpha-enabled .sp-top{margin-bottom:28px!important}
        .sp-alpha-enabled .sp-alpha{display:block}
        .sp-alpha-handle{position:absolute;top:-3px;cursor:pointer;height:16px;border-radius:50%;width:16px;margin-right:5px;left:-2px;right:0;background:#f9f9f9;box-shadow:0 0 2px 0 #3a3a3a}
        .sp-alpha{display:none;position:absolute;bottom:-18px;right:0;left:0;height:10px}
        .sp-alpha-inner{border-radius:4px}
        .sp-clear{display:none}
        .sp-clear.sp-clear-display{background-position:center}
        .sp-clear-enabled .sp-clear{display:block;position:absolute;top:3px;right:0;bottom:0;cursor:pointer;left:initial;height:12px;width:12px}
        .sp-alpha,.sp-alpha-handle,.sp-clear,.sp-container,.sp-container button,.sp-container.sp-dragging .sp-input,.sp-dragger,.sp-preview,.sp-replacer,.sp-slider{-webkit-user-select:none;-moz-user-select:-moz-none;-o-user-select:none;user-select:none}
        .sp-container.sp-input-disabled .sp-input-container{display:none}
        .sp-container.sp-buttons-disabled .sp-button-container{display:none}
        .sp-container.sp-palette-buttons-disabled .sp-palette-button-container{display:none}
        .sp-palette-only .sp-picker-container{display:none}
        .sp-palette-disabled .sp-palette-container{display:none}
        .sp-initial-disabled .sp-initial{display:none}
        .sp-sat{background-image:-webkit-gradient(linear,0 0,100% 0,from(#fff),to(rgba(204,154,129,0)));background-image:-webkit-linear-gradient(left,#fff,rgba(204,154,129,0));background-image:-moz-linear-gradient(left,#fff,rgba(204,154,129,0));background-image:-o-linear-gradient(left,#fff,rgba(204,154,129,0));background-image:-ms-linear-gradient(left,#fff,rgba(204,154,129,0));background-image:linear-gradient(to right,#fff,rgba(204,154,129,0))}
        .sp-val{border-radius:4px;background-image:-webkit-gradient(linear,0 100%,0 0,from(#000),to(rgba(204,154,129,0)));background-image:-webkit-linear-gradient(bottom,#000,rgba(204,154,129,0));background-image:-moz-linear-gradient(bottom,#000,rgba(204,154,129,0));background-image:-o-linear-gradient(bottom,#000,rgba(204,154,129,0));background-image:-ms-linear-gradient(bottom,#000,rgba(204,154,129,0));background-image:linear-gradient(to top,#000,rgba(204,154,129,0))}
        .sp-hue{background:-moz-linear-gradient(top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background:-ms-linear-gradient(top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background:-o-linear-gradient(top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background:-webkit-gradient(linear,left top,left bottom,from(red),color-stop(.17,#ff0),color-stop(.33,#0f0),color-stop(.5,#0ff),color-stop(.67,#00f),color-stop(.83,#f0f),to(red));background:-webkit-linear-gradient(top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background:linear-gradient(to bottom,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}
        .sp-1{height:17%}
        .sp-2{height:16%}
        .sp-3{height:17%}
        .sp-4{height:17%}
        .sp-5{height:16%}
        .sp-6{height:17%}
        .sp-hidden{display:none!important}
        .sp-cf:after,.sp-cf:before{content:"";display:table}
        .sp-cf:after{clear:both}
        @media (max-device-width:480px){.sp-color{right:40%}
        .sp-hue{left:63%}
        .sp-fill{padding-top:60%}
        }
        .sp-dragger{border-radius:5px;height:10px;width:10px;border:1px solid #fff;cursor:pointer;position:absolute;top:0;left:0;margin-left:3px;margin-top:3px;box-shadow:0 0 2px 1px rgba(0,0,0,.2)}
        .sp-slider{position:absolute;top:0;cursor:pointer;height:16px;border-radius:50%;width:16px;left:-2px;background:#f9f9f9;box-shadow:0 0 2px 0 #3a3a3a;margin-top:8px}
        .sp-container{display:inline-flex;border-radius:0;background-color:#fff;padding:0;border-radius:4px;color:#000;box-shadow:0 0 0 1px rgba(99,114,130,.16),0 8px 16px rgba(27,39,51,.08)}
        .sp-clear,.sp-color,.sp-container,.sp-container button,.sp-container input,.sp-hue{font-size:12px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}
        .sp-top{margin-bottom:10px}
        .sp-clear,.sp-color,.sp-hue,.sp-sat,.sp-val{border-radius:3px}
        .sp-input-container{margin-top:-5px}
        .sp-button-container.sp-cf,.sp-initial.sp-thumb.sp-cf,.sp-input-container.sp-cf{height:25px}
        .sp-picker-container .sp-cf{margin-bottom:10px}
        .sp-palette-row-initial>span:first-child{cursor:pointer}
        .sp-initial-disabled .sp-input-container{width:100%}
        .sp-input{padding:0 5px!important;margin:0;width:100%;box-shadow:none!important;height:100%!important;background:0 0;color:#3a3a3a;border-radius:2px!important;border:1px solid #e0e0e0!important;text-align:center;font-family:monospace;font-size:inherit!important}
        .sp-input.sp-validation-error{border:1px solid red;background:#fdd}
        .sp-palette-container,.sp-picker-container{position:relative;padding:10px}
        .sp-picker-container{width:200px;padding-bottom:0}
        .sp-palette-container{border-right:solid 1px #ccc}
        .sp-palette-only .sp-palette-container{border:0}
        .sp-palette .sp-thumb-el{display:block;position:relative;float:left;width:24px;height:15px;margin:3px;cursor:pointer;border:solid 2px transparent}
        .sp-palette .sp-thumb-el.sp-thumb-active,.sp-palette .sp-thumb-el:hover{border-color:orange}
        .sp-thumb-el{position:relative}
        .sp-initial{float:left}
        .sp-initial span{width:30px;height:25px;border:none;display:block;float:left;margin:0}
        .sp-initial .spe-thumb-el.sp-thumb-active{border-radius:0 5px 5px 0}
        .sp-initial .spe-thumb-el{border-radius:5px 0 0 5px}
        .sp-initial .sp-clear-display{background-position:center}
        .sp-button-container{float:right;display:none;}
        .sp-palette-button-container{margin-top:10px}
        .sp-replacer{position:relative;overflow:hidden;cursor:pointer;display:inline-block;border-radius:3px;border:1px solid #aaa;color:#666;transition:border-color .3s;vertical-align:middle;width:40px;height:20px;margin: 1.5px 3px;}
        .sp-replacer.sp-active,.sp-replacer:hover{border:1px solid #666;color:#000}
        .sp-replacer.sp-disabled{cursor:default;border-color:silver;color:silver}
        .sp-dd{position:absolute;font-size:10px;right:0;top:0;bottom:0;padding:0 1px;line-height:22px;background-color:#fff;border-left: 1px solid #aaa;}
        .sp-preview{position:relative;width:100%;height:100%;float:left;z-index:0}
        .sp-preview-inner{transition:background-color .2s}
        .sp-preview-inner.sp-clear-display{display:none}
        .sp-palette .sp-thumb-el{width:16px;height:16px;margin:3px;border:none;border-radius:3px}
        .sp-container button{border-radius:3px;border:none;background:0 0;line-height:1;padding:0 8px;height:25px;text-transform:capitalize;text-align:center;vertical-align:middle;cursor:pointer;color:#606c72;font-weight:700}
        .sp-container button.sp-choose{background-color:#3cab3b;color:#fff;margin-left:5px}
        .sp-container button:hover{opacity:.8}
        .sp-container button.sp-palette-toggle{width:100%;background-color:#f3f3f3;margin:0}
        .sp-palette span.sp-thumb-active,.sp-palette span:hover{border-color:#000}
        .sp-alpha,.sp-preview,.sp-thumb-el{position:relative;background-image:url(${IMG_ICON_ALPHA})}
        .sp-alpha-inner,.sp-preview-inner,.sp-thumb-inner{display:block;position:absolute;top:0;left:0;bottom:0;right:0}
        .sp-palette .sp-thumb-inner{border-radius:3px;background-position:50% 50%;background-repeat:no-repeat}
        .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner{background-image:url(${IMG_ICON_CHECK_BLACK})}
        .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner{background-image:url(${IMG_ICON_CHECK_WHITE})}
        .sp-clear-display{background-repeat:no-repeat;background-position:center;background-image:url(${IMG_ICON_BLOCK})}
        `
    }
    /**
     * 自动翻页模块
     * @name AutoPage
     * @description 此模块提供了脚本自动翻页的功能
     *              提供一个高级配置可以设置自动翻页的检测阈值
     */
    const AutoPage = {
        name: 'AutoPage',
        title: '自动翻页',
        settings: [{
            type: 'normal',
            key: 'autoPage',
            default: true,
            title: '自动翻页',
            menu: 'right'
        }],
        $window: $(window),
        initFunc() {
            script.setting.normal.autoPage && $('body').addClass('reply-fixed')
        },
        renderAlwaysFunc() {
            const _this = this
            try {
                if (script.setting.normal.autoPage && script.setting.normal.excelMode) {
                    let ticking = false;
                    let once = false; // 如果要每次到底都点,把这行改成 false 并删除下面的 once 判断
                    $(window).off('scroll.autoPage').on('scroll.autoPage', function () {
                        if (ticking) return;
                        ticking = true;

                        const st = $(window).scrollTop();
                        const wh = $(window).height();
                        const dh = $(document).height();

                        if (!once && (st + wh >= dh - 200)) {
                        const $btn = $('#autopbn:visible').first();
                        if ($btn.length) {
                            // 点击
                            $btn[0].click?.();
                            $btn.trigger('click');
                            // 如果只想点一次,保留下面两行;想多次触发就删掉
                            once = true;
                            // $(window).off('scroll.autoPage'); // 点一次后直接解绑也行
                        } else {
                            console.log('[autoPage] no #autopbn found');
                        }
                        }
                        setTimeout(() => { ticking = false; }, 80);
                    });
                }
            } catch (e) {
            console.error('[autoPage] init error:', e);
            }
        }
    }
    /**
     * 关键字屏蔽模块
     * @name KeywordsBlock
     * @description 此模块提供了关键字屏蔽功能
     *              提供一个高级配置可以设置是否过滤标题
     */
    const KeywordsBlock = {
        name: 'KeywordsBlock',
        title: '关键字屏蔽',
        settings: [{
            type: 'normal',
            key: 'keywordsBlock',
            default: true,
            title: '关键字屏蔽',
            menu: 'right',
            extra: {
                type: 'button',
                label: '关键字管理',
                id: 'keywords_manage'
            }
        }, {
            type: 'advanced',
            key: 'kwdBlockContent',
            default: 'ALL',
            options: [{
                label: '标题跟正文',
                value: 'ALL'
            }, {
                label: '仅标题',
                value: 'TITLE'
            }, {
                label: '仅正文',
                value: 'BODY'
            }],
            title: '关键字屏蔽方式',
            desc: '此配置表示关键字的屏蔽方式',
            menu: 'right'
        }],
        keywordsList: [],
        initFunc() {
            const _this = this
            // 同步本地数据
            const localKeywordsList = script.getValue('yamibo_keywords_list')
            try {
                localKeywordsList && (_this.keywordsList = JSON.parse(localKeywordsList))
            } catch {
                localKeywordsList && (_this.keywordsList = localKeywordsList.split(','))
                script.setValue('yamibo_keywords_list', JSON.stringify(_this.keywordsList))
            }
            // 添加到导入导出配置
            script.getModule('BackupModule').addItem({
                title: '关键字列表',
                writeKey: 'keywords_list',
                valueKey: 'keywordsList',
                module: this
            })
            /**
             * Bind:Click
             * 管理弹窗面板
             */
            $('body').on('click', '#keywords_manage', function () {
                if($('#keywords_panel').length > 0) return
                $('#setting_cover').append(`<div id="keywords_panel" class="list-panel animated fadeInUp">
                <a href="javascript:void(0)" class="setting-close">×</a>
                <div>
                <div class="list-c"><p>屏蔽关键字</p><textarea row="20" id="keywords_list_textarea"></textarea><p class="list-desc help" help="以/开头会被识别为正则表达式">一行一条,支持正则表达式</p></div>
                </div>
                <div class="btn-groups"><button class="btn" id="save_keywords">保存列表</button></div>
                </div>`)
                $('#keywords_list_textarea').val(_this.keywordsList.join('\n'))
            })
            /**
             * Bind:Click
             * 保存关键字
             */
            $('body').on('click', '#save_keywords', function () {
                let keywordsList = $('#keywords_list_textarea').val().split('\n')
                keywordsList = _this.removeBlank(keywordsList)
                keywordsList = _this.uniq(keywordsList)
                _this.keywordsList = keywordsList
                script.setValue('yamibo_keywords_list', JSON.stringify(_this.keywordsList))
                $('#keywords_panel').remove()
                script.popMsg('保存成功,刷新页面生效')
            })
        },
        renderThreadsFunc($el) {
            const title = $el.find('.c2>a').text()
            if ((script.setting.advanced.kwdBlockContent === 'ALL' || script.setting.advanced.kwdBlockContent === 'TITLE') && script.setting.normal.keywordsBlock && this.keywordsList.length > 0) {
                for (let keyword of this.keywordsList) {
                    if (this.isKeywordHits(keyword, title)) {
                        script.printLog(`关键字屏蔽: 命中关键字: ${keyword} 标题: ${title} 连接: ${$el.find('.c2>a').attr('href')}`)
                        $el.remove()
                        break
                    }
                }
            }
        },
        renderFormsFunc($el) {
            const _this = this
            if (script.setting.normal.keywordsBlock && this.keywordsList.length > 0 && (script.setting.advanced.kwdBlockContent === 'ALL' || script.setting.advanced.kwdBlockContent === 'BODY')) {
                const $postcontent = $el.find('.postcontent')
                const $postcontentClone = $postcontent.clone()
                const consoleLog = (text) => script.printLog(`关键字屏蔽: 内容: ${text}`)
                let postcontentQuote = ''
                let postcontentText = ''

                if ($postcontent.find('.quote').length > 0) {
                    $postcontentClone.find('.quote').remove()
                    let postcontentText = $postcontent.find('.quote').text()
                    const endIndex = postcontentText.indexOf(')')
                    postcontentQuote = postcontentText.substring(endIndex + 1)
                }

                postcontentText = $postcontentClone.text()
                let blockCount = 0
                for (let keyword of this.keywordsList) {
                    if (postcontentText && this.isKeywordHits(keyword, postcontentText)) {
                        consoleLog(postcontentText)
                        $el.remove()
                        blockCount += 1
                        break
                    }
                    if (postcontentQuote && this.isKeywordHits(keyword, postcontentQuote)) {
                        consoleLog(postcontentQuote)
                        blockCount += 1
                        $postcontent.find('.quote').remove()
                    }
                }
                const $commentCList = $el.find('.comment_c')
                if ($commentCList.length > 0) {
                    let postcontentReply = ''
                    $commentCList.each(function () {
                        let postcontentReplyText = $el.find('.ubbcode').text()
                        const end_index = postcontentReplyText.indexOf(')')
                        postcontentReply = postcontentReplyText.substring(end_index + 1)
                        for (let keyword of _this.keywordsList) {
                            if (postcontentReply && postcontentReply.includes(keyword)) {
                                consoleLog(postcontentReply)
                                blockCount += 1
                                $(this).remove()
                            }
                        }
                    })
                }
            }
        },
        /**
         * 列表去空
         * @method removeBlank
         * @param {Array} array 列表
         * @return {Array} 处理后的列表
         */
        removeBlank(array) {
            let r = []
            array.map(function (val, index) {
                if (val !== '' && val != undefined) {
                    r.push(val)
                }
            });
            return r
        },
        /**
         * 判断是否命中关键字
         * @method isKeywordHits
         * @param {String} keyword 关键字
         * @param {String} text 文本
         * @return {Boolean} 是否命中
         */
        isKeywordHits(keyword, text) {
            if (keyword.startsWith('/')) {
                const matchRegKeyword = keyword.match(/^\/(.*)\/([gimsuy]*)$/)
                if (matchRegKeyword) {
                    const regexPattern = matchRegKeyword[1]
                    const flags = matchRegKeyword[2]
                    try {
                        const regex = new RegExp(regexPattern, flags)
                        return regex.test(text)
                    } catch {
                        return false
                    }
                } else {
                    return false
                }
            }
            return text.includes(keyword)
        },
        /**
         * 列表去重
         * @method uniq
         * @param {Array} array 列表
         * @return {Array} 处理后的列表
         */
        uniq(array) {
            return [...new Set(array)]
        },
        style: `
        #keywords_panel {width:182px;}
        #keywords_panel .list-c {width:100%;}
        `
    }
    /**
     * 黑名单标记模块
     * @name MarkAndBan
     * @description 此模块提供了黑名单屏蔽功能,标签标记功能
     *              提供高级配置可以设置标记/备注风格
     *              提供高级配置可以设置功能面板显示方式
     *              提供高级配置可以设置黑名单屏蔽策略
     */
    const MarkAndBan = {
        name: 'MarkAndBan',
        title: '黑名单标记',
        settings: [{
            type: 'normal',
            key: 'markAndBan',
            default: true,
            title: '拉黑/标签功能',
            menu: 'right',
            extra: {
                type: 'button',
                label: '名单管理',
                id: 'list_manage'
            }
        }, {
            type: 'advanced',
            key: 'classicRemark',
            default: false,
            title: '经典备注风格',
            desc: '此配置表示标记功能的风格显示\n选中时: v2.9及以前的备注风格(仿微博),此风格不能更改颜色\n取消时: 新版标记风格',
            menu: 'right'
        }, {
            type: 'advanced',
            key: 'autoHideBanIcon',
            default: false,
            title: '按需显示标注拉黑按钮',
            desc: '选中时: 默认隐藏标注与拉黑按钮, 当鼠标停留区域时, 才会显示\n取消时: 一直显示',
            menu: 'right'
        }, {
            type: 'advanced',
            key: 'banStrictMode',
            default: 'HIDE',
            options: [{
                label: '屏蔽',
                value: 'HIDE'
            }, {
                label: '删除',
                value: 'REMOVE'
            }, {
                label: '全部删除',
                value: 'ALL'
            }],
            title: '拉黑模式',
            desc: '此配置表示拉黑某人后对帖子的屏蔽策略\n屏蔽: 保留楼层, 仅会屏蔽用户的回复\n删除: 将会删除楼层\n全部删除: 回复被拉黑用户的回复也会被删除',
            menu: 'right'
        }],
        banList: [],
        markList: [],
        markedTags: [],
        initFunc() {
            const _this = this
            // 读取本地数据
            const localBanList = script.getValue('yamibo_ban_list')
            try {
                localBanList && (_this.banList = JSON.parse(localBanList))
            } catch(e) {
                script.throwError(`【yamibo-Script】无法加载黑名单列表,数据解析失败!\n错误问题: ${e}\n\n请尝试使用【修复脚本】来修复此问题`)
            }
            const localMarkList = script.getValue('yamibo_mark_list')
            try {
                if (localMarkList) {
                    _this.markList = JSON.parse(localMarkList)
                    // 统计已添加过的标签
                    _this.markList.forEach(item => {
                        item.marks.forEach(mark => {
                            const exist_tag = _this.markedTags.find(t => t.mark == mark.mark)
                            if (exist_tag) {
                                exist_tag.count += 1
                            } else {
                                _this.markedTags.push({
                                    mark: mark.mark,
                                    text_color: mark.text_color,
                                    bg_color: mark.bg_color,
                                    count: 1
                                })
                            }
                        })
                    })
                    _this.markedTags.sort((a, b) => {return b.count - a.count})
                }
            } catch(e) {
                script.throwError(`【yamibo-Script】无法加载标记列表,数据解析失败!\n错误问题: ${e}\n\n请尝试使用【修复脚本】来修复此问题`)
            }
            // 添加到导入导出配置
            script.getModule('BackupModule').addItem({
                title: '黑名单列表',
                writeKey: 'ban_list',
                valueKey: 'banList',
                module: this
            })
            script.getModule('BackupModule').addItem({
                title: '标记名单列表',
                writeKey: 'mark_list',
                valueKey: 'markList',
                module: this
            })
            // 拉黑标签-名单
            if (script.setting.normal.markAndBan) {
                /**
                 * Bind:Click
                 * 操作按钮点击事件
                 */
                $('body').on('click', '.extra-icon', function () {
                    const type = $(this).data('type')
                    const name = $(this).data('name')
                    const uid = $(this).data('uid') + ''
                    $('.dialog').length > 0 && $('.dialog').remove()
                    if (type == 'ban') {
                        _this.banlistPopup({
                            type: 'confirm',
                            name,
                            uid,
                            top: $(this).offset().top+20,
                            left: $(this).offset().left-10
                        })
                    }
                    if (type == 'mark') {
                        _this.userMarkPopup({
                            name,
                            uid,
                            top: $(this).offset().top+20,
                            left: $(this).offset().left-10
                        })
                    }
                })
                /**
                 * Bind:Click
                 * 屏蔽按钮
                 */
                $('body').on('click', '.banned-block', function(){
                    if ($(this).parent().hasClass('quote')) {
                        $(this).parent().prev().show()
                        $(this).parent().hide()
                    } else {
                        $(this).prev().show()
                        $(this).hide()
                    }
                })
                /**
                 * Bind:Click
                 * 名单管理
                 */
                $('body').on('click', '#list_manage', function () {
                    if($('#banlist_panel').length > 0) return
                    $('#setting_cover').append(`<div id="banlist_panel"  class="list-panel animated fadeInUp">
                    <a href="javascript:void(0)" class="setting-close">×</a>
                    <div class="tab-header"><span class="table-active">简易模式</span><span>原始数据</span></div>
                    <div class="tab-content format-list table-active">
                    <div class="list-c"><p>黑名单</p>
                    <div class="scroll-area">
                    <table class="table table-banlist">
                    <thead><tr><th width="175">用户名</th><th>UID</th><th>备注</th><th width="25">操作</th></tr></thead><tbody id="banlist"></tbody></table>
                    </div>
                    <div class="table-banlist-buttons"><button id="banlist_add_btn" class="btn">+添加用户</button></div>
                    </div>
                    <div class="list-c"><p>标签名单</p>
                    <div class="scroll-area">
                    <table class="table table-banlist">
                    <thead><tr><th width="100">用户名</th><th>UID</th><th width="50">标签数</th><th width="50">操作</th></tr></thead><tbody id="marklist"></tbody></table>
                    </div>
                    <div class="table-banlist-buttons"><button id="marklist_add_btn" class="btn">+添加用户</button></div>
                    </div>
                    </div>
                    <div class="tab-content source-list">
                    <div class="list-c"><p>黑名单</p><textarea row="20" id="ban_list_textarea"></textarea><p class="list-desc" title='[{\n    "uid": "UID",\n    "name": "用户名"\n  }, ...]'>查看数据结构</p></div>
                    <div class="list-c"><p>标签名单</p><textarea row="20" id="mark_list_textarea"></textarea><p class="list-desc" title='[{\n    "uid": "UID",\n    "name": "用户名",\n    "marks": [{\n        "mark": "标记",\n        "text_color": "文字色",\n        "bg_color": "背景色"\n    }, ...]\n  }, ...]'>查看数据结构</p></div>
                    <div class="btn-groups" style="width: 100%;"><button class="btn" id="save_banlist">保存列表</button></div>
                    </div>
                    </div>`)
                    /**
                     * Bind:Click
                     * 切换选项卡
                     */
                    $('body').on('click', '.tab-header > span', function(){
                        $('.tab-header > span, .tab-content').removeClass('table-active')
                        $(this).addClass('table-active')
                        $('.tab-content').eq($(this).index()).addClass('table-active')
                    })
                    /**
                     * Bind:Click
                     * 移除黑名单
                     */
                    $('body').on('click', '.bl-del', function(){
                        const index = $(this).data('index')
                        _this.banList.splice(index, 1)
                        script.setValue('yamibo_ban_list', JSON.stringify(_this.banList))
                        _this.reloadBanlist()
                    })
                    /**
                     * Bind:Click
                     * 添加黑名单
                     */
                    $('body').on('click', '#banlist_add_btn', function(){
                        _this.banlistPopup({
                            type: 'add',
                            name: $(this).data('name'),
                            uid: $(this).data('uid'),
                            top: $(this).offset().top + 30,
                            left: $(this).offset().left - 5,
                            callback: () => {_this.reloadBanlist()}
                        })
                    })
                    /**
                     * Bind:Click
                     * 保存黑名单
                     */
                    $('body').on('click', '#save_banlist', function(){
                        const banList = $('#ban_list_textarea').val()
                        const markList = $('#mark_list_textarea').val()
                        try {
                            _this.banList = JSON.parse(banList)
                            script.setValue('yamibo_ban_list', banList)
                            _this.reloadBanlist()
                        } catch {
                            script.popMsg('黑名单数据有误!', 'err')
                            return
                        }
                        try {
                            _this.markList = JSON.parse(markList)
                            script.setValue('yamibo_mark_list', markList)
                            _this.reloadMarklist()
                        } catch {
                            script.popMsg('标记单数据有误!', 'err')
                            return
                        }
                        script.popMsg('数据已成功')
                    })
                    /**
                     * Bind:Click
                     * 修改标记
                     */
                    $('body').on('click', '.ml-edit', function(){
                        const name = $(this).data('name')
                        const uid = $(this).data('uid') + ''
                        _this.userMarkPopup({
                            name,
                            uid,
                            top: $(this).offset().top + 30,
                            left: $(this).offset().left - 5,
                            callback: () => {_this.reloadMarklist()}
                        })
                    })
                    /**
                     * Bind:Click
                     * 删除标记
                     */
                    $('body').on('click', '.ml-del', function(){
                        const index = $(this).data('index')
                        _this.markList.splice(index, 1)
                        script.setValue('yamibo_mark_list', JSON.stringify(_this.markList))
                        _this.reloadMarklist()
                    })
                    /**
                     * Bind:Click
                     * 添加标记
                     */
                    $('body').on('click', '#marklist_add_btn', function(){
                        _this.userMarkPopup({
                            type: 'add',
                            name: $(this).data('name'),
                            uid: $(this).data('uid'),
                            top: $(this).offset().top + 30,
                            left: $(this).offset().left - 5,
                            callback: () => {_this.reloadMarklist()}
                        })
                    })
                    //重载名单
                    _this.reloadBanlist()
                    _this.reloadMarklist()
                })
            }
        },
        renderThreadsFunc($el) {
            const title = $el.find('.c2>a').text()
            const uid = ($el.find('.author').attr('href') && $el.find('.author').attr('href').indexOf('uid=') > -1) ? $el.find('.author').attr('href').split('uid=')[1] + '' : ''
            const name = $el.find('.author').text()
            if (script.setting.normal.markAndBan) {
                const banUser = this.getBanUser({name, uid})
                //黑名单屏蔽
                if (this.banList.length > 0 && banUser) {
                    script.printLog(`黑名单屏蔽: 标题: ${title}  连接: ${$el.find('.c2>a').attr('href')}`)
                    $el.parents('tbody').remove()
                }
            }
        },
        renderFormsFunc($el) {
            const _this = this
            if (script.setting.normal.markAndBan) {
                // 插入操作面板
                const currentUid = $el.find('[name=uid]').text() + ''
                $el.find('.small_colored_text_btn.block_txt_c2.stxt').each(function () {
                    let currentName = ''
                    if ($(this).parents('td').prev('td').html() == '') {
                        currentName = $(this).parents('table').prev('.posterinfo').children('.author').text()
                    } else {
                        currentName = $(this).parents('td').prev('td').find('.author').text()
                    }
                    currentName.endsWith('楼主') && (currentName = currentName.substring(0, currentName.length - 2))
                    const mbDom = `
                        <a class="extra-icon help" data-type="mark" help="标签此用户" data-name="${currentName}" data-uid="${currentUid}">🏷️</a>
                        <a class="extra-icon help" help="拉黑此用户(屏蔽所有言论)" data-type="ban"  data-name="${currentName}" data-uid="${currentUid}">⛔</a>
                    `
                    script.setting.advanced.autoHideBanIcon ? $(this).after(`<span class="extra-icon-box">${mbDom}</span>`) : $(this).append(mbDom)
                })
                // 标记DOm
                $el.find('a.userlink').each(function () {
                    const uid = ($(this).attr('href') && $(this).attr('href').indexOf('uid=') > -1) ? $(this).attr('href').split('uid=')[1] + '' : ''
                    let name = ''
                    if ($(this).find('span.post-author').length > 0 || $(this).find('span.remark').length > 0) {
                        const $a = $(this).clone()
                        $a.find('span.post-author, span.remark').remove()
                        name = $a.text()
                    } else {
                        name = $(this).attr('hld-mark-before-name') || $(this).text().replace('[', '').replace(']', '')
                    }
                    const banUser = _this.getBanUser({name, uid})
                    if (banUser) {
                        //拉黑用户实现
                        if (script.setting.advanced.banStrictMode == 'HIDE') {
                            if ($(this).hasClass('author')) {
                                const $blocktips = $('<div class="banned banned-block">此用户在你的黑名单中,已屏蔽其言论,点击查看</div>')
                                if ($(this).parents('div.comment_c').length > 0) {
                                    $(this).parents('div.comment_c').find('.ubbcode').hide()
                                    $(this).parents('div.comment_c').find('.ubbcode').after($blocktips)
                                } else {
                                    $(this).parents('.forumbox.postbox').find('.c2 .postcontent').hide()
                                    $(this).parents('.forumbox.postbox').find('.c2 .postcontent').after($blocktips)
                                }
                            } else {
                                if (!$(this).parent().is(':hidden')) {
                                    $(this).parent().hide()
                                    $(this).parent().after('<div class="quote"><div class="banned banned-block">此用户在你的黑名单中,已屏蔽其言论,点击查看</div></div>')
                                }
                            }
                        } else if (script.setting.advanced.banStrictMode == 'ALL') {
                            if ($(this).parents('div.comment_c').length > 0) $(this).parents('div.comment_c').remove()
                            else $(this).parents('.forumbox.postbox').remove()
                        } else {
                            if ($(this).hasClass('author')) {
                                if ($(this).parents('div.comment_c').length > 0) $(this).parents('div.comment_c').remove()
                                else $(this).parents('.forumbox.postbox').remove()
                            } else {
                                $(this).parent().html('<div class="banned">此用户在你的黑名单中,已删除其言论</div>')
                            }
                        }
                        if (banUser.desc) {
                            $(this).parents('.postrow').find('.banned').append(`<div>备注: ${banUser.desc}</div>`)
                        }
                        script.printLog(`黑名单屏蔽: 用户: ${name}, UID:${uid}, 备注:${banUser.desc}`)
                    }
                    if(script.setting.advanced.classicRemark) {
                        //经典备注风格
                        const userMarks = _this.getUserMarks({name, uid})
                        if (userMarks) {
                            let f = []
                            userMarks.marks.forEach(e => f.push(e.mark))
                            $(this).attr('hld-mark-before-name', name).append(`<span class="remark"> (${f.join(', ')}) </span>`)
                        }
                    }else {
                        //新版标签风格
                        const userMarks = _this.getUserMarks({name, uid})
                        if(userMarks) {
                            const $el = $(this).parents('.c1').find('.clickextend')
                            let marksDom = ''
                            userMarks.marks.forEach(item => marksDom += `<span ${item.desc ? 'class="help" help="'+item.desc+'"' : ''} style="color: ${item.text_color};background-color: ${item.bg_color};">${item.mark}</span>`);
                            $el.before(`<div class="marks-container">标签: ${marksDom}</div>`)
                        }
                    }
                })
            }
        },
        /**
         * 黑名单弹窗
         * @method banlistPopup
         * @param {Object} setting 设置项
         * @param {String} setting.name 用户昵称
         * @param {String} setting.uid UID
         * @param {String} setting.type 模式
         * @param {Number} setting.top  pos.top位置
         * @param {Number} setting.left pos.left 位置
         * @param {Function} setting.callback 回调函数
         */
        banlistPopup(setting) {
            const _this = this
            $('.dialog').length > 0 && $('.dialog').remove()
            let $banDialog = $(`<div class="dialog dialog-sub-top list-panel animated zoomIn"  style="top: ${setting.top}px;left: ${setting.left}px;"><a href="javascript:void(0)" class="setting-close">×</a><div id="container_dom"></div><div class="dialog-buttons"></div></div>`)
            if (setting.type == 'confirm') {
                $banDialog.find('#container_dom').append(`<div><span>您确定要拉黑用户</span><span class="dialog-user">${setting.name}</span><span>吗?</span></div><div><input type="text" class="ban-desc" placeholder="可选备注"></div>`)
                let $okBtn = $('<button class="btn">拉黑</button>')
                $okBtn.click(function(){
                    _this.setBanUser({
                        name: setting.name,
                        uid: setting.uid,
                        desc: $('.ban-desc').val().trim()
                    })
                    $('.dialog').remove()
                    script.popMsg('拉黑成功,重载页面生效')
                })
                $banDialog.find('.dialog-buttons').append($okBtn)
            }else if (setting.type == 'add') {
                $banDialog.find('#container_dom').append(`<div>添加用户: </div><div><input id="dialog_add_uid" type="text" value="" placeholder="UID"></div><div><input id="dialog_add_name" type="text" value="" placeholder="用户名"></div><div><input type="text" id="dialog_add_desc" placeholder="可选备注"></div>`)
                let $okBtn = $('<button class="btn">添加</button>')
                $okBtn.click(function(){
                    const name = $banDialog.find('#dialog_add_name').val().trim()
                    const uid = $banDialog.find('#dialog_add_uid').val().trim() + ''
                    const desc = $banDialog.find('#dialog_add_desc').val().trim()
                    if (!name && !uid) {
                        script.popMsg('UID与用户名必填一个,其中UID权重较大', 'err')
                        return
                    }
                    !_this.getBanUser({name, uid}) && _this.banList.push({name, uid, desc})
                    script.setValue('yamibo_ban_list', JSON.stringify(_this.banList))
                    $('.dialog').remove()
                    setting.callback()
                })
                $banDialog.find('.dialog-buttons').append($okBtn)
            }
            $('body').append($banDialog)
        },
        /**
         * 获取黑名单用户
         * @method getBanUser
         * @param {Object} banObj 黑名单对象
         * @return {Object|null} 获取的用户对象
         */
        getBanUser(banObj) {
            const _this = this
            for (let u of _this.banList) {
                if ((u.uid && banObj.uid && u.uid == banObj.uid) || 
                    (u.name && banObj.name && u.name == banObj.name)) {
                    if ((!u.uid && banObj.uid) || (!u.name && banObj.name)) {
                        u.uid = banObj.uid + '' || ''
                        u.name = banObj.name || ''
                        script.setValue('yamibo_ban_list', JSON.stringify(_this.banList))
                    }
                    return u
                }
            }
            return null
        },
        /**
         * 拉黑用户
         * @method setBanUser
         * @param {Object} banObj 黑名单对象, {uid, name}
         */
        setBanUser(banObj) {
            !this.getBanUser(banObj) && this.banList.push(banObj)
            script.setValue('yamibo_ban_list', JSON.stringify(this.banList))
        },
        /**
         * 重新渲染黑名单列表
         * @method reloadBanlist
         */
        reloadBanlist() {
            const _this = this
            $('#banlist').empty()
            _this.banList.forEach((item, index) => $('#banlist').append(`
                <tr>
                    <td title="${item.name}">${item.name}</td>
                    <td title="${item.uid}">${item.uid}</td>
                    <td title="${item.desc}">${item.desc || ''}</td>
                    <td>
                        <span class="us-action us-del bl-del" title="删除" data-index="${index}" data-name="${item.name}" data-uid="${item.uid}">
                            <svg t="1686881304570" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2976" width="48" height="48"><path d="M341.312 85.312l64-85.312h213.376l64 85.312H960v85.376H64V85.312h277.312zM170.688 256h682.624v768H170.688V256zM256 341.312v597.376h512V341.312H256z m213.312 85.376v426.624H384V426.688h85.312z m170.688 0v426.624H554.688V426.688H640z" fill="#111111" p-id="2977"></path></svg>
                        </span>
                    </td>
                </tr>
            `))
            $('#ban_list_textarea').val(JSON.stringify(_this.banList))
        },
        /**
         * 重新渲染标签列表
         * @method reloadMarklist
         */
        reloadMarklist() {
            const _this = this
            $('#marklist').empty()
            _this.markList.forEach((user_mark, index) => {
                $('#marklist').append(`
                    <tr>
                        <td title="${user_mark.name}">${user_mark.name}</td>
                        <td title="${user_mark.uid}">${user_mark.uid}</td>
                        <td title="${user_mark.marks.length}">${user_mark.marks.length}</td>
                        <td>
                            <span class="us-action us-edit ml-edit" title="编辑" data-index="${index}" data-name="${user_mark.name}" data-uid="${user_mark.uid}">
                                <svg t="1686881523486" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4234" width="48" height="48"><path d="M652.4 156.6125a112.5 112.5 0 1 1 155.925 161.15625L731.375 394.71875 572.3 235.5875l79.5375-79.5375 0.5625 0.5625zM333.63125 792.40625v0.1125H174.5v-159.1875l358.03125-357.975 159.075 159.13125-357.975 357.91875zM62 849.5h900v112.5H62v-112.5z" fill="#111111" p-id="4235"></path></svg>
                            </span>
                            <span class="us-action us-del ml-del" title="删除" data-index="${index}" data-name="${user_mark.name}" data-uid="${user_mark.uid}">
                                <svg t="1686881304570" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2976" width="48" height="48"><path d="M341.312 85.312l64-85.312h213.376l64 85.312H960v85.376H64V85.312h277.312zM170.688 256h682.624v768H170.688V256zM256 341.312v597.376h512V341.312H256z m213.312 85.376v426.624H384V426.688h85.312z m170.688 0v426.624H554.688V426.688H640z" fill="#111111" p-id="2977"></path></svg>
                            </span>
                        </td>
                    </tr>
                `)
            })
            $('#mark_list_textarea').val(JSON.stringify(_this.markList))
        },
        /**
         * 标记弹窗
         * @method userMarkPopup
         * @param {Object} setting 设置项
         * @param {String} setting.name 用户名
         * @param {String} setting.uid UID
         * @param {String} setting.type 模式
         * @param {Number} setting.top  pos.top位置
         * @param {Number} setting.left pos.left 位置
         * @param {Function} setting.callback 回调函数
         */
        userMarkPopup(setting) {
            const _this = this
            $('.dialog').length > 0 && $('.dialog').remove()
            let $markDialog = $(`<div class="dialog dialog-sub-top list-panel animated zoomIn" style="top: ${setting.top}px;left: ${setting.left}px;">
            <a href="javascript:void(0)" class="setting-close">×</a>
            ${setting.type == 'add' ? `<div style="display:block;">添加用户: <input id="dialog_add_uid" type="text" value="" placeholder="UID"><input id="dialog_add_name" type="text" value="" placeholder="用户名"></div>` : ''}
            <table class="dialog-mark-table">
            <thead>
            <tr>
            <th width="100">标签</th><th width="50">文字</th><th width="50">背景</th><th>备注</th><th>操作</th>
            </tr>
            </thead>
            <tbody id="mark_body"></tbody>
            </table>
            <div class="dialog-buttons button-insert" style="justify-content: left !important;"></div>
            <div class="mark_history"><div class="mark_history-title">选择已添加过的标签</div><div class="mark_history-content"><div class="mark_history-scrollarea">暂无</div></div></div>
            <div class="dialog-buttons button-save" style="justify-content: right !important;"></div>
            </div>`)
            const insertRemarkRow = (r='', t='#ffffff', b='#1f72f1', d='', n=true) => {
                let $tr = $(`<tr>
                <td><input type="text" class="mark-mark" value="${r}"></td>
                <td><input class="dialog-color-picker mark-text-color" value="${t}"></td>
                <td><input class="dialog-color-picker mark-bg-color" value="${b}"></td>
                <td><textarea rows="1" class="mark-desc"/></td>
                <td><button title="删除此标签" class="mark-del">x</button></td>
                </tr>`)
                $tr.find('.mark-del').click(function(){$(this).parents('tr').remove()})
                $tr.find('.mark-desc').val(d)
                script.getModule('AuthorMark').initSpectrum($tr.find('.dialog-color-picker'))
                $markDialog.find('#mark_body').append($tr)
                n && $tr.find('.mark-mark').focus()
            }

            _this.markedTags.length > 0 && $markDialog.find('.mark_history-scrollarea').empty()
            _this.markedTags.forEach(tag => {
                $markDialog.find('.mark_history-scrollarea').append(`
                    <span title="${tag.mark}" textcolor="${tag.text_color}" bgcolor="${tag.bg_color}" desc="${tag.desc}" style="color: ${tag.text_color};background-color: ${tag.bg_color};">${tag.mark} (${tag.count})</span>
                `)
            })
            $markDialog.on('click', '.mark_history-scrollarea > span', function (e) {
                insertRemarkRow($(this).attr('title'), $(this).attr('textcolor'), $(this).attr('bgcolor'), '', false)
            })

            //恢复标签
            const existMark = _this.getUserMarks({name: setting.name, uid: setting.uid})
            existMark !== null && existMark.marks.forEach(item => insertRemarkRow(item.mark, item.text_color, item.bg_color, item.desc, false))

            let $addBtn = $('<button class="btn">+添加新标签</button>')
            $addBtn.click(() => insertRemarkRow())
            $markDialog.find('.button-insert').append($addBtn)
            let $okBtn = $('<button class="btn">保存</button>')

            $okBtn.click(function(){
                let userMarks = {marks: []}
                if (setting.type == 'add') {
                    userMarks.name = $markDialog.find('#dialog_add_name').val().trim()
                    userMarks.uid = $markDialog.find('#dialog_add_uid').val().trim() + ''
                } else {
                    userMarks.name = setting.name
                    userMarks.uid = setting.uid + ''
                }
                if (!userMarks.name && !userMarks.uid) {
                    script.popMsg('UID与用户名必填一个,其中UID权重较大', 'err')
                    return
                }
                $('#mark_body > tr').each(function(){
                    const mark = $(this).find('.mark-mark').val().trim()
                    const textColor = $(this).find('.mark-text-color').val()
                    const bgColor = $(this).find('.mark-bg-color').val()
                    const desc = $(this).find('.mark-desc').val().trim()
                    if(mark) {
                        userMarks.marks.push({mark, text_color: textColor, bg_color: bgColor, desc})
                    }
                })
                if (setting.type == 'add' && userMarks.marks.length == 0) {
                    script.popMsg('至少添加一个标签内容', 'err')
                    return
                }
                _this.setUserMarks(userMarks)
                script.popMsg('保存成功,重载页面生效')
                $('.dialog').remove()
                setting.callback()
            })
            $markDialog.find('.button-save').append($okBtn)
            $('body').append($markDialog)
            script.getModule('AuthorMark').initSpectrum('.dialog-color-picker')
        },
        /**
         * 获取用户标签对象
         * @method getUserMarks
         * @param {String} uid UID
         * @param {String} user 用户名
         * @return {Object|null} 标签对象
         */
        getUserMarks(user) {
            const check = this.markList.findIndex(v => (v.uid && user.uid && v.uid == user.uid) || (v.name && user.name && v.name == user.name))
            if(check > -1) {
                let userMark = this.markList[check]
                if ((!userMark.uid && user.uid) || (!userMark.name && user.name)) {
                    userMark.uid = user.uid + '' || ''
                    userMark.name = user.name || ''
                    script.setValue('yamibo_mark_list', JSON.stringify(this.markList))
                }
                return userMark
            } else {
                return null
            }
        },
        /**
         * 保存标签
         * @method setUserMarks
         * @param {Object} userMarks 标签对象
         */
        setUserMarks(userMarks) {
            // 检查是否已有标签
            const _this = this
            const check = _this.markList.findIndex(v => (v.uid && userMarks.uid && v.uid == userMarks.uid) || 
            (v.name && userMarks.name && v.name == userMarks.name))
            if(check > -1) {
                if (userMarks.marks.length == 0) {
                    _this.markList.splice(check, 1)
                } else {
                    _this.markList[check] = userMarks
                }
            }else {
                _this.markList.push(userMarks)
            }
            script.setValue('yamibo_mark_list', JSON.stringify(_this.markList))
        },
        style: `
        #setting {color:#6666CC !important;cursor:pointer;}
        .list-panel {position:fixed;background:#fff8e7;padding:15px 20px;border-radius:10px;box-shadow:0 0 10px #666;border:1px solid #591804;z-index:9999;}
        #banlist_panel {width:500px;}
        #keywords_panel {width:182px;}
        .extra-icon-box {padding: 5px 5px 5px 0;opacity: 0;transition: all ease .2s;}
        .extra-icon-box:hover {opacity: 1;}
        .extra-icon {position: relative;padding:0 2px;text-decoration:none;cursor:pointer;}
        .extra-icon {text-decoration:none !important;}
        span.remark {color:#666;font-size:0.8em;}
        .banned {display: inline-block;color:#ba2026;border: 1px dashed #ba2026;padding: 10px 20px;font-weight: bold;}
        .banned > div {font-weight: normal;}
        .banned-block:hover {text-decoration: underline;cursor: pointer;}
        .dialog{position:absolute;padding-right:35px}
        .dialog>div{line-height:30px}
        .dialog:before{position:absolute;content:' ';width:10px;height:10px;background-color:#fff6df;left:10px;transform:rotate(45deg)}
        .dialog-sub-top:before{top:-6px;border-top:1px solid #591804;border-left:1px solid #591804}
        .dialog-sub-bottom:before{bottom:-5px;border-bottom:1px solid #591804;border-right:1px solid #591804}
        .dialog-buttons{display:flex;justify-content:flex-end!important;margin-top:10px}
        .dialog-buttons>button{cursor:pointer}
        .dialog-user{font-size:1.5em;color:red;margin:0 5px}
        .dialog input[type=text]{width:100px;margin-right:15px}
        .dialog-mark-table td{padding-bottom:3px}
        .dialog-mark-table button{padding:0 6px;margin:0;height:20px;line-height:20px;width:20px;text-align:center;cursor:pointer}
        .mark_history {margin-top: 10px;}
        .mark_history .mark_history-title {font-weight: bold;}
        .mark_history .mark_history-content {max-height: 200px;overflow: hidden;overflow-y: scroll;}
        .mark_history .mark_history-scrollarea  {display: flex;flex-wrap: wrap;width:250px;}
        .mark_history .mark_history-content span {display: inline-block;padding: 2px 5px;border-radius: 3px;margin-right: 5px;margin-top: 5px;line-height: 20px;cursor: pointer;}
        .ban-desc {width: 100% !important;}
        .mark-desc {width: 50px;resize: none;}
        .mark-desc:focus {width:150px;height:3em;}
        .tab-content {display:flex;justify-content:space-between;flex-wrap: wrap;}
        .table-keyword {margin-top:10px;width:200px;}
        .table-keyword tr td:last-child {text-align:center;}
        .table-keyword input[type=text] {width:48px;text-transform:uppercase;text-align:center;}
        .tab-header{height:40px}
        .tab-header>span{margin-right:10px;padding:5px;cursor:pointer}
        .tab-header .table-active,.tab-header>span:hover{color:#591804;font-weight:700;border-bottom:3px solid #591804}
        .tab-content{display:none}
        .tab-content.table-active{display:flex}
        .marks-container>span{display: inline-block;padding:1px 5px;border-radius:3px;margin-right:5px;margin-top:5px;color:#fff;background-color:#1f72f1}
        .table{table-layout:fixed;border-top:1px solid #ead5bc;border-left:1px solid #ead5bc}
        .table-banlist-buttons{margin-top:10px}
        .table thead{background:#591804;border:1px solid #591804;color:#fff}
        .scroll-area{position:relative;height:200px;overflow:auto;border:1px solid #ead5bc}
        .scroll-area::-webkit-scrollbar{width:6px;height:6px}
        .scroll-area::-webkit-scrollbar-thumb{border-radius:10px;box-shadow:inset 0 0 5px rgba(0,0,0,.2);background:#591804}
        .scroll-area::-webkit-scrollbar-track{box-shadow:inset 0 0 5px rgba(0,0,0,.2);border-radius:10px;background:#ededed}
        .table td,.table th{padding:3px 5px;border-bottom:1px solid #ead5bc;border-right:1px solid #ead5bc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
        .us-action{display: inline-block;width:18px;height:18px;margin:0 3px;}
        .us-action svg{width:100%;height:100%;}
        .us-action:hover{opacity:.8}
        `
    }
    /**
     * 字体大小调整
     * @name FontResize
     * @description 此模块提供了调整字体大小的功能
     */
    const FontResize = {
        name: 'FontResize',
        title: '字体大小调整',
        setting: {
            type: 'advanced',
            key: 'fontResize',
            default: 12,
            title: '字体大小调整',
            desc: '字体大小调整,单位为像素(px),初始值是12,注意: 此值调整过大会导致页面混乱',
            menu: 'left'
        },
        initFunc() {
            const fontResizeInput = script.setting.advanced.fontResize
            try {
                const fontSize = parseInt(fontResizeInput)
                if (fontSize && fontSize != 12) {
                    $('body').css('font-size', fontSize + 'px')
                }
            } catch {
                script.printLog(`字体大小的值${script.setting.advanced.fontResize}无效,不是一个有效的数字`)
            }
        }
    }
    /**
     * 扩展坞模块
     * @name ExtraDocker
     * @description 此模块提供了一个悬浮的扩展坞,来添加某些功能
     *              目前添加的功能有: 
     *                  返回顶部: 无跳转返回当前页面的第一页/刷新当页
     *                  打开菜单: 打开个人主菜单
     *                  收藏: 收藏主题
     *                  回复: 回复主题
     *                  跳转尾页: 跳转到当前帖子的尾页
     */
    const ExtraDocker = {
        name: 'ExtraDocker',
        title: '扩展坞',
        settings: [{
            shortCutCode: 84, // T
            key: 'backTop',
            title: '返回顶部'
        }, {
            shortCutCode: 66, // B
            key: 'backBottom',
            title: '跳转尾页'
        }],
        initFunc() {
            const _this = this
            const $dockerDom = $(`
                <div class="docker">
                    <div class="docker-sidebar">
                        <svg t="1603961015993" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3634" width="64" height="64"><path d="M518.344359 824.050365c-7.879285 0-15.758569-2.967523-21.693614-9.004897l-281.403018-281.403018c-5.730389-5.730389-9.004897-13.609673-9.004897-21.693614s3.274508-15.963226 9.004897-21.693614l281.403018-281.403018c11.972419-11.972419 31.41481-11.972419 43.387229 0 11.972419 11.972419 11.972419 31.41481 0 43.387229L280.32857 511.948836l259.709403 259.709403c11.972419 11.972419 11.972419 31.41481 0 43.387229C534.0006 821.082842 526.223643 824.050365 518.344359 824.050365z" p-id="3635" fill="#888888"></path><path d="M787.160987 772.88618c-7.879285 0-15.758569-2.967523-21.693614-9.004897l-230.238833-230.238833c-11.972419-11.972419-11.972419-31.41481 0-43.387229l230.238833-230.238833c11.972419-11.972419 31.41481-11.972419 43.387229 0 11.972419 11.972419 11.972419 31.41481 0 43.387229L600.309383 511.948836l208.545218 208.545218c11.972419 11.972419 11.972419 31.41481 0 43.387229C802.817228 769.918657 794.937943 772.88618 787.160987 772.88618z" p-id="3636" fill="#888888"></path></svg>
                    </div>
                    <div class="docker-btns">
                        <div data-type="TOP" id="jump_top"><svg t="1603962702679" title="返回顶部" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9013" width="64" height="64"><path d="M528.73 161.5c-9.39-9.38-24.6-9.38-33.99 0L319.65 336.59a24.028 24.028 0 0 0-7.05 23.59A24.04 24.04 0 0 0 330 377.6c8.56 2.17 17.62-0.52 23.6-7.02l158.14-158.14 158.1 158.14a23.901 23.901 0 0 0 17 7.09c6.39 0 12.5-2.55 17-7.09 9.38-9.39 9.38-24.61 0-34L528.73 161.5zM63.89 607.09h102.79V869.5h48.04V607.09h102.79v-48.04H63.89v48.04z m518.69-48.05h-127.3c-15.37 0-30.75 5.85-42.49 17.59a59.846 59.846 0 0 0-17.59 42.49v190.3c0 15.37 5.89 30.75 17.59 42.49 11.74 11.74 27.12 17.59 42.49 17.59h127.3c15.37 0 30.75-5.85 42.49-17.59 11.7-11.74 17.59-27.12 17.59-42.49V619.17a59.903 59.903 0 0 0-17.53-42.55 59.912 59.912 0 0 0-42.55-17.54v-0.04z m12 250.38c0 2.31-0.6 5.59-3.5 8.54a11.785 11.785 0 0 1-8.5 3.5h-127.3c-3.2 0.02-6.26-1.26-8.5-3.54a11.785 11.785 0 0 1-3.5-8.5V619.17c0-2.31 0.6-5.59 3.5-8.54 2.24-2.27 5.31-3.53 8.5-3.5h127.3c2.27 0 5.55 0.64 8.5 3.55 2.27 2.24 3.53 5.31 3.5 8.5v190.29-0.05z m347.4-232.78a59.846 59.846 0 0 0-42.49-17.59H734.74V869.5h48.04V733.32h116.71a59.94 59.94 0 0 0 42.54-17.55 59.923 59.923 0 0 0 17.55-42.54v-54.07c0-15.37-5.85-30.74-17.59-42.49v-0.03z m-30.44 96.64c0 2.26-0.64 5.55-3.55 8.5a11.785 11.785 0 0 1-8.5 3.5H782.78v-78.15h116.71c2.27 0 5.59 0.6 8.54 3.5 2.27 2.24 3.53 5.31 3.5 8.5v54.15z m0 0" p-id="9014" fill="#591804"></path></svg></div>
                        <div data-type="MENU" id="jump_menu"><svg t="1687167394269" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5137" width="48" height="48"><path d="M708.367 353.656c0-56.745-22.729-110.092-63.996-150.218s-96.132-62.224-154.494-62.224-113.229 22.099-154.498 62.224-63.996 93.473-63.996 150.218c0 43.987 13.713 86.196 39.651 122.064 7.273 10.060 21.559 12.479 31.904 5.406 10.343-7.073 12.834-20.963 5.561-31.019-20.486-28.329-31.315-61.684-31.315-96.451 0-92.585 77.471-167.911 172.694-167.911s172.689 75.325 172.689 167.911-77.471 167.906-172.694 167.906c-47.055 0-92.711 8.965-135.702 26.646-41.516 17.076-78.796 41.509-110.806 72.632-32.007 31.123-57.142 67.371-74.705 107.736-18.181 41.808-27.401 86.199-27.401 131.948 0 12.298 10.252 22.266 22.898 22.266s22.898-9.968 22.898-22.266c0-162.35 135.843-294.425 302.816-294.425 58.361 0 113.229-22.099 154.497-62.22s63.996-93.477 63.996-150.221zM530.991 631.551c0 12.298 10.252 22.266 22.898 22.266h304.337c12.647 0 22.898-9.968 22.898-22.266s-10.252-22.266-22.898-22.266h-304.337c-12.647 0-22.898 9.968-22.898 22.266zM858.229 722.671h-304.337c-12.65 0-22.898 9.968-22.898 22.266s10.252 22.266 22.898 22.266h304.337c12.647 0 22.898-9.968 22.898-22.266 0-12.294-10.252-22.266-22.898-22.266zM858.229 836.056h-304.337c-12.65 0-22.898 9.967-22.898 22.266s10.252 22.266 22.898 22.266h304.337c12.647 0 22.898-9.968 22.898-22.266 0-12.294-10.252-22.266-22.898-22.266z" fill="#591804" p-id="5138"></path></svg></div>
                        <div data-type="FAVOR" id="jump_favor"><svg t="1687168828546" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6134" width="48" height="48"><path d="M512 776.533333l-238.933333 85.333334 8.533333-251.733334L128 405.333333l243.2-72.533333L512 128l140.8 209.066667L896 405.333333l-153.6 200.533334 8.533333 251.733333-238.933333-81.066667z m0-93.866666l149.333333 51.2-4.266666-157.866667 98.133333-123.733333-153.6-42.666667L512 277.333333 422.4 409.6l-153.6 42.666667 98.133333 123.733333-4.266666 157.866667L512 682.666667z" fill="#591804" p-id="6135"></path></svg></div>
                        <div data-type="REPLY" id="jump_reply"><svg t="1687169791224" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8570" width="48" height="48"><path d="M415.937331 320 415.937331 96 20.001331 438.176C-6.718669 461.28-6.622669 498.784 20.033331 521.824L415.937331 864 415.937331 640C639.937331 640 847.937331 688 1023.937331 928 943.937331 480 607.937331 320 415.937331 320" p-id="8571" fill="#591804"></path></svg></div>
                        <div data-type="BOTTOM" id="jump_bottom"><svg t="1603962680160" title="跳转至最后一页" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7501" width="64" height="64"><path d="M792.855 465.806c-6.24-6.208-14.369-9.312-22.56-9.312s-16.447 3.169-22.688 9.44l-207.91 207.74v-565.28c0-17.697-14.336-32-32-32s-32.002 14.303-32.002 32v563.712l-206.24-206.164c-6.271-6.209-14.432-9.344-22.624-9.344-8.224 0-16.417 3.135-22.656 9.407-12.511 12.513-12.48 32.768 0.032 45.248L483.536 770.38c3.265 3.263 7.104 5.6 11.136 7.135 4 1.793 8.352 2.88 13.024 2.88 1.12 0 2.08-0.544 3.2-0.64 8.288 0.064 16.608-3.009 22.976-9.408l259.11-259.292c12.48-12.511 12.448-32.8-0.127-45.248z m99.706 409.725c0 17.665-14.303 32.001-31.999 32.001h-704c-17.665 0-32-14.334-32-31.999s14.335-32 32-32h704c17.696 0 32 14.334 32 31.998z" p-id="7502" fill="#591804"></path></svg></div>
                    </div>
                </div>
            `)
            $('body').append($dockerDom)
            /**
             * Bind:Click
             * 按钮点击事件
             */
            $('body').on('click', '.docker-btns>div', function (e) {
                const type = $(this).data('type')
                if (type == 'TOP') {
                    const $nav_link = $('#pt .z a')
                    if ($nav_link.length > 0) {
                        $nav_link[$nav_link.length-1].click()
                    }
                }
                if (type == 'MENU') {
                    unsafeWindow.commonui.mainMenu.menuOpen()
                    unsafeWindow.commonui.mainMenu.menuOpenAct({
                        clientX: window.screen.width - 30,
                        clientY: 30,
                        pageX: window.screen.width - 30,
                        pageY: 30
                    }, null, 8)
                }
                if (type == 'FAVOR') {
                    const tid = script.getModule('AuthorMark').getQueryString('tid')
                    if (script.isForms() && tid) {
                        unsafeWindow.commonui.favor(e, null, tid)
                    }
                }
                if (type == 'REPLY') {
                    if (script.isForms()) {
                        window.location.href = $('#postbbtm a.rep.uitxt1').attr('href')
                    }
                }
                if (type == 'BOTTOM') {
                    let queryset = _this.getQuerySet()
                    queryset.page = 9999
                    let search = ''
                    for (let key in queryset) {
                        search += `${search == '' ? '?' : '&'}${key}=${queryset[key]}`
                    }
                    window.location.href = `${window.location.origin}${window.location.pathname}${search}`
                }
            })
        },
        renderAlwaysFunc(script) {
            (script.isThreads() || script.isForms()) ? $('.docker').show() : $('.docker').hide()
            $('#jump_favor').toggle(script.isForms())
            $('#jump_reply').toggle(script.isForms())
        },
        shortcutFunc: {
            backTop() {
                $('#jump_top').click()
                script.popNotification('返回顶部')
            },
            backBottom() {
                $('#jump_bottom').click()
                script.popNotification('最后一页')
            }
        },
        /**
         * 获取URL参数对象
         * @method getQuerySet
         * @return {Object} 参数对象
         */
        getQuerySet() {
            let queryList = {}
            let url = decodeURI(window.location.search.replace(/&amp;/g, "&"))
            url.startsWith('?') && (url = url.substring(1))
            url.split('&').forEach(item => {
                let t = item.split('=')
                if (t[0] && t[1]) {
                    queryList[t[0]] = t[1]
                }
            })
            return queryList
        },
        style: `
        .docker{position:fixed;height:80px;width:30px;bottom:180px;right:0;transition:all ease .2s}
        .docker:hover{width:150px;height:300px;bottom:75px}
        .docker-sidebar{background:#0f0;position:fixed;height:50px;width:20px;bottom:195px;right:0;display:flex;justify-content:center;align-items:center;background:#fff6df;border:1px solid #591804;box-shadow:0 0 1px #333;border-right:none;border-radius:5px 0 0 5px}
        .excel-body .docker-sidebar{background:#fff;border:1px solid #bbb}
        .docker-btns{position:absolute;top:0;left:50px;bottom:0;right:50px;display:flex;justify-content:center;align-items:center;flex-direction:column}
        .docker .docker-btns>div{opacity:0;flex-shrink: 0;}
        .docker:hover .docker-btns>div{opacity:1}
        .docker-btns>div{background:#fff6df;border:1px solid #591804;box-shadow:0 0 5px #444;width:50px;height:50px;border-radius:50%;margin:10px 0;cursor:pointer;display:flex;justify-content:center;align-items:center}
        .excel-body .docker-btns>div{background:#fff;border:1px solid #bbb}
        .docker-btns svg{width:30px;height:30px;transition:all ease .2s}
        .docker-btns svg:hover{width:40px;height:40px}
        .excel-body .docker-sidebar{background:#fff;border:1px solid #bbb}
        .excel-body .docker-btns>div{background:#fff;border:1px solid #bbb}
        `
    }
    /**
     * 域名重定向
     * @name DomainRedirect
     * @description 此模块提供了将不同域名重定向到一个指定的目标域名
     */
    const DomainRedirect = {
        name: 'DomainRedirect',
        title: '域名重定向',
        setting: {
            type: 'advanced',
            key: 'domainRedirectTarget',
            default: '',
            options: [{
                label: '未配置',
                value: ''
            }, {
                label: 'bbs.yamibo.com',
                value: 'bbs.yamibo.com'
            }, {
                label: 'bbs.yamibo.com',
                value: 'bbs.yamibo.com'
            }, {
                label: 'bbs.yamibo.com',
                value: 'bbs.yamibo.com'
            }],
            title: '域名重定向目标',
            desc: '此配置设置将域名重定向到的目标域名\n警告:不同域名的配置文件中的此配置应该保持一致,如不一致将会反复重定向陷入死循环!',
            menu: 'left'
        },
        initFunc() {
            const domainRedirectTarget = script.setting.advanced.domainRedirectTarget
            if (domainRedirectTarget && window.location.host != domainRedirectTarget) {
                const newRedirectUrl = window.location.href.replace(window.location.host, domainRedirectTarget)
                window.location.replace(newRedirectUrl)
            }
        }
    }
    // /**
    //  * 用户增强
    //  * @name UserEnhance
    //  * @description 此模块提供了用户功能类的增强,如显示注册(不可用)天数,IP所属地等
    //  */
    // const UserEnhance = {
    //     name: 'UserEnhance',
    //     title: '用户增强',
    //     settings: [{
    //         type: 'normal',
    //         key: 'userEnhance',
    //         default: true,
    //         title: '用户增强',
    //         menu: 'right'
    //     }, {
    //         type: 'advanced',
    //         key: 'locationFlagMode',
    //         default: 'FLAG_AND_TEXT',
    //         options: [{
    //             label: '国旗',
    //             value: 'FLAG'
    //         }, {
    //             label: '文字',
    //             value: 'TEXT'
    //         }, {
    //             label: '国旗加文字',
    //             value: 'FLAG_AND_TEXT'
    //         }],
    //         title: '属地显示模式',
    //         desc: '调整属地显示模式: \n全部国旗: 显示国旗不显示文字\n全部文字: 显示文字不显示国旗\n国旗加文字: 前面显示国旗后面显示文字',
    //         menu: 'right'
    //     }],
    //     forumData: {
    //         '108': '垃圾处理'
    //     },
    //     chart: null,
    //     activeCount: [],
    //     requestTasks: [],
    //     currentUserInfo: {},
    //     pageInfo: {},
    //     queryTimer: null,
    //     store: null,
    //     initFunc() {
    //         // 创建storage示例
    //         this.store = script.createStorageInstance('yamibo_BBS_Script__UserInfoCache')
    //         this.preprocessing()
    //     },
    //     renderFormsFunc($el) {
    //         if (!script.setting.normal.userEnhance) return
    //         const uid = parseInt($el.find('a[name="uid"]').text())
    //         const userInfo = unsafeWindow.commonui.userInfo.users[uid]
    //         if (!userInfo || uid <= 0) return
    //         const regSeconds = Math.ceil(new Date().getTime() / 1000) - userInfo.regdate
    //         const regDays = Math.round(regSeconds / 3600 / 24)
    //         const regYear = (regSeconds / 3600 / 24 / 365).toFixed(1)
    //         // 插入UI
    //         const $userEnhanceContainer = $(`<div class="user-enhance user-enhance-${uid}"></div>`)
    //         const $node = $el.find('.posterinfo div.stat .clickextend').siblings('div:first-child')
    //         $node.after($userEnhanceContainer)
    //         $userEnhanceContainer.append(`<div><span title="注册(不可用)天数: ${regDays}天\n注册(不可用)年数: ${regYear}年">坛龄: <span class="numeric userval" name="regday">${regDays}天</span></span></div>`)
    //         $userEnhanceContainer.append(`<div><span title="发帖数量: ${userInfo.postnum}">发帖: <span class="numeric userval" name="regday">${userInfo.postnum}</span></span></div>`)
    //         $userEnhanceContainer.append(`<div><span style="display: inline-flex;align-items: center;" class="user-location">属地: <span class="userval numeric req-retry" style="margin-left:5px;">点击获取</span></span></div>`)
    //         $userEnhanceContainer.append(`<div class="qbc"><button>查看用户活动记录</button></div>`)
    //         $userEnhanceContainer.find('.user-location > span').click(e => {
    //             if (!$(e.target).hasClass('req-retry')) return
    //             this.getUserLocation(uid)
    //         })
    //         $el.find('.qbc > button').click(() => this.queryUserActivityRecords(userInfo))
    //         // this.getUserLocation(uid)
    //     },
    //     /**
    //      * 预处理
    //      */
    //     async preprocessing() {
    //         // 初始化的时候清理超过一定时间的数据,避免无限增长数据
    //         // 出于性能考虑,每日只执行一次
    //         const currentDate = new Date()
    //         const lastClear = await this.store.getItem('USERENHANCE_CLEAR_DAY')
    //         if (lastClear != currentDate.getDate()) {
    //             const exprieSeconds = 7 * 24 * 3600  // 7天
    //             const currentTime = Math.ceil(currentDate.getTime() / 1000)
    //             let removedCount = 0
    //             this.store.iterate((value, key, iterationNumber) => {
    //                 if (key.startsWith('USERINFO_')) {
    //                     if (!value._queryTime || currentTime - value._queryTime >= exprieSeconds) {
    //                         this.store.removeItem(key)
    //                         removedCount += 1
    //                     }
    //                 }
    //             })
    //             .then(() => {
    //                 this.store.setItem('USERENHANCE_CLEAR_DAY', currentDate.getDate())
    //                 script.printLog(`用户增强: 已清除${removedCount}条用户超期数据`)
    //             })
    //             .catch(err => {
    //                 console.error('用户增强清除超期数据失败,错误原因:', err)
    //             })
    //         }
    //         // 获取所有版面字典,扁平化提纯fid:name
    //         if (!window?.script_muti_get_var_store?.data) {
    //             await $.ajax({
    //                 url: unsafeWindow.__API.indexForumList(),
    //                 dataType: 'script',
    //                 cache: true
    //             })
    //         }
    //         const _forumData = script_muti_get_var_store.data?.['0']?.all
    //         if (_forumData && typeof _forumData == 'object') {
    //             for (const v1 of Object.values(_forumData)) {
    //                 if (v1.content && typeof v1.content == 'object') {
    //                     for (const v2 of Object.values(v1.content)) {
    //                         if (v2.content && typeof v2.content == 'object') {
    //                             for (const v3 of Object.values(v2.content)) {
    //                                 this.forumData[v3.fid] = v3.name
    //                             }
    //                         }
    //                     }
    //                 }
    //             }
    //         }

    //     },
    //     getUserLocation(uid) {
    //         $('.user-enhance-'+uid).find('.user-location > span').attr('class', 'userval numeric loading').empty()
    //         // 调用数据接口获取属地
    //         this.getRemoteUserInfo(uid)
    //         .then(remoteUserInfo => {
    //             $('.user-enhance-'+uid).find('.user-location').attr('title', `IP属地: ${remoteUserInfo.ipLoc}`)
    //             $('.user-enhance-'+uid).find('.user-location > span').replaceWith(this.getCountryFlag(remoteUserInfo.ipLoc))
    //         })
    //         .catch(err => {
    //             $('.user-enhance-'+uid).find('.user-location > span').attr('class', 'userval numeric req-retry').html(`获取失败(${err.status}), 点击重试`)
    //         })
    //     },
    //     /**
    //      * 调用接口获取用户信息
    //      * @param {String} uid 用户UID
    //      * @returns Promise 用户信息对象
    //      */
    //     getRemoteUserInfo(uid) {
    //         const storageKey = `USERINFO_${uid}`
    //         return new Promise((resolve, reject) => {
    //             this.store.getItem(storageKey)
    //             .then(value => {
    //                 if (value) {
    //                     resolve(value)
    //                 } else {
    //                     $.ajax({url: `https://${window.location.host}/nuke.php?__output=11&__act=get&__lib=ucp&uid=${uid}`})
    //                     .then(res => {
    //                         if (res.data && Array.isArray(res.data) && res.data.length > 0) {
    //                             const remoteUserInfo = res.data[0]
    //                             remoteUserInfo['_queryTime'] = res.time
    //                             this.store.setItem(storageKey, remoteUserInfo)
    //                             resolve(res.data[0])
    //                         }
    //                     })
    //                     .catch(err => reject(err))
    //                 }
    //             })
    //         })
    //     },
    //     /**
    //      * 获取属地标识代码
    //      * @param {String} chsName 中文国家名称
    //      * @returns HTML代码
    //      */
    //     getCountryFlag(chsName) {
    //         let textElement = `<span class="numeric userval" name="location">${chsName}</span>`
    //         let flagElement = ''
    //         if (script.setting.advanced.locationFlagMode != 'TEXT') {
    //             const flagUrl = `https://www.huuua.com/zi/scss/icons/flag-icon-css/flags`
    //             if (CHINESE_CONVERT_ISO3166_1[chsName]) {
    //                 flagElement = `<img class="country-flag" onerror="this.style.width='auto'" alt="${chsName}" src="${flagUrl}/${CHINESE_CONVERT_ISO3166_1[chsName].toLowerCase()}.svg"/>`
    //             } else if (CHINA_PROVINCE.includes(chsName.endsWith('省') ? chsName.slice(0, -1) : chsName)) {
    //                 flagElement = `<img class="country-flag" onerror="this.style.width='auto'" alt="中国" src="${flagUrl}/cn.svg"/> `
    //                 const specialArea = ['香港', '澳门', '台湾'].find(name => chsName.endsWith(name))
    //                 if (specialArea) {
    //                     flagElement += `<img class="country-flag" onerror="this.style.width='auto'" alt="中国${chsName}" src="${flagUrl}/${CHINESE_CONVERT_ISO3166_1['中国'+chsName].toLowerCase()}.svg"/> `
    //                 }
    //             }
    //         }
    //         switch (script.setting.advanced.locationFlagMode) {
    //             case 'FLAG':
    //                 return flagElement
    //             case 'TEXT':
    //                 return textElement
    //             case 'FLAG_AND_TEXT':
    //                 return flagElement + textElement
    //             default:
    //                 return textElement
    //         }
    //     },
    //     /**
    //      * 查询用户最近活动记录(3页)
    //      * @param {Object} userInfo 用户信息对象
    //      */
    //     async queryUserActivityRecords(userInfo) {
    //         $('#chart_cover').remove()
    //         if (typeof echarts === 'undefined') {
    //             script.popMsg('该功能所需资源库正在加载,请稍后再试', 'warn')
    //             return
    //         }
    //         $('body').append(`
    //             <div id="chart_cover" class="animated zoomIn">
    //                 <a href="javascript:void(0)" class="setting-close">×</a>
    //                 <div id="chart_container">
    //                     <div class="loading"></div>
    //                 </div>
    //                 <div class="chart-statistics">
    //                     <div class="statistics-status">
    //                         <div class="st-t">🏷️ 当前统计的数据量</div>
    //                         <div class="st-s1">用户发布的主题(页):</div>
    //                         <div class="st-s1-1">- 已统计
    //                             <span class="st-c" id="statistics_post_pages">0</span>页
    //                             <span class="st-l" id="statistics_post_status"></span>
    //                         </div>
    //                         <div class="st-s1">用户回复的主题(页)</div>
    //                         <div class="st-s1-1">- 已统计
    //                             <span class="st-c" id="statistics_reply_pages">0</span>页
    //                             <span class="st-l" id="statistics_reply_status"></span>
    //                         </div>
    //                         <div class="st-s1">数据天数跨度</div>
    //                         <div class="st-s1-1">- 已统计
    //                             <span class="st-c" id="statistics_days_range">-</span>天内
    //                         </div>
    //                         <div class="st-t">🏷️ 统计结果</div>
    //                         <div class="st-s2">✔️ 发布主题: <span class="st-c" id="statistics_post_count">-</span></div>
    //                         <div class="st-s2">✔️ 回复主题: <span class="st-c" id="statistics_reply_count">-</span></div>
    //                         <div class="st-s2">✔️ 总计发帖: <span class="st-c" id="statistics_total_count">-</span></div>
    //                     </div>
    //                     <button id="chart_deep_query">深度统计</button>
    //                 </div>
    //             </div>
    //         `)
    //         $('#chart_cover .setting-close').click(() => {
    //             this.queryUserDeepRecords('end')
    //             $('#chart_cover').remove()
    //         })
    //         $('#chart_cover #chart_deep_query').click(() => this.queryUserDeepRecords())

    //         this.activeCount = []
    //         this.requestTasks = []
    //         this.currentUserInfo = userInfo
    //         this.pageInfo = {
    //             post: {
    //                 label: '发布主题',
    //                 pages: 0,
    //                 status: '',
    //                 earliestPostdate: new Date().getTime() / 1000
    //             },
    //             reply: {
    //                 label: '回复主题',
    //                 pages: 0,
    //                 status: '',
    //                 earliestPostdate: new Date().getTime() / 1000
    //             }
    //         }
    //         // 查询发帖记录
    //         // tips: 由于yamibo限流, 此处暂先拉取一页回复记录
    //         for (let i=0;i<1;i++) {
    //             // 查询发帖记录
    //             // this.requestTasks.push(this.requestUserRecords(userInfo.uid, 'post', i+1))
    //             // 查询回复记录
    //             this.requestTasks.push(this.requestUserRecords(userInfo.uid, 'reply', i+1))
    //         }
    //         Promise.allSettled(this.requestTasks)
    //         .then(() => {
    //             // 渲染chart
    //             const chartContainer = document.getElementById('chart_container')
    //             if (!chartContainer) return
    //             this.chart = echarts.init(chartContainer)
    //             this.statisticsCount()
    //             this.updateChart()
    //         })
    //         .catch(err => {
    //             script.popMsg(`查询【${this.pageInfo[err.type].label}第${err.page}页】数据接口失败! 原因: ${err.errMsg}`, 'err')
    //         })
    //     },
    //     /**
    //      * 查询当前用户深度活动记录(到上限)
    //      */
    //     async queryUserDeepRecords(status) {
    //         if (status != 'end' && !$('#chart_deep_query').hasClass('query-loading')) {
    //             // 步进统计
    //             $('#chart_deep_query').addClass('query-loading').text('暂停统计')
    //             this.queryTimer = setInterval(async () => {
    //                 try {
    //                     if (!this.pageInfo.post.status.endsWith('max')) {
    //                         await this.requestUserRecords(this.currentUserInfo.uid, 'post', this.pageInfo.post.pages + 1)
    //                     } else if (!this.pageInfo.reply.status.endsWith('max')) {
    //                         await this.requestUserRecords(this.currentUserInfo.uid, 'reply', this.pageInfo.reply.pages + 1)
    //                     }
    //                     if (this.pageInfo.post.status.endsWith('max') && this.pageInfo.reply.status.endsWith('max')) {
    //                         this.queryUserDeepRecords('end')  // 停止(完成)统计
    //                     }
    //                 } catch (err) {
    //                     script.popMsg(`查询【${this.pageInfo[err.type].label}第${err.page}页】数据接口失败! 原因: ${err.errMsg}`, 'err')
    //                     this.queryUserDeepRecords('pause')  // 停止(暂停)统计
    //                 } finally {
    //                     this.statisticsCount()
    //                     this.updateChart()
    //                 }
    //             }, 2000)
    //         } else {
    //             // 暂停&完成统计
    //             $('#chart_deep_query').removeClass('query-loading').text('继续统计')
    //             if (status == 'end') {
    //                 $('#chart_deep_query').attr('disabled', 'disabled').text('统计完成')
    //             }
    //             if (this.queryTimer) {
    //                 clearInterval(this.queryTimer)
    //                 this.queryTimer = null
    //             }
    //         }
    //     },
    //     /**
    //      * 统计数量
    //      */
    //     statisticsCount(validList, incrField) {
    //         if (validList && incrField) {
    //             validList.forEach(item => {
    //                 const pName = item.parent && item.parent['2'] ? item.parent['2'] : ''
    //                 let existRecord = this.activeCount.find(p => p.fid == item.fid)
    //                 if (!existRecord) {
    //                     existRecord = {fid: item.fid, name: pName, postdate: item.postdate, value: 0, post: 0, reply: 0}
    //                     this.activeCount.push(existRecord)
    //                 }
    //                 existRecord['fid'] = item.fid
    //                 existRecord['name'] ||= pName
    //                 existRecord['value'] += 1
    //                 existRecord[incrField] += 1
    //             })
    //         }
    //         const postCount = this.activeCount.reduce((p, c) => p + c.post, 0)
    //         const replyCount = this.activeCount.reduce((p, c) => p + c.reply, 0)
    //         // 计算统计数据
    //         $('#statistics_post_pages').text(this.pageInfo.post.pages)
    //         $('#statistics_post_status').attr('class', `st-l ${this.pageInfo.post.status}`)
    //         $('#statistics_reply_pages').text(this.pageInfo.reply.pages)
    //         $('#statistics_reply_status').attr('class', `st-l ${this.pageInfo.reply.status}`)
    //         $('#statistics_post_count').text(postCount)
    //         $('#statistics_reply_count').text(replyCount)
    //         $('#statistics_total_count').text(postCount + replyCount)
    //         // 计算时间跨度
    //         const minPostDate = Math.min(this.pageInfo.post.earliestPostdate, this.pageInfo.reply.earliestPostdate)
    //         const daysRange = Math.ceil((new Date().getTime() / 1000 - minPostDate) / 86400)
    //         $('#statistics_days_range').text(daysRange)
    //     },
    //     /**
    //      * 发起查询用户记录
    //      */
    //     requestUserRecords(uid, type, page) {
    //         return new Promise((resolve, reject) => {
    //             let url = `https://${window.location.host}/thread.php?__output=11&authorid=${uid}&page=${page}`
    //             if (type == 'reply') {
    //                 url += '&searchpost=1'
    //             }
    //             $.ajax({url})
    //             .then(postRes => {
    //                 const err = postRes.error
    //                 if (postRes.data && postRes.data.__T) {
    //                     if (page > this.pageInfo[type].pages) {
    //                         this.pageInfo[type].pages = page
    //                     }
    //                     if (this.pageInfo[type].status != 'grab-max') {
    //                         this.pageInfo[type].status = ''
    //                     }
    //                     postRes.data.__T.forEach(item => {
    //                         if (item?.__P?.postdate && item.__P.postdate < this.pageInfo[type].earliestPostdate) {
    //                             this.pageInfo[type].earliestPostdate = item.__P.postdate
    //                         }
    //                     })
    //                     this.statisticsCount(postRes.data.__T, type)
    //                 }
    //                 if (err) {
    //                     const errMsg = (err && Array.isArray(err)) ? err.join(' ') : err
    //                     if (errMsg.includes('没有符合条件的结果')) {
    //                         this.pageInfo[type].status = 'grab-max'
    //                     } else {
    //                         this.pageInfo[type].status = 'grab-err'
    //                         reject({errMsg, type, page})
    //                         return
    //                     }
    //                 }
    //                 resolve()
    //             })
    //             .catch(err => reject({
    //                 errMsg: `服务器HTTP返回:${err.status}`,
    //                 type,
    //                 page
    //             }))
    //         })
    //     },
    //     /**
    //      * 更新图表
    //      */
    //     updateChart() {
    //         // 处理未命名板块
    //         this.activeCount.forEach(item => item.name ||= (this.forumData[item.fid] || `板块FID: ${item.fid}`))
    //         this.chart.setOption({
    //             title: {
    //                 text: '用户活跃板块记录',
    //                 subtext: this.currentUserInfo.username || `UID: ${this.currentUserInfo.username}`,
    //                 top: 10,
    //                 left: 'center'
    //             },
    //             tooltip: {
    //                 formatter: function(row) {
    //                     return `${row.data.name}<br />总计: ${row.data.value}<br>发布: ${row.data.post}<br>回复: ${row.data.reply}`
    //                 }
    //             },
    //             toolbox: {
    //                 show: true,
    //                 bottom: 10,
    //                 left: 10,
    //                 itemSize: 16,
    //                 feature: {
    //                     saveAsImage: {show: true},
    //                 },
    //             },
    //             legend: {
    //                 type: 'scroll',
    //                 orient: 'vertical',
    //                 left: 10,
    //                 top: 'middle'
    //             },
    //             series: [{
    //                 name: '板块',
    //                 type: 'pie',
    //                 radius: '50%',
    //                 label: {
    //                     formatter: function(row) {
    //                         return `{name|${row.data.name}}\n{detail|发布: ${row.data.post}} {detail|回复: ${row.data.reply}}`
    //                     },
    //                     minMargin: 5,
    //                     edgeDistance: 10,
    //                     lineHeight: 15,
    //                     rich: {detail: {
    //                         fontSize: 10,
    //                         color: '#999'
    //                     }}
    //                 },
    //                 labelLine: {
    //                     length: 15,
    //                     length2: 0,
    //                     maxSurfaceAngle: 80
    //                 },
    //                 labelLayout: params => {
    //                     const isLeft = params.labelRect.x < this.chart.getWidth() / 2
    //                     const points = params.labelLinePoints
    //                     if (points) {
    //                         points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
    //                     }
    //                     return {labelLinePoints: points}
    //                 },
    //                 data: this.activeCount,
    //                 emphasis: {
    //                     itemStyle: {shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)'}
    //                 }
    //             }],
    //             graphic: [{
    //                 type: 'image',
    //                 right: 10,
    //                 bottom: 30,
    //                 style: {
    //                     image: POWER_BY_yamiboSCRIPT,
    //                     width: 150
    //                 }
    //             }]
    //         })
    //         $('.chart-statistics').show()
    //     },
    //     style: `
    //     .user-enhance {display:flex;flex-wrap:wrap;}
    //     .user-enhance > div {box-sizing:border-box;width:50%;padding-right:3px;}
    //     .user-enhance span[name=location] {margin-left:5px;}
    //     .country-flag {width:20px;height:auto;margin-left:5px;}
    //     .user-location .loading {width:8px;height:8px;border:1px solid #9c958b;border-top-color:transparent;border-radius:100%;animation:loading-circle infinite 0.75s linear;}
    //     .user-location .req-retry:hover {text-decoration: underline;cursor: pointer;}
    //     .qbc {width:100% !important;padding:5px 0;}
    //     .qbc > button {margin:0;}
    //     #chart_cover {position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);border-radius:10px;background:#FFF;border:1px solid #AAA;box-shadow:0 0 10px rgba(0,0,0,.3);z-index:9993;}
    //     #chart_cover > .setting-close {background:#FFF;border:1px solid #AAA;color:#AAA;}
    //     #chart_cover > .setting-close:hover {background:#AAA;border:1px solid #FFF;color:#FFF;}
    //     #chart_container {width:1000px;height:600px;}
    //     #chart_cover .loading {position:absolute;top: 50%;left:50%;margin-top:-20px;margin-left:-25px;width:40px;height:40px;border:2px solid #AAA;border-top-color:transparent;border-radius:100%;animation:loading-circle infinite 0.75s linear;}
    //     .chart-statistics {display:none;position:absolute;top:calc(50% - 220px);right:10px;min-width:140px;height:400px;}
    //     .statistics-status > div {padding: 2px 0;}
    //     .statistics-status > .st-t {font-weight:bold;font-size:1.1em;padding-top: 25px;}
    //     .statistics-status > .st-s1 {margin-top: 10px;}
    //     .statistics-status > .st-s1-1 {font-size:0.9em;color:#00000073;}
    //     .statistics-status .st-c {font-weight:bold;font-size:18px;color:#1677ff;margin:0px 2px;}
    //     .statistics-status .st-l {display: inline-block;padding: 1px 5px;color: #FFF;transform: scale(0.8);border-radius: 5px;}
    //     .statistics-status .st-l.grab-max {background: #67c23a;}
    //     .statistics-status .st-l.grab-max:after {content: '最大';}
    //     .statistics-status .st-l.grab-err {background: #f56c6c;}
    //     .statistics-status .st-l.grab-err:after {content: '错误';}
    //     #chart_deep_query {display:flex;align-items:center;margin-top:30px;background:#1677ff;border-color:#1677ff;color:#FFF;padding:6px 15px;border-radius:8px;text-align:center;cursor:pointer;}
    //     #chart_deep_query:not(disabled):hover {opacity:.7;}
    //     #chart_deep_query:disabled {background: #67c23a;}
    //     .query-loading:before {content:"";display: inline-block;margin-right: 5px;width: 8px;height: 8px;border: 2px solid #fff;border-top-color: transparent;border-radius: 100%;animation: loading-circle infinite 0.75s linear;}
    //     .statistics-status. {padding:10px 0;}
    //     @keyframes loading-circle {0% {transform:rotate(0);}100% {transform:rotate(360deg);}}
    //     `
    // }
    /**
     * 插件支持模块
     * @name PluginSupport
     * @description 此模块提供了插件支持的能力
     */
    const PluginSupport = {
        name: 'PluginSupport',
        title: '插件支持',
        pluginSetting: null,
        preProcFunc() {
            script.setting.plugin = {}
            this.pluginSetting = script.setting.plugin
        },
        initFunc() {
            // 添加到配置面板的设置入口
            script.getModule('SettingPanel').addButton({
                title: '插件管理',
                desc: '插件管理',
                click: () => $('#plugin_panel').show()
            })
            try {
                // 注册(不可用)插件管理面板
                GM_registerMenuCommand('插件管理', () => $('#plugin_panel').show())
            } catch {}
            // 添加插件到导入导出配置
            script.getModule('BackupModule').addItem({
                title: '插件配置',
                writeKey: 'plugin_setting',
                valueKey: 'pluginSetting',
                module: this
            })
            // 添加插件
            if (unsafeWindow.yamiboScriptPlugins) {
                script.printLog(`检测到个${unsafeWindow.yamiboScriptPlugins.length}插件`)
                unsafeWindow.yamiboScriptPlugins.forEach(module => {
                    module.name && this.addPlugin(module)
                })
            }
            // 加载配置
            this.loadSetting()
            this.initSettingPanel()
        },
        initSettingPanel() {
            const _this = this
            // 插件设置面板
            const $pluginPanel = $(`
                <div id="plugin_panel" class="list-panel animated fadeInUp">
                    <a href="javascript:void(0)" class="setting-close" close-type="hide">×</a>
                    <div class="plugin-header"><b>插件管理</b></div>
                    <div class="plugin-scorllarea">
                        <div class="plugin-content"></div>
                    </div>
                    <div class="plugin-footer">
                        <button class="btn" id="plugin_getmore">获取更多插件</button>
                        <button class="btn" id="plugin_save">保存</button>
                    </div>
                </div>
            `)
            const yamiboScriptPlugins = unsafeWindow?.yamiboScriptPlugins || []
            yamiboScriptPlugins.forEach(module => {
                const $plugin = $(`
                    <div class="plugin">
                        <div class="plugin-info">
                            <div class="plugin-name">
                                <a href="${module.meta.updateURL || module.meta.namespace}" target="_blank">${module.title || module.name}<span>v${module.version}</span></a>
                            </div>
                            <div class="plugin-desc" title="${module.desc}">${module.desc}</div>
                        </div>
                    </div>
                `)
                if (module.error) {
                    // 插件有误
                    $plugin.addClass('plugin-error help')
                    $plugin.attr('error', module.error).attr('help', '插件未执行,原因: ' + module.errorMsg)
                }
                const pluginID = this.getPluginID(module)
                if (!module.error && (module.setting || module.settings)) {
                    let settings = []
                    if (Array.isArray(module.settings)) {
                        settings.push(...module.settings)
                    } else if (Array.isArray(module.setting)) {
                        settings.push(...module.setting)
                    } else if (typeof module.setting == 'object') {
                        settings.push(module.setting)
                    }
                    const $pluginSettings = $('<div class="plugin-settings"><table></table></div>')
                    // 渲染表单
                    settings.forEach(setting => {
                        let formItem = ''
                        const valueType = typeof setting.default
                        if (valueType === 'boolean') {
                            formItem = `<input type="checkbox" plugin-id="${pluginID}" plugin-setting-key="${setting.key}">`
                        }
                        if (valueType === 'number') {
                            formItem = `<input type="number" plugin-id="${pluginID}" plugin-setting-key="${setting.key}">`
                        }
                        if (valueType === 'string') {
                            if (setting.options) {
                                let t = ''
                                for (const option of setting.options) {
                                    t += `<option value="${option.value}">${option.label}</option>`
                                }
                                formItem = `<select plugin-id="${pluginID}" plugin-setting-key="${setting.key}">${t}</select>`
                            } else if (setting.type == 'textarea') {
                                formItem = `<textarea rows="3" plugin-id="${pluginID}" plugin-setting-key="${setting.key}" />`
                            } else {
                                formItem = `<input type="text" plugin-id="${pluginID}" plugin-setting-key="${setting.key}" />`
                            }
                        }
                        $pluginSettings.find('table').append(`
                            <tr>
                                <td><span ${setting.desc ? 'class="help" help="' + setting.desc + '" ' : ''}>${setting.title || setting.key}</span></td>
                                <td>${formItem}</td>
                            </tr>
                        `)
                        // 恢复设置
                        let defaultValue = setting.default
                        if (script.setting.plugin?.[pluginID]?.[setting.key] != undefined) {
                            defaultValue = script.setting.plugin[pluginID][setting.key]
                        }
                        const $input = $pluginSettings.find(`[plugin-id="${pluginID}"][plugin-setting-key="${setting.key}"]`)
                        if (typeof defaultValue == 'boolean') {
                            $input[0].checked = defaultValue
                        }
                        if (typeof defaultValue == 'number' || typeof defaultValue == 'string') {
                            $input.val(defaultValue)
                        }
                        setting.$el = $input
                    })
                    // 添加按钮及设置面板
                    $plugin.append($pluginSettings)
                    $plugin.find('.plugin-info').append(`<div class="plugin-expand help" help="查看插件设置"><img src="${SVG_ICON_SETTING}"></div>`)
                }
                // 自定义按钮
                if (module.buttons && Array.isArray(module.buttons) && module.buttons.length > 0) {
                    const $pluginButtons = $('<div class="plugin-buttons"></div>')
                    module.buttons.forEach((button, index) => {
                        const buttonid = `${pluginID}_button_${index}`
                        const $button = $(`<button class="btn" id="${buttonid}">${button.title || '未命名按钮'}</button>`)
                        $button.click(() => {
                            // 插件注入对象
                            const moduleProxy = this.createModuleProxy(module)
                            if (typeof button.action == 'string' && typeof module[button.action] == 'function') {
                                module[button.action].apply(moduleProxy, [button.args])
                            }
                            if (typeof button.action == 'function') {
                                button.action.apply(moduleProxy, [button.args])
                            }
                        })
                        button.$el = $button
                        $pluginButtons.append($button)
                    })
                    $plugin.find('.plugin-settings').append($pluginButtons)
                }
                if ($plugin.find('.plugin-settings tr').length == 0 && $plugin.find('.plugin-settings button').length == 0) {
                    $plugin.find('.plugin-settings').append('<div class="plugin-nosettings">暂无可配置项</div>')
                }
                $pluginPanel.find('.plugin-content').append($plugin)
            })
            if (yamiboScriptPlugins.length == 0) {
                $pluginPanel.find('.plugin-content').html('<div class="hld_plugin-empty">未安装任何插件</div>')
            }
            // 展开设置
            $pluginPanel.find('.plugin-expand').click(function(){
                $(this).parent().siblings('.plugin-settings').slideToggle(100)
            })
            // 获取更多插件
            $pluginPanel.find('#plugin_getmore').click(function(){
                window.open('https://gf.qytechs.cn/zh-CN/scripts?q=yamibo%E4%BC%98%E5%8C%96%E6%91%B8%E9%B1%BC%E4%BD%93%E9%AA%8C%E6%8F%92%E4%BB%B6')
            })
            // 保存设置
            $pluginPanel.find('#plugin_save').click(function(){
                _this.savePluginSetting()
            })
            $('body').append($pluginPanel)
        },
        /**
         * 添加插件
         * @param {*} module 插件模块
         */
        addPlugin(module) {
            module.type = 'plugin'
            module.title = module.title || module.meta.name
            module.desc = module.desc || module.meta.description
            module.author = module.author || module.meta.author || 'Unknown'
            module.version = module.meta.version || '1.0.0'
            // 检测是否与标准模块命名冲突,此检测是为了未来可以合并插件功能进主脚本内而做的预设性检查
            const pluginID = this.getPluginID(module)
            const existBasicModule = script.modules.find(m => m.type != 'plugin' && m.name == module.name)
            if (existBasicModule) {
                module.error = 'UNIQUE_BASIC'
                module.errorMsg = `插件[${module.name}]与标准模块重名,可能是标准模块已有此功能`
                script.printLog(`[${module.name}]${module.errorMsg}`)
                return
            }
            // 检测重复插件,插件Name@Author字符串为唯一key值,如重复导入将不会运行
            const duplicatePlugin = script.modules.find(m => m.type == 'plugin' && `${m.name}@${this.hashCode(m.author)}` == pluginID)
            if (duplicatePlugin) {
                module.error = 'DUPLICATED'
                module.errorMsg = `重复导入,插件[${pluginID}]当前已加载`
                script.printLog(`[${module.name}]${module.errorMsg}`)
                return
            }
            // 插件预处理函数
            if (module.preProcFunc) {
                try {
                    module.preProcFunc()
                } catch (error) {
                    this.printLog(`[${module.name}]插件在[preProcFunc()]中运行失败!`)
                    console.log(error)
                }
            }
            // 添加设置
            const addSetting = setting => {
                // // 插件配置
                // if (setting.shortCutCode && script.setting.plugin.shortcutKeys) {
                //     script.setting.plugin.shortcutKeys.push(setting.shortCutCode)
                // }
                if (setting.key) {
                    if (!script.setting.plugin[pluginID]) {
                        script.setting.plugin[pluginID] = {}
                    }
                    script.setting.plugin[pluginID][setting.key] = setting.default ?? ''
                    script.setting.original.push(Object.assign({type: 'plugin', pluginID}, setting))
                }
            }
            // 功能板块
            if (module.setting && !Array.isArray(module.setting)) {
                addSetting(module.setting)
            }
            if (module.settings && Array.isArray(module.settings)) {
                for (const setting of module.settings) {
                    addSetting(setting)
                }
            }
            // 添加样式
            if (module.style) {
                script.style += module.style
            }
            const moduleProxy = this.createModuleProxy(module)
            script.modules.push(moduleProxy)
        },
        /**
         * 插件注入对象
         * @param {*} module 插件模块
         */
        createModuleProxy(module) {
            const pluginID = this.getPluginID(module)
            return new Proxy(module, {
                get: function (target, key) {
                    if (key == 'mainScript') return script  // 主脚本
                    if (key == 'pluginID') return pluginID  // 插件ID
                    if (key == 'pluginSettings') return script.setting.plugin[pluginID]  // 插件保存配置
                    // 插件输入控件dom
                    if (key == 'pluginInputs') {
                        const pluginInputs = {}
                        Object.keys(script.setting.plugin[pluginID]).forEach(key => {
                            pluginInputs[key] = $(`[plugin-id="${pluginID}"][plugin-setting-key="${key}"]`)
                        })
                        return pluginInputs
                    }
                    return target[key]
                },
                set: function (target, key, newValue) {
                    if (['mainScript', 'pluginID', 'pluginSettings', 'pluginInputs'].includes(key)) {
                        throw new TypeError(`[${key}]为插件保留字段,不可手动设值`)
                    }
                    target[key] = newValue
                    return true
                }
            })
        },
        /**
         * 读取插件配置
         * @method loadSetting
         */
        loadSetting() {
            try {
                // 插件设置
                const pluginSettingStr = script.getValue('yamibo_plugin_setting')
                if (pluginSettingStr) {
                    let localPluginSetting = JSON.parse(pluginSettingStr)
                    for (const pluginName of Object.keys(localPluginSetting)) {
                        let currentSetting = script.setting.plugin[pluginName]
                        let localSetting = localPluginSetting[pluginName]
                        if (currentSetting) {
                            for (let k in currentSetting) {
                                !localSetting.hasOwnProperty(k) && (localSetting[k] = currentSetting[k])
                            }
                            for (let k in localSetting) {
                                !currentSetting.hasOwnProperty(k) && delete localSetting[k]
                            }
                        }
                        script.setting.plugin[pluginName] = localSetting
                    }
                }
            } catch(e) {
                script.throwError(`【yamibo-Script】读取插件配置文件出现错误,无法加载配置文件!\n错误问题: ${e}\n\n请尝试使用【修复脚本】来修复此问题`)
            }
        },
        /**
         * 保存插件配置
         * @method savePluginSetting
         * @param {String} msg 自定义消息信息
         */
        savePluginSetting (msg='保存插件配置成功,刷新页面生效') {
            script.modules.forEach(module => {
                if (module.type == 'plugin' && module.name) {
                    const pluginID = this.getPluginID(module)
                    const pluginSetting = Object.assign({}, script.setting.plugin[pluginID])
                    const $controls = $(`[plugin-id="${pluginID}"]`)
                    if (pluginSetting && $controls) {
                        $controls.each((index, element) => {
                            const k = $(element).attr('plugin-setting-key')
                            const inputType = $(element)[0].nodeName
                            const originalSetting = script.setting.original.find(s => s.type == 'plugin' && s.pluginID == pluginID && s.key == k)
                            const valueType = typeof originalSetting.default
                            if (inputType == 'SELECT') {
                                pluginSetting[k] = $(element).val()
                            } else {
                                if (valueType == 'boolean') {
                                    pluginSetting[k] = $(element)[0].checked
                                }
                                if (valueType == 'number') {
                                    pluginSetting[k] = +$(element).val()
                                }
                                if (valueType == 'string') {
                                    pluginSetting[k] = $(element).val()
                                }
                            }
                        })
                        // 预检查配置参数
                        if (module.beforeSaveSettingFunc) {
                            const errorMsg = module.beforeSaveSettingFunc(pluginSetting)
                            if (errorMsg && typeof errorMsg === 'string') {
                                script.throwError(`插件【${module.title || module.name || 'UNKNOW'}】检查配置返回错误: \n${'-'.repeat(50)}\n${errorMsg}`)
                            }
                        }
                        script.setting.plugin[pluginID] = Object.assign({}, pluginSetting)
                    }
                }
            })
            script.setValue('yamibo_plugin_setting', JSON.stringify(script.setting.plugin))
            console.log("yamibo_plugin_setting",script.getValue('yamibo_plugin_setting'));
            msg && script.popMsg(msg)
            $('#plugin_panel').hide()
        },
        /**
         * 获取插件的唯一ID
         * @param {obj} module 插件对象
         */
        getPluginID(module) {
            return `${module.name}@${this.hashCode(module.author)}`
        },
        /**
         * 生成字符串哈希值
         * @param {String} str 字符串
         * @returns 哈希值
         */
        hashCode(str) {
            return str.split('').reduce((prevHash, currVal) => (((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0)
        },
        style: `
        #plugin_panel {display:none;width:400px;min-height:300px;}
        #plugin_panel .plugin-header {min-height:20px;}
        #plugin_panel .plugin-scorllarea {margin:10px 0;height:300px;padding-right:10px;overflow-y:auto;border-top:1px solid #e0c19e;border-bottom:1px solid #e0c19e;}
        #plugin_panel .plugin-content {height:auto;}
        #plugin_panel .hld_plugin-empty {margin-top:20%;text-align:center;font-size:16px;color:#666;}
        #plugin_panel .plugin-footer {min-height:32px;display:flex;justify-content:space-between;}
        #plugin_panel button {transition:all .2s ease;cursor:pointer;}
        .plugin {padding:10px 0;border-bottom:1px dashed #666;}
        .plugin-error {text-decoration: line-through;}
        .plugin-info {position:relative;padding-right:30px;box-sizing:border-box;}
        .plugin-name {margin-bottom:5px;}
        .plugin-name a {font-weight:bold;font-size:16px;color:#591804;}
        .plugin-name span {margin-left:4px;font-size:70%;color:#666;}
        .plugin-desc {white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
        .plugin-expand {width:20px;display:flex;align-items:center;cursor:pointer;position:absolute;top:10px;right:0px;}
        .plugin-expand img {width:100%;transition:all .2s ease}
        .plugin-expand:hover img {width:100%;transform:rotate(45deg);}
        .plugin-settings {display:none;width:100%;height:auto;border-top:1px dashed #999;margin-top:10px;padding-top:10px;}
        .plugin-settings table td {padding-right:10px;}
        .plugin-settings textarea {resize:none;}
        .plugin-settings input[type=number] {border: 1px solid #e6c3a8;box-shadow: 0 0 2px 0 #7c766d inset;border-radius: 0.25em;}
        .plugin-buttons {padding-top:5px;}
        .plugin-buttons > button {margin-right:5px;margin-top:5px;}
        .plugin-nosettings {color:#666;}
        `
    }

    /**
     * 初始化脚本
     */
    const script = new yamiboBBSScript()
    /**
     * 添加模块
     */
    script.addModule(SettingPanel)
    script.addModule(ShortCutKeys)
    script.addModule(BackupModule)
    script.addModule(PluginSupport)
    // script.addModule(RewardPanel)
    script.addModule(HideAvatar)
    script.addModule(HideSmile)
    script.addModule(HideImage)
    script.addModule(ImgResize)
    script.addModule(HideSign)
    script.addModule(HideHeader)
    script.addModule(ExcelMode)
    script.addModule(FoldQuote)
    // script.addModule(UserEnhance)
    script.addModule(LinkTargetBlank)
    script.addModule(DirectLinkJump)
    // script.addModule(ImgEnhance)
    // script.addModule(AuthorMark)
    script.addModule(AutoPage)
    script.addModule(KeywordsBlock)
    script.addModule(MarkAndBan)
    script.addModule(FontResize)
    script.addModule(ExtraDocker)
    script.addModule(DomainRedirect)
    /**
     * 运行脚本
     */
    script.run()
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址