// ==UserScript== // @name 复制授权拦截器 // @namespace https://viayoo.com/ // @version 2.3 // @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, copyText = '') => { 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: 90%; width: fit-content; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.15); display: flex; flex-direction: column; gap: 12px; `; const text = document.createElement('p'); text.textContent = message; text.style.cssText = `margin: 0; font-size: 16px; color: #333; line-height: 1.4;`; const preview = document.createElement('div'); preview.textContent = copyText.slice(0, 100) + (copyText.length > 100 ? '...' : ''); preview.style.cssText = ` margin: 0; padding: 12px; background: #f8f8f8; border-radius: 8px; font-family: monospace; font-size: 14px; color: #222; max-height: 120px; overflow: auto; text-align: left; word-break: break-all; display: ${copyText ? 'block' : 'none'}; `; 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; }; allowBtn.onclick = () => { cleanup(); resolve(true); }; denyBtn.onclick = () => { cleanup(); resolve(false); }; modal.onclick = (e) => { if (e.target === modal) denyBtn.onclick(); }; dialog.append(text, preview, buttonContainer); buttonContainer.append(denyBtn, allowBtn); modal.appendChild(dialog); document.body.appendChild(modal); allowBtn.focus(); }); }; const performCopy = (text) => { if (originalClipboardWriteText) { return originalClipboardWriteText.call(navigator.clipboard, text); } else { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); const result = originalExecCommand.call(document, 'copy'); document.body.removeChild(ta); return result ? Promise.resolve() : Promise.reject(); } }; const blockCopy = (e) => { if (freeCopyMode || !enabled || denyCount >= MAX_DENY) { if (!enabled || denyCount >= MAX_DENY) e.preventDefault(); return; } e.preventDefault(); e.stopImmediatePropagation(); const selection = window.getSelection().toString(); if (!selection) return; showCustomConfirm('允许复制内容?', selection).then((allow) => { if (allow) { performCopy(selection).catch(() => {}); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } }); }; const hookClipboardAPI = () => { try { if (originalClipboardWriteText) { navigator.clipboard.writeText = function(text) { return new Promise((resolve, reject) => { if (freeCopyMode) return originalClipboardWriteText.call(this, text).then(resolve).catch(reject); if (!enabled || denyCount >= MAX_DENY) return reject(new Error('Copy disabled')); showCustomConfirm('允许复制内容?', text).then((allow) => { if (allow) { originalClipboardWriteText.call(this, text).then(resolve).catch(reject); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } reject(new Error('Copy cancelled')); } }); }); }; } if (originalClipboardWrite) { navigator.clipboard.write = function(data) { return new Promise((resolve, reject) => { if (freeCopyMode) return originalClipboardWrite.call(this, data).then(resolve).catch(reject); if (!enabled || denyCount >= MAX_DENY) return reject(new Error('Copy disabled')); const text = data[0]?.type === 'text/plain' ? data[0].getData('text/plain') : ''; showCustomConfirm('允许复制内容?', text).then((allow) => { if (allow) { originalClipboardWrite.call(this, data).then(resolve).catch(reject); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } reject(new Error('Copy cancelled')); } }); }); }; } if (navigator.clipboard && !navigator.clipboard._proxied) { Object.defineProperty(navigator, 'clipboard', { value: new Proxy(navigator.clipboard, { get(t, p) { const v = t[p]; return typeof v === 'function' ? v.bind(t) : v; } }), writable: false, configurable: false }); navigator.clipboard._proxied = true; } } catch (e) {} }; const hookExecCommand = () => { try { document.execCommand = function(command, showUI, value) { if (command !== 'copy') return originalExecCommand.apply(this, arguments); 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('允许复制内容?', selection).then((allow) => { if (allow) { const ta = document.createElement('textarea'); ta.value = selection; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { originalExecCommand.call(document, 'copy'); } catch (e) {} document.body.removeChild(ta); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } }); return false; }; } catch (e) {} }; const hookEvents = () => { document.removeEventListener('copy', blockCopy, true); document.addEventListener('copy', blockCopy, { capture: true, passive: false }); }; const setupIframe = (iframe) => { try { const doc = iframe.contentDocument; if (!doc || doc.readyState !== 'complete') { iframe.addEventListener('load', () => setTimeout(() => setupIframe(iframe), 0)); return; } doc.removeEventListener('copy', blockCopy, true); doc.addEventListener('copy', blockCopy, { capture: true, passive: false }); const iframeExec = doc.execCommand; doc.execCommand = function(command, showUI, value) { if (command !== 'copy') return iframeExec.apply(this, arguments); if (freeCopyMode) return iframeExec.apply(this, arguments); if (!enabled || denyCount >= MAX_DENY) return false; const sel = doc.defaultView.getSelection().toString(); if (!sel) return iframeExec.apply(this, arguments); showCustomConfirm('允许复制内容?', sel).then((allow) => { if (allow) { const ta = doc.createElement('textarea'); ta.value = sel; ta.style.position = 'fixed'; ta.style.opacity = '0'; doc.body.appendChild(ta); ta.select(); try { iframeExec.call(doc, 'copy'); } catch (e) {} doc.body.removeChild(ta); } else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDotAppearance(); } } }); return false; }; if (doc.defaultView.navigator.clipboard) { const cb = doc.defaultView.navigator.clipboard; const orig = cb.writeText; cb.writeText = function(text) { return new Promise((res, rej) => { if (freeCopyMode) return orig.call(this, text).then(res).catch(rej); if (!enabled || denyCount >= MAX_DENY) return rej(); showCustomConfirm('允许复制内容?', text).then((a) => { a ? orig.call(this, text).then(res).catch(rej) : (denyCount++, denyCount >= MAX_DENY && (enabled = false, updateDotAppearance()), rej()); }); }); }; } } catch (e) {} }; const observeFrames = () => { new MutationObserver(muts => { for (const m of muts) for (const n of m.addedNodes) if (n.nodeType === 1) (n.tagName === 'IFRAME' ? [n] : n.getElementsByTagName('iframe')).forEach(setupIframe); }).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 .1s ease,opacity .1s ease;`; function updateDotAppearance() { dot.style.backgroundColor = freeCopyMode ? 'green' : (!enabled ? 'gray' : 'red'); } let isMoving = false, startX = 0, startY = 0, initialLeft = 0, initialTop = 0; const handleStart = (e) => { isMoving = true; const t = e.touches ? e.touches[0] : e; const r = dot.getBoundingClientRect(); startX = t.clientX; startY = t.clientY; initialLeft = r.left; initialTop = r.top; dot.style.transform = 'scale(1.3)'; dot.style.opacity = '1'; e.preventDefault(); }; const handleMove = (e) => { if (!isMoving) return; const t = e.touches ? e.touches[0] : e; dot.style.left = (initialLeft + t.clientX - startX) + 'px'; dot.style.top = (initialTop + t.clientY - startY) + 'px'; dot.style.right = dot.style.bottom = 'auto'; e.preventDefault(); }; let lastTap = 0, taps = 0, timer = null; const handleEnd = (e) => { const t = e.changedTouches ? e.changedTouches[0] : e; const moved = Math.abs(t.clientX - startX) > 5 || Math.abs(t.clientY - startY) > 5; if (isMoving && moved) { isMoving = false; return; } isMoving = false; const now = Date.now(); if (now - lastTap < 300) taps++; else taps = 1; lastTap = now; if (taps === 1) timer = setTimeout(() => { (!enabled ? (enabled = true, denyCount = 0, freeCopyMode = false) : freeCopyMode = !freeCopyMode); updateDotAppearance(); taps = 0; }, 300); else if (taps === 2) { clearTimeout(timer); freeCopyMode = true; enabled = true; denyCount = 0; updateDotAppearance(); taps = 0; } dot.style.transform = 'scale(1)'; dot.style.opacity = '0.8'; }; dot.addEventListener('touchstart', handleStart, {passive:false}); dot.addEventListener('touchmove', handleMove, {passive:false}); dot.addEventListener('touchend', handleEnd, {passive:false}); dot.addEventListener('mousedown', handleStart); document.addEventListener('mousemove', handleMove); document.addEventListener('mouseup', () => { isMoving = false; }); let mc = 0, mct = null; dot.addEventListener('click', (e) => { if (isMoving) { isMoving = false; return; } mc++; if (mc === 1) mct = setTimeout(() => { handleSingleTap(); mc = 0; }, 300); else if (mc === 2) { clearTimeout(mct); handleDoubleTap(); mc = 0; } e.stopPropagation(); }); const handleSingleTap = () => { freeCopyMode = enabled ? !freeCopyMode : (enabled = true, denyCount = 0, false); updateDotAppearance(); }; const handleDoubleTap = () => { freeCopyMode = true; enabled = true; denyCount = 0; updateDotAppearance(); }; const init = () => { hookEvents(); hookClipboardAPI(); hookExecCommand(); observeFrames(); const addDot = () => { if (document.body && !document.body.contains(dot)) document.body.appendChild(dot); else requestAnimationFrame(addDot); }; document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', addDot) : addDot(); }; document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', () => setTimeout(init, 0)) : setTimeout(init, 0); const prot = setInterval(() => { if (!navigator.clipboard._proxied) hookClipboardAPI(); if (document.execCommand === originalExecCommand) hookExecCommand(); }, 1000); window.addEventListener('beforeunload', () => clearInterval(prot)); })();