复制授权拦截器

复制必弹窗,2次允许后开始自由复制,3次拒绝永久禁用(变灰,点击灰点恢复变红),点击小红点恢复,红绿切换自由复制。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         复制授权拦截器
// @namespace    https://viayoo.com/
// @version      3.28
// @description  复制必弹窗,2次允许后开始自由复制,3次拒绝永久禁用(变灰,点击灰点恢复变红),点击小红点恢复,红绿切换自由复制。
// @author       Aloazny & Deepseek
// @match        *://*/*
// @run-at       document-start
// @icon       data:image/png;base64,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
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    const KEY = 'copyAuth:v3'; if (window[KEY]) return; window[KEY] = true;
    let denyCount = 0, allowCount = 0, enabled = true, freeCopyMode = false, isShowingModal = false;
    const MAX_DENY = 3, AUTO_FREE_AFTER_ALLOW = 2, COPY_TIME_THRESHOLD = 1000, COPY_COUNT_THRESHOLD = 3;
    let copyRecords = [], dot = null, hideTimer = null;
    const originalExec = document.execCommand;
    const origWriteText = navigator.clipboard?.writeText;
    const origWrite = navigator.clipboard?.write;

    const disableAllCopyAPIs = () => {
        const emptyFunc = () => false;
        const emptyPromise = () => Promise.reject('复制功能已被永久禁用');

        Object.defineProperty(document, 'execCommand', {
            value: emptyFunc,
            writable: false,
            configurable: false
        });

        if (navigator.clipboard) {
            Object.defineProperty(navigator.clipboard, 'writeText', {
                value: emptyPromise,
                writable: false,
                configurable: false
            });
            if (navigator.clipboard.write) {
                Object.defineProperty(navigator.clipboard, 'write', {
                    value: emptyPromise,
                    writable: false,
                    configurable: false
                });
            }
        }

        setInterval(() => {
            document.execCommand = emptyFunc;
            if (navigator.clipboard) {
                navigator.clipboard.writeText = emptyPromise;
                navigator.clipboard.write = emptyPromise;
            }
        }, 800);
    };

    const showToast = (msg) => {
        if (window.via && typeof window.via.toast === 'function') {
            window.via.toast(msg);
        } else {
            const t = document.createElement('div');
            t.textContent = msg;
            t.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.8);color:white;padding:12px 20px;border-radius:8px;z-index:2147483647;font-size:14px;';
            document.body.appendChild(t);
            setTimeout(() => t.remove(), 2000);
        }
    };

    const showConfirm = (msg, txt = '') => {
        if (isShowingModal) return Promise.resolve(false);
        return new Promise(r => { isShowingModal = true;
            const modal = document.createElement('div'), dialog = document.createElement('div'), title = document.createElement('h3'), pre = document.createElement('div'), btns = document.createElement('div'), allow = document.createElement('button'), deny = document.createElement('button');
            modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(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;backdrop-filter:blur(8px);padding:16px;box-sizing:border-box;';
            dialog.style.cssText = 'background:rgba(255,255,255,.94);border-radius:18px;width:88vw;max-width:420px;text-align:center;box-shadow:0 12px 40px rgba(0,0,0,.25);display:flex;flex-direction:column;gap:16px;border:1px solid rgba(0,0,0,.06);overflow:hidden;';
            title.style.cssText = 'margin:0;padding:0 20px;font-size:18px;font-weight:700;color:#111;line-height:1.3;';
            pre.style.cssText = 'margin:0 20px;padding:14px;background:#f8f8f8;border-radius:12px;font-family:monospace;font-size:13px;color:#111;max-height:140px;overflow:auto;word-break:break-all;display:' + (txt?'block':'none') + ';line-height:1.4;';
            btns.style.cssText = 'display:flex;gap:12px;margin:0 20px;padding-bottom:8px;';
            allow.style.cssText = 'flex:1;height:48px;border:none;border-radius:14px;background:#007AFF;color:#fff;font-size:16px;font-weight:600;cursor:pointer;box-shadow:0 4px 14px rgba(0,122,255,.35);transition:transform .12s;';
            deny.style.cssText = 'flex:1;height:48px;border:none;border-radius:14px;background:#FF3B30;color:#fff;font-size:16px;font-weight:600;cursor:pointer;box-shadow:0 4px 14px rgba(255,59,48,.35);transition:transform .12s;';
            title.textContent = msg; pre.textContent = txt.slice(0,200)+(txt.length>200?'...':''); allow.textContent='允许'; deny.textContent='拒绝';
            allow.onclick = () => { clean(); r(true); };
            deny.onclick = () => { clean(); r(false); };
            modal.onclick = e => e.target===modal && deny.onclick();
            const clean = () => { modal.remove(); isShowingModal = false; };
            ['touchstart','mousedown'].forEach(ev => {
                allow.addEventListener(ev, () => allow.style.transform = 'translateY(1px)', {passive:true});
                deny.addEventListener(ev, () => deny.style.transform = 'translateY(1px)', {passive:true});
                document.addEventListener(ev==='touchstart'?'touchend':'mouseup', () => { allow.style.transform = deny.style.transform = ''; }, {once:true, passive:true});
            });
            dialog.append(title, pre, btns); btns.append(deny, allow); modal.appendChild(dialog); document.body.appendChild(modal); allow.focus();
        });
    };

    const updateDot = () => { if (!dot) return; dot.style.backgroundColor = freeCopyMode ? '#34C759' : (!enabled ? '#8E8E93' : '#FF3B30'); };

    const showDot = () => {
        if (dot) { clearTimeout(hideTimer); return; }
        dot = document.createElement('div');
        dot.style.cssText = 'position:fixed;bottom:45%;right:10px;width:20px;height:20px;border-radius:10px;background:#FF3B30;z-index:2147483647;opacity:0;transform:scale(0);border:2px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.3);transition:all .3s cubic-bezier(0.34,1.56,0.64,1);cursor:pointer;touch-action:none;';
        document.body.appendChild(dot);
        requestAnimationFrame(() => { dot.style.opacity = '.8'; dot.style.transform = 'scale(1)'; });
        updateDot();
        let taps = 0, timer = null;
        const handleTap = () => {
            taps++; clearTimeout(timer);
            timer = setTimeout(() => {
                if (taps === 1) {
                    if (enabled && !freeCopyMode) { freeCopyMode = true; enabled = true; updateDot(); }
                    else if (freeCopyMode) { freeCopyMode = false; updateDot(); }
                    else { enabled = true; denyCount = 0; updateDot(); }
                } else if (taps === 2) { freeCopyMode = enabled = true; denyCount = 0; updateDot(); }
                taps = 0;
            }, 300);
        };
        dot.onclick = handleTap; dot.ontouchstart = e => { e.preventDefault(); handleTap(); };
    };

    const tryAutoFreeOnAllow = () => { allowCount++; if (allowCount >= AUTO_FREE_AFTER_ALLOW && !freeCopyMode) { freeCopyMode = true; enabled = true; if (dot) updateDot(); } };

    const hideDotIfNeeded = () => {
        if (!dot) return; clearTimeout(hideTimer);
        if (freeCopyMode || !enabled) return;
        hideTimer = setTimeout(() => { if (dot) { dot.remove(); dot = null; } }, 3000);
    };

    const recordCopy = (method) => {
        const now = Date.now();
        copyRecords.push({ timestamp: now, method });

        copyRecords = copyRecords.filter(r => now - r.timestamp <= COPY_TIME_THRESHOLD);

        if (copyRecords.length >= COPY_COUNT_THRESHOLD) {
            enabled = false;
            updateDot();
            copyRecords = [];
            disableAllCopyAPIs();
            showToast('检测到恶意复制行为,复制功能已被永久禁用');
            return false;
        }
        return true;
    };

    const hookAPI = () => {
        if (origWriteText) navigator.clipboard.writeText = txt => new Promise((res, rej) => {
            if (!recordCopy('writeText')) return rej(new Error('恶意复制已被阻止'));
            if (freeCopyMode) return origWriteText.call(navigator.clipboard, txt).then(res).catch(rej);
            if (!enabled || denyCount >= MAX_DENY) return rej();
            showDot();
            showConfirm('允许复制内容?', txt).then(ok => {
                if (ok) { origWriteText.call(navigator.clipboard, txt).then(res).catch(rej); tryAutoFreeOnAllow(); }
                else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDot(); disableAllCopyAPIs(); } rej(new Error('用户拒绝复制')); }
                hideDotIfNeeded();
            });
        });

        if (origWrite) navigator.clipboard.write = data => new Promise((res, rej) => {
            if (!recordCopy('write')) return rej(new Error('恶意复制已被阻止'));
            if (freeCopyMode) return origWrite.call(navigator.clipboard, data).then(res).catch(rej);
            if (!enabled || denyCount >= MAX_DENY) return rej();
            const txt = data[0]?.type === 'text/plain' ? data[0].getData('text/plain') : '';
            showDot();
            showConfirm('允许复制内容?', txt).then(ok => {
                if (ok) { origWrite.call(navigator.clipboard, data).then(res).catch(rej); tryAutoFreeOnAllow(); }
                else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDot(); disableAllCopyAPIs(); } rej(new Error('用户拒绝复制')); }
                hideDotIfNeeded();
            });
        });
    };

    const hookExec = () => {
        document.execCommand = (cmd, ui, val) => {
            if (cmd === 'copy') {
                if (!recordCopy('execCommand')) return false;
                if (freeCopyMode) return originalExec.call(document, cmd, ui, val);
                if (!enabled || denyCount >= MAX_DENY) return false;
                const sel = window.getSelection().toString();
                if (!sel) return originalExec.apply(document, arguments);
                showDot();
                showConfirm('允许复制内容?', sel).then(ok => {
                    if (ok) { 
                        const ta = document.createElement('textarea'); 
                        ta.value = sel; ta.style = 'position:fixed;opacity:0;'; 
                        document.body.appendChild(ta); ta.select(); 
                        originalExec.call(document, 'copy'); 
                        ta.remove(); 
                        tryAutoFreeOnAllow(); 
                    } else { 
                        denyCount++; 
                        if (denyCount >= MAX_DENY) { enabled = false; updateDot(); disableAllCopyAPIs(); } 
                    }
                    hideDotIfNeeded();
                });
                return false;
            }
            return originalExec.apply(document, arguments);
        };
    };

    const setupFrame = iframe => {
        try {
            const doc = iframe.contentDocument; if (!doc) return;
            const win = doc.defaultView; if (!win) return;
            const frameOrigExec = doc.execCommand;
            doc.execCommand = (cmd, ui, val) => {
                if (cmd === 'copy') {
                    if (!recordCopy('frameExecCommand')) return false;
                    if (freeCopyMode) return frameOrigExec.call(doc, cmd, ui, val);
                    if (!enabled || denyCount >= MAX_DENY) return false;
                    const sel = win.getSelection().toString();
                    if (!sel) return frameOrigExec.apply(doc, arguments);
                    showDot();
                    showConfirm('允许复制内容?', sel).then(ok => {
                        if (ok) {
                            const ta = doc.createElement('textarea');
                            ta.value = sel; ta.style = 'position:fixed;opacity:0;';
                            doc.body.appendChild(ta); ta.select();
                            frameOrigExec.call(doc, 'copy');
                            ta.remove();
                            tryAutoFreeOnAllow();
                        } else {
                            denyCount++;
                            if (denyCount >= MAX_DENY) { enabled = false; updateDot(); disableAllCopyAPIs(); }
                        }
                        hideDotIfNeeded();
                    });
                    return false;
                }
                return frameOrigExec.apply(doc, arguments);
            };
        } catch(e) {}
    };

    const obs = new MutationObserver(m => {
        for (const r of m) for (const n of r.addedNodes)
            if (n.nodeType === 1 && n.tagName === 'IFRAME') setupFrame(n);
    });
    obs.observe(document.documentElement, { childList: true, subtree: true });
    document.querySelectorAll('iframe').forEach(setupFrame);

    const init = () => { hookAPI(); hookExec(); };
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => setTimeout(init, 0));
    } else {
        setTimeout(init, 0);
    }

    const prot = setInterval(() => {
        if (navigator.clipboard && navigator.clipboard.writeText === origWriteText) hookAPI();
        if (document.execCommand === originalExec) hookExec();
    }, 1000);
    window.addEventListener('beforeunload', () => clearInterval(prot));
})();