您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
多等级解锁复制:默认最保守(等级1),可逐级增强到等级6;为每站点记忆;菜单与快捷键快速升降;尽量降低被风控检测的概率
// ==UserScript== // @name Enable Select & Copy — Multi-Level (L1–L6) // @namespace tm-copy-unlock // @version 3.1 // @description 多等级解锁复制:默认最保守(等级1),可逐级增强到等级6;为每站点记忆;菜单与快捷键快速升降;尽量降低被风控检测的概率 // @match *://*/* // @run-at document-start // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== (function () { 'use strict'; /*************************************************************************** * 总览 * L1 纯样式 —— 最安全,仅 user-select:text * L2 复制瞬间放行 —— 监听 Ctrl/⌘+C,临时宽松 copy * L3 选择与右键修复 —— 少量事件捕获拦截 + 根节点清理 inline * L4 动态节点定向修复 —— Scoped MutationObserver + 定向容器 * L5 全局防御(激进) —— 全站事件捕获 + 全树扫描 + 全站 MO + Shadow * L6 复制瞬间直接写剪贴板 —— 不修页面,只在复制瞬间写 clipboard(兜底) ***************************************************************************/ // ===== 0) 配置 & 存储 Key ===== const DEFAULT_LEVEL = 1; // 全局默认等级(1 最保守) const STORAGE = { SITE_LEVEL_PREFIX: 'copyUnlock.level.', // + hostname => 1..6 HOTKEY_ENABLED: 'copyUnlock.hotkeyEnabled', // 是否启用快捷键 DIAG_ENABLED: 'copyUnlock.diagEnabled', // 轻量诊断日志 }; // 可按需调整:用于 L4 的“定向容器”白名单(仅对这些容器子树做修复) const L4_SCOPE_SELECTORS = [ 'article', 'main', '#main', '.content', '.post', '.article', '.RichContent', '[role="article"]' ]; const host = location.hostname; const SITE_KEY = STORAGE.SITE_LEVEL_PREFIX + host; // 读取/保存 const read = (k, d) => { const v = GM_getValue(k); return v === undefined ? d : v; }; const save = (k, v) => GM_setValue(k, v); let currentLevel = clamp(read(SITE_KEY, DEFAULT_LEVEL), 1, 6); let hotkeyEnabled = read(STORAGE.HOTKEY_ENABLED, true); let diag = read(STORAGE.DIAG_ENABLED, false); function clamp(n, a, b) { return Math.max(a, Math.min(b, n | 0)); } // ===== 1) 轻量日志 & 提示 ===== function log(...args) { if (diag) console.log('[CopyUnlock]', ...args); } function toast(msg) { try { alert(msg); } catch { console.log('[CopyUnlock][toast]', msg); } } // ===== 2) 诊断探针(可选,超轻量)===== const CopyProbe = (() => { let lastCopySuccess = null; // true/false/null let recent = []; // 最近结果 function note(ok) { lastCopySuccess = !!ok; recent.push({ t: Date.now(), ok: !!ok }); if (recent.length > 10) recent.shift(); log('Copy result:', ok); } function stats() { const total = recent.length; const ok = recent.filter(x => x.ok).length; return { total, ok, fail: total - ok, lastCopySuccess }; } return { note, stats, last: () => lastCopySuccess }; })(); // ===== 3) 工具 ===== function isMac() { return navigator.platform && /mac/i.test(navigator.platform); } function isCopyCombo(e) { const meta = isMac() ? e.metaKey : e.ctrlKey; return meta && (e.key === 'c' || e.key === 'C'); } function addCSS(css, root = document.documentElement) { if (typeof GM_addStyle === 'function' && root === document.documentElement) { GM_addStyle(css); return; } const st = document.createElement('style'); st.textContent = css; (root.shadowRoot ? root.shadowRoot : root).appendChild(st); } // 读取选区文本 function getSelectionText() { try { return String(window.getSelection ? window.getSelection().toString() : ''); } catch { return ''; } } // 尝试写剪贴板(优先 clipboard.writeText,退回 execCommand) async function writeClipboard(text) { if (!text) return false; try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); return true; } } catch (e) { log('clipboard.writeText failed', e); } try { const ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', 'readonly'); ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); const ok = document.execCommand('copy'); document.body.removeChild(ta); return !!ok; } catch (e) { log('execCommand(copy) failed', e); return false; } } // 仅在捕获阶段“拦链不拦默认”(用于 L3/L5) function makeStopper(allowCtrlC = true) { return function stopper(e) { if (allowCtrlC && e.type === 'keydown' && isCopyCombo(e)) return; // 放行原生复制 e.stopImmediatePropagation(); }; } // 基础清理(少量) const INLINE_ATTRS = ['oncopy', 'oncut', 'oncontextmenu', 'onselectstart', 'ondragstart', 'onbeforecopy', 'onkeydown']; function cleanNodeLight(el) { if (!el || el.nodeType !== 1) return; for (const a of INLINE_ATTRS) { if (a in el) { try { el[a] = null; } catch {} } if (el.hasAttribute && el.hasAttribute(a)) el.removeAttribute(a); } const s = el.style; if (s) { try { s.setProperty('user-select', 'text', 'important'); s.setProperty('-webkit-user-select', 'text', 'important'); } catch {} } } function scanTreeLight(root) { cleanNodeLight(root); const it = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT); let n; while ((n = it.nextNode())) cleanNodeLight(n); } // ===== 4) 各等级实现 ===== const Levels = { 1() { // 纯样式(最安全) addCSS(` html, body, * { -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; user-select: text !important; } `); log('L1 enabled'); }, 2() { // 复制瞬间临时放行:仅在按下 Ctrl/⌘+C 时,尽量保证复制能成功 Levels[1](); // 保留 L1 的样式 const onKeyDown = (e) => { if (!isCopyCombo(e)) return; // 短暂“疏通” copy 事件链:在捕获阶段抢先阻断站点监听 const tempStopper = (ev) => { ev.stopImmediatePropagation(); }; document.addEventListener('copy', tempStopper, true); setTimeout(() => { document.removeEventListener('copy', tempStopper, true); }, 100); // 瞬时生效 // 同时兜底:如果站点仍阻止,我们手工写剪贴板(不破坏默认) setTimeout(async () => { const text = getSelectionText(); if (!text) return; // 检测是否已经复制成功:无法可靠检测,只做兜底写入 const ok = await writeClipboard(text); CopyProbe.note(ok); }, 0); }; window.addEventListener('keydown', onKeyDown, true); log('L2 enabled'); }, 3() { // 选择与右键修复(中等):少量事件捕获 + 根节点清理 inline Levels[1](); const stopper = makeStopper(true); ['selectstart', 'contextmenu', 'copy'].forEach(t => { document.addEventListener(t, stopper, true); window.addEventListener(t, stopper, true); }); // 根节点轻量清理一次 cleanNodeLight(document.documentElement); cleanNodeLight(document.body); log('L3 enabled'); }, 4() { // 动态节点定向修复:Scoped MO + 指定容器 Levels[3](); const scopeNodes = []; const pushIf = (el) => { if (el) scopeNodes.push(el); }; for (const sel of L4_SCOPE_SELECTORS) { try { pushIf(document.querySelector(sel)); } catch {} } // 如果没匹配到,就退回 body(仍算“定向”,别全站乱扫) if (!scopeNodes.length) pushIf(document.body); const mo = new MutationObserver(muts => { for (const m of muts) { if (m.type === 'attributes') cleanNodeLight(m.target); else if (m.addedNodes && m.addedNodes.length) { m.addedNodes.forEach(n => { if (n.nodeType === 1) scanTreeLight(n); }); } } }); scopeNodes.forEach(node => { try { mo.observe(node, { subtree: true, childList: true, attributes: true }); } catch {} }); log('L4 enabled on scopes:', scopeNodes); }, 5() { // 全局防御(激进):全站事件捕获 + 初始化全树清理 + 全站 MO + Shadow 样式注入 Levels[1](); const stopper = makeStopper(true); const BLOCKED = ['copy','cut','contextmenu','selectstart','dragstart','beforecopy','keydown']; BLOCKED.forEach(t => { document.addEventListener(t, stopper, true); window.addEventListener(t, stopper, true); }); // 初始化全树清理(谨慎:只做一次) try { scanTreeLight(document.documentElement); } catch {} // 全站观察(注意:可能影响性能,若页面很重可改成节流/选择器限定) const mo = new MutationObserver(muts => { for (const m of muts) { if (m.type === 'attributes') cleanNodeLight(m.target); else if (m.addedNodes && m.addedNodes.length) { m.addedNodes.forEach(n => { if (n.nodeType === 1) scanTreeLight(n); }); } } }); try { mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true }); } catch {} // Shadow DOM:不覆写原型,不改函数签名。仅“被动探测已存在的 shadowRoot”并注入样式 const shadowMO = new MutationObserver(muts => { for (const m of muts) { if (m.addedNodes) { m.addedNodes.forEach(n => { try { if (n.shadowRoot) { addCSS(` :host, * { -webkit-user-select: text !important; user-select: text !important; } `, n.shadowRoot); // 在 shadowRoot 内也拦常见事件(捕获) const s2 = makeStopper(true); ['copy','contextmenu','selectstart'].forEach(t => n.shadowRoot.addEventListener(t, s2, true)); } } catch {} }); } } }); try { shadowMO.observe(document.documentElement, { subtree: true, childList: true }); } catch {} log('L5 enabled'); }, 6() { // 复制瞬间直接写剪贴板(兜底):不解锁右键/选择;仅在 Ctrl/⌘+C 时写剪贴板 const onKeyDown = async (e) => { if (!isCopyCombo(e)) return; const text = getSelectionText(); if (!text) return; // 主动拦下默认 copy,直接写入,避免被站点逻辑“洗稿/插尾注” e.preventDefault(); const ok = await writeClipboard(text); CopyProbe.note(ok); }; window.addEventListener('keydown', onKeyDown, true); log('L6 enabled'); }, }; // ===== 5) 运行入口 ===== function bootstrap(level) { log(`Boot at level ${level} for ${host}`); // 分等级延迟:某些站点初始化期间会做完整性检查,低等级不需延迟 const delayByLevel = { 1: 0, 2: 0, 3: 0, 4: 200, 5: 600, 6: 0 }; const run = () => { try { Levels[level](); } catch (e) { console.error('[CopyUnlock] init error', e); } }; if (document.readyState === 'loading') { // 对于 L5/L4 可等 DOM 基本完成后再注入,降低被检概率 const dly = delayByLevel[level] || 0; if (dly > 0) { document.addEventListener('DOMContentLoaded', () => setTimeout(run, dly), { once: true }); } else { document.addEventListener('DOMContentLoaded', run, { once: true }); } } else { const dly = delayByLevel[level] || 0; if (dly > 0) setTimeout(run, dly); else run(); } } // ===== 6) 菜单 & 快捷键 ===== function setLevel(lv, silent = false) { currentLevel = clamp(lv, 1, 6); save(SITE_KEY, currentLevel); if (!silent) toast(`已设置本站等级为:L${currentLevel}(${host})\n刷新页面后生效。`); } function incLevel() { setLevel(currentLevel + 1); } function decLevel() { setLevel(currentLevel - 1); } function resetLevel() { setLevel(DEFAULT_LEVEL); } GM_registerMenuCommand(`当前等级:L${currentLevel}(点击查看说明)`, () => { toast( `等级说明(越高越强,越易触发风控): L1 纯样式(最安全) L2 复制瞬间放行 L3 选择/右键修复(少量事件捕获) L4 动态节点定向修复(Scoped MO) L5 全局防御(激进) L6 复制瞬间直写剪贴板(兜底) 建议:从 L1 开始,遇阻再升;如遇异常/风控,立即降级或切 L6。` ); }, { autoClose: true }); GM_registerMenuCommand('升一级 (L↑)', () => incLevel(), { autoClose: true }); GM_registerMenuCommand('降一级 (L↓)', () => decLevel(), { autoClose: true }); GM_registerMenuCommand('重置为默认等级 (L1)', () => resetLevel(), { autoClose: true }); GM_registerMenuCommand(`${hotkeyEnabled ? '关闭' : '开启'} 快捷键 (Alt+Shift+↑/↓/0)`, () => { hotkeyEnabled = !hotkeyEnabled; save(STORAGE.HOTKEY_ENABLED, hotkeyEnabled); toast(`快捷键已${hotkeyEnabled ? '开启' : '关闭'}`); }, { autoClose: true }); GM_registerMenuCommand(`${diag ? '关闭' : '开启'} 诊断日志`, () => { diag = !diag; save(STORAGE.DIAG_ENABLED, diag); toast(`诊断日志已${diag ? '开启' : '关闭'}`); }, { autoClose: true }); // 快捷键:Alt+Shift+↑/↓ 调整等级;Alt+Shift+0 复位 window.addEventListener('keydown', (e) => { if (!hotkeyEnabled) return; if (!e.altKey || !e.shiftKey) return; const k = e.key; if (k === 'ArrowUp') { e.preventDefault(); incLevel(); } else if (k === 'ArrowDown') { e.preventDefault(); decLevel(); } else if (k === '0' || k === ')') { e.preventDefault(); resetLevel(); } }, true); // ===== 7) 启动当前等级 ===== bootstrap(currentLevel); // ===== 8) 合规提醒(一次性提示,可按需保留/删除)===== // console.warn('[CopyUnlock] 请在合法、合规且尊重版权的前提下使用本脚本。'); })();