网页限制解除(超级助手Plus优化版)

【超级助手Plus项目优化】通杀大部分网站,解除禁止复制、剪切、选择文本、右键菜单的限制。本脚本基于 yuanjie221 版本 (原作者Cat73) 修改。为zcool.com.cn添加特定规则以修复链接点击问题。确保排除规则优先执行。

// ==UserScript==
// @name              网页限制解除(超级助手Plus优化版)
// @namespace         https://greasyfork.org/zh-CN/users/106222-qxin-i
// @version           4.5.3
// @description       【超级助手Plus项目优化】通杀大部分网站,解除禁止复制、剪切、选择文本、右键菜单的限制。本脚本基于 yuanjie221 版本 (原作者Cat73) 修改。为zcool.com.cn添加特定规则以修复链接点击问题。确保排除规则优先执行。
// @author            Cat73 & yuanjie221 (原作者) & 超级助手Plus (优化)
// @contributor       yuanjie221, 超级助手Plus
// @match             *://*/*
// @exclude           *www.bilibili.com/video*
// @exclude           *www.bilibili.com/v*
// @exclude           *www.bilibili.com/s/*
// @exclude           *www.bilibili.com/bangumi*
// @exclude           https://www.bilibili.com/medialist/play/*
// @exclude           *www.youtube.com/watch*
// @exclude           *www.panda.tv*
// @exclude           *www.github.com*
// @exclude           https://lanhuapp.com/*
// @exclude           https://www.douyu.com/*
// @exclude           *://www.doubao.com/*
// @exclude           https://www.zhihu.com/signin?*
// @exclude           https://tieba.baidu.com/*
// @exclude           https://v.qq.com/*
// @exclude           *.taobao.com/*
// @exclude           *tmall.com*
// @exclude           *signin*
// @exclude           *://jimeng.jianying.com/*
// @grant             GM_getValue
// @grant             GM_setValue
// @grant             GM_addStyle
// @grant             GM_deleteValue
// @grant             GM_setClipboard
// @grant             GM_registerMenuCommand
// @grant             GM_info
// @run-at            document-start
// ==/UserScript==

(function() {
    'use strict';

    /**
     * 检查当前页面是否被脚本的 @exclude 规则排除。
     * 必须在脚本非常早期调用此函数。
     * @returns {boolean} 如果被排除则返回 true,否则返回 false。
     */
    function 检查页面是否被排除提前执行() {
        const 当前链接检查 = window.location.href;
        try {
            const 脚本元信息 = GM_info.scriptMetaStr;
            if (!脚本元信息) {
                // console.warn('[网页限制解除] 无法获取脚本元信息,可能影响排除规则判断。');
                return false;
            }
            const 排除规则行 = 脚本元信息.match(/@exclude\s+(.*)/g) || [];

            for (const 行 of 排除规则行) {
                let 规则表达式 = 行.substring(行.indexOf(' ') + 1).trim();
                try {
                    规则表达式 = 规则表达式.replace(/\*/g, '.*?').replace(/\?/g, '.');
                    if (new RegExp(规则表达式).test(当前链接检查)) {
                        // console.log(`[网页限制解除] 提前检查:当前页面 ${当前链接检查} 被规则 "${行.substring(行.indexOf(' ') + 1).trim()}" 排除。脚本将不执行。`);
                        return true;
                    }
                } catch (e) {
                    // console.error(`[网页限制解除] 提前检查:解析排除规则 "${规则表达式}" 失败:`, e);
                }
            }
        } catch (err) {
            // console.error('[网页限制解除] 提前检查排除规则时发生错误:', err);
        }
        return false;
    }

    if (检查页面是否被排除提前执行()) {
        return;
    }

    const 脚本名称 = GM_info.script.name;
    const 日志前缀 = `[${脚本名称}]:`;

    // 默认配置数据
    const 默认配置 = {
        status: 1,
        version: 0.2,
        message: '网页限制解除脚本正在运行',
        positionTop: '0',
        positionLeft: '0',
        positionRight: 'auto',
        addBtn: true,
        shortcut: 3,
        rules: {
            rule_def: {
                name: '默认规则',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousemove|beforeunload',
                unhook_eventNames: 'mousedown|mouseup|keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            },
            rule_plus: {
                name: '增强规则',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousedown|mouseup|mousemove|beforeunload',
                unhook_eventNames: 'keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            },
            rule_zhihu: {
                name: '知乎专用规则',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousemove',
                unhook_eventNames: 'keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            },
            rule_zcool: {
                name: '站酷专用规则',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut',
                unhook_eventNames: 'keydown|keyup',
                dom0: false,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            }
        },
        data: [
            'b.faloo.com', 'bbs.coocaa.com', 'book.hjsm.tom.com', 'book.zhulang.com',
            'book.zongheng.com', 'chokstick.com', 'chuangshi.qq.com', 'city.udn.com',
            'cutelisa55.pixnet.net', 'huayu.baidu.com', 'imac.hk', 'life.tw',
            'luxmuscles.com', 'news.missevan.com', 'read.qidian.com', 'www.15yan.com',
            'www.17k.com', 'www.18183.com', 'www.360doc.com', 'www.coco01.net',
            'www.eyu.com', 'www.hongshu.com', 'www.hongxiu.com', 'www.imooc.com',
            'www.jjwxc.net', 'www.readnovel.com', 'www.tadu.com', 'www.xxsy.net',
            'www.z3z4.com', 'www.zhihu.com', 'yuedu.163.com', 'www.ppkao.com',
            'movie.douban.com', 'www.ruiwen.com', 'vipreader.qidian.com', 'www.pigai.org',
            'www.shangc.net', 'www.myhtlmebook.com', 'www.yuque.com', 'www.longmabookcn.com',
            'www.alphapolis.co.jp', 'www.sdifen.com', 'votetw.com', 'boke112.com',
            'www.myhtebooks.com', 'www.xiegw.cn', 'www.uta-net.com', 'www.bimiacg.net',
            'www.dianyuan.com', 'origenapellido.com', '3g.163.com', 'www.lu-xu.com',
            'leetcode.cn', 'www.jianbiaoku.com', 'www.soyoung.com', 'doc.guandang.net',
            'www.51dongshi.com', 'm.haodf.com', 'www.daodoc.com', 'www.wcqjyw.com',
            'www.szxx.com.cn'
        ]
    };

    let 用户配置 = GM_getValue('rwl_userData_v2');
    if (!用户配置 || typeof 用户配置 !== 'object') {
        用户配置 = JSON.parse(JSON.stringify(默认配置));
    } else {
        用户配置 = 合并配置(JSON.parse(JSON.stringify(默认配置)), 用户配置);
    }
    GM_setValue('rwl_userData_v2', 用户配置);


    const 当前域名 = window.location.hostname;
    const 当前链接 = window.location.href;
    let 侧边按钮节点 = null;
    let 当前应用规则 = null;
    let 白名单列表 = [];

    const 事件名存储前缀 = 'iqxinEventData_';
    let 目标事件名列表, 非目标事件名列表, 所有相关事件名列表;

    const 原始EventTargetaddEventListener = EventTarget.prototype.addEventListener;
    const 原始DocumentEventListener = document.addEventListener;
    const 原始EventPreventDefault = Event.prototype.preventDefault;


    迁移旧版数据();
    白名单列表 = 获取当前白名单();

    document.addEventListener('DOMContentLoaded', function() {
        if (用户配置.addBtn) {
            初始化侧边按钮();
            const 按钮检查定时器 = setInterval(function() {
                const 复选框节点 = document.getElementById('rwl_iqxin_blacklist_checkbox');
                if (复选框节点) {
                    侧边按钮节点 = 复选框节点;
                    clearInterval(按钮检查定时器);
                    启动核心功能();
                } else {
                    if (!document.getElementById('rwl_iqxin_panel')) {
                       初始化侧边按钮();
                    }
                }
            }, 500);
        } else {
            if (检查域名是否在白名单(白名单列表, 当前域名)) {
                初始化解除限制逻辑();
            }
        }
    });

    window.addEventListener('load', function() {
        if (检查域名是否在白名单(白名单列表, 当前域名) || (侧边按钮节点 && 侧边按钮节点.checked)) {
            执行DOM0事件清理();
        }
    });

    GM_registerMenuCommand('网页限制解除 - 设置', 显示设置菜单);

    function 合并配置(target, source) {
        for (const key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)) {
                if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key]) &&
                    target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
                    合并配置(target[key], source[key]);
                } else {
                    target[key] = source[key];
                }
            }
        }
        return target;
    }

    function 启动核心功能() {
        初始化拖拽功能();
        绑定设置按钮事件();

        if (检查域名是否在白名单(白名单列表, 当前域名)) {
            try {
                if (用户配置.addBtn && 侧边按钮节点) {
                    侧边按钮节点.checked = true;
                }
            } catch (错误) {
                console.error(`${日志前缀} 设置复选框状态出错:`, 错误);
            }
            初始化解除限制逻辑();
        } else if (用户配置.addBtn && 侧边按钮节点) {
            侧边按钮节点.checked = false;
        }
    }

    function 初始化侧边按钮() {
        if (!用户配置.addBtn || document.getElementById('rwl_iqxin_panel')) return;

        const 面板容器 = document.createElement('div');
        面板容器.id = 'rwl_iqxin_panel';
        面板容器.className = 'rwl-exempt';

        const 屏幕高度 = document.documentElement.clientHeight;
        let 顶部位置 = parseFloat(用户配置.positionTop);
        顶部位置 = 顶部位置 > 屏幕高度 ? 屏幕高度 - 40 : (顶部位置 < 0 ? 0 : 顶部位置);

        GM_addStyle(`
            #rwl_iqxin_panel.rwl-exempt {
                position: fixed !important; top: ${顶部位置}px !important;
                left: ${用户配置.positionLeft === 'auto' ? 'auto' : 用户配置.positionLeft + 'px'} !important;
                right: ${用户配置.positionRight === 'auto' ? 'auto' : 用户配置.positionRight + 'px'} !important;
                transform: ${用户配置.positionLeft !== 'auto' ? 'translate(-95%, 0)' : 'translate(95%, 0)'};
                width: auto !important; min-width: 85px !important; height: 25px !important;
                font-size: 12px !important; font-weight: 500 !important; font-family: Verdana, Arial, '宋体', sans-serif !important;
                color: #fff !important; background: #333 !important; z-index: 2147483647 !important;
                margin: 0 !important; opacity: 0.1 !important;
                transition: opacity 0.3s, transform 0.3s, height 0.3s, line-height 0.3s !important;
                overflow: hidden !important; user-select: none !important; -webkit-user-select: none !important; -moz-user-select: none !important;
                text-align: center !important; white-space: nowrap !important; line-height: 25px !important;
                padding: 0 10px !important; border: 1px solid #ccc !important;
                border-width: ${用户配置.positionLeft !== 'auto' ? '1px 1px 1px 0' : '1px 0 1px 1px'} !important;
                border-top-right-radius: ${用户配置.positionLeft !== 'auto' ? '5px' : '0'} !important;
                border-bottom-right-radius: ${用户配置.positionLeft !== 'auto' ? '5px' : '0'} !important;
                border-top-left-radius: ${用户配置.positionLeft === 'auto' ? '5px' : '0'} !important;
                border-bottom-left-radius: ${用户配置.positionLeft === 'auto' ? '5px' : '0'} !important;
                box-sizing: content-box !important; display: flex !important; align-items: center !important;
            }
            #rwl_iqxin_panel.rwl-exempt.rwl-active-iqxin {
                transform: translate(0, 0) !important; opacity: 0.9 !important;
                height: 32px !important; line-height: 32px !important;
            }
            #rwl_iqxin_panel.rwl-exempt input[type="checkbox"].rwl-exempt {
                margin: 0 0 0 5px !important; padding: 0 !important; vertical-align: middle !important;
                -webkit-appearance: checkbox !important; -moz-appearance: checkbox !important; appearance: checkbox !important;
                position: relative !important; top: -1px; opacity: 1 !important; cursor: pointer !important;
                width: auto !important; height: auto !important;
            }
            #rwl_iqxin_panel.rwl-exempt .rwl-drag-handle.rwl-exempt {
                cursor: move !important; font-size: 12px !important; padding: 0 5px;
                flex-grow: 1; text-align: left;
            }
            #rwl_iqxin_panel.rwl-exempt .rwl-setbtn.rwl-exempt {
                margin: 0 5px 0 0 !important; padding: 2px 4px !important; border: none !important;
                border-radius: 3px !important; cursor: pointer !important; background: #fff !important;
                color: #000 !important; line-height: normal !important; font-size: 11px !important;
            }
        `);

        window.addEventListener('resize', function() {
            const 当前屏幕高度 = document.documentElement.clientHeight;
            let 当前顶部位置 = parseFloat(用户配置.positionTop);
            当前顶部位置 = 当前顶部位置 > 当前屏幕高度 ? 当前屏幕高度 - 40 : (当前顶部位置 < 0 ? 0 : 当前顶部位置);
            const 已存在面板 = document.getElementById('rwl_iqxin_panel');
            if (已存在面板) {
                已存在面板.style.top = 当前顶部位置 + 'px';
            }
        });

        面板容器.innerHTML = `
            <button type="button" id="rwl_iqxin_setbtn" class="rwl-setbtn rwl-exempt">设置</button>
            <span class="rwl-drag-handle rwl-exempt">解除限制</span>
            <input type="checkbox" name="rwl_iqxin_blacklist_checkbox_name" id="rwl_iqxin_blacklist_checkbox" class="rwl-exempt">
        `;

        if (window.self === window.top) {
            const 父容器 = document.body || document.documentElement;
            if (父容器) {
                父容器.appendChild(面板容器);
                侧边按钮节点 = document.getElementById('rwl_iqxin_blacklist_checkbox');
                if (侧边按钮节点) {
                    侧边按钮节点.addEventListener('change', function() {
                        处理白名单切换(this.checked);
                    });
                }
            } else {
                console.error(`${日志前缀} 无法添加侧边按钮:document.body 和 document.documentElement 均不可用。`);
            }
        }

        面板容器.addEventListener('mouseover', function() { this.classList.add('rwl-active-iqxin'); });
        面板容器.addEventListener('mouseleave', function() { this.classList.remove('rwl-active-iqxin'); });
    }

    function 绑定设置按钮事件() {
        const 设置按钮 = document.getElementById('rwl_iqxin_setbtn');
        if (设置按钮) {
            设置按钮.addEventListener('click', 显示设置菜单);
        }
    }

    function 显示设置菜单() {
        const 旧菜单 = document.getElementById('rwl_iqxin_setMenu');
        if (旧菜单) {
            旧菜单.parentNode.removeChild(旧菜单);
            return;
        }

        const 当前用户配置 = GM_getValue('rwl_userData_v2') || 用户配置;
        const 按钮显示已选中 = 当前用户配置.addBtn ? 'checked' : '';

        const 菜单元素 = document.createElement('div');
        菜单元素.id = 'rwl_iqxin_setMenu';
        菜单元素.className = 'rwl-exempt';

        GM_addStyle(`
            #rwl_iqxin_setMenu.rwl-exempt {
                position: fixed !important; top: 50px !important; left: 50%; transform: translateX(-50%) !important;
                width: 500px !important; max-width: 90vw !important; padding: 20px !important;
                background: #f0f0f0 !important; border-radius: 8px !important; text-align: left !important;
                font-size: 14px !important; font-family: 'Microsoft YaHei', Arial, sans-serif !important;
                z-index: 2147483647 !important; border: 1px solid #bbb !important;
                box-shadow: 0 5px 15px rgba(0,0,0,0.2) !important; color: #333 !important; overflow-y: auto; max-height: 80vh;
            }
            #rwl_iqxin_setMenu.rwl-exempt p { margin: 10px 0 !important; line-height: 1.7 !important; }
            #rwl_iqxin_setMenu.rwl-exempt input[type="text"].rwl-exempt,
            #rwl_iqxin_setMenu.rwl-exempt select.rwl-exempt,
            #rwl_iqxin_setMenu.rwl-exempt textarea.rwl-exempt {
                border: 1px solid #ccc !important; padding: 8px 10px !important; border-radius: 4px !important;
                width: calc(100% - 22px) !important; box-sizing: border-box !important;
                font-family: inherit !important; font-size: 13px !important; margin-top: 3px;
            }
            #rwl_iqxin_setMenu.rwl-exempt input[type="checkbox"].rwl-exempt { margin-right: 8px !important; vertical-align: middle !important; }
            #rwl_iqxin_setMenu.rwl-exempt .rwl-menu-button.rwl-exempt {
                margin: 10px 8px 0 0 !important; padding: 8px 15px !important; border: 1px solid transparent !important;
                border-radius: 4px !important; cursor: pointer !important; font-size: 14px !important;
                background-color: #ddd !important; color: #333 !important; transition: all 0.2s ease !important;
            }
            #rwl_iqxin_setMenu.rwl-exempt .rwl-menu-button.rwl-exempt:hover { opacity: 0.85; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_reset_btn.rwl-exempt { border-color: #d9534f !important; color: #d9534f !important; background-color: white !important; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_reset_btn.rwl-exempt:hover { background-color: #d9534f !important; color:white !important; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_save_btn.rwl-exempt { border-color: #5cb85c !important; color: #5cb85c !important; background-color: white !important; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_save_btn.rwl-exempt:hover { background-color: #5cb85c !important; color:white !important; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_close_btn.rwl-exempt { border-color: #777 !important; color: #777 !important; background-color: white !important; }
            #rwl_iqxin_setMenu.rwl-exempt #rwl_close_btn.rwl-exempt:hover { background-color: #777 !important; color:white !important; }
            #rwl_iqxin_setMenu.rwl-exempt a { color: #007bff !important; text-decoration: none !important; }
            #rwl_iqxin_setMenu.rwl-exempt a:hover { text-decoration: underline !important; }
            #rwl_iqxin_setMenu.rwl-exempt textarea.rwl-exempt { min-height: 180px !important; resize: vertical !important; font-family: monospace !important; font-size: 12px !important;}
            #rwl_iqxin_setMenu.rwl-exempt .rwl-menu-title { font-size: 18px; font-weight: bold; margin-bottom:15px; color:#111; }
            #rwl_iqxin_setMenu.rwl-exempt .rwl-menu-footer { font-size:0.8em; color: #777; display: block; margin-top: 15px; border-top: 1px solid #ccc; padding-top:10px; }
        `);

        菜单元素.innerHTML = `
            <div class="rwl-menu-title rwl-exempt">网页限制解除 - 设置</div>
            <p class="rwl-exempt">侧边按钮距顶部距离 (像素): <input id='rwl_pos_top_input' type='text' value="${当前用户配置.positionTop}" class="rwl-exempt"></p>
            <p class="rwl-exempt" title='快捷键设置'>复制快捷键:
                <select id='rwl_shortcut_select' class="rwl-exempt">
                    <option value='0' ${当前用户配置.shortcut == 0 ? 'selected' : ''}>关闭快捷键</option>
                    <option value='1' ${当前用户配置.shortcut == 1 ? 'selected' : ''}>F1</option>
                    <option value='2' ${当前用户配置.shortcut == 2 ? 'selected' : ''}>Ctrl + F1</option>
                    <option value='3' ${当前用户配置.shortcut == 3 ? 'selected' : ''}>Ctrl + C / Ctrl + X (尝试增强复制)</option>
                </select>
            </p>
            <label class="rwl-exempt"><p class="rwl-exempt"><input id='rwl_show_btn_check' type='checkbox' ${按钮显示已选中} class="rwl-exempt"> 显示侧边按钮 (取消后需通过油猴扩展菜单重开设置)</p></label>
            <p class="rwl-exempt"><b>白名单列表</b> (JSON数组格式,每个域名用双引号包裹,逗号分隔):</p>
            <textarea id="rwl_whitelist_area" wrap='off' class="rwl-exempt">${JSON.stringify(当前用户配置.data, null, 2)}</textarea>
            <p class="rwl-exempt">
                <button id='rwl_save_btn' class="rwl-menu-button rwl-exempt">保存设置并刷新</button>
                <button id='rwl_reset_btn' class="rwl-menu-button rwl-exempt">恢复默认设置</button>
                <button id='rwl_close_btn' class="rwl-menu-button rwl-exempt" title='关闭设置面板'>关闭</button>
            </p>
            <div class="rwl-menu-footer rwl-exempt">
                问题反馈: <a target='_blank' href='https://greasyfork.org/zh-CN/scripts/35977/feedback'>GreasyFork反馈区</a> |
                原作者: Cat73, yuanjie221 | 当前版本: ${GM_info.script.version} (优化 by 超级助手Plus) <br>
                提示: 脚本尽力解除通用限制,但无法保证完美兼容所有网站。部分复杂网站可能需要手动排除或定制规则。
            </div>
        `;
        document.body.appendChild(菜单元素);

        document.getElementById('rwl_save_btn').addEventListener('click', 保存设置);
        document.getElementById('rwl_close_btn').addEventListener('click', 关闭设置菜单);
        document.getElementById('rwl_reset_btn').addEventListener('click', 重置设置);
    }

    function 保存设置() {
        const 顶部位置输入 = document.getElementById('rwl_pos_top_input').value;
        const 快捷键选择 = document.getElementById('rwl_shortcut_select').value;
        const 显示按钮已选中 = document.getElementById('rwl_show_btn_check').checked;
        const 白名单文本域 = document.getElementById('rwl_whitelist_area').value;

        try {
            const 解析后白名单 = JSON.parse(白名单文本域);
            if (!Array.isArray(解析后白名单)) {
                alert('白名单数据格式错误:必须是一个JSON数组。\n例如:["example.com", "another.com"]');
                return;
            }

            用户配置.addBtn = 显示按钮已选中;
            用户配置.data = 解析后白名单.map(item => String(item).trim()).filter(item => item.length > 0);
            用户配置.positionTop = String(parseInt(顶部位置输入, 10) || 0);
            用户配置.shortcut = parseInt(快捷键选择, 10);

            GM_setValue('rwl_userData_v2', 用户配置);
            alert('设置已保存!页面将自动刷新以应用更改。');
            setTimeout(() => window.location.reload(), 300);
        } catch (错误) {
            alert(`保存失败!白名单数据JSON格式无效,请检查。\n错误详情: ${错误.message}`);
        }
    }

    function 重置设置() {
        if (confirm('您确定要将所有设置恢复为默认状态吗?此操作不可撤销。')) {
            GM_deleteValue('rwl_userData_v2');
            用户配置 = JSON.parse(JSON.stringify(默认配置));
            alert('设置已恢复为默认值。页面将自动刷新。');
            setTimeout(() => window.location.reload(), 300);
        }
    }

    function 关闭设置菜单() {
        const 菜单 = document.getElementById('rwl_iqxin_setMenu');
        if (菜单) 菜单.parentNode.removeChild(菜单);
    }

    function 初始化拖拽功能() {
        setTimeout(function() {
            try {
                const 可拖动面板 = document.getElementById('rwl_iqxin_panel');
                if (!可拖动面板) return;

                const 拖动句柄 = 可拖动面板.querySelector('.rwl-drag-handle.rwl-exempt');
                const 实际拖动目标 = 拖动句柄 || 可拖动面板;

                实际拖动目标.addEventListener('mousedown', function(mouseDownEvent) {
                    if (mouseDownEvent.button !== 0) return;

                    可拖动面板.style.transition = 'none';
                    const 初始鼠标X = mouseDownEvent.clientX;
                    const 初始鼠标Y = mouseDownEvent.clientY;
                    const 面板初始左 = 可拖动面板.offsetLeft;
                    const 面板初始顶 = 可拖动面板.offsetTop;
                    const 面板宽度 = 可拖动面板.offsetWidth;

                    function 处理鼠标移动(mouseMoveEvent) {
                        let 新左侧位置 = 面板初始左 + (mouseMoveEvent.clientX - 初始鼠标X);
                        let 新顶部位置 = 面板初始顶 + (mouseMoveEvent.clientY - 初始鼠标Y);
                        新顶部位置 = Math.max(0, Math.min(新顶部位置, window.innerHeight - 可拖动面板.offsetHeight));
                        新左侧位置 = Math.max(0, Math.min(新左侧位置, window.innerWidth - 面板宽度));
                        可拖动面板.style.left = 新左侧位置 + 'px';
                        可拖动面板.style.top = 新顶部位置 + 'px';
                        可拖动面板.style.right = 'auto';
                    }

                    function 处理鼠标松开() {
                        document.removeEventListener('mousemove', 处理鼠标移动);
                        document.removeEventListener('mouseup', 处理鼠标松开);
                        可拖动面板.style.transition = 'opacity 0.3s, transform 0.3s, height 0.3s, line-height 0.3s';
                        if (可拖动面板.offsetLeft < (window.innerWidth - 面板宽度) / 2) {
                            用户配置.positionLeft = String(可拖动面板.offsetLeft);
                            用户配置.positionRight = 'auto';
                            可拖动面板.style.transform = 'translate(-95%, 0)';
                        } else {
                            用户配置.positionRight = String(window.innerWidth - (可拖动面板.offsetLeft + 面板宽度));
                            用户配置.positionLeft = 'auto';
                            可拖动面板.style.left = 'auto';
                            可拖动面板.style.right = 用户配置.positionRight + 'px';
                            可拖动面板.style.transform = 'translate(95%, 0)';
                        }
                        用户配置.positionTop = String(可拖动面板.offsetTop);
                        GM_setValue('rwl_userData_v2', 用户配置);
                    }
                    document.addEventListener('mousemove', 处理鼠标移动);
                    document.addEventListener('mouseup', 处理鼠标松开);
                    mouseDownEvent.preventDefault();
                });
            } catch (错误) {
                console.error(`${日志前缀} 初始化拖拽功能出错:`, 错误);
            }
        }, 1000);
    }

    function 初始化解除限制逻辑() {
        当前应用规则 = 获取当前网站规则();
        if (!当前应用规则) return;

        目标事件名列表 = 当前应用规则.hook_eventNames ? 当前应用规则.hook_eventNames.split('|') : [];
        非目标事件名列表 = 当前应用规则.unhook_eventNames ? 当前应用规则.unhook_eventNames.split('|') : [];
        所有相关事件名列表 = 目标事件名列表.concat(非目标事件名列表);

        if (当前应用规则.dom0) 执行DOM0事件清理();
        if (当前应用规则.hook_addEventListener) 代理AddEventListener();
        if (当前应用规则.hook_preventDefault) 代理PreventDefault();
        if (当前应用规则.hook_set_returnValue && typeof Event.prototype.__defineSetter__ === 'function') 代理EventReturnValue();
        if (当前应用规则.add_css) 注入强制CSS();
    }

    function 代理AddEventListener() {
        EventTarget.prototype.addEventListener = function (类型, 监听器, 选项) {
            if (非目标事件名列表.includes(类型)) {
                const 是否捕获阶段 = (typeof 选项 === 'boolean' ? 选项 : (选项 && 选项.capture));
                const 函数存储名 = 事件名存储前缀 + 类型 + (是否捕获阶段 ? '_t' : '_f');
                if (this[函数存储名] === undefined) {
                    this[函数存储名] = [];
                    原始EventTargetaddEventListener.call(this, 类型, (是否捕获阶段 ? unhook事件处理函数_捕获 : unhook事件处理函数_冒泡), 选项);
                }
                if (typeof 监听器 === 'function') this[函数存储名].push(监听器);
            } else {
                原始EventTargetaddEventListener.call(this, 类型, 监听器, 选项);
            }
        };
        document.addEventListener = function (类型, 监听器, 选项) {
            if (非目标事件名列表.includes(类型)) {
                const 是否捕获阶段 = (typeof 选项 === 'boolean' ? 选项 : (选项 && 选项.capture));
                const 函数存储名 = 事件名存储前缀 + 类型 + (是否捕获阶段 ? '_t' : '_f');
                if (this[函数存储名] === undefined) {
                    this[函数存储名] = [];
                    原始DocumentEventListener.call(this, 类型, (是否捕获阶段 ? unhook事件处理函数_捕获 : unhook事件处理函数_冒泡), 选项);
                }
                 if (typeof 监听器 === 'function') this[函数存储名].push(监听器);
            } else {
                原始DocumentEventListener.call(this, 类型, 监听器, 选项);
            }
        };
    }

    function 代理PreventDefault() {
        Event.prototype.preventDefault = function () {
            if (!目标事件名列表.includes(this.type)) {
                原始EventPreventDefault.apply(this, arguments);
            }
        };
    }

    function 代理EventReturnValue() {
        try {
            Object.defineProperty(Event.prototype, 'returnValue', {
                configurable: true,
                get: function() { return this._actualReturnValue === undefined ? true : this._actualReturnValue; },
                set: function(值) {
                    if (目标事件名列表.includes(this.type) && 值 === false) {
                        this._actualReturnValue = true;
                    } else {
                        this._actualReturnValue = 值;
                    }
                }
            });
        } catch (错误) {
            console.warn(`${日志前缀} 设置 Event.prototype.returnValue 代理失败:`, 错误);
        }
    }

    function 执行DOM0事件清理() {
        if (!当前应用规则 || !当前应用规则.dom0) return;
        const 页面所有元素 = 获取页面所有元素(true);
        for (const 当前元素 of 页面所有元素) {
            if (typeof 当前元素 !== 'object' || 当前元素 === null) continue;
            if (typeof 当前元素.closest === 'function' && 当前元素.closest('.rwl-exempt')) continue;

            for (const 事件名 of 所有相关事件名列表) {
                const on事件属性名 = 'on' + 事件名;
                try {
                    if (typeof 当前元素[on事件属性名] === 'function' && 当前元素[on事件属性名] !== onxxx事件替代函数) {
                        if (非目标事件名列表.includes(事件名)) {
                            当前元素[事件名存储前缀 + on事件属性名] = 当前元素[on事件属性名];
                            当前元素[on事件属性名] = onxxx事件替代函数;
                        } else if (目标事件名列表.includes(事件名)) {
                            当前元素[on事件属性名] = null;
                        }
                    }
                } catch (错误) { /* console.warn */ }
            }
        }
    }

    function unhook事件处理(事件对象, 目标元素, 回调存储属性名) {
        const 回调列表 = 目标元素[回调存储属性名];
        if (回调列表 && Array.isArray(回调列表)) {
            for (const 单个回调 of 回调列表) {
                try {
                    if (typeof 单个回调 === 'function') 单个回调.call(目标元素, 事件对象);
                    else if (typeof 单个回调.handleEvent === 'function') 单个回调.handleEvent.call(单个回调, 事件对象);
                } catch(e) { console.error(`${日志前缀} 执行unhook回调时出错:`, e, 单个回调); }
            }
        }
        if (事件对象) 事件对象.returnValue = true;
        return true;
    }
    function unhook事件处理函数_捕获(e) { return unhook事件处理(e, this, 事件名存储前缀 + e.type + '_t'); }
    function unhook事件处理函数_冒泡(e) { return unhook事件处理(e, this, 事件名存储前缀 + e.type + '_f'); }

    function onxxx事件替代函数(事件对象) {
        const 原始处理函数名 = 事件名存储前缀 + 'on' + 事件对象.type;
        if (typeof this[原始处理函数名] === 'function') {
            try { this[原始处理函数名](事件对象); }
            catch(e) { console.error(`${日志前缀} 执行onxxx替代回调时出错:`, e, this[原始处理函数名]); }
        }
        事件对象.returnValue = true;
        return true;
    }

    function 获取页面所有元素(includeFrames = true) {
        let 元素集合 = [];
        try {
            元素集合 = Array.from(document.querySelectorAll('*'));
            元素集合.push(document); 元素集合.push(window);
            if (includeFrames) {
                const frames = document.querySelectorAll('iframe, frame');
                for (const frame of frames) {
                    try {
                        if (frame.contentWindow && frame.contentWindow.document) {
                            const frameDocument = frame.contentWindow.document;
                            元素集合.push(frame.contentWindow); 元素集合.push(frameDocument);
                            元素集合 = 元素集合.concat(Array.from(frameDocument.querySelectorAll('*')));
                        }
                    } catch (错误) { /* console.warn */ }
                }
            }
        } catch (错误) { console.error(`${日志前缀} 获取页面元素时出错:`, 错误); }
        return 元素集合;
    }

    function 获取当前白名单() {
        return (用户配置.data || []).filter(item => typeof item === 'string' && item.trim().length > 1);
    }

    function 检查域名是否在白名单(检查用白名单, 待检查域名) {
        for (let i = 0; i < 检查用白名单.length; i++) {
            if (待检查域名.includes(检查用白名单[i])) return i + 1;
        }
        return false;
    }

    function 处理白名单切换(是否选中加入白名单) {
        const 最新用户配置 = GM_getValue('rwl_userData_v2') || 用户配置;
        let 当前生效白名单 = (最新用户配置.data || []).slice();
        const 检查结果 = 检查域名是否在白名单(当前生效白名单, 当前域名);
        let 配置已更改 = false;

        if (是否选中加入白名单 && !检查结果) {
            当前生效白名单.push(当前域名);
            配置已更改 = true;
            初始化解除限制逻辑();
            执行DOM0事件清理();
        } else if (!是否选中加入白名单 && 检查结果) {
            当前生效白名单.splice(检查结果 - 1, 1);
            配置已更改 = true;
            setTimeout(() => window.location.reload(true), 350);
        }

        if (配置已更改) {
            最新用户配置.data = 当前生效白名单.filter(item => typeof item === 'string' && item.trim().length > 0).filter((item, index, self) => self.indexOf(item) === index).sort();
            GM_setValue('rwl_userData_v2', 最新用户配置);
            用户配置 = 最新用户配置;
            白名单列表 = 获取当前白名单();
        }
    }

    function 复制到剪贴板() {
        try {
            const 选中文本 = window.getSelection().toString();
            if (选中文本 && 选中文本.length > 0) GM_setClipboard(选中文本);
        } catch (错误) {
            console.error(`${日志前缀} 复制到剪贴板失败:`, 错误);
            alert("复制到剪贴板失败。请检查浏览器控制台或油猴扩展权限。");
        }
    }

    function 处理快捷键(事件对象) {
        if (用户配置.shortcut == 0) return;
        const 键码 = 事件对象.keyCode;
        let 应阻止默认行为 = false;
        switch (用户配置.shortcut) {
            case 1: if (键码 === 112) { 复制到剪贴板(); 应阻止默认行为 = true; } break;
            case 2: if (事件对象.ctrlKey && 键码 === 112) { 复制到剪贴板(); 应阻止默认行为 = true; } break;
            case 3: if (事件对象.ctrlKey && (键码 === 67 || 键码 === 88 )) setTimeout(复制到剪贴板, 50); break;
        }
        if (应阻止默认行为) { 事件对象.preventDefault(); 事件对象.stopPropagation(); return false; }
    }
    document.addEventListener('keydown', 处理快捷键, true);

    function 获取当前网站规则() {
        switch (当前域名) {
            case 'www.zcool.com.cn': case 'zcool.com.cn': return 用户配置.rules.rule_zcool || 用户配置.rules.rule_def;
            case 'chuangshi.qq.com': 处理创世中文网内容(); return 用户配置.rules.rule_def;
            case 'votetw.com': 处理Votetw网站样式(); return 用户配置.rules.rule_def;
            case 'www.myhtebooks.com': case 'www.myhtlmebook.com': 移除页面覆盖元素('.fullimg'); return 用户配置.rules.rule_def;
            case 'www.z3z4.com': 移除页面覆盖元素('.moviedownaddiv'); return 用户配置.rules.rule_def;
            case 'huayu.baidu.com': 移除页面覆盖元素('#jqContextMenu'); return 用户配置.rules.rule_def;
            case 'www.szxx.com.cn': 移除页面覆盖元素('img#adCover'); return 用户配置.rules.rule_def;
            case 'zhihu.com': case 'www.zhihu.com': return 用户配置.rules.rule_zhihu;
            case 't.bilibili.com': 处理B站动态链接(); return 用户配置.rules.rule_def;
            case 'www.uslsoftware.com': 移除页面覆盖元素('.protect_contents-overlay'); 移除页面覆盖元素('.protect_alert'); return 用户配置.rules.rule_plus;
            case 'www.longmabookcn.com': 移除页面覆盖元素('.fullimg'); return 用户配置.rules.rule_plus;
            case 'boke112.com': return 用户配置.rules.rule_plus;
            case 'www.shangc.net': return 用户配置.rules.rule_plus;
            case 'www.daodoc.com': case 'www.wcqjyw.com': 隐藏水印标记('.marks'); return 用户配置.rules.rule_def;
            case 'www.jianbiaoku.com': 隐藏水印标记('.layui-layer-shade'); return 用户配置.rules.rule_def;
            default: return 用户配置.rules.rule_def;
        }
    }

    function 移除页面覆盖元素(选择器) {
        try {
            const 元素 = document.querySelector(选择器);
            if (元素 && 元素.parentNode) 元素.parentNode.removeChild(元素);
        } catch (错误) { console.warn(`${日志前缀} 移除覆盖元素 "${选择器}" 失败:`, 错误.message); }
    }

    function 处理B站动态链接() {
        try {
            const 描述容器 = document.querySelector('.description');
            if (描述容器) {
                const 内容省略节点 = 描述容器.querySelector('.content-ellipsis');
                if (内容省略节点) 描述容器.appendChild(内容省略节点);
            }
        } catch (错误) { console.warn(`${日志前缀} 处理B站动态链接失败:`, 错误.message); }
    }

    function 处理Votetw网站样式() {
        try {
            document.querySelectorAll('.mw-parser-output>div').forEach(元素 => {
                if (元素 && typeof 元素.setAttribute === 'function') 元素.setAttribute('style', '');
            });
        } catch (错误) { console.warn(`${日志前缀} 处理Votetw网站样式失败:`, 错误.message); }
    }

    function 隐藏水印标记(节点选择器) {
        GM_addStyle(`${节点选择器} { display: none !important; visibility: hidden !important; }`);
    }

    function 处理创世中文网内容() {
        try {
            function tounicode(data) { if(!data)return"";var str="";for(var i=0;i<data.length;i++){str+="\\u"+parseInt(data[i].charCodeAt(0),10).toString(16)}return str }
            function tohanzi(data) { if(!data)return"";data=data.split("\\u");var str="";for(var i=0;i<data.length;i++){if(data[i]){str+=String.fromCharCode(parseInt(data[i],16))}}return str }
            RegExp.escape=function(str){return new String(str).replace(/([.*+?^=!:${}()|[\]/\\])/g,'\\$1')};function properties(obj){var props=[];for(var p in obj)if(Object.prototype.hasOwnProperty.call(obj,p))props.push(p);return props}
            const 内容元素 = document.querySelector('.bookreadercontent');
            if (内容元素 && 内容元素.innerText) {
                let 原始文本 = 内容元素.innerText; let 文本Unicode = tounicode(原始文本);
                const 替换表 = {'e2af':'4e09','e2c9':'4e3b','e2d6':'4e48','e2b2':'4e4b','e2a6':'4e5f','e294':'4e8b','e2e9':'4e8c','e30a':'4e8e','e292':'4e94','e298':'4e9b','e2a2':'4ee3','e2f0':'4f46','e30e':'4f4d','e305':'4f53','e296':'4f5c','e2d3':'4f60','e2db':'4f7f','e29b':'516c','e2b0':'5176','e2ed':'51fa','e2eb':'5206','e2f1':'5229','e307':'5230','e2ce':'5236','e2e6':'524d','e2ea':'529b','e2a8':'52a0','e2a5':'5316','e2bd':'5341','e302':'539f','e2df':'53bb','e2c7':'53c8','e303':'53cd','e2ac':'53d1','e2f8':'53ea','e30b':'5404','e29c':'5408','e2d7':'540c','e2d8':'540e','e306':'5411','e2c5':'547d','e2b4':'56db','e2f9':'56e0','e2ca':'5730','e2ef':'5916','e2bc':'591a','e301':'5929','e29a':'597d','e2b7':'5b50','e2cc':'5b83','e2ee':'5b9a','e2ff':'5bb6','e2e8':'5c0f','e2d4':'5c31','e2d5':'5c55','e2a1':'5de5','e2a0':'5e73','e2fe':'5e74','e2c4':'5e76','e2c8':'5ea6','e2ae':'5efa','e304':'5f62','e291':'5f88','e2e2':'5f97','e2f2':'5fc3','e295':'6027','e2d9':'60c5','e2be':'60f3','e2c3':'610f','e30d':'6210','e2ba':'6216','e2fa':'6240','e29e':'628a','e2a7':'63d0','e2d2':'653f','e2ad':'6599','e2cd':'65b0','e2f3':'65b9'};
                const 正则表达式 = new RegExp(properties(替换表).map(RegExp.escape).join('|'),'g');
                文本Unicode = 文本Unicode.replace(正则表达式, (匹配项) => 替换表[匹配项]).replace(/\\u0(?=[0-9a-fA-F]{3})/g, '\\u');
                内容元素.innerText = tohanzi(文本Unicode);
            }
        } catch (错误) { console.error(`${日志前缀} 处理创世中文网内容解密失败:`, 错误); }
    }

    function 注入强制CSS() {
        if (!当前应用规则 || !当前应用规则.add_css) return;
        GM_addStyle(`
            html *:not(.rwl-exempt):not(.rwl-exempt *), body *:not(.rwl-exempt):not(.rwl-exempt *), *:not(.rwl-exempt):not(.rwl-exempt *) {
                -webkit-user-select: text !important; -moz-user-select: text !important;
                -ms-user-select: text !important; user-select: text !important;
            }
            ::selection { color: HighlightText !important; background-color: Highlight !important; }
            ::-moz-selection { color: HighlightText !important; background-color: Highlight !important; }
        `);
    }

    function 迁移旧版数据() {
        const 旧数据_black_list = GM_getValue('black_list');
        const 旧数据_rwl_userData = GM_getValue('rwl_userData');
        let 需要保存新配置 = false;

        if (旧数据_rwl_userData && typeof 旧数据_rwl_userData === 'object') {
            if (Array.isArray(旧数据_rwl_userData.data)) {
                const 合并后白名单 = Array.from(new Set([...(用户配置.data || []), ...旧数据_rwl_userData.data])).filter(item => typeof item === 'string' && item.trim().length > 0).sort();
                用户配置.data = 合并后白名单;
            }
            if (旧数据_rwl_userData.hasOwnProperty('positionTop')) 用户配置.positionTop = String(旧数据_rwl_userData.positionTop);
            if (旧数据_rwl_userData.hasOwnProperty('shortcut')) 用户配置.shortcut = parseInt(旧数据_rwl_userData.shortcut, 10);
            if (旧数据_rwl_userData.hasOwnProperty('addBtn')) 用户配置.addBtn = !!旧数据_rwl_userData.addBtn;
            GM_deleteValue('rwl_userData');
            需要保存新配置 = true;
        }

        if (旧数据_black_list && typeof 旧数据_black_list === 'object' && Array.isArray(旧数据_black_list.data)) {
            const 合并后白名单 = Array.from(new Set([...(用户配置.data || []), ...旧数据_black_list.data])).filter(item => typeof item === 'string' && item.trim().length > 0).sort();
            用户配置.data = 合并后白名单;
            GM_deleteValue('black_list'); GM_deleteValue('rwl_userdata');
            需要保存新配置 = true;
        }
        if (需要保存新配置) GM_setValue('rwl_userData_v2', 用户配置);
    }
})();