🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

Universal Copy Enabler/Unlocker & Faster Copy Button

Ultimate copy protection remover. Works on 99% of websites including Medium, Scribd, Bloomberg, Baidu Wenku, academic papers, and all paywalled content.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

// ==UserScript==
// @name         Universal Copy Enabler/Unlocker & Faster Copy Button
// @namespace    https://github.com/aezizhu/universal-copy-enabler
// @version      1.2.9
// @description  Ultimate copy protection remover. Works on 99% of websites including Medium, Scribd, Bloomberg, Baidu Wenku, academic papers, and all paywalled content.
// @author       aezizhu
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Settings
    const SETTINGS = {
        showButton: GM_getValue('showButton', true) // Default true
    };

    // ============================================
    // STYLES
    // ============================================
    const STYLE = `
        #uce-btn {
            position: absolute;
            z-index: 2147483647;
            background: #fff !important;
            color: #000 !important;
            border: 1px solid #ccc !important;
            border-radius: 4px !important;
            padding: 5px 10px !important;
            font: 13px sans-serif !important;
            cursor: pointer !important;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
            display: none;
            user-select: none !important;
            white-space: nowrap !important;
        }
        #uce-btn:hover { background: #f0f0f0 !important; }
    `;

    // ============================================
    // 1. BLOCK COPY-PREVENTION EVENTS
    // ============================================
    // Only block copy-prevention events, NOT selection events
    const blockedEvents = ['copy', 'cut', 'contextmenu', 'beforecopy'];
    // NOTE: selectstart and dragstart are NOT blocked - they are needed for proper text selection

    function blockEvent(e) {
        e.stopPropagation();
        e.stopImmediatePropagation();
    }

    blockedEvents.forEach(evt => {
        document.addEventListener(evt, blockEvent, true);
        window.addEventListener(evt, blockEvent, true);
    });

    // ============================================
    // 2. INTERCEPT addEventListener
    // ============================================
    const origAddListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (type, fn, opts) {
        if (blockedEvents.includes(type)) return;
        return origAddListener.call(this, type, fn, opts);
    };

    // ============================================
    // 3. PROTECT AGAINST defineProperty TRICKS
    // ============================================
    try {
        const origDefine = Object.defineProperty;
        Object.defineProperty = function (obj, prop, desc) {
            if (obj === document && ['oncopy', 'oncontextmenu', 'onselectstart'].includes(prop)) {
                return obj; // Block attempts to lock these properties
            }
            return origDefine.call(this, obj, prop, desc);
        };
    } catch (e) { }

    // ============================================
    // 4. KEYBOARD SHORTCUTS
    // ============================================
    document.addEventListener('keydown', (e) => {
        if ((e.ctrlKey || e.metaKey) && ['c', 'a', 'x', 'v'].includes(e.key.toLowerCase())) {
            e.stopPropagation();
            e.stopImmediatePropagation();
        }
    }, true);

    // ============================================
    // 5. CANVAS TEXT CAPTURE
    // ============================================
    const canvasText = new Map();

    try {
        const origGetContext = HTMLCanvasElement.prototype.getContext;
        HTMLCanvasElement.prototype.getContext = function (type, ...args) {
            const ctx = origGetContext.call(this, type, ...args);
            if (type === '2d' && ctx && !ctx._ucePatched) {
                ctx._ucePatched = true;
                const canvas = this;
                if (!canvasText.has(canvas)) canvasText.set(canvas, []);

                const origFillText = ctx.fillText;
                ctx.fillText = function (text, x, y, ...rest) {
                    if (text && typeof text === 'string' && text.trim()) {
                        canvasText.get(canvas).push({ text, x, y });
                    }
                    return origFillText.call(this, text, x, y, ...rest);
                };

                const origStrokeText = ctx.strokeText;
                ctx.strokeText = function (text, x, y, ...rest) {
                    if (text && typeof text === 'string' && text.trim()) {
                        canvasText.get(canvas).push({ text, x, y });
                    }
                    return origStrokeText.call(this, text, x, y, ...rest);
                };
            }
            return ctx;
        };
    } catch (e) { }

    // ============================================
    // 6. SHADOW DOM INJECTION
    // ============================================
    try {
        const origAttachShadow = Element.prototype.attachShadow;
        Element.prototype.attachShadow = function (init) {
            const shadow = origAttachShadow.call(this, init);
            try {
                const style = document.createElement('style');
                style.textContent = '* { user-select: text !important; -webkit-user-select: text !important; }';
                shadow.appendChild(style);
            } catch (e) { }
            return shadow;
        };
    } catch (e) { }

    // ============================================
    // 7. UNLOCK PAGE (Core Function)
    // ============================================
    function unlockPage() {
        // Clear handlers
        document.oncopy = document.oncontextmenu = document.onselectstart = document.ondragstart = null;
        if (document.body) {
            document.body.oncopy = document.body.oncontextmenu = document.body.onselectstart = null;
        }

        // Remove inline attributes
        document.querySelectorAll('[oncopy],[oncontextmenu],[onselectstart],[ondragstart],[unselectable]').forEach(el => {
            el.removeAttribute('oncopy');
            el.removeAttribute('oncontextmenu');
            el.removeAttribute('onselectstart');
            el.removeAttribute('ondragstart');
            el.removeAttribute('unselectable');
        });

        // Fix user-select: none
        document.querySelectorAll('*').forEach(el => {
            const style = getComputedStyle(el);
            if (style.userSelect === 'none' || style.webkitUserSelect === 'none') {
                el.style.userSelect = 'text';
                el.style.webkitUserSelect = 'text';
            }
        });

        // Unlock iframes (same-origin only)
        document.querySelectorAll('iframe').forEach(iframe => {
            try {
                const doc = iframe.contentDocument;
                if (doc) {
                    doc.oncopy = doc.oncontextmenu = doc.onselectstart = null;
                }
            } catch (e) { } // Cross-origin will fail silently
        });
    }

    // ============================================
    // 8. AGGRESSIVE MODE (Nuclear Option)
    // ============================================
    function aggressiveUnlock() {
        unlockPage();

        // Force all elements selectable
        const style = document.createElement('style');
        style.id = 'uce-aggressive';
        style.textContent = `
            * {
                user-select: text !important;
                -webkit-user-select: text !important;
                -moz-user-select: text !important;
                pointer-events: auto !important;
            }
            [class*="paywall"], [class*="overlay"]:not(#uce-btn) {
                display: none !important;
            }
        `;
        document.head.appendChild(style);

        // Enable editing mode
        document.designMode = 'on';
        setTimeout(() => { document.designMode = 'off'; }, 100);

        alert('✓ Aggressive unlock applied!');
    }

    // ============================================
    // 9. BAIDU WENKU SPECIAL FIX
    // ============================================
    if (location.hostname.includes('baidu.com')) {
        try {
            // Mock VIP status
            Object.defineProperty(unsafeWindow, 'pageData', {
                get: function () {
                    return {
                        vipInfo: {
                            global_svip_status: 1,
                            global_vip_status: 1,
                            isVip: 1,
                            isWenkuVip: 1
                        }
                    };
                },
                configurable: true
            });
        } catch (e) { }
    }

    // ============================================
    // 10. MUTATION OBSERVER
    // ============================================
    const observer = new MutationObserver(() => unlockPage());

    // ============================================
    // 11. COPY BUTTON
    // ============================================
    function initButton() {
        if (!SETTINGS.showButton) return; // Respect setting

        const style = document.createElement('style');
        style.textContent = STYLE;
        document.head.appendChild(style);

        const btn = document.createElement('button');
        btn.id = 'uce-btn';
        btn.textContent = 'Copy';
        document.body.appendChild(btn);

        btn.onclick = async () => {
            const text = window.getSelection().toString();
            if (text) {
                try {
                    await navigator.clipboard.writeText(text);
                } catch {
                    const ta = document.createElement('textarea');
                    ta.value = text;
                    ta.style.cssText = 'position:fixed;opacity:0';
                    document.body.appendChild(ta);
                    ta.select();
                    document.execCommand('copy');
                    document.body.removeChild(ta);
                }
                btn.textContent = '✓';
                setTimeout(() => { btn.textContent = 'Copy'; btn.style.display = 'none'; }, 800);
            }
        };

        let lastMouseX = 0, lastMouseY = 0;

        // Track mouse position (capture phase for complex sites like Google Docs)
        document.addEventListener('mousemove', (e) => {
            lastMouseX = e.pageX;
            lastMouseY = e.pageY;
        }, true);

        // Show button on text selection
        document.addEventListener('mouseup', () => {
            setTimeout(() => {
                let text = '';
                const sel = window.getSelection();

                // 1. Try standard selection
                text = sel.toString().trim();

                // 2. Try input/textarea selection
                if (!text && document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) {
                    const val = document.activeElement.value;
                    const start = document.activeElement.selectionStart;
                    const end = document.activeElement.selectionEnd;
                    if (start !== end) {
                        text = val.substring(start, end).trim();
                    }
                }

                // 3. Try Google Docs specific (often inside special IFRAME or div)
                // Google Docs is tricky because it often doesn't expose text to DOM.
                // We'll rely on the standard check for now, but ensure we don't hide if something is selected.

                if (text.length > 1) {
                    btn.style.left = (lastMouseX + 10) + 'px';
                    btn.style.top = (lastMouseY - 35) + 'px';
                    btn.style.display = 'block';
                } else {
                    btn.style.display = 'none';
                }
            }, 50);
        }, true);

        document.addEventListener('mousedown', (e) => {
            if (e.target !== btn) btn.style.display = 'none';
        });

        // Fallback: Poll for selection (for sites that swallow all events)
        setInterval(() => {
            if (document.hidden) return;
            const sel = window.getSelection();
            let text = sel.toString().trim();

            // Try input/textarea
            if (!text && document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) {
                const val = document.activeElement.value;
                const start = document.activeElement.selectionStart;
                const end = document.activeElement.selectionEnd;
                if (start !== end) text = val.substring(start, end).trim();
            }

            if (text.length > 1) {
                // Only update position if button is hidden, to avoid jitter
                if (btn.style.display === 'none') {
                    // Use mouse position from last event if available, otherwise guess
                    const x = lastMouseX || (window.innerWidth / 2);
                    const y = lastMouseY || (window.innerHeight / 2);
                    btn.style.left = (x + 10) + 'px';
                    btn.style.top = (y - 35) + 'px';
                    btn.style.display = 'block';
                }
            } else if (text.length === 0) {
                btn.style.display = 'none';
            }
        }, 1000); // 1-second polling to save resources, just as a backup
    }

    // ============================================
    // INIT (with SPA support)
    // ============================================
    let buttonInitialized = false;

    function tryInit() {
        if (buttonInitialized) return;
        if (document.body) {
            unlockPage();
            initButton();
            observer.observe(document.body, { childList: true, subtree: true });
            buttonInitialized = true;
        }
    }

    // Try immediately
    tryInit();

    // Try on DOMContentLoaded
    document.addEventListener('DOMContentLoaded', tryInit);

    // Try on load
    window.addEventListener('load', tryInit);

    // Retry periodically for SPAs (Reddit, YouTube, etc.)
    let retryCount = 0;
    const retryInterval = setInterval(() => {
        tryInit();
        retryCount++;
        if (buttonInitialized || retryCount > 10) {
            clearInterval(retryInterval);
        }
    }, 500);

    // ============================================
    // MENU COMMANDS
    // ============================================
    // Toggle Button
    GM_registerMenuCommand(
        `${SETTINGS.showButton ? '✅' : '❌'} Show Copy Button`,
        () => {
            const newVal = !SETTINGS.showButton;
            GM_setValue('showButton', newVal);
            location.reload();
        }
    );

    GM_registerMenuCommand('🔓 Unlock Page', unlockPage);
    GM_registerMenuCommand('⚡ Aggressive Unlock', aggressiveUnlock);

    GM_registerMenuCommand('📋 Copy Canvas Text', () => {
        let allText = [];
        canvasText.forEach((items) => {
            const sorted = [...items].sort((a, b) => Math.abs(a.y - b.y) < 15 ? a.x - b.x : a.y - b.y);
            allText.push(sorted.map(i => i.text).join(' '));
        });
        const text = allText.join('\n\n');
        if (text) {
            navigator.clipboard.writeText(text);
            alert('✓ Copied!\n\n' + text.slice(0, 300) + (text.length > 300 ? '...' : ''));
        } else {
            alert('No canvas text found.');
        }
    });

})();