Enable Select & Copy — Multi-Level (L1–L6)

多等级解锁复制:默认最保守(等级1),可逐级增强到等级6;为每站点记忆;菜单与快捷键快速升降;尽量降低被风控检测的概率

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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] 请在合法、合规且尊重版权的前提下使用本脚本。');
})();