Greasy Fork

网页复制破解助手

破解网页防复制限制,支持破解百度文库 (wk.baidu.com)、腾讯文档 (docs.qq.com) 和道客巴巴 (doc88.com) 等网站的文本复制。

// ==UserScript==
// @name        网页复制破解助手
// @namespace   [email protected]
// @version     0.2
// @description  破解网页防复制限制,支持破解百度文库 (wk.baidu.com)、腾讯文档 (docs.qq.com) 和道客巴巴 (doc88.com) 等网站的文本复制。
// @author      ack20
// @match       *://*/*
// @grant       none
// @run-at      document-end
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 调试模式设置
    const DEBUG = false;

    // 日志函数
    function log(...args) {
        if (DEBUG) {
            console.log('[SuperCopy]', ...args);
        }
    }

    // CSS样式
    const copyCSS = `
        #_copy{
            align-items: center;
            background: #4494d5;
            border-radius: 3px;
            color: #fff;
            cursor: pointer;
            display: flex;
            font-size: 13px;
            height: 30px;
            justify-content: center;
            position: absolute;
            width: 60px;
            z-index: 1000
        }
        #select-tooltip, #sfModal, .modal-backdrop, div[id^=reader-helper]{
            display: none!important
        }
        .modal-open{
            overflow: auto!important
        }
        ._sf_adjust_body{
            padding-right: 0!important
        }
        :not(input):not(textarea)::selection {
            background-color: #2440B3 !important;
            color: #fff !important;
        }
        :not(input):not(textarea)::-moz-selection {
            background-color: #2440B3 !important;
            color: #fff !important;
        }
        * {
            user-select: text !important;
            -webkit-user-select: text !important;
            -webkit-touch-callout: text !important;
        }
    `;

    // 添加样式
    function addStyle(css) {
        const styleId = '_supercopy_style';
        // 避免重复添加样式
        if (document.getElementById(styleId)) return;

        const styleEl = document.createElement('style');
        styleEl.id = styleId;
        styleEl.textContent = css;
        document.head.appendChild(styleEl);
        log('添加样式完成');
    }

    // 解除复制限制的主要功能
    function enableCopy() {
        log('执行解除复制限制');
        const doc = document;
        const docEl = document.documentElement;
        const body = document.body;

        // 移除所有阻止复制的事件监听器
        function removeEventListeners() {
            docEl.onselectstart = docEl.oncopy = docEl.oncut = docEl.onpaste =
            docEl.onkeyup = docEl.onkeydown = docEl.oncontextmenu =
            docEl.onmousemove = docEl.onmousedown = docEl.onmouseup =
            docEl.ondragstart = null;

            body.onselectstart = body.oncopy = body.oncut = body.onpaste =
            body.onkeyup = body.onkeydown = body.oncontextmenu =
            body.onmousemove = body.onmousedown = body.onmouseup =
            body.ondragstart = null;

            doc.onselectstart = doc.oncopy = doc.oncut = doc.onpaste =
            doc.onkeyup = doc.onkeydown = doc.oncontextmenu =
            doc.onmousemove = doc.onmousedown = doc.onmouseup =
            doc.ondragstart = null;

            window.onkeyup = window.onkeydown = null;
        }

        // 移除元素的事件监听器和属性
        function cleanElement(el) {
            if (!el || el.nodeType !== 1) return;

            try {
                el.removeAttribute('oncontextmenu');
                el.removeAttribute('ondragstart');
                el.removeAttribute('onselect');
                el.removeAttribute('onselectstart');
                el.removeAttribute('onselectend');
                el.removeAttribute('oncopy');
                el.removeAttribute('onbeforecopy');
                el.removeAttribute('oncut');
                el.removeAttribute('onpaste');
                el.removeAttribute('onclick');
                el.removeAttribute('onkeydown');
                el.removeAttribute('onkeyup');
                el.removeAttribute('onmousedown');
                el.removeAttribute('onmouseup');
                el.removeAttribute('unselectable');

                // 尝试使用jQuery清理(如果存在)
                if (window.jQuery && jQuery(body) && typeof jQuery(body).off !== 'undefined') {
                    jQuery(el).off('contextmenu copy cut beforecopy beforecut beforepaste');
                    jQuery(el).unbind();
                }
            } catch (e) {
                log('清理元素属性失败:', e);
            }
        }

        // 处理所有HTML元素
        function processAllElements() {
            const tags = ['html', 'body', 'div', 'p', 'b', 'strong', 'small', 'span',
                         'pre', 'a', 'form', 'iframe', 'ul', 'li', 'dl', 'dt', 'dd',
                         'table', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];

            for (const tagName of tags) {
                const elements = document.getElementsByTagName(tagName);
                log(`处理 ${tagName} 元素, 数量: ${elements.length}`);
                for (let i = 0; i < elements.length; i++) {
                    const el = elements[i];

                    // 设置用户选择样式
                    try {
                        if (el && el.nodeType === 1) {
                            const style = el.currentStyle || window.getComputedStyle(el, null);
                            if (style && style.userSelect === 'none') {
                                el.setAttribute('style', (el.getAttribute('style') || '') + '; user-select: text !important;-webkit-user-select: text !important;-webkit-touch-callout: text !important;');
                            }

                            cleanElement(el);

                            // 添加允许默认行为的事件监听
                            const events = ['select', 'selectstart', 'selectend', 'copy', 'cut', 'paste',
                                         'keydown', 'keyup', 'keypress', 'contextmenu', 'dragstart'];
                            for (const event of events) {
                                el.addEventListener(event, allowDefault);
                            }
                        }
                    } catch (e) {
                        log('处理元素失败:', e);
                    }
                }
            }
        }

        // 添加全局事件监听器
        function addGlobalEventListeners() {
            const events = ['copy', 'cut', 'contextmenu', 'selectstart', 'mousedown',
                           'mouseup', 'mousemove', 'keydown', 'keypress', 'keyup'];

            for (const event of events) {
                document.documentElement.addEventListener(event, allowDefault, { capture: true });
            }
        }

        // 允许默认行为的事件处理函数
        function allowDefault(e) {
            e.stopPropagation();
            if (e.stopImmediatePropagation) e.stopImmediatePropagation();
            e.returnValue = true;
            return true;
        }

        // 清理jQuery事件(如果存在)
        function cleanJQuery() {
            if (window.jQuery && jQuery(body) && typeof jQuery(body).off !== 'undefined') {
                try {
                    jQuery(body).off('contextmenu copy cut beforecopy beforecut beforepaste');
                } catch (e) {
                    log('清理jQuery事件失败:', e);
                }
            }
        }

        // 执行所有清理函数
        removeEventListeners();
        processAllElements();
        cleanJQuery();
        addGlobalEventListeners();

        // 动态处理文档树变化 (MutationObserver)
        setupMutationObserver();

        log('完成解除复制限制过程');
    }

    // 设置MutationObserver监视DOM变化
    function setupMutationObserver() {
        log('设置MutationObserver');
        // 如果已经设置了观察器,不再重复设置
        if (window._superCopyObserver) return;

        // 防抖函数 - 避免频繁处理DOM变化
        const debounce = (func, delay) => {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), delay);
            };
        };

        // 处理DOM变化的防抖函数
        const processMutations = debounce((mutations) => {
            log(`处理 ${mutations.length} 个DOM变化`);

            // 处理新添加的元素
            const processNewNodes = (nodes) => {
                nodes.forEach(node => {
                    if (node.nodeType === 1) { // 元素节点
                        // 处理添加的元素
                        try {
                            const style = node.currentStyle || window.getComputedStyle(node, null);
                            if (style && style.userSelect === 'none') {
                                node.setAttribute('style', (node.getAttribute('style') || '') + '; user-select: text !important;-webkit-user-select: text !important;-webkit-touch-callout: text !important;');
                            }

                            // 移除限制复制的属性
                            node.removeAttribute('oncontextmenu');
                            node.removeAttribute('oncopy');
                            node.removeAttribute('oncut');
                            node.removeAttribute('onselectstart');
                            node.removeAttribute('unselectable');

                            // 添加允许复制的事件处理
                            node.addEventListener('copy', (e) => { e.stopPropagation(); });
                            node.addEventListener('cut', (e) => { e.stopPropagation(); });
                            node.addEventListener('contextmenu', (e) => { e.stopPropagation(); });
                            node.addEventListener('selectstart', (e) => { e.stopPropagation(); });

                            // 递归处理子元素
                            if (node.children && node.children.length) {
                                processNewNodes(Array.from(node.children));
                            }
                        } catch (e) {
                            log('处理新节点失败:', e);
                        }
                    }
                });
            };

            // 处理每个变化
            mutations.forEach(mutation => {
                // 处理添加的节点
                if (mutation.addedNodes && mutation.addedNodes.length) {
                    processNewNodes(Array.from(mutation.addedNodes));
                }

                // 处理属性变化
                if (mutation.type === 'attributes' && mutation.target.nodeType === 1) {
                    const target = mutation.target;
                    const attrName = mutation.attributeName;

                    if (attrName === 'style') {
                        try {
                            const style = target.currentStyle || window.getComputedStyle(target, null);
                            if (style && style.userSelect === 'none') {
                                target.setAttribute('style', (target.getAttribute('style') || '') + '; user-select: text !important;-webkit-user-select: text !important;-webkit-touch-callout: text !important;');
                            }
                        } catch (e) {
                            log('处理样式属性变化失败:', e);
                        }
                    } else if (attrName === 'oncontextmenu' || attrName === 'oncopy' ||
                              attrName === 'oncut' || attrName === 'onselectstart' ||
                              attrName === 'unselectable') {
                        // 移除限制复制的属性
                        target.removeAttribute(attrName);
                    }
                }
            });

            log('DOM变化处理完成');
        }, 300); // 300ms防抖延迟

        // 创建并启动MutationObserver
        try {
            const observer = new MutationObserver(processMutations);
            observer.observe(document.documentElement, {
                childList: true,     // 监视子节点添加或删除
                subtree: true,       // 监视所有后代节点
                attributes: true,    // 监视属性变化
                attributeFilter: ['style', 'oncontextmenu', 'oncopy', 'oncut', 'onselectstart', 'unselectable'] // 监视特定属性
            });

            // 保存观察器引用,避免重复设置
            window._superCopyObserver = observer;
            log('MutationObserver设置成功');
        } catch (e) {
            log('设置MutationObserver失败:', e);
        }
    }

    // 添加复制按钮功能
    function setupCopyButton() {
        log('设置复制按钮功能');

        // 防止重复添加事件监听
        if (window._superCopyButtonSetup) return;
        window._superCopyButtonSetup = true;

        // 添加复制功能
        document.body.addEventListener('mouseup', function(e) {
            // 获取选中的文本
            const selectedText = window.getSelection ? window.getSelection().toString() :
                                document.getSelection ? document.getSelection().toString() :
                                document.selection ? document.selection.createRange().text : '';

            if (!selectedText) return;
            log('检测到文本选择:', selectedText.substring(0, 20) + (selectedText.length > 20 ? '...' : ''));

            // 移除已有的复制按钮
            const existingCopy = document.getElementById('_copy');
            if (existingCopy) existingCopy.remove();

            // 创建复制按钮
            const copyButton = document.createElement('div');
            copyButton.id = '_copy';
            copyButton.style.left = (e.pageX + 30) + 'px';
            copyButton.style.top = e.pageY + 'px';
            copyButton.textContent = '复制';
            copyButton.setAttribute('data-clipboard-text', selectedText);

            // 防止事件冒泡
            copyButton.addEventListener('mousedown', function(e) {
                e.stopPropagation();
                return false;
            });

            copyButton.addEventListener('mouseup', function(e) {
                e.stopPropagation();
                return false;
            });

            // 点击复制
            copyButton.addEventListener('click', function() {
                log('点击复制按钮');
                const textarea = document.createElement('textarea');
                textarea.value = selectedText;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();

                try {
                    const successful = document.execCommand('copy');
                    copyButton.textContent = successful ? '复制成功!' : '复制失败!';
                } catch (err) {
                    copyButton.textContent = '复制失败!';
                    log('复制失败:', err);
                }

                document.body.removeChild(textarea);
                setTimeout(function() {
                    copyButton.style.display = 'none';
                    setTimeout(() => copyButton.remove(), 500);
                }, 1000);
            });

            document.body.appendChild(copyButton);
        });

        log('复制按钮功能设置完成');
    }

    // 检测当前网站
    function detectSite() {
        const url = window.location.href;

        // 百度文库
        if (url.match(/.*wk\.baidu\.com\/view\/.+/)) {
            return 'baiduwenku';
        }
        // 豆丁文档
        else if (url.match(/.*doc88\.com\/.+/)) {
            return 'doc88';
        }
        // QQ文档
        else if (url.match(/.*docs\.qq\.com\/.+/)) {
            return 'qqdoc';
        }
        // 语雀
        else if (url.match(/.*yuque\.com\/.+/)) {
            return 'yuque';
        }
        // 博客园
        else if (url.match(new RegExp('.+://boke112.com/post/.+'))) {
            return 'boke112';
        }
        // 起点小说
        else if (url.match(/qidian/)) {
            return 'qidian';
        }
        // CSDN
        else if (url.match(/csdn\.net/)) {
            return 'csdn';
        }
        // 掘金
        else if (url.match(/juejin\.cn/)) {
            return 'juejin';
        }
        // 简书
        else if (url.match(/jianshu\.com/)) {
            return 'jianshu';
        }
        // 知乎
        else if (url.match(/zhihu\.com/)) {
            return 'zhihu';
        }
        // 其他网站
        return 'other';
    }

    // 针对特定网站的处理
    function handleSpecificSites(siteType) {
        log('处理特定网站:', siteType);

        switch(siteType) {
            case 'baiduwenku':
                document.head.insertAdjacentHTML('beforeend', '<style>@media print { body{ display:block; } }</style>');
                break;

            case 'qqdoc':
                // 处理QQ文档的特殊限制
                const qqStyle = document.createElement('style');
                qqStyle.id = 'copy-hide';
                qqStyle.textContent = '#_copy{display: none !important;}';
                document.body.appendChild(qqStyle);
                break;

            case 'boke112':
                // 处理博客园的特殊限制
                document.addEventListener('click', function(e) {
                    e.stopPropagation();
                    return true;
                }, true);
                break;

            case 'qidian':
                // 处理起点的特殊限制
                const qidianStyle = document.createElement('style');
                qidianStyle.textContent = 'body { user-select: auto !important; -webkit-user-select: auto !important; }';
                document.body.appendChild(qidianStyle);

                // 移除起点的复制拦截
                document.querySelector('.main-read-container')?.addEventListener('copy', function(e) {
                    e.stopPropagation();
                    return true;
                });

                document.querySelector('.main-read-container')?.addEventListener('contextmenu', function(e) {
                    e.stopPropagation();
                    return true;
                });
                break;

            case 'csdn':
                // 处理CSDN复制限制
                const csdnStyle = document.createElement('style');
                csdnStyle.textContent = `
                    .hljs-button.signin, .hljs-button { display: none !important; }
                    code { user-select: text !important; }
                `;
                document.head.appendChild(csdnStyle);
                // 移除CSDN代码块的复制限制
                document.querySelectorAll('code').forEach(code => {
                    code.addEventListener('copy', e => e.stopPropagation());
                });
                break;

            case 'juejin':
            case 'jianshu':
            case 'zhihu':
                // 这些平台通常使用事件拦截或CSS限制
                // 通用样式处理已足够应对
                break;
        }
    }

    // Ajax完成事件监听
    function setupAjaxListener() {
        log('设置Ajax监听');

        // 防止重复设置
        if (window._superCopyAjaxSetup) return;
        window._superCopyAjaxSetup = true;

        try {
            // 覆盖原生XMLHttpRequest
            const originalXHR = window.XMLHttpRequest;
            function newXHR() {
                const xhr = new originalXHR();
                const originalOnReadyStateChange = xhr.onreadystatechange;

                xhr.onreadystatechange = function() {
                    if (this.readyState === 4) {
                        log('检测到XMLHttpRequest完成');
                        // 延迟执行,确保DOM已更新
                        setTimeout(function() {
                            enableCopy();
                        }, 300);
                    }

                    if (originalOnReadyStateChange) {
                        originalOnReadyStateChange.apply(this, arguments);
                    }
                };

                return xhr;
            }

            window.XMLHttpRequest = newXHR;

            // 监听fetch API
            if (window.fetch) {
                const originalFetch = window.fetch;
                window.fetch = function(...args) {
                    log('检测到fetch请求');
                    return originalFetch.apply(this, args).then(response => {
                        // 返回结果后延迟处理DOM
                        setTimeout(enableCopy, 300);
                        return response;
                    });
                };
            }

            log('Ajax监听设置成功');
        } catch (e) {
            log('设置Ajax监听失败:', e);
        }
    }

    // 处理定时触发
    function setupPeriodicExecution() {
        // 处理潜在的动态加载内容
        const intervals = [1000, 3000, 5000, 10000, 15000];
        intervals.forEach(timeout => {
            setTimeout(() => {
                log(`定时执行 - ${timeout}ms`);
                enableCopy();
            }, timeout);
        });

        // 每30秒定期检查一次,持续5分钟
        let count = 0;
        const intervalId = setInterval(() => {
            count++;
            log(`周期性检查 #${count}`);
            enableCopy();

            // 5分钟后停止周期性检查
            if (count >= 10) {
                clearInterval(intervalId);
                log('周期性检查结束');
            }
        }, 30000);
    }

    // 监听页面滚动事件,处理懒加载内容
    function setupScrollListener() {
        log('设置滚动监听');

        if (window._superCopyScrollSetup) return;
        window._superCopyScrollSetup = true;

        // 使用防抖函数避免过于频繁调用
        const debounce = (func, delay) => {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), delay);
            };
        };

        // 滚动处理函数
        const handleScroll = debounce(() => {
            log('滚动事件触发');
            enableCopy();
        }, 500);

        // 添加滚动事件监听
        window.addEventListener('scroll', handleScroll);
    }

    // 主函数
    function main() {
        log('启动Super Copy');

        try {
            // 添加CSS样式
            addStyle(copyCSS);

            // 检测网站类型
            const siteType = detectSite();

            // 处理特定网站
            handleSpecificSites(siteType);

            // 启用复制功能
            enableCopy();

            // 设置复制按钮
            setupCopyButton();

            // 设置Ajax监听
            setupAjaxListener();

            // 设置滚动监听
            setupScrollListener();

            // 设置定期执行
            setupPeriodicExecution();

            log('Super Copy初始化完成');
        } catch (e) {
            console.error('[SuperCopy] 执行出错:', e);
        }
    }

    // 执行主函数
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', main);
    } else {
        main();
    }
})();