Greasy Fork

完全解除任意网站复制粘贴限制 & 原生复制粘贴使用体验

完全解除复制/粘贴限制,保留 Ctrl+Z 撤销,并兼容大部分浏览器环境

目前为 2025-04-29 提交的版本。查看 最新版本

// ==UserScript==
// @name         完全解除任意网站复制粘贴限制 & 原生复制粘贴使用体验
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  完全解除复制/粘贴限制,保留 Ctrl+Z 撤销,并兼容大部分浏览器环境
// @author       AMT
// @match        *://*/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
  'use strict';

  // —— 0. 拦截所有禁止选中/右键的事件 ——
  const blockedEvents = ['selectstart', 'mousedown', 'mouseup', 'mousemove', 'contextmenu', 'dragstart'];
  blockedEvents.forEach(evt => {
    document.addEventListener(evt, e => e.stopImmediatePropagation(), true);
  });

  if (document.body) document.body.onselectstart = null;

  // —— 1. 强制允许文本选中 ——
  const style = document.createElement('style');
  style.textContent = `
    * {
      user-select: text !important;
      -webkit-user-select: text !important;
    }
  `;
  document.head.appendChild(style);

  // —— 2. 放行 Copy / Cut ——
  ['copy', 'cut'].forEach(evt => {
    document.addEventListener(evt, e => {
      e.stopImmediatePropagation();
      // 不 preventDefault,允许浏览器原生复制/剪切
    }, true);
  });

  // —— 3. 粘贴拦截(只在可编辑区域生效) ——
  document.addEventListener('paste', async e => {
    e.stopImmediatePropagation();
    e.preventDefault();

    const target = document.activeElement;
    if (!isEditable(target)) return; // 只在输入框/文本区/可编辑div里处理

    // 获取剪贴板文字
    let text = e.clipboardData?.getData('text')
             || (window.clipboardData && window.clipboardData.getData('Text'))
             || '';
    if (!text && navigator.clipboard?.readText) {
      try { text = await navigator.clipboard.readText(); } catch {}
    }
    if (!text) return;

    // 用 execCommand 插入并保留撤销
    target.focus();
    document.execCommand('insertText', false, text);

    // 通知变化
    target.dispatchEvent(new Event('input', { bubbles: true }));
  }, true);

  // —— 4. 辅助函数:判断元素是否可编辑 ——
  function isEditable(el) {
    if (!el) return false;
    if (el.isContentEditable) return true;
    const tag = el.tagName?.toLowerCase();
    return (tag === 'input' && ['text', 'search', 'email', 'url', 'tel', 'password'].includes(el.type))
        || tag === 'textarea';
  }

})();