复制授权拦截器

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

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

(function () {
    const KEY = 'copyAuth:v2'; 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, originalExec = document.execCommand, origWriteText = navigator.clipboard?.writeText, origWrite = navigator.clipboard?.write;

    const isMobile = () => window.innerWidth <= 768 || 'ontouchstart' in window;
    const getDialogWidth = () => isMobile() ? '88vw' : '420px';
    const getFontSize = () => isMobile() ? '15px' : '16px';
    const getBtnHeight = () => isMobile() ? '44px' : '48px';

    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);-webkit-backdrop-filter:blur(8px);padding:16px;box-sizing:border-box;`;
            dialog.style.cssText = `background:rgba(255,255,255,.94);border-radius:18px;width:${getDialogWidth()};max-width:100%;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:${isMobile()?'17px':'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:${getBtnHeight()};border:none;border-radius:14px;background:#007AFF;color:#fff;font-size:${getFontSize()};font-weight:600;cursor:pointer;box-shadow:0 4px 14px rgba(0,122,255,.35);transition:transform .12s ease,box-shadow .12s ease;display:flex;align-items:center;justify-content:center;`;
            deny.style.cssText = `flex:1;height:${getBtnHeight()};border:none;border-radius:14px;background:#FF3B30;color:#fff;font-size:${getFontSize()};font-weight:600;cursor:pointer;box-shadow:0 4px 14px rgba(255,59,48,.35);transition:transform .12s ease,box-shadow .12s ease;display:flex;align-items:center;justify-content:center;`;
            title.textContent = msg; pre.textContent = txt.slice(0,200)+(txt.length>200?'...':''); allow.textContent='允许'; deny.textContent='拒绝';
            allow.onmousedown = deny.onmousedown = e => e.preventDefault();
            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 tryAutoFreeOnAllow = () => {
        allowCount++;
        if (allowCount >= AUTO_FREE_AFTER_ALLOW && !freeCopyMode) {
            freeCopyMode = true; enabled = true; updateDot();
        }
    };

    const copyText = txt => origWriteText ? origWriteText.call(navigator.clipboard, txt) : new Promise((res, rej) => {
        const ta = document.createElement('textarea'); ta.value = txt; ta.style = 'position:fixed;opacity:0;'; document.body.append(ta); ta.select();
        originalExec.call(document, 'copy') ? (ta.remove(), res()) : (ta.remove(), rej());
    });

    let dotCreated = false;
    const createDot = () => {
        if (dotCreated) return;
        dotCreated = true;
        const 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:.8;touch-action:none;cursor:pointer;user-select:none;border:2px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.3);transition:all .2s ease;';
        window.dot = dot;
        const updateDot = () => dot.style.backgroundColor = freeCopyMode ? '#34C759' : (!enabled ? '#8E8E93' : '#FF3B30');
        window.updateDot = updateDot;
        let moving = false, sx = 0, sy = 0, il = 0, it = 0, last = 0, taps = 0, timer = null;
        const start = e => { moving = true; const t = e.touches ? e.touches[0] : e; const r = dot.getBoundingClientRect(); sx = t.clientX; sy = t.clientY; il = r.left; it = r.top; dot.style.transform = 'scale(1.3)'; dot.style.opacity = '1'; e.preventDefault(); };
        const move = e => { if (!moving) return; const t = e.touches ? e.touches[0] : e; dot.style.left = (il + t.clientX - sx) + 'px'; dot.style.top = (it + t.clientY - sy) + 'px'; dot.style.right = dot.style.bottom = 'auto'; e.preventDefault(); };
        const end = e => { const t = e.changedTouches ? e.changedTouches[0] : e; const moved = Math.abs(t.clientX - sx) > 5 || Math.abs(t.clientY - sy) > 5; if (moving && moved) { moving = false; return; } moving = false;
            const now = Date.now(); if (now - last < 300) taps++; else taps = 1; last = now;
            if (taps === 1) timer = setTimeout(() => { freeCopyMode = enabled ? !freeCopyMode : (enabled = true, denyCount = 0, false); updateDot(); taps = 0; }, 300);
            else if (taps === 2) { clearTimeout(timer); freeCopyMode = enabled = true; denyCount = 0; updateDot(); taps = 0; }
            dot.style.transform = 'scale(1)'; dot.style.opacity = '.8';
        };
        dot.addEventListener('touchstart', start, {passive:false}); dot.addEventListener('touchmove', move, {passive:false}); dot.addEventListener('touchend', end, {passive:false});
        dot.addEventListener('mousedown', start); document.addEventListener('mousemove', move); document.addEventListener('mouseup', () => moving = false);
        let mc = 0, mct = null; dot.addEventListener('click', e => { if (moving) { moving = false; return; } mc++; if (mc===1) mct=setTimeout(()=>{freeCopyMode=enabled?!freeCopyMode:(enabled=true,denyCount=0,false);updateDot();mc=0;},300); else if(mc===2){clearTimeout(mct);freeCopyMode=enabled=true;denyCount=0;updateDot();mc=0;} e.stopPropagation(); });
        const add = () => { if (document.body) document.body.appendChild(dot); else requestAnimationFrame(add); }; add();
    };

    const block = e => {
        if (freeCopyMode || !enabled || denyCount >= MAX_DENY) { if (!enabled || denyCount >= MAX_DENY) e.preventDefault(); return; }
        e.preventDefault(); e.stopImmediatePropagation();
        const sel = window.getSelection().toString(); if (!sel) return;
        createDot();
        showConfirm('允许复制内容?', sel).then(ok => {
            if (ok) { copyText(sel).catch(() => {}); tryAutoFreeOnAllow(); }
            else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDot(); } }
        });
    };

    const hookAPI = () => {
        if (origWriteText) navigator.clipboard.writeText = txt => new Promise((res, rej) => {
            if (freeCopyMode) return origWriteText.call(this, txt).then(res).catch(rej);
            if (!enabled || denyCount >= MAX_DENY) return rej();
            createDot();
            showConfirm('允许复制内容?', txt).then(ok => {
                ok ? (origWriteText.call(this, txt).then(res).catch(rej), tryAutoFreeOnAllow()) : (denyCount++, denyCount>=MAX_DENY&&(enabled=false,updateDot()), rej());
            });
        });
        if (origWrite) navigator.clipboard.write = data => new Promise((res, rej) => {
            if (freeCopyMode) return origWrite.call(this, data).then(res).catch(rej);
            if (!enabled || denyCount >= MAX_DENY) return rej();
            const txt = data[0]?.type === 'text/plain' ? data[0].getData('text/plain') : '';
            createDot();
            showConfirm('允许复制内容?', txt).then(ok => {
                ok ? (origWrite.call(this, data).then(res).catch(rej), tryAutoFreeOnAllow()) : (denyCount++, denyCount>=MAX_DENY&&(enabled=false,updateDot()), rej());
            });
        });
    };

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

    const setupFrame = iframe => { try { const doc = iframe.contentDocument; if (!doc) return;
        const win = doc.defaultView; if (!win) return;
        doc.addEventListener('copy', e => { if (win.getSelection().toString()) createDot(); block(e); }, {capture:true,passive:false});
        const orig = doc.execCommand; doc.execCommand = (cmd, ui, val) => {
            if (cmd !== 'copy' || freeCopyMode || !enabled || denyCount >= MAX_DENY) return cmd === 'copy' && (!enabled || denyCount >= MAX_DENY) ? false : orig.call(this, cmd, ui, val);
            const sel = win.getSelection().toString(); if (!sel) return orig.apply(this, arguments);
            createDot();
            showConfirm('允许复制内容?', sel).then(ok => {
                if (ok) { const ta = doc.createElement('textarea'); ta.value = sel; ta.style = 'position:fixed;opacity:0;'; doc.body.append(ta); ta.select(); orig.call(doc, 'copy'); ta.remove(); tryAutoFreeOnAllow(); }
                else { denyCount++; if (denyCount >= MAX_DENY) { enabled = false; updateDot(); } }
            });
            return false;
        };
        if (win.navigator.clipboard) { const cb = win.navigator.clipboard, ow = cb.writeText; cb.writeText = txt => new Promise((res, rej) => {
            if (freeCopyMode) return ow.call(this, txt).then(res).catch(rej);
            if (!enabled || denyCount >= MAX_DENY) return rej();
            createDot();
            showConfirm('允许复制内容?', txt).then(ok => ok ? (ow.call(this, txt).then(res).catch(rej), tryAutoFreeOnAllow()) : (denyCount++, denyCount>=MAX_DENY&&(enabled=false,updateDot()), rej()));
        }); }
    } 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 = () => { document.addEventListener('copy', block, {capture:true,passive:false}); hookAPI(); hookExec(); };
    document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', () => setTimeout(init,0)) : setTimeout(init,0);

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址