您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【超级助手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', 用户配置); } })();