网页复制破解助手

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
    }
})();