Unlock Text Selection & Copy

Universal unlock for text selection, copy, right-click menu, and keyboard shortcuts. Defeats CSS user-select, event interception, key blocking, and clipboard clearing.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Unlock Text Selection & Copy
// @name:zh-CN   解除网页禁止选中/复制限制
// @name:en      Unlock Text Selection & Copy
// @namespace    https://alicexia.app/
// @version      2.0.0
// @description  Universal unlock for text selection, copy, right-click menu, and keyboard shortcuts. Defeats CSS user-select, event interception, key blocking, and clipboard clearing.
// @description:zh-CN 通用解除网页禁止选中文本、复制文本、右键菜单、快捷键等所有限制。支持 CSS user-select、事件拦截、快捷键封锁、剪贴板清空等全部手段。
// @author       alicexia
// @match        https://*.hotjob.cn/*
// @match        https://*.zhiye.com/*
// @match        https://*.iguopin.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// @contributionURL https://github.com/alicexiacn
// @homepageURL  https://github.com/alicexiacn/unlock-copy
// @supportURL   https://github.com/alicexiacn/unlock-copy/issues
// @icon         data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%234CAF50' d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E
// @compatible   chrome
// @compatible   edge
// @compatible   firefox
// @compatible   opera
// @compatible   safari
// ==/UserScript==

(function () {
    'use strict';

    // ============================================================
    // 第一层:CSS 防护解除
    // 覆盖 user-select: none / pointer-events: none
    // ============================================================
    function injectCSS() {
        var style = document.createElement('style');
        style.id = 'wb-unlock-select-style';
        style.textContent = [
            '*, *::before, *::after {',
            '    -webkit-user-select: text !important;',
            '    -moz-user-select: text !important;',
            '    -ms-user-select: text !important;',
            '    user-select: text !important;',
            '    -webkit-touch-callout: default !important;',
            '    pointer-events: auto !important;',
            '}'
        ].join('\n');
        (document.head || document.documentElement).appendChild(style);
    }

    // ============================================================
    // 第二层:事件拦截解除
    // 在捕获阶段(capture)用 stopImmediatePropagation 阻断
    // 网站自己注册的 contextmenu / selectstart / copy 等监听器
    // ============================================================
    var blockedEvents = [
        'contextmenu',      // 右键菜单
        'selectstart',      // 开始选中
        'select',           // 选中过程
        'copy',             // 复制
        'cut',              // 剪切
        'paste',            // 粘贴
        'dragstart',        // 拖拽
        'beforecopy',       // 复制前
        'beforecut',        // 剪切前
        'beforepaste',      // 粘贴前
        'mousedown',        // 鼠标按下(部分网站用它阻止选区起点)
        'mouseup'           // 鼠标抬起
    ];

    // 对普通用户来说,mousedown/mouseup 全局拦截可能影响交互
    // 这里只阻断那些调用了 preventDefault 的处理
    blockedEvents.forEach(function (eventName) {
        document.addEventListener(eventName, function (e) {
            e.stopPropagation();
            // stopImmediatePropagation 会阻止同元素上后续注册的同类监听器执行
            // 但只对 selectstart / contextmenu / copy 等关键事件使用
            if (['contextmenu', 'selectstart', 'copy', 'cut', 'paste', 'dragstart'].indexOf(eventName) !== -1) {
                e.stopImmediatePropagation();
            }
            // 放行默认行为
            try { e.returnValue = true; } catch (_) {}
        }, true);  // true = 捕获阶段,比网站的监听器更早执行
    });

    // ============================================================
    // 第三层:快捷键解除
    // 阻断网站对 Ctrl+C / Ctrl+A / Ctrl+X / Ctrl+V 的拦截
    // ============================================================
    document.addEventListener('keydown', function (e) {
        if (e.ctrlKey || e.metaKey) {
            var key = (e.key || '').toLowerCase();
            if (['c', 'a', 'x', 'v', 'insert'].indexOf(key) !== -1) {
                e.stopPropagation();
                e.stopImmediatePropagation();
            }
        }
        // F12 / Ctrl+Shift+I / Ctrl+Shift+J / Ctrl+U 也一并放行
        if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && ['i', 'j', 'c'].indexOf((e.key || '').toLowerCase()) !== -1) || (e.ctrlKey && (e.key || '').toLowerCase() === 'u')) {
            e.stopPropagation();
            e.stopImmediatePropagation();
        }
    }, true);

    // ============================================================
    // 第四层:清除内联事件处理器 & 保护剪贴板
    // ============================================================
    var inlineProps = [
        'oncontextmenu', 'onselectstart', 'oncopy', 'oncut',
        'onpaste', 'ondragstart', 'onmousedown', 'onmouseup',
        'onbeforecopy', 'onbeforecut', 'onbeforepaste'
    ];

    function clearInlineHandlers() {
        inlineProps.forEach(function (prop) {
            try {
                document[prop] = null;
                if (document.body) { document.body[prop] = null; }
                document.documentElement[prop] = null;
            } catch (_) {}
        });
    }

    // 用 Object.defineProperty 彻底锁死,防止网站重新赋值
    inlineProps.forEach(function (prop) {
        try {
            Object.defineProperty(document, prop, {
                get: function () { return null; },
                set: function () { /* 吞掉赋值 */ },
                configurable: true
            });
        } catch (_) {}
    });

    // ============================================================
    // 第五层:MutationObserver 持续监控
    // 部分网站会在 DOM 动态变化后重新注入限制
    // ============================================================
    var observer = new MutationObserver(function () {
        clearInlineHandlers();
    });

    function startObserver() {
        observer.observe(document.documentElement || document, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['oncontextmenu', 'onselectstart', 'oncopy', 'oncut',
                              'onpaste', 'ondragstart', 'style']
        });
    }

    // ============================================================
    // 初始化:多个时机执行,确保覆盖所有加载阶段
    // ============================================================
    // 1. 立即注入 CSS(document-start 时 head 可能还不存在,先放 html 上)
    injectCSS();

    // 2. DOMContentLoaded 时清除内联事件 + 重新注入 CSS + 启动 Observer
    document.addEventListener('DOMContentLoaded', function () {
        clearInlineHandlers();
        injectCSS();
        startObserver();
    });

    // 3. window.load 时再补一次(处理懒加载脚本)
    window.addEventListener('load', function () {
        clearInlineHandlers();
        injectCSS();
    });

    // 4. 如果 document 已经 ready,立即执行
    if (document.readyState !== 'loading') {
        clearInlineHandlers();
        injectCSS();
        startObserver();
    }

    console.log('解除选中/复制限制脚本已加载 ✓');
})();