解除复制限制

解除网页文本复制限制

// ==UserScript==
// @name         解除复制限制
// @namespace    https://greasyfork.org/zh-CN/users/1257285-dreamprostudio
// @version      1.0
// @description  解除网页文本复制限制
// @author       DreamProStudio
// @run-at       document-start
// @match        *
// @grant        none
// @license      GPL-3.0-or-later
// @homepageURL  http://www.coolapk.com/u/28432077
// @supportURL   https://greasyfork.org/zh-CN/users/1257285-dreamprostudio
// ==/UserScript==

(function() {
    // 定义规则和黑名单列表
    const rules = {
        black_rule: {
            name: "black",
            hook_eventNames: "",
            unhook_eventNames: ""
        },
        default_rule: {
            name: "default",
            hook_eventNames: "contextmenu|select|selectstart|copy|cut|dragstart",
            unhook_eventNames: "mousedown|mouseup|keydown|keyup",
            dom0: true,
            hook_addEventListener: true,
            hook_preventDefault: true,
            hook_set_returnValue: true,
            add_css: true
        }
    };

    const lists = {
        black_list: [
            /.*\.youtube\.com.*/,
            /.*\.wikipedia\.org.*/,
            /mail\.qq\.com.*/,
            /translate\.google\..*/,
            /.*\.bing\.com.*/
        ]
    };

    // 随机生成存储名以避免冲突
    const storageName = getRandStr('qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM', parseInt(Math.random() * 12 + 8));
    const originalAddEventListener = EventTarget.prototype.addEventListener;
    const originalPreventDefault = Event.prototype.preventDefault;

    let hook_eventNames, unhook_eventNames, eventNames;

    // 重写 addEventListener 方法
    function addEventListener(type, listener, options) {
        if (hook_eventNames.includes(type)) {
            originalAddEventListener.call(this, type, returnTrue, options);
        } else if (unhook_eventNames.includes(type)) {
            const funcsName = `${storageName}${type}${options ? 't' : 'f'}`;
            if (!this[funcsName]) {
                this[funcsName] = [];
                originalAddEventListener.call(this, type, options ? unhook_t : unhook_f, options);
            }
            this[funcsName].push(listener);
        } else {
            originalAddEventListener.call(this, type, listener, options);
        }
    }

    // 清理 DOM0 事件处理程序
    function clearLoop() {
        const elements = [...document.querySelectorAll('*'), document];
        elements.forEach(element => {
            eventNames.forEach(eventName => {
                const name = 'on' + eventName;
                if (element[name] !== null && element[name] !== handleEvent) {
                    if (unhook_eventNames.includes(eventName)) {
                        element[storageName + name] = element[name];
                        element[name] = handleEvent;
                    } else {
                        element[name] = null;
                    }
                }
            });
        });
    }

    // 处理事件的方法
    function handleEvent(e) {
        const name = storageName + 'on' + e.type;
        if (this[name]) this[name](e);
        e.returnValue = true;
        return true;
    }

    // 解除钩子事件的方法
    function unhook(e, self, funcsName) {
        self[funcsName].forEach(func => func(e));
        e.returnValue = true;
        return true;
    }

    // 用于捕获捕获阶段的解除钩子事件处理程序
    function unhook_t(e) {
        return unhook(e, this, `${storageName}${e.type}t`);
    }

    // 用于冒泡阶段的解除钩子事件处理程序
    function unhook_f(e) {
        return unhook(e, this, `${storageName}${e.type}f`);
    }

    // 返回 true 的事件处理程序
    function returnTrue() {
        return true;
    }

    // 生成随机字符串
    function getRandStr(chars, len) {
        return Array.from({ length: len }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
    }

    // 添加自定义样式到页面
    function addStyle(css) {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    // 根据 URL 获取适用的规则
    function getRule(url) {
        return lists.black_list.some(regex => regex.test(url)) ? rules.black_rule : rules.default_rule;
    }

    // 初始化脚本
    function init() {
        const url = `${window.location.host}${window.location.pathname}`;
        const rule = getRule(url);

        hook_eventNames = rule.hook_eventNames.split("|");
        unhook_eventNames = rule.unhook_eventNames.split("|");
        eventNames = [...hook_eventNames, ...unhook_eventNames];

        // DOM0 事件处理和样式添加
        if (rule.dom0) {
            const observer = new MutationObserver(clearLoop);
            observer.observe(document, { childList: true, subtree: true });
            window.addEventListener('load', clearLoop, true);
            clearLoop();
        }

        // 重写事件监听和默认行为
        if (rule.hook_addEventListener) {
            EventTarget.prototype.addEventListener = addEventListener;
        }

        if (rule.hook_preventDefault) {
            Event.prototype.preventDefault = function() {
                if (!eventNames.includes(this.type)) {
                    originalPreventDefault.apply(this, arguments);
                }
            };
        }

        if (rule.hook_set_returnValue) {
            Object.defineProperty(Event.prototype, 'returnValue', {
                set: function(value) {
                    if (value !== true && eventNames.includes(this.type)) {
                        this.returnValue = true;
                    }
                }
            });
        }

        console.debug('url:', url, 'storageName:', storageName, 'rule:', rule.name);
        
        // 添加自定义 CSS
        if (rule.add_css) {
            addStyle('html, * { -webkit-user-select: text !important; }');
        }
    }

    // 执行初始化
    init();
})();