文字助手-2025新年贺岁版

解锁强制复制,取消所有网站【禁止复制】和【禁止粘贴】限制,支持【免费OCR识别】

// ==UserScript==
// @name         文字助手-2025新年贺岁版
// @version      2.1
// @description  解锁强制复制,取消所有网站【禁止复制】和【禁止粘贴】限制,支持【免费OCR识别】
// @author       诸葛
// @license      MIT
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @namespace https://greasyfork.org/users/1271291
// ==/UserScript==

(function() {
    'use strict';

    /*****************************************************************************
     *                        0. 配置与全局变量
     *****************************************************************************/
    const DISABLED_EVENTS = [
        'contextmenu', 'selectstart',
        'copy', 'cut', 'paste',
        'keydown', 'keypress', 'keyup',
        'mousedown', 'mouseup'
    ];
    const OCR_BUTTON_HIDE_KEY = 'ocrButtonHidePermanently';
    const UNLOCK_ENABLED_KEY = 'unlockFeatureEnabled'; // 是否启用解锁的标记

    // OCR按钮 & 弹窗引用
    let ocrBtn = null;
    let popup = null;

    // 用于捕获阶段事件移除
    const captureHandlers = {};

    // 在脚本开始时,先读取标记,决定是否启用解锁
    let isEnabled = (localStorage.getItem(UNLOCK_ENABLED_KEY) === 'true');

    /*****************************************************************************
     *                        1. 如果标记已开启,则立即拦截事件
     *****************************************************************************/
    if (isEnabled) {
        // 在最早(document-start)阶段拦截,效果最好
        enableUnlockCore();
    }

    /**
     * 核心启用函数:真正拦截事件
     * 只在 document-start 阶段执行才能 100% 覆盖网站脚本
     */
    function enableUnlockCore() {
        const originalAdd = EventTarget.prototype.addEventListener;
        EventTarget.prototype.addEventListener = function(type, listener, options) {
            if (DISABLED_EVENTS.includes(type)) {
                return; // 拦截
            }
            return originalAdd.call(this, type, listener, options);
        };

        const originalRemove = EventTarget.prototype.removeEventListener;
        EventTarget.prototype.removeEventListener = function(type, listener, options) {
            return originalRemove.call(this, type, listener, options);
        };

        // 清空 document/window 上的 onXXX 事件
        DISABLED_EVENTS.forEach(evt => {
            document['on' + evt] = null;
            window['on' + evt] = null;
        });

        // 在捕获阶段阻断各类事件
        DISABLED_EVENTS.forEach(evt => {
            const handler = function(e) {
                e.stopPropagation();
                e.stopImmediatePropagation();
            };
            captureHandlers[evt] = handler;
            document.addEventListener(evt, handler, true);
        });

        console.log('【解锁功能】已在 document-start 阶段启用');
    }

    /**
     * 关闭核心功能
     * 仅在刷新页面后才会彻底复原到未启用状态
     */
    function disableUnlockCore() {
        // 尝试移除捕获阶段监听器
        Object.keys(captureHandlers).forEach(evt => {
            const handler = captureHandlers[evt];
            document.removeEventListener(evt, handler, true);
        });
        console.log('【解锁功能】已关闭核心逻辑(下次刷新生效)');
    }

    /*****************************************************************************
     *   2. DOM 加载完成后,插入控制按钮 & OCR按钮(若已启用)
     *****************************************************************************/
    function onDOMReady() {
        createControlButtons();

        // 如果功能已启用,并且OCR没有被永久关闭,则显示OCR按钮
        if (isEnabled && localStorage.getItem(OCR_BUTTON_HIDE_KEY) !== 'true') {
            createOcrButton();
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', onDOMReady);
    } else {
        onDOMReady();
    }

    /*****************************************************************************
     *                3. “启用/关闭”控制按钮 & 刷新流程
     *****************************************************************************/
    function createControlButtons() {
        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.left = '20px';
        container.style.top = '50%';
        container.style.transform = 'translateY(-50%)';
        container.style.zIndex = 999998;
        container.style.display = 'flex';
        container.style.flexDirection = 'column';
        container.style.gap = '10px';

        if (!isEnabled) {
            // 若当前为关闭状态,显示“启用功能”按钮
            const enableBtn = document.createElement('button');
            enableBtn.textContent = '启用功能';
            styleButton(enableBtn, '#17a2b8'); // 蓝绿色
            enableBtn.addEventListener('click', () => {
                if (isEnabled) {
                    showMessage('功能已是开启状态,无需重复开启');
                    return;
                }
                localStorage.setItem(UNLOCK_ENABLED_KEY, 'true');
                showMessage('功能已启用,页面将刷新以生效', 2000);
                setTimeout(() => {
                    location.reload();
                }, 2000);
            });
            container.appendChild(enableBtn);
        } else {
            // 若当前为启用状态,显示“关闭功能”按钮
            const disableBtn = document.createElement('button');
            disableBtn.textContent = '关闭功能';
            styleButton(disableBtn, '#6c757d'); // 灰色
            disableBtn.addEventListener('click', () => {
                if (!isEnabled) {
                    showMessage('功能已是关闭状态,无需重复关闭');
                    return;
                }
                localStorage.setItem(UNLOCK_ENABLED_KEY, 'false');
                showMessage('功能已关闭,页面将刷新以生效', 2000);
                // 先局部disable
                disableUnlockCore();
                setTimeout(() => {
                    location.reload();
                }, 2000);
            });
            container.appendChild(disableBtn);
        }

        document.body.appendChild(container);
    }

    // 给按钮统一加点样式
    function styleButton(btn, bgColor) {
        btn.style.padding = '10px 16px';
        btn.style.borderRadius = '6px';
        btn.style.border = 'none';
        btn.style.backgroundColor = bgColor;
        btn.style.color = '#fff';
        btn.style.cursor = 'pointer';
        btn.style.fontSize = '14px';
        btn.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
    }

    /*****************************************************************************
     *               4. 文字助手按钮 & 弹窗
     *****************************************************************************/
    function createOcrButton() {
        if (ocrBtn) return;
        ocrBtn = document.createElement('button');
        ocrBtn.textContent = '文字助手';
        styleButton(ocrBtn, '#28a745'); // 绿色
        ocrBtn.style.position = 'fixed';
        ocrBtn.style.left = '20px';
        ocrBtn.style.top = '50%';
        ocrBtn.style.transform = 'translateY(-50%)';
        ocrBtn.style.zIndex = 999999;
        ocrBtn.style.marginTop = '80px'; // 与控制按钮错开一点
        ocrBtn.addEventListener('click', showOcrPopup);
        document.body.appendChild(ocrBtn);
    }

    function removeOcrButton() {
        if (ocrBtn) {
            ocrBtn.remove();
            ocrBtn = null;
        }
        closePopup();
    }

    function showOcrPopup() {
        if (popup) {
            popup.style.display = 'block';
            return;
        }

        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.top = 0;
        overlay.style.left = 0;
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'rgba(0,0,0,0.3)';
        overlay.style.zIndex = 1000000;

        popup = document.createElement('div');
        popup.style.position = 'fixed';
        popup.style.left = '50%';
        popup.style.top = '50%';
        popup.style.transform = 'translate(-50%, -50%)';
        popup.style.width = '320px';
        popup.style.backgroundColor = '#fff';
        popup.style.borderRadius = '8px';
        popup.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
        popup.style.padding = '20px';
        popup.style.textAlign = 'center';

        const text = document.createElement('p');
        text.textContent = '文字助手暂未完全开发,这里提供免费OCR链接供使用。';
        text.style.margin = '0 0 20px 0';
        popup.appendChild(text);

        const btnContainer = document.createElement('div');
        btnContainer.style.display = 'flex';
        btnContainer.style.justifyContent = 'space-between';
        btnContainer.style.gap = '10px';

        // 使用助手
        const useBtn = document.createElement('button');
        useBtn.textContent = '使用助手';
        styleButton(useBtn, '#007bff');
        useBtn.style.flex = '1';
        useBtn.addEventListener('click', () => {
            window.open('https://hiroi-sora.lanzoul.com/s/umi-ocr', '_blank');
            closePopup();
        });
        btnContainer.appendChild(useBtn);

        // 本次关闭
        const closeOnceBtn = document.createElement('button');
        closeOnceBtn.textContent = '本次关闭';
        styleButton(closeOnceBtn, '#6c757d');
        closeOnceBtn.style.flex = '1';
        closeOnceBtn.addEventListener('click', () => {
            removeOcrButton();
            closePopup();
        });
        btnContainer.appendChild(closeOnceBtn);

        // 永久关闭
        const closeForeverBtn = document.createElement('button');
        closeForeverBtn.textContent = '永久关闭';
        styleButton(closeForeverBtn, '#dc3545');
        closeForeverBtn.style.flex = '1';
        closeForeverBtn.addEventListener('click', () => {
            localStorage.setItem(OCR_BUTTON_HIDE_KEY, 'true');
            removeOcrButton();
            closePopup();
        });
        btnContainer.appendChild(closeForeverBtn);

        popup.appendChild(btnContainer);
        overlay.appendChild(popup);
        document.body.appendChild(overlay);
    }

    function closePopup() {
        if (popup) {
            popup.remove();
            popup = null;
        }
        const overlay = document.querySelector('div[style*="background-color: rgba(0,0,0,0.3)"]');
        if (overlay) overlay.remove();
    }

    /*****************************************************************************
     *               5. 全局提示函数 (默认3秒自动消失)
     *****************************************************************************/
    function showMessage(msg, duration = 3000) {
        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.top = 0;
        overlay.style.left = 0;
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'rgba(0,0,0,0.2)';
        overlay.style.zIndex = 1000002;

        const box = document.createElement('div');
        box.style.position = 'fixed';
        box.style.left = '50%';
        box.style.top = '50%';
        box.style.transform = 'translate(-50%, -50%)';
        box.style.minWidth = '200px';
        box.style.backgroundColor = '#fff';
        box.style.borderRadius = '8px';
        box.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
        box.style.padding = '16px';
        box.style.textAlign = 'center';

        const p = document.createElement('p');
        p.textContent = msg;
        p.style.margin = '0 0 12px 0';
        box.appendChild(p);

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '关闭';
        styleButton(closeBtn, '#007bff');
        closeBtn.style.padding = '5px 10px';
        closeBtn.addEventListener('click', () => {
            overlay.remove();
        });
        box.appendChild(closeBtn);

        overlay.appendChild(box);
        document.body.appendChild(overlay);

        setTimeout(() => {
            if (overlay.parentNode) {
                overlay.remove();
            }
        }, duration);
    }
})();