您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动解除网页中所有输入框的粘贴限制,带控制面板和开关
// ==UserScript== // @name zhs自动解除粘贴|复制限制 (增强版) // @namespace http://tampermonkey.net/ // @version 2.0 // @description 自动解除网页中所有输入框的粘贴限制,带控制面板和开关 // @author You & AI // @match *://*.zhihuishu.com/* // @icon https://qah5.zhihuishu.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ================================================ // Configuration & State Management // ================================================ let config = { scriptEnabled: GM_getValue('scriptEnabled', true), copyUnlockEnabled: GM_getValue('copyUnlockEnabled', true), pasteUnlockEnabled: GM_getValue('pasteUnlockEnabled', true), panelVisible: GM_getValue('panelVisible', true), }; function saveConfig() { GM_setValue('scriptEnabled', config.scriptEnabled); GM_setValue('copyUnlockEnabled', config.copyUnlockEnabled); GM_setValue('pasteUnlockEnabled', config.pasteUnlockEnabled); GM_setValue('panelVisible', config.panelVisible); } // ================================================ // Global Variables & Handlers // ================================================ let observer = null; let panelElement = null; // Named event handlers for easy removal const selectStartHandler = function(e) { e.stopImmediatePropagation(); }; const pasteHandler = function(e) { const tag = e.target.tagName; const contentEditable = e.target.contentEditable; if (tag === 'INPUT' || tag === 'TEXTAREA' || contentEditable === 'true') { e.stopImmediatePropagation(); return; } if (isProtectedElement(e.target)) { return; } e.stopImmediatePropagation(); }; // ================================================ // Core Functionality: Copy Unlock // ================================================ function enableCopyUnlock() { if (!config.scriptEnabled || !config.copyUnlockEnabled) return; document.addEventListener('selectstart', selectStartHandler, true); const elementsToCheck = document.querySelectorAll('body, body *'); elementsToCheck.forEach(el => { if (el.onselectstart) { el.onselectstart = null; } }); console.log('[解除限制] 复制功能已启用'); } function disableCopyUnlock() { document.removeEventListener('selectstart', selectStartHandler, true); // Note: Restoring original onselectstart is complex and often not necessary. // This script aims to override, so disabling means removing our override. console.log('[解除限制] 复制功能已禁用'); } // ================================================ // Core Functionality: Paste Unlock // ================================================ function enablePasteUnlock() { if (!config.scriptEnabled || !config.pasteUnlockEnabled) return; document.addEventListener('paste', pasteHandler, true); const editableElements = document.querySelectorAll('input, textarea, [contenteditable="true"]'); editableElements.forEach(el => { el.onpaste = null; el.dataset.pasteUnlocked = 'true'; }); console.log('[解除限制] 粘贴功能已启用'); setupEfficientObserver(); // Observer depends on paste unlock } function disablePasteUnlock() { document.removeEventListener('paste', pasteHandler, true); if (observer) { observer.disconnect(); observer = null; console.log('[解除限制] MutationObserver已停止 (因粘贴禁用)'); } console.log('[解除限制] 粘贴功能已禁用'); } // ================================================ // DOM Change Handling (MutationObserver) // ================================================ function setupEfficientObserver() { if (!config.scriptEnabled || !config.pasteUnlockEnabled) { if (observer) observer.disconnect(); return; } if (observer) observer.disconnect(); // Ensure only one observer runs observer = new MutationObserver(mutations => { // Debounce or delay processing setTimeout(checkForNewInputs, 300); }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); console.log('[解除限制] MutationObserver已启动'); } function checkForNewInputs() { if (!config.scriptEnabled || !config.pasteUnlockEnabled) return; const newEditableElements = document.querySelectorAll('input:not([data-paste-unlocked]), textarea:not([data-paste-unlocked]), [contenteditable="true"]:not([data-paste-unlocked])'); newEditableElements.forEach(input => { input.onpaste = null; input.dataset.pasteUnlocked = 'true'; }); } // ================================================ // Protected Elements // ================================================ function isProtectedElement(element) { if (!element || !element.tagName) return false; const protectedTags = ['BUTTON', 'A', 'NAV', 'MENU', 'VIDEO', 'AUDIO']; if (protectedTags.includes(element.tagName.toUpperCase())) return true; const protectedClasses = ['player', 'video-control', 'menu', 'navigation', 'btn', 'button']; if (element.classList && Array.from(element.classList).some(cls => protectedClasses.includes(cls))) { return true; } const protectedIds = ['player', 'controls', 'navigation-bar', 'sidebar']; if (element.id && protectedIds.includes(element.id)) return true; return false; } // ================================================ // Control Panel // ================================================ function createControlPanel() { if (document.getElementById('unlocker-panel-v2')) return; panelElement = document.createElement('div'); panelElement.id = 'unlocker-panel-v2'; panelElement.style.cssText = ` position: fixed; top: 70px; right: 20px; z-index: 2147483647; background: rgba(30, 30, 45, 0.95); color: #e8e8ff; border-radius: 12px; padding: 18px 22px; font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; box-shadow: 0 6px 20px rgba(0,0,0,0.5); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(100, 100, 150, 0.4); font-size: 14px; transition: transform 0.3s ease-out, opacity 0.3s ease-out; opacity: ${config.panelVisible ? '1' : '0'}; transform: ${config.panelVisible ? 'translateX(0)' : 'translateX(100%) translateY(-20px) scale(0.9)'}; pointer-events: ${config.panelVisible ? 'auto' : 'none'}; `; const panelHTML = ` <style> #unlocker-panel-v2 h3 { margin:0 0 15px; font-size:17px; color:#7afFAF; border-bottom:1px solid rgba(255,255,255,0.15); padding-bottom:10px; display: flex; align-items: center; } #unlocker-panel-v2 h3 .icon { display:inline-block; width:12px; height:12px; border-radius:50%; background:#7afFAF; margin-right:10px; box-shadow: 0 0 8px #7afFAF; } #unlocker-panel-v2 .control-group { margin-bottom: 15px; } #unlocker-panel-v2 label { display: flex; align-items: center; margin-bottom: 8px; color:#c0c0ff; font-weight: 500; } #unlocker-panel-v2 .switch { position: relative; display: inline-block; width: 44px; height: 24px; margin-left: auto; } #unlocker-panel-v2 .switch input { opacity: 0; width: 0; height: 0; } #unlocker-panel-v2 .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #505070; transition: .3s; border-radius: 24px; } #unlocker-panel-v2 .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; } #unlocker-panel-v2 input:checked + .slider { background-color: #6cffa8; } #unlocker-panel-v2 input:checked + .slider:before { transform: translateX(20px); } #unlocker-panel-v2 .sub-controls { padding-left: 10px; border-left: 2px solid #404060; margin-top: 10px; } #unlocker-panel-v2 .sub-controls.disabled { opacity: 0.5; pointer-events: none; } #unlocker-panel-v2 .panel-actions { text-align: center; margin-top: 15px; } #unlocker-panel-v2 #toggle-panel-visibility { padding:6px 14px; background:rgba(100,100,150,0.3); color:#c0c0ff; border:1px solid rgba(100,100,150,0.6); border-radius:6px; cursor:pointer; transition:all 0.2s; font-size:13px; } #unlocker-panel-v2 #toggle-panel-visibility:hover { background:rgba(120,120,170,0.4); } </style> <h3><span class="icon"></span>解除限制面板</h3> <div class="control-group"> <label>启用脚本总开关 <span class="switch"> <input type="checkbox" id="toggle-script-enabled"> <span class="slider"></span> </span> </label> </div> <div id="sub-controls-container" class="sub-controls"> <div class="control-group"> <label>解除复制限制 <span class="switch"> <input type="checkbox" id="toggle-copy-unlock"> <span class="slider"></span> </span> </label> </div> <div class="control-group"> <label>解除粘贴限制 <span class="switch"> <input type="checkbox" id="toggle-paste-unlock"> <span class="slider"></span> </span> </label> </div> </div> <div class="panel-actions"> <button id="toggle-panel-visibility">隐藏面板 (Alt+Q)</button> </div> `; panelElement.innerHTML = panelHTML; document.body.appendChild(panelElement); // Cache elements const scriptEnabledToggle = document.getElementById('toggle-script-enabled'); const copyUnlockToggle = document.getElementById('toggle-copy-unlock'); const pasteUnlockToggle = document.getElementById('toggle-paste-unlock'); const subControlsContainer = document.getElementById('sub-controls-container'); // Set initial states scriptEnabledToggle.checked = config.scriptEnabled; copyUnlockToggle.checked = config.copyUnlockEnabled; pasteUnlockToggle.checked = config.pasteUnlockEnabled; function updateSubControlsState() { if (config.scriptEnabled) { subControlsContainer.classList.remove('disabled'); } else { subControlsContainer.classList.add('disabled'); } } updateSubControlsState(); // Add event listeners scriptEnabledToggle.addEventListener('change', function() { config.scriptEnabled = this.checked; updateSubControlsState(); if (config.scriptEnabled) { activateFeatures(); } else { deactivateFeatures(); } saveConfig(); }); copyUnlockToggle.addEventListener('change', function() { config.copyUnlockEnabled = this.checked; if (config.scriptEnabled) { // Only apply if script is globally enabled if (this.checked) enableCopyUnlock(); else disableCopyUnlock(); } saveConfig(); }); pasteUnlockToggle.addEventListener('change', function() { config.pasteUnlockEnabled = this.checked; if (config.scriptEnabled) { // Only apply if script is globally enabled if (this.checked) enablePasteUnlock(); else disablePasteUnlock(); } saveConfig(); }); document.getElementById('toggle-panel-visibility').addEventListener('click', togglePanelVisibility); } function togglePanelVisibility() { config.panelVisible = !config.panelVisible; if (panelElement) { panelElement.style.opacity = config.panelVisible ? '1' : '0'; panelElement.style.transform = config.panelVisible ? 'translateX(0)' : 'translateX(100%) translateY(-20px) scale(0.9)'; panelElement.style.pointerEvents = config.panelVisible ? 'auto' : 'none'; const button = document.getElementById('toggle-panel-visibility'); if(button) button.textContent = config.panelVisible ? '隐藏面板 (Alt+Q)' : '显示面板 (Alt+Q)'; } saveConfig(); } function handlePanelShortcut(e) { if (e.altKey && e.key.toLowerCase() === 'q') { e.preventDefault(); e.stopPropagation(); togglePanelVisibility(); } } // ================================================ // Activation & Deactivation // ================================================ function activateFeatures() { console.log('[解除限制] 脚本功能已激活'); if (config.copyUnlockEnabled) enableCopyUnlock(); if (config.pasteUnlockEnabled) enablePasteUnlock(); // This will also setup observer if needed } function deactivateFeatures() { console.log('[解除限制] 脚本功能已停用'); disableCopyUnlock(); disablePasteUnlock(); // This will also disconnect observer if (observer) { observer.disconnect(); observer = null; console.log('[解除限制] MutationObserver已停止'); } } // ================================================ // Initialization // ================================================ function initialize() { // Create panel after a slight delay to ensure body is ready setTimeout(() => { createControlPanel(); // Initial application of features based on saved config if (config.scriptEnabled) { activateFeatures(); } else { // Ensure sub-controls are visually disabled if master is off const subControlsContainer = document.getElementById('sub-controls-container'); if (subControlsContainer) subControlsContainer.classList.add('disabled'); } }, 1000); // Delay for panel creation & initial feature activation // Add shortcut listener document.addEventListener('keydown', handlePanelShortcut, true); // Memory monitoring (optional, kept from original) monitorMemoryUsage(); } // ================================================ // Memory Monitoring (from original, kept as is) // ================================================ function monitorMemoryUsage() { if (typeof performance?.memory?.usedJSHeapSize === 'undefined') return; const logInterval = setInterval(() => { if (!config.scriptEnabled) return; // Only monitor if script is doing something const memoryUsed = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024 * 100) / 100; // console.debug(`[解除限制] 内存使用: ${memoryUsed} MB`); if (memoryUsed > 1024) { // 1GB console.warn('[解除限制] 内存使用过高,安全清理中...'); deactivateFeatures(); // Full deactivation if (panelElement) panelElement.remove(); // Remove panel clearInterval(logInterval); // Stop this monitor } }, 30000); // Check every 30s window.addEventListener('beforeunload', () => clearInterval(logInterval)); } // ================================================ // Adaptive Execution (modified for readiness) // ================================================ function adaptiveInit() { const initFn = () => { // Ensure init runs only once if (window.copyPasteUnlockerInitialized) return; window.copyPasteUnlockerInitialized = true; initialize(); }; if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(initFn, 500); // Slight delay even if ready } else { window.addEventListener('DOMContentLoaded', () => setTimeout(initFn, 500)); } } // Start the script adaptiveInit(); // Provide manual control if needed (e.g., from browser console) window.copyPasteUnlocker = { enable: () => { config.scriptEnabled = true; activateFeatures(); if(panelElement) document.getElementById('toggle-script-enabled').checked = true; saveConfig(); console.log('[解除限制] 手动启用完成。'); }, disable: () => { config.scriptEnabled = false; deactivateFeatures(); if(panelElement) document.getElementById('toggle-script-enabled').checked = false; saveConfig(); console.log('[解除限制] 手动禁用完成。'); }, togglePanel: togglePanelVisibility, getConfig: () => config }; })();