Greasy Fork

Greasy Fork is available in English.

yamibo优化摸鱼体验

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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      http://greasyfork.icu/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: 'http://greasyfork.icu/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('http://greasyfork.icu/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="http://greasyfork.icu/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="http://greasyfork.icu/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="http://greasyfork.icu/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('http://greasyfork.icu/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()
})();