Greasy Fork

Greasy Fork is available in English.

解除禁止复制粘贴和右键菜单

解除网页的禁止选择、复制、粘贴和右键菜单。注意:这个脚本可能严重影响某些网站功能!

// ==UserScript==
// @name         解除禁止复制粘贴和右键菜单
// @namespace    https://github.com/LaLa-HaHa-Hei/
// @version      1.0.0
// @description  解除网页的禁止选择、复制、粘贴和右键菜单。注意:这个脚本可能严重影响某些网站功能!
// @author       代码见三
// @license      GPL-3.0-or-later
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// ==/UserScript==

(function() {
    'use strict';
 
    console.log("运行了Disable Restriction")
    const checkAllElementsForOnEvents = false // 解除限制时检查全部元素,值为true且网页复杂时将导致严重性能问题
    let autoDisableAllRestriction = GM_getValue("dr-autoDisableAllRestriction", false) // 启动页面后自动解除所有限制
    let injectIframesWrittenByJs = GM_getValue("dr-injectIframesWrittenByJs", false) // 部分iframe是由js写入的,不是通过src获取的

    let id1 = GM_registerMenuCommand(
        "自动解除限制:" + (autoDisableAllRestriction === true ? "已开" : "未开"),
        menu1Click,
    "a");
    function menu1Click() {
        GM_unregisterMenuCommand(id1)
        autoDisableAllRestriction = !autoDisableAllRestriction
        GM_setValue("dr-autoDisableAllRestriction", autoDisableAllRestriction)
        
        id1 = GM_registerMenuCommand(
            "自动解除限制:" + (autoDisableAllRestriction === true ? "已开" : "未开"),
            menu1Click,
        "a");
    }
    
    let id2 = GM_registerMenuCommand(
        "包括js写入的iframe:" + (injectIframesWrittenByJs === true ? "已开" : "未开"),
        menu2Click,
    "i");
    function menu2Click() {
        GM_unregisterMenuCommand(id2)
        injectIframesWrittenByJs = !injectIframesWrittenByJs
        GM_setValue("dr-injectIframesWrittenByJs", injectIframesWrittenByJs)
        
        id2 = GM_registerMenuCommand(
            "包括js写入的iframe:" + (injectIframesWrittenByJs === true ? "已开" : "未开"),
            menu2Click,
        "i");
    }

    drMain(autoDisableAllRestriction, checkAllElementsForOnEvents)

    if (injectIframesWrittenByJs)
    {
        injectToAllIframes()
        const injectToAllIframesInterval = setInterval(injectToAllIframes, 2 * 1000)
    }

    // 注入所有js写入的iframe,带有src的ifrmae用match *://*/*匹配
    function injectToAllIframes() {
        const iframes = document.querySelectorAll('iframe');
        iframes.forEach(iframe => {
            try {
                const doc = iframe.contentDocument || iframe.contentWindow.document;
                if (!doc) 
                    return;

                // 避免重复注入
                if (doc.body && doc.body.getAttribute("dr-injected"))
                    return;

                // 注入 drMain()
                const script = doc.createElement('script');
                script.type = 'text/javascript';
                script.textContent = `(${drMain.toString()})(${autoDisableAllRestriction}, ${checkAllElementsForOnEvents});`
                doc.head.appendChild(script);
                doc.body.setAttribute("dr-injected", "true");
                // console.log("已注入 iframe:", iframe);
                console.log("已注入 iframe");
            } catch (e) {
                console.warn("无法注入 iframe(可能是跨域):", e);
            }
        })
    }

    function drMain(autoDisableAllRestriction, checkAllElementsForOnEvents = false){
        if (document.body && document.body.getAttribute("dr-injected-main")) {
            console.log("drMain已经在此frame运行过");
            return;
        }
        if (document.body) {
            document.body.setAttribute("dr-injected-main", "true");
        }
        
        injectCSS()
        injectHTML()

        if (autoDisableAllRestriction)
        {
            disableSelectionRestriction(checkAllElementsForOnEvents)
            disableCopyRestriction(checkAllElementsForOnEvents)
            disablePasteRestriction(checkAllElementsForOnEvents)
            disableContextMenuRestriction(checkAllElementsForOnEvents)
        }

        // 直接绑定会导致找不到元素
        setTimeout(() => {
            bindEvents()
        }, 0);
        
        // 防止被覆盖,重新注入,每2秒检测一次,
        const preventCoveredInterval = setInterval(() => {
            if (!document.querySelector('#dr-html')) {
                console.log('检测到UI被移除,重新注入...');
                injectHTML();
                setTimeout(() => {
                    bindEvents()
                }, 0);
                if (autoDisableAllRestriction)
                {
                    disableSelectionRestriction(checkAllElementsForOnEvents)
                    disableCopyRestriction(checkAllElementsForOnEvents)
                    disablePasteRestriction(checkAllElementsForOnEvents)
                    disableContextMenuRestriction(checkAllElementsForOnEvents)
                }
            }
            if (!document.querySelector('style[dr-style]')) {
                console.log('选择样式被移除,重新注入...');
                injectCSS();
            }
        }, 2 * 1000)
        setTimeout(() => clearInterval(preventCoveredInterval), 7 * 1000); // 监听7秒后不再防止覆盖
        
        // 解除禁止选择,对于实现了拖动功能的元素可能造成影响
        function disableSelectionRestriction(checkAllElementsForOnEvents) {
            // 设置全局css可选则
            const styleId = 'enable-selection-style';
            if (document.getElementById(styleId)) {
                console.log('允许选择css已存在.');
            }
            else {
                const style = document.createElement('style');
                style.id = styleId;
                // 使用 !important 提高优先级,覆盖网站可能使用的 !important
                style.innerHTML = `
                * {
                    user-select: auto !important;
                    -webkit-user-select: auto !important;
                    -moz-user-select: auto !important;
                    -ms-user-select: auto !important;
                }
            `;
                (document.head || document.documentElement).appendChild(style);
                console.log('允许选择css注入完成')
            }

            // 阻止其他addEventListener事件执行
            document.addEventListener('selectstart', e => e.stopImmediatePropagation(), true)
            // onxxx事件和addEventListener本质不同,需要单独阻止
            const elements = checkAllElementsForOnEvents ? document.querySelectorAll('*') : [document, document.body]
            elements.forEach(element => {
                if (typeof element.onselectstart === 'function') {
                    element.onselectstart = null
                }
            })
            console.log('允许选择js注入完成')
        }
        
        // 解除禁止复制
        function disableCopyRestriction(checkAllElementsForOnEvents) {
            document.addEventListener('copy', e => e.stopImmediatePropagation(), true)
            const elements = checkAllElementsForOnEvents ? document.querySelectorAll('*') : [document, document.body]
            elements.forEach(element => {
                if (typeof element.oncopy === 'function') {
                    element.oncopy = null
                }
            })
            console.log('允许复制js注入完成')
        }

        // 解除禁止粘贴
        function disablePasteRestriction(checkAllElementsForOnEvents) {
            document.addEventListener('paste', e => e.stopImmediatePropagation(), true)
            const elements = checkAllElementsForOnEvents ? document.querySelectorAll('*') : document.querySelectorAll('textarea, input, [contenteditable="true"]')
            elements.forEach(element => {
                if (typeof element.onpaste === 'function') {
                    element.onpaste = null
                }
            })
            console.log('允许粘贴js注入完成')
        }
        
        // 解除禁止右键菜单,对于带有特殊菜单的网页可能造成影响
        function disableContextMenuRestriction(checkAllElementsForOnEvents) {
            document.addEventListener('contextmenu', e => e.stopImmediatePropagation(), true)
            // 尝试移除 body/document 上的 oncontextmenu
            const elements = checkAllElementsForOnEvents ? document.querySelectorAll('*') : [document, document.body]
            elements.forEach(element => {
                if (typeof element.oncontextmenu === 'function') {
                    element.oncontextmenu = null
                }
            });
            console.log('允许右键菜单js注入完成');
        }

        function bindEvents(){
            const btn = document.querySelector('#dr-floating-btn');
            const menu = document.querySelector('#dr-menu');
            document.querySelector('#dr-allow-all-btn')?.addEventListener('click', () => {
                disableSelectionRestriction(checkAllElementsForOnEvents)
                disableCopyRestriction(checkAllElementsForOnEvents)
                disablePasteRestriction(checkAllElementsForOnEvents)
                disableContextMenuRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });
            document.querySelector('#dr-allow-select-copy-paste-btn')?.addEventListener('click', () => {
                disableSelectionRestriction(checkAllElementsForOnEvents)
                disableCopyRestriction(checkAllElementsForOnEvents)
                disablePasteRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });
            document.querySelector('#dr-allow-select-btn')?.addEventListener('click', () => {
                disableSelectionRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });
            document.querySelector('#dr-allow-copy-btn')?.addEventListener('click', () => {
                disableCopyRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });
            document.querySelector('#dr-allow-paste-btn')?.addEventListener('click', () => {
                disablePasteRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });
            document.querySelector('#dr-allow-context-menu-btn')?.addEventListener('click', () => {
                disableContextMenuRestriction(checkAllElementsForOnEvents)
                menu.style.display = 'none'
            });

            // 按钮拖动,点击按钮弹出窗口
            let isDragging = false;
            let isClick = false;
            let offsetY = 0;

            // 点击菜单外关闭菜单
            // 电脑
            document.addEventListener('mousedown', function (e) {
                if (!btn.contains(e.target) && !menu.contains(e.target)) {
                    menu.style.display = 'none';
                }
            });
            // 手机
            document.addEventListener('touchstart', function (e) {
                // 对于触摸事件,e.target 也是触发事件的元素
                if (!btn.contains(e.target) && !menu.contains(e.target)) {
                    menu.style.display = 'none';
                }
            }, { passive: true });

            // 电脑端拖动
            btn.addEventListener('mousedown', handleDragStart);
            document.addEventListener('mousemove', handleDragMove);
            document.addEventListener('mouseup', handleDragEnd);
            // 手机端拖动
            btn.addEventListener("touchstart", handleDragStart);
            document.addEventListener("touchmove", handleDragMove);
            document.addEventListener("touchend", handleDragEnd);

            function handleDragStart(event){
                isDragging = true;
                isClick = true;
                offsetY = (event.clientY || event.touches[0].clientY) - btn.getBoundingClientRect().top;
                menu.style.display = 'none';
                event.preventDefault();
            }
            function handleDragMove(event){
                if (isDragging) {
                    isClick = false;
                    let newTop = (event.clientY || event.touches[0].clientY) - offsetY;
                    // 限制按钮不超出窗口
                    newTop = Math.max(0, Math.min(window.innerHeight - btn.offsetHeight, newTop));
                    btn.style.top = newTop + 'px';
                    event.preventDefault();
                }
            }
            function handleDragEnd(event){
                isDragging = false;
                if (isClick){
                    // 菜单位置跟随按钮
                    const rect = btn.getBoundingClientRect();
                    menu.style.top = rect.top + 'px';
                    menu.style.display = menu.style.display = 'block';
                }
                isClick = false;
            }
        }
            
        // 注入按钮和菜单
        function injectHTML() {
            const injectedTHML = `
                <button id="dr-floating-btn">解除限制</button>
                <div id="dr-menu">
                    <button class="dr-menu-item" id="dr-allow-all-btn">解除所有限制</button>
                    <button class="dr-menu-item" id="dr-allow-select-copy-paste-btn">允许选择复制粘贴</button>
                    <button class="dr-menu-item" id="dr-allow-select-btn">允许选择</button>
                    <button class="dr-menu-item" id="dr-allow-copy-btn">允许复制</button>
                    <button class="dr-menu-item" id="dr-allow-paste-btn">允许粘贴</button>
                    <button class="dr-menu-item" id="dr-allow-context-menu-btn">允许右键菜单</button>
                </div>
            `
            const divElement = document.createElement('div')
            divElement.innerHTML = injectedTHML
            divElement.id = "dr-html"
            document.body.appendChild(divElement)
            // document.body.insertAdjacentHTML('afterbegin', injectedTHML)
        }
        // 注入css
        function injectCSS(){
            const injectedCSS = `
                #dr-floating-btn {
                    position: fixed;
                    left: 0;
                    top: 40%;
                    z-index: 9999;
                    background: #007bff;
                    color: #fff;
                    border: none;
                    border-radius: 0 20px 20px 0;
                    padding: 6px 14px;
                    cursor: pointer;
                    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
                    user-select: none;
                    transition: background 0.2s;
                }

                #dr-floating-btn:active {
                    background: #0056b3;
                }

                #dr-menu {
                    display: none;
                    position: fixed;
                    left: 60px;
                    top: 40%;
                    background: #fff;
                    border-radius: 8px;
                    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
                    padding: 8px 0;
                    z-index: 10000;
                    min-width: 160px;
                }

                .dr-menu-item {
                    display: block;
                    width: 100%;
                    background: none;
                    border: none;
                    text-align: left;
                    padding: 8px 16px;
                    font-size: 15px;
                    color: #333;
                    cursor: pointer;
                    transition: background 0.2s;
                }

                .dr-menu-item:hover {
                    background: #f0f0f0;
                }
            `
            const styleElement = document.createElement('style')
            styleElement.textContent = injectedCSS
            styleElement.setAttribute('dr-style', '')
            document.head.appendChild(styleElement)
        }
    }
})();