// ==UserScript== // @name 复制授权拦截器 // @namespace https://viayoo.com/ // @version 2.2 // @description 复制必弹窗,3次拒绝永久禁用,点击小红点恢复,红绿切换自由复制。 // @author Aloazny & Deepseek // @match *://*/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const KEY = 'copyAuth:v2'; if (window[KEY]) return; window[KEY] = true; let denyCount = 0; let enabled = true; let freeCopyMode = false; // 默认设定三次后禁止复制写入行为 const MAX_DENY = 3; const originalExecCommand = document.execCommand; const originalClipboardWriteText = navigator.clipboard?.writeText; const originalClipboardWrite = navigator.clipboard?.write; let isShowingModal = false; const showCustomConfirm = (message) => { if (isShowingModal) { return Promise.resolve(false); } return new Promise((resolve) => { isShowingModal = true; const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: white; padding: 20px; border-radius: 12px; max-width: 300px; width: 80%; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.15); `; const text = document.createElement('p'); text.textContent = message; text.style.cssText = ` margin: 0 0 20px 0; font-size: 16px; color: #333; line-height: 1.4; `; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 10px; justify-content: center; `; const allowBtn = document.createElement('button'); allowBtn.textContent = '允许'; allowBtn.style.cssText = ` padding: 10px 20px; border: none; border-radius: 6px; background: #007AFF; color: white; font-size: 16px; cursor: pointer; flex: 1; `; const denyBtn = document.createElement('button'); denyBtn.textContent = '拒绝'; denyBtn.style.cssText = ` padding: 10px 20px; border: none; border-radius: 6px; background: #FF3B30; color: white; font-size: 16px; cursor: pointer; flex: 1; `; const cleanup = () => { if (document.body.contains(modal)) { document.body.removeChild(modal); } isShowingModal = false; }; const handleAllow = () => { cleanup(); resolve(true); }; const handleDeny = () => { cleanup(); resolve(false); }; allowBtn.addEventListener('click', handleAllow); denyBtn.addEventListener('click', handleDeny); modal.addEventListener('click', (e) => { if (e.target === modal) { handleDeny(); } }); dialog.appendChild(text); buttonContainer.appendChild(denyBtn); buttonContainer.appendChild(allowBtn); dialog.appendChild(buttonContainer); modal.appendChild(dialog); document.body.appendChild(modal); allowBtn.focus(); }); }; const blockCopy = (e) => { if (freeCopyMode) return; if (!enabled || denyCount >= MAX_DENY) { e.preventDefault(); e.stopImmediatePropagation(); return; } e.preventDefault(); e.stopImmediatePropagation(); showCustomConfirm('允许复制内容?').then((allowCopy) => { if (!allowCopy) { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } else { const selection = window.getSelection().toString(); if (selection) { if (originalClipboardWriteText) { navigator.clipboard.writeText(selection).catch(() => {}); } else { const textArea = document.createElement('textarea'); textArea.value = selection; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; document.body.appendChild(textArea); textArea.select(); try { originalExecCommand.call(document, 'copy'); } catch (err) {} document.body.removeChild(textArea); } } } }); }; const hookClipboardAPI = () => { try { if (originalClipboardWriteText) { navigator.clipboard.writeText = function(text) { return new Promise((resolve, reject) => { if (freeCopyMode) { originalClipboardWriteText.call(this, text).then(resolve).catch(reject); return; } if (!enabled || denyCount >= MAX_DENY) { reject(new Error('Copy disabled')); return; } showCustomConfirm('允许复制内容?').then((allowCopy) => { if (allowCopy) { originalClipboardWriteText.call(this, text).then(resolve).catch(reject); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } reject(new Error('Copy cancelled by user')); } }); }); }; } if (originalClipboardWrite) { navigator.clipboard.write = function(data) { return new Promise((resolve, reject) => { if (freeCopyMode) { originalClipboardWrite.call(this, data).then(resolve).catch(reject); return; } if (!enabled || denyCount >= MAX_DENY) { reject(new Error('Copy disabled')); return; } showCustomConfirm('允许复制内容?').then((allowCopy) => { if (allowCopy) { originalClipboardWrite.call(this, data).then(resolve).catch(reject); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } reject(new Error('Copy cancelled by user')); } }); }); }; } if (navigator.clipboard && !navigator.clipboard._proxied) { const clipboardProxy = new Proxy(navigator.clipboard, { get(target, prop) { const value = target[prop]; if (typeof value === 'function') { return value.bind(target); } return value; } }); Object.defineProperty(navigator, 'clipboard', { value: clipboardProxy, writable: false, configurable: false }); navigator.clipboard._proxied = true; } } catch (error) {} }; const hookExecCommand = () => { try { document.execCommand = function(command, showUI, value) { if (command === 'copy') { if (freeCopyMode) { return originalExecCommand.apply(this, arguments); } if (!enabled || denyCount >= MAX_DENY) { return false; } const selection = window.getSelection().toString(); if (!selection) { return originalExecCommand.apply(this, arguments); } showCustomConfirm('允许复制内容?').then((allowCopy) => { if (allowCopy) { const textArea = document.createElement('textarea'); textArea.value = selection; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; document.body.appendChild(textArea); textArea.select(); try { originalExecCommand.call(document, 'copy'); } catch (err) {} document.body.removeChild(textArea); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } }); return false; } return originalExecCommand.apply(this, arguments); }; } catch (error) {} }; const hookEvents = () => { document.removeEventListener('copy', blockCopy, true); document.addEventListener('copy', blockCopy, { capture: true, passive: false }); }; const setupIframe = (iframe) => { try { if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') { const doc = iframe.contentDocument; doc.removeEventListener('copy', blockCopy, true); doc.addEventListener('copy', blockCopy, { capture: true, passive: false }); const iframeExecCommand = doc.execCommand; doc.execCommand = function(command, showUI, value) { if (command === 'copy') { if (freeCopyMode) { return iframeExecCommand.apply(this, arguments); } if (!enabled || denyCount >= MAX_DENY) { return false; } const selection = doc.defaultView.getSelection().toString(); if (!selection) { return iframeExecCommand.apply(this, arguments); } showCustomConfirm('允许复制内容?').then((allowCopy) => { if (allowCopy) { const textArea = doc.createElement('textarea'); textArea.value = selection; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; doc.body.appendChild(textArea); textArea.select(); try { iframeExecCommand.call(doc, 'copy'); } catch (err) {} doc.body.removeChild(textArea); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } }); return false; } return iframeExecCommand.apply(this, arguments); }; try { if (doc.defaultView && doc.defaultView.navigator.clipboard) { const iframeClipboard = doc.defaultView.navigator.clipboard; const originalIframeWriteText = iframeClipboard.writeText; iframeClipboard.writeText = function(text) { return new Promise((resolve, reject) => { if (freeCopyMode) { originalIframeWriteText.call(this, text).then(resolve).catch(reject); return; } if (!enabled || denyCount >= MAX_DENY) { reject(new Error('Copy disabled')); return; } showCustomConfirm('允许复制内容?').then((allowCopy) => { if (allowCopy) { originalIframeWriteText.call(this, text).then(resolve).catch(reject); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } reject(new Error('Copy cancelled by user')); } }); }); }; } } catch (e) {} } else { iframe.addEventListener('load', () => { setTimeout(() => setupIframe(iframe), 0); }); } } catch (error) {} }; const observeFrames = () => { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === 'IFRAME') { setupIframe(node); } else { const iframes = node.getElementsByTagName('iframe'); for (const iframe of iframes) { setupIframe(iframe); } } } } } }); observer.observe(document.documentElement, { childList: true, subtree: true }); document.querySelectorAll('iframe').forEach(setupIframe); }; const dot = document.createElement('div'); dot.style.cssText = ` position: fixed; bottom: 45%; right: 10px; width: 20px; height: 20px; border-radius: 10px; background: red; z-index: 2147483647; opacity: 0.8; touch-action: none; cursor: pointer; user-select: none; -webkit-user-select: none; border: 2px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.3); transition: transform 0.1s ease, opacity 0.1s ease; `; function updateDotAppearance() { if (freeCopyMode) { dot.style.backgroundColor = 'green'; } else if (!enabled) { dot.style.backgroundColor = 'gray'; } else { dot.style.backgroundColor = 'red'; } } let isMoving = false; let startX = 0, startY = 0, initialLeft = 0, initialTop = 0; const handleTouchStart = (e) => { isMoving = true; const touch = e.touches[0]; const rect = dot.getBoundingClientRect(); startX = touch.clientX; startY = touch.clientY; initialLeft = rect.left; initialTop = rect.top; dot.style.transform = 'scale(1.3)'; dot.style.opacity = '1'; e.preventDefault(); e.stopPropagation(); }; const handleTouchMove = (e) => { if (!isMoving) return; const touch = e.touches[0]; const deltaX = touch.clientX - startX; const deltaY = touch.clientY - startY; dot.style.left = (initialLeft + deltaX) + 'px'; dot.style.top = (initialTop + deltaY) + 'px'; dot.style.right = 'auto'; dot.style.bottom = 'auto'; e.preventDefault(); e.stopPropagation(); }; let lastTapTime = 0; let tapCount = 0; let tapTimer = null; const handleTouchEnd = (e) => { const touch = e.changedTouches[0]; const deltaX = Math.abs(touch.clientX - startX); const deltaY = Math.abs(touch.clientY - startY); const moved = deltaX > 5 || deltaY > 5; if (isMoving && moved) { isMoving = false; return; } isMoving = false; const currentTime = Date.now(); const tapDelay = currentTime - lastTapTime; if (tapDelay < 300 && tapDelay > 0) { tapCount++; } else { tapCount = 1; } lastTapTime = currentTime; if (tapCount === 1) { tapTimer = setTimeout(() => { if (tapCount === 1) { handleSingleTap(); } tapCount = 0; }, 300); } else if (tapCount === 2) { clearTimeout(tapTimer); handleDoubleTap(); tapCount = 0; } dot.style.transform = 'scale(1)'; dot.style.opacity = '0.8'; e.preventDefault(); e.stopPropagation(); }; const handleSingleTap = () => { if (!enabled) { enabled = true; denyCount = 0; freeCopyMode = false; } else { freeCopyMode = !freeCopyMode; } updateDotAppearance(); }; const handleDoubleTap = () => { freeCopyMode = true; enabled = true; denyCount = 0; updateDotAppearance(); }; dot.addEventListener('touchstart', handleTouchStart, { passive: false }); dot.addEventListener('touchmove', handleTouchMove, { passive: false }); dot.addEventListener('touchend', handleTouchEnd, { passive: false }); dot.addEventListener('mousedown', (e) => { isMoving = true; startX = e.clientX; startY = e.clientY; const rect = dot.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; e.preventDefault(); e.stopPropagation(); }); let clickTimer = null; let clickCount = 0; dot.addEventListener('click', (e) => { if (isMoving) { isMoving = false; return; } clickCount++; if (clickCount === 1) { clickTimer = setTimeout(() => { handleSingleTap(); clickCount = 0; }, 300); } else if (clickCount === 2) { clearTimeout(clickTimer); handleDoubleTap(); clickCount = 0; } e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!isMoving) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; dot.style.left = (initialLeft + deltaX) + 'px'; dot.style.top = (initialTop + deltaY) + 'px'; dot.style.right = 'auto'; dot.style.bottom = 'auto'; }); document.addEventListener('mouseup', () => { isMoving = false; }); const init = () => { hookEvents(); hookClipboardAPI(); hookExecCommand(); observeFrames(); const addDot = () => { if (document.body) { if (!document.body.contains(dot)) { document.body.appendChild(dot); } } else { requestAnimationFrame(addDot); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addDot); } else { addDot(); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(init, 0); }); } else { setTimeout(init, 0); } let protectionInterval = setInterval(() => { if (!navigator.clipboard._proxied) { hookClipboardAPI(); } if (document.execCommand === originalExecCommand) { hookExecCommand(); } }, 1000); window.addEventListener('beforeunload', () => { clearInterval(protectionInterval); }); })();