Greasy Fork

Greasy Fork is available in English.

解除复制限制

解除网页复制限制并提供可视化控制

当前为 2025-02-01 提交的版本,查看 最新版本

// ==UserScript==
// @name            解除复制限制
// @name:zh         解除复制限制
// @name:en         Unlock Copy Restrictions
// @name:ja         コピー制限解除
// @name:ko         복사 제한 해제
// @name:es         Desbloquear restricciones de copia
// @namespace       gura8390/copy/2
// @version         1.6.1
// @license         MIT
// @icon            https://img.icons8.com/nolan/64/password1.png
// @description     解除网页复制限制并提供可视化控制
// @description:zh  解除网页复制限制并提供可视化控制
// @description:en  Unlock web copy restrictions with visual control
// @description:ja  ウェブのコピー制限を解除し、視覚的な制御を提供。
// @description:ko  웹 페이지의 복사 제한을 해제하고 시각적 제어를 제공하며
// @description:es Desbloquea restricciones de copia en la web y proporciona control visual
// @author          lbihhe
// @match           *://*/*
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_registerMenuCommand
// @grant           GM_xmlhttpRequest
// @run-at          document-end
// ==/UserScript==

(function() {
    'use strict';

    // =====================
    // 1. 多语言本地化配置
    // =====================
    const locales = {
        en: {
            menu_toggle_script: "🔄 Toggle Script State",
            menu_toggle_button: "👁️ Toggle Button Display",
            btn_unlock: "🔓 Unlock Restrictions",
            btn_lock: "🔒 Restore Defaults",
            toast_unlocked: "✔️ Copy restrictions unlocked!",
            toast_locked: "✔️ Restrictions restored!"
        },
        zh: {
            menu_toggle_script: "🔄 切换脚本状态",
            menu_toggle_button: "👁️ 切换按钮显示",
            btn_unlock: "🔓 解除限制",
            btn_lock: "🔒 恢复原状",
            toast_unlocked: "✔️ 复制限制已解除!",
            toast_locked: "✔️ 限制已恢复!"
        },
        ja: {
            menu_toggle_script: "🔄 スクリプト状態を切り替え",
            menu_toggle_button: "👁️ ボタン表示を切り替え",
            btn_unlock: "🔓 制限解除",
            btn_lock: "🔒 デフォルトに戻す",
            toast_unlocked: "✔️ コピー制限が解除されました!",
            toast_locked: "✔️ 制限が復元されました!"
        },
        ko: {
            menu_toggle_script: "🔄 스크립트 상태 전환",
            menu_toggle_button: "👁️ 버튼 표시 전환",
            btn_unlock: "🔓 제한 해제",
            btn_lock: "🔒 기본값 복원",
            toast_unlocked: "✔️ 복사 제한이 해제되었습니다!",
            toast_locked: "✔️ 제한이 복원되었습니다!"
        },
        es: {
            menu_toggle_script: "🔄 Cambiar estado del script",
            menu_toggle_button: "👁️ Cambiar visualización del botón",
            btn_unlock: "🔓 Desbloquear restricciones",
            btn_lock: "🔒 Restaurar valores predeterminados",
            toast_unlocked: "✔️ ¡Restricciones de copia desbloqueadas!",
            toast_locked: "✔️ ¡Restricciones restauradas!"
        }
    };

    const lang = navigator.language.toLowerCase();
    let userLang = 'en';
    if (lang.startsWith('zh')) userLang = 'zh';
    else if (lang.startsWith('ja')) userLang = 'ja';
    else if (lang.startsWith('ko')) userLang = 'ko';
    else if (lang.startsWith('es')) userLang = 'es';
    const t = locales[userLang];

    // =============================
    // 2. 解除复制限制的核心逻辑
    // =============================
    const CONFIG = {
        ENABLED: 'copy_enabled',
        SHOW_BUTTON: 'show_button'
    };

    let unlockStyle = null;
    let floatButton = null;
    const stopPropagation = e => e.stopPropagation();
    const eventsList = ['contextmenu', 'copy', 'selectstart'];

    const initConfig = () => {
        // 强制初始化为布尔值
        if (typeof GM_getValue(CONFIG.ENABLED) !== 'boolean') {
            GM_setValue(CONFIG.ENABLED, true);
        }
        if (typeof GM_getValue(CONFIG.SHOW_BUTTON) !== 'boolean') {
            GM_setValue(CONFIG.SHOW_BUTTON, true);
        }
    };

    const toggleButtonDisplay = show => {
        if (show) {
            if (!floatButton) {
                floatButton = createFloatButton();
                // 确保添加到可视区域
                document.documentElement.appendChild(floatButton);
            }
        } else {
            if (floatButton) {
                floatButton.remove();
                floatButton = null;
            }
        }
    };

    const registerMenu = () => {
        GM_registerMenuCommand(t.menu_toggle_script, () => {
            const current = GM_getValue(CONFIG.ENABLED);
            GM_setValue(CONFIG.ENABLED, !current);
            location.reload();
        });

        GM_registerMenuCommand(t.menu_toggle_button, () => {
            const newState = !GM_getValue(CONFIG.SHOW_BUTTON);
            GM_setValue(CONFIG.SHOW_BUTTON, newState);
            toggleButtonDisplay(newState);
        });
    };

    const unlockCopy = () => {
        if (!unlockStyle) {
            unlockStyle = document.createElement('style');
            unlockStyle.id = 'copy-unlocker-style';
            unlockStyle.textContent = '*{user-select:auto!important;-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;}';
            document.head.appendChild(unlockStyle);
        }
        eventsList.forEach(event => document.body.addEventListener(event, stopPropagation, true));
    };

    const restoreCopy = () => {
        if (unlockStyle) {
            unlockStyle.remove();
            unlockStyle = null;
        }
        eventsList.forEach(event => document.body.removeEventListener(event, stopPropagation, true));
    };

    const showSuccessToast = (msg, bgColor = '#4CAF50') => {
        const toast = document.createElement('div');
        toast.textContent = msg;
        toast.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            background: ${bgColor};
            color: white;
            padding: 12px 24px;
            border-radius: 8px;
            z-index: 9999;
            opacity: 0;
            animation: fadeSlideIn 0.6s forwards, fadeOut 0.6s 2.5s forwards;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
        `;
        document.body.appendChild(toast);
        setTimeout(() => toast.remove(), 3200);
        if (!document.getElementById('toast-animations')) {
            const style = document.createElement('style');
            style.id = 'toast-animations';
            style.textContent = `
                @keyframes fadeSlideIn {
                    0% { transform: translateY(100%); opacity: 0; }
                    100% { transform: translateY(0); opacity: 1; }
                }
                @keyframes fadeOut {
                    to { opacity: 0; }
                }
            `;
            document.head.appendChild(style);
        }
    };

    const createFloatButton = () => {
        const btn = document.createElement('button');
        btn.id = 'copy-unlocker-btn';
        const updateLabel = () => {
            btn.textContent = GM_getValue(CONFIG.ENABLED) ? t.btn_lock : t.btn_unlock;
        };
        updateLabel();

        // 增强样式兼容性
        btn.style.cssText = `
            position: fixed !important;
            bottom: 20px !important;
            right: 20px !important;
            z-index: 2147483647 !important;
            padding: 8px 17px !important;
            background: linear-gradient(45deg, #00c6ff, #0072ff) !important;
            color: #fff !important;
            border: none !important;
            border-radius: 10px !important;
            cursor: pointer !important;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
            font-family: system-ui, sans-serif !important;
            font-size: 16px !important;
            margin: 0 !important;
            line-height: 1.5 !important;
        `;

        // 防止网站样式覆盖
        btn.setAttribute('style', btn.style.cssText);

        btn.addEventListener('click', () => {
            const enabled = GM_getValue(CONFIG.ENABLED);
            GM_setValue(CONFIG.ENABLED, !enabled);
            if (!enabled) {
                unlockCopy();
                showSuccessToast(t.toast_unlocked);
            } else {
                restoreCopy();
                showSuccessToast(t.toast_locked, '#F44336');
            }
            updateLabel();
        });

        return btn;
    };

    // ===============================
    // 3. 针对 doc88.com 的特殊优化处理
    // ===============================
    let path = "";
    const website_rule_doc88 = {
        regexp: /doc88\.com/,
        init: () => {
            const style = document.createElement('style');
            style.textContent = '#left-menu { display: none !important; }';
            document.head.appendChild(style);
            GM_xmlhttpRequest({
                url: 'https://res3.doc88.com/resources/js/modules/main-v2.min.js',
                onload: (r) => (path = (r.responseText.match(/\("#cp_textarea"\)\.val\(([\w.]+)\)/) || [])[1])
            });
            if (typeof unsafeWindow.copyText === 'function') {
                path = (unsafeWindow.copyText.toString().match(/<textarea[^>]*>'\+([\w.]+)\+<\/textarea>/) || [])[1];
            }
        }
    };

    if (website_rule_doc88.regexp.test(location.href)) {
        website_rule_doc88.init();
    }

    // ===============================
    // 4. 主执行函数
    // ===============================
    const main = () => {
        initConfig();
        registerMenu();

        // 应用初始状态
        GM_getValue(CONFIG.ENABLED) ? unlockCopy() : restoreCopy();
        toggleButtonDisplay(GM_getValue(CONFIG.SHOW_BUTTON));

        // 确保按钮在动态内容加载后仍存在
        new MutationObserver(() => {
            if (GM_getValue(CONFIG.SHOW_BUTTON) && !floatButton) {
                toggleButtonDisplay(true);
            }
        }).observe(document.body, { childList: true, subtree: true });
    };

    main();

})();