// ==UserScript== // @name 网页复制解锁 // @namespace https://cxbank.icu/copyfree // @version 1.0.0 // @description 解除网页的复制、右键、选中限制。一键开关,不影响页面其他功能。 // @author ZZ // @match *://*/* // @grant none // @run-at document-start // @noframes false // @license MIT // ==/UserScript== (function () { 'use strict'; var KEY = 'copyfree_on'; var S = { on: true, removedAttrs: 0, removedClasses: 0, }; try { S.on = localStorage.getItem(KEY) !== '0'; } catch (e) {} /* 需要"放行"的事件类型 */ var BLOCKED = { copy: 1, cut: 1, contextmenu: 1, selectstart: 1, dragstart: 1 }; /* ---------- 1) 拦截 preventDefault ---------- */ var origPD = Event.prototype.preventDefault; Event.prototype.preventDefault = function () { try { if (S.on && this && BLOCKED[this.type]) { /* 记录一下,面板上能看到"本站拦了几个" */ S.blockedCalls = (S.blockedCalls || 0) + 1; return; } } catch (e) {} return origPD.apply(this, arguments); }; /* ---------- 2) 清掉内联事件 ---------- */ function stripInline(root) { try { var attrs = ['oncopy', 'oncut', 'oncontextmenu', 'onselectstart', 'ondragstart', 'onmousedown', 'onmouseup']; var all = (root || document).querySelectorAll('*'); for (var i = 0; i < all.length; i++) { var el = all[i]; for (var k = 0; k < attrs.length; k++) { if (el.getAttribute && el.getAttribute(attrs[k])) { el.removeAttribute(attrs[k]); S.removedAttrs++; } } } /* 文档级 */ ['oncopy', 'oncut', 'oncontextmenu', 'onselectstart', 'ondragstart'].forEach(function (a) { try { document[a] = null; } catch (e) {} try { document.body && (document.body[a] = null); } catch (e) {} }); } catch (e) {} } /* ---------- 3) 去掉 user-select: none ---------- */ var style = null; function applyStyle() { if (!style) { style = document.createElement('style'); style.id = 'copyfree-style'; style.textContent = 'html,body,*{-webkit-user-select:text !important;user-select:text !important;' + '-webkit-touch-callout:default !important}'; (document.head || document.documentElement).appendChild(style); } style.disabled = !S.on; } /* ---------- 4) 清理常见"禁止选中"的 class ---------- */ var BAD_CLASS = /(noselect|no-select|noselection|unselectable|disable-select|user-select-none)/i; function stripClasses(root) { try { var all = (root || document).querySelectorAll('[class]'); for (var i = 0; i < all.length; i++) { var el = all[i]; var c = String(el.className || ''); if (!BAD_CLASS.test(c)) continue; el.className = c.split(/\s+/).filter(function (x) { return !BAD_CLASS.test(x); }).join(' '); S.removedClasses++; } } catch (e) {} } /* ---------- 5) 兜底:Alt+C 强制复制选中内容 ---------- */ function forceCopy() { try { var sel = String(window.getSelection ? window.getSelection() : ''); if (!sel) return false; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(sel); return true; } var ta = document.createElement('textarea'); ta.value = sel; ta.style.cssText = 'position:fixed;left:-9999px;top:0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); return true; } catch (e) { return false; } } document.addEventListener('keydown', function (e) { if (e.altKey && (e.key === 'c' || e.key === 'C')) { if (forceCopy()) { e.preventDefault(); toast('已复制选中内容'); } } }, true); /* ---------- 面板 ---------- */ var host = null, root = null, dot = null, tip = null; var CSS = [ '.dot{position:fixed;right:14px;bottom:56px;width:38px;height:38px;border-radius:50%;', 'background:linear-gradient(145deg,#34c759,#28a745);color:#fff;border:none;cursor:pointer;', 'font:600 13px/1 -apple-system,"Segoe UI",sans-serif;z-index:2147483000;', 'box-shadow:0 8px 20px rgba(52,199,89,.4);display:flex;align-items:center;justify-content:center}', '.dot.off{background:linear-gradient(145deg,#8e8e93,#6c6c70);box-shadow:0 8px 20px rgba(0,0,0,.2)}', '.card{position:fixed;right:14px;bottom:102px;width:236px;z-index:2147483001;', 'background:rgba(255,255,255,.97);border:.5px solid rgba(0,0,0,.1);border-radius:14px;', 'padding:12px 14px;font:13px/1.7 -apple-system,"Segoe UI",sans-serif;color:#1c1c1e;', 'box-shadow:0 12px 34px rgba(0,0,0,.22);display:none}', '.card h4{margin:0 0 8px;font-size:13px}', '.row{display:flex;justify-content:space-between;color:#8e8e93;font-size:11.5px;margin-bottom:6px}', '.row b{color:#1c1c1e}', '.sw{display:flex;align-items:center;justify-content:space-between;margin:10px 0 4px;font-size:12.5px}', '.tg{width:44px;height:24px;border-radius:99px;background:#e5e5ea;position:relative;cursor:pointer;transition:.2s}', '.tg.on{background:#34c759}', '.tg i{position:absolute;left:2px;top:2px;width:20px;height:20px;border-radius:50%;background:#fff;transition:.2s}', '.tg.on i{left:22px}', '.hint{color:#8e8e93;font-size:11px;margin-top:8px;line-height:1.6}', ].join(''); function toast(t) { if (tip) { tip.textContent = t; tip.style.opacity = '1'; return; } tip = document.createElement('div'); tip.textContent = t; tip.style.cssText = 'position:fixed;left:50%;top:18px;transform:translateX(-50%);z-index:2147483002;' + 'background:rgba(28,28,30,.92);color:#fff;padding:8px 16px;border-radius:99px;' + 'font:600 12.5px/1.4 -apple-system,sans-serif;transition:opacity .3s'; document.body.appendChild(tip); setTimeout(function () { if (tip) { tip.style.opacity = '0'; setTimeout(function () { if (tip) { tip.remove(); tip = null; } }, 400); } }, 1400); } function render() { if (!dot) return; dot.classList.toggle('off', !S.on); dot.title = S.on ? '复制解锁:已开启(点击设置)' : '复制解锁:已关闭'; try { if (root) { root.getElementById('st').textContent = S.on ? '已开启' : '已关闭'; var sw = root.getElementById('sw'); if (sw) sw.classList.toggle('on', S.on); root.getElementById('a1').textContent = S.removedAttrs; root.getElementById('a2').textContent = S.removedClasses; root.getElementById('a3').textContent = S.blockedCalls || 0; } } catch (e) {} } function build() { if (!document.body || document.getElementById('copyfree-host')) return; host = document.createElement('div'); host.id = 'copyfree-host'; root = host.attachShadow({ mode: 'open' }); var st = document.createElement('style'); st.textContent = CSS; root.appendChild(st); dot = document.createElement('button'); dot.className = 'dot'; dot.textContent = '解开'; root.appendChild(dot); var card = document.createElement('div'); card.className = 'card'; card.innerHTML = '