Aloazny123 / 复制授权拦截器

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

(function () {
    const KEY = 'copyAuth:v5'; 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, shadowRoot = null, container = null;
    const UI_TYPES = ['DEFAULT', 'IOS', 'MIUI', 'EDGE'];
    let currentUI = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_ui_style', 'EDGE') : 'EDGE');
    let editAbortController = null;
    const originalExec = document.execCommand;
    const origWriteText = navigator.clipboard?.writeText;
    const origWrite = navigator.clipboard?.write;

    if (typeof GM_registerMenuCommand !== 'undefined') {
        const updateMenu = () => {
            GM_registerMenuCommand('剪切板设定 🛠️', () => {
                if (shadowRoot?.querySelector('.auth-settings-mask')) return;
                ensureShadow();
                const mask = document.createElement('div'), panel = document.createElement('div');
                mask.className = 'auth-settings-mask'; panel.className = 'auth-settings-panel';
                const authMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_auth_mode_' + location.host, 0) : 0);
                const css = `
                    .auth-settings-mask { position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.3); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); z-index:2147483646; display:flex; align-items:center; justify-content:center; animation: settings-fade-in 0.3s ease; }
                    .auth-settings-panel { position:relative; background:rgba(255,255,255,0.85); border-radius:28px; box-shadow:0 25px 50px -12px rgba(0,0,0,0.25); padding:24px; display:flex; flex-direction:column; gap:12px; min-width:240px; font-family:system-ui,-apple-system,sans-serif; animation: settings-slide-in 0.4s cubic-bezier(0.16, 1, 0.3, 1); border:1px solid rgba(255,255,255,0.4); }
                    .auth-settings-panel button { border:none; border-radius:14px; padding:16px; cursor:pointer; font-size:14px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); display:flex; align-items:center; justify-content:space-between; font-weight:600; }
                    .auth-settings-panel button:active { transform: scale(0.94); }
                    .btn-ui { background:#fff; color:#007AFF; box-shadow:0 4px 12px rgba(0,122,255,0.1); }
                    .btn-status { background:#fff; border:none; }
                    .btn-close { margin-top:6px; background:none; color:#888; border:none; font-size:13px; cursor:pointer; text-align:center; font-weight:500; }
                    .btn-close:hover { color:#444; }
                    @media (prefers-color-scheme: dark) {
                        .auth-settings-panel { background:rgba(30,30,30,0.85); border:1px solid rgba(255,255,255,0.1); box-shadow:0 25px 50px -12px rgba(0,0,0,0.5); }
                        .btn-ui { background:#2c2c2e; color:#0A84FF; box-shadow:0 4px 12px rgba(0,0,0,0.2); }
                        .btn-status { background:rgba(255,255,255,0.05) !important; }
                        .btn-close { color:#aaa; }
                        .btn-close:hover { color:#fff; }
                    }
                    @keyframes settings-fade-in { from { opacity:0; } to { opacity:1; } }
                    @keyframes settings-slide-in { from { opacity:0; transform: translateX(120px) scale(0.9); } to { opacity:1; transform: translateX(0) scale(1); } }
                `;
                const style = document.createElement('style'); style.textContent = css; shadowRoot.appendChild(style);
                const btnUI = document.createElement('button');
                btnUI.className = 'btn-ui';
                const updateBtnText = () => { btnUI.innerHTML = `<span>界面风格</span> <small style="opacity:0.6;font-weight:400">${currentUI}</small>`; };
                updateBtnText();
                btnUI.onclick = (e) => {
                    e.stopPropagation();
                    currentUI = UI_TYPES[(UI_TYPES.indexOf(currentUI) + 1) % UI_TYPES.length];
                    if (typeof GM_setValue !== 'undefined') GM_setValue('clipboard_ui_style', currentUI);
                    updateBtnText();
                };
                const btnStatus = document.createElement('button');
                btnStatus.className = 'btn-status';
                const updateStatusUI = (mode) => {
                    const modes = { 0: ['监控中', '#30d158'], 1: ['禁止写入', '#FF3B30'], 2: ['自由复制', '#007AFF'] };
                    const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
                    btnStatus.innerHTML = `<span>复制授权</span> <small style="font-weight:700">${modes[mode][0]}</small>`;
                    btnStatus.style.color = modes[mode][1];
                    btnStatus.style.boxShadow = `0 4px 12px ${modes[mode][1]}22`;
                    if (mode === 1) {
                        enabled = false; freeCopyMode = false;
                        disableAllCopyAPIs();
                        if(dot) { dot.remove(); dot = null; }
                        hideAllModals();
                    } else if (mode === 2) {
                        enabled = true; freeCopyMode = true;
                        restoreAllCopyAPIs();
                        showDot();
                    } else {
                        enabled = true; freeCopyMode = false;
                        restoreAllCopyAPIs();
                        hookAPI();
                        hookExec();
                    }
                };
                updateStatusUI(authMode);
                btnStatus.onclick = (e) => {
                    e.stopPropagation();
                    const currentMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_auth_mode_' + location.host, 0) : 0);
                    const nextMode = (currentMode + 1) % 3;
                    if (typeof GM_setValue !== 'undefined') GM_setValue('clipboard_auth_mode_' + location.host, nextMode);
                    updateStatusUI(nextMode);
                };
                const btnEdit = document.createElement('button');
                btnEdit.className = 'btn-ui';
                const updateEditBtn = () => {
                    const active = editAbortController !== null;
                    btnEdit.innerHTML = `<span>编辑模式</span> <small style="opacity:0.6;font-weight:400">${active ? 'ON' : 'OFF'}</small>`;
                    btnEdit.style.color = active ? '#FF9500' : '#007AFF';
                };
                updateEditBtn();
                btnEdit.onclick = (e) => {
                    e.stopPropagation();
                    if (editAbortController !== null) {
                        disableContentEdit();
                    } else {
                        enableContentEdit();
                    }
                    updateEditBtn();
                };
                const unlockMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_unlock_mode_' + location.host, 1) : 1);
                const btnUnlock = document.createElement('button');
                btnUnlock.className = 'btn-status';
                const updateUnlockUI = (mode) => {
                    const modes = { 0: ['已禁用', '#FF3B30'], 1: ['温和模式', '#30d158'], 2: ['强力模式', '#FF9500'] };
                    const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
                    btnUnlock.innerHTML = `<span>解除复制限制</span> <small style="font-weight:700">${modes[mode][0]}</small>`;
                    btnUnlock.style.color = modes[mode][1];
                    btnUnlock.style.boxShadow = `0 4px 12px ${modes[mode][1]}22`;
                };
                updateUnlockUI(unlockMode);
                btnUnlock.onclick = (e) => {
                    e.stopPropagation();
                    const currentMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_unlock_mode_' + location.host, 1) : 1);
                    const nextMode = (currentMode + 1) % 3;
                    if (typeof GM_setValue !== 'undefined') GM_setValue('clipboard_unlock_mode_' + location.host, nextMode);
                    updateUnlockUI(nextMode);
                    toggleUnlock(nextMode);
                };
                const close = document.createElement('button');
                close.className = 'btn-close'; close.textContent = '保存并关闭';
                const cleanup = () => { mask.remove(); style.remove(); };
                close.onclick = cleanup;
                mask.onclick = (e) => { if(e.target === mask) cleanup(); };
                panel.append(btnUI, btnStatus, btnUnlock, btnEdit, close);
                mask.appendChild(panel);
                shadowRoot.appendChild(mask);
            }, { id: 'ui_settings_main' });
        };
        updateMenu();
    }

    const ensureShadow = () => {
        if (shadowRoot) return;
        container = document.createElement('div');
        container.id = 'copy-auth-interceptor';
        container.style.cssText = 'position:absolute;top:0;left:0;z-index:2147483647;';
        document.documentElement.appendChild(container);
        shadowRoot = container.attachShadow({ mode: 'closed' });
    };

    const disableAllCopyAPIs = () => {
        const emptyFunc = () => false;
        const emptyPromise = () => Promise.reject('复制功能已被永久禁用');
        document.execCommand = emptyFunc;
        if (navigator.clipboard) {
            navigator.clipboard.writeText = emptyPromise;
            if (navigator.clipboard.write) navigator.clipboard.write = emptyPromise;
        }
    };

    const restoreAllCopyAPIs = () => {
        if (originalExec) document.execCommand = originalExec;
        if (origWriteText) navigator.clipboard.writeText = origWriteText;
        if (origWrite) navigator.clipboard.write = origWrite;
    };

    const showToast = (msg) => {
        if (window.via && typeof window.via.toast === 'function') {
            window.via.toast(msg);
        } else {
            ensureShadow();
            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;pointer-events:none;';
            shadowRoot.appendChild(t);
            setTimeout(() => t.remove(), 2000);
        }
    };

    const hideAllModals = () => {
        if (!shadowRoot) return;
        const modals = shadowRoot.querySelectorAll('.auth-modal-bg');
        modals.forEach(m => m.remove());
        isShowingModal = false;
    };

    const showConfirm = (msg, txt = '') => {
        if (isShowingModal) return Promise.resolve(false);
        return new Promise(r => { 
            isShowingModal = true; ensureShadow();
            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.className = 'auth-modal-bg';
            const safeTxt = txt.slice(0, 500);
            const scrollStyle = 'word-break:break-all;white-space:pre-wrap;overflow-y:auto;';
            const commonStyle = document.createElement('style');
            if (currentUI === 'IOS') {
                modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;z-index:2147483647;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);';
                dialog.style.cssText = 'background:rgba(255,255,255,.75);backdrop-filter:blur(25px);-webkit-backdrop-filter:blur(25px);border-radius:13px;width:270px;text-align:center;display:flex;flex-direction:column;box-shadow:0 10px 30px rgba(0,0,0,.15);overflow:hidden;animation:ios-zoom .25s cubic-bezier(0.1, 0.9, 0.2, 1);';
                const content = document.createElement('div');
                content.style.cssText = 'padding:20px 16px;';
                title.style.cssText = 'margin:0;font-size:17px;font-weight:600;color:#000;line-height:1.3;';
                pre.style.cssText = 'margin-top:6px;font-size:13px;color:#000;max-height:120px;display:' + (txt?'block':'none') + ';line-height:1.4;' + scrollStyle;
                btns.style.cssText = 'display:flex;border-top:0.5px solid rgba(0,0,0,.15);height:44px;';
                deny.style.cssText = 'flex:1;background:none;border:none;border-right:0.5px solid rgba(0,0,0,.15);color:#007AFF;font-size:17px;cursor:pointer;';
                allow.style.cssText = 'flex:1;background:none;border:none;color:#007AFF;font-size:17px;font-weight:600;cursor:pointer;';
                commonStyle.textContent = `@keyframes ios-zoom{from{opacity:0;transform:scale(1.15)}to{opacity:1;transform:scale(1)}} @media (prefers-color-scheme: dark) { .auth-modal-bg { background:rgba(0,0,0,.5); } .auth-modal-bg div:first-child { background:rgba(30,30,30,.75) !important; } h3, .auth-modal-bg div div { color:#fff !important; } .auth-modal-bg div div:last-child { border-top-color:rgba(255,255,255,.1) !important; } button { color:#0A84FF !important; border-right-color:rgba(255,255,255,.1) !important; } }`;
                title.textContent = msg; pre.textContent = safeTxt; allow.textContent='允许'; deny.textContent='拒绝';
                content.append(title, pre); btns.append(deny, allow); dialog.append(content, btns);
            } else if (currentUI === 'MIUI') {
                modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);display:flex;align-items:flex-end;justify-content:center;z-index:2147483647;padding:20px;box-sizing:border-box;backdrop-filter:blur(4px);';
                dialog.style.cssText = 'background:#fff;border-radius:28px;width:100%;max-width:400px;text-align:left;box-shadow:0 10px 30px rgba(0,0,0,.1);display:flex;flex-direction:column;gap:12px;padding:24px;animation:miui-in .3s cubic-bezier(0.2,0.8,0.2,1);';
                title.style.cssText = 'margin:0;font-size:20px;font-weight:600;color:#1a1a1a;';
                pre.style.cssText = 'margin:4px 0;padding:12px;background:#f2f2f2;border-radius:16px;font-size:14px;color:#666;max-height:150px;display:' + (txt?'block':'none') + ';' + scrollStyle;
                btns.style.cssText = 'display:flex;gap:12px;margin-top:8px;';
                allow.style.cssText = 'flex:1;height:52px;border:none;border-radius:16px;background:#0078FF;color:#fff;font-size:16px;font-weight:600;cursor:pointer;';
                deny.style.cssText = 'flex:1;height:52px;border:none;border-radius:16px;background:#eee;color:#333;font-size:16px;font-weight:600;cursor:pointer;';
                commonStyle.textContent = `@keyframes miui-in{from{transform:translateY(100%)}to{transform:translateY(0)}} @media (min-width: 768px) { .auth-modal-bg { align-items: center !important; padding-bottom: 10vh; } } @media (prefers-color-scheme: dark) { .auth-modal-bg div:first-child { background:#222 !important; } h3 { color:#eee !important; } .auth-modal-bg div div:nth-child(2) { background:#333 !important; color:#bbb !important; } button:last-child { background:#333 !important; color:#aaa !important; } }`;
                title.textContent = msg; pre.textContent = safeTxt; allow.textContent='允许'; deny.textContent='拒绝';
                dialog.append(title, pre, btns); btns.append(allow, deny);
            } else if (currentUI === 'EDGE') {
                modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);display:flex;align-items:flex-end;justify-content:center;z-index:2147483647;backdrop-filter:blur(5px);padding-bottom:40px;box-sizing:border-box;';
                dialog.style.cssText = 'background:#fff;border-radius:24px;width:92%;max-width:350px;padding:28px 24px;display:flex;flex-direction:column;gap:20px;box-shadow:0 12px 40px rgba(0,0,0,0.2);animation:edge-pop 0.3s cubic-bezier(0.16, 1, 0.3, 1);box-sizing:border-box;';
                title.style.cssText = 'margin:0;font-size:20px;font-weight:700;color:#1a1a1a;line-height:1.4;text-align:left;';
                pre.style.cssText = 'margin:0;padding:14px;background:#f5f5f5;border-radius:10px;font-size:13px;color:#444;max-height:120px;display:' + (txt?'block':'none') + ';line-height:1.5;' + scrollStyle;
                btns.style.cssText = 'display:flex;flex-direction:column;gap:12px;';
                allow.style.cssText = 'width:100%;height:52px;border:none;border-radius:14px;background:#2F78EE;color:#fff;font-size:16px;font-weight:600;cursor:pointer;';
                deny.style.cssText = 'width:100%;height:52px;border:none;background:none;color:#2F78EE;font-size:16px;font-weight:600;cursor:pointer;';
                commonStyle.textContent = `@keyframes edge-pop{from{opacity:0;transform:translateY(30px) scale(0.98)}to{opacity:1;transform:translateY(0) scale(1)}} @media (min-width: 768px) { .auth-modal-bg { align-items: center !important; padding-bottom: 10vh; } } @media (prefers-color-scheme: dark) { .auth-modal-bg div:first-child { background:#1c1c1c !important; border:1px solid #333; } h3 { color:#eee !important; } .auth-modal-bg div div:nth-child(2) { background:#2b2b2b !important; color:#bbb !important; } }`;
                title.textContent = msg; pre.textContent = safeTxt; allow.textContent='允许复制'; deny.textContent='拒绝复制';
                btns.append(allow, deny); dialog.append(title, pre, btns);
            } else {
                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;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:16px 0 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:160px;display:' + (txt?'block':'none') + ';line-height:1.4;' + scrollStyle;
                btns.style.cssText = 'display:flex;gap:12px;margin:0 20px;padding-bottom:20px;';
                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);';
                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);';
                commonStyle.textContent = `@media (prefers-color-scheme: dark) { .auth-modal-bg div:first-child { background:rgba(40,40,40,.94) !important; border-color:rgba(255,255,255,.1) !important; } h3, .auth-modal-bg div div { color:#eee !important; } .auth-modal-bg div div:nth-child(2) { background:#222 !important; } }`;
                title.textContent = msg; pre.textContent = safeTxt; allow.textContent='允许'; deny.textContent='拒绝';
                dialog.append(title, pre, btns); btns.append(deny, allow);
            }
            shadowRoot.appendChild(commonStyle);
            const clean = () => { modal.remove(); commonStyle.remove(); isShowingModal = false; };
            allow.onclick = () => { clean(); r(true); };
            deny.onclick = () => { clean(); r(false); };
            modal.onclick = e => e.target===modal && deny.onclick();
            modal.appendChild(dialog); shadowRoot.appendChild(modal);
        });
    };

    const updateDot = () => { 
        if (!dot) return; 
        dot.style.backgroundColor = freeCopyMode ? '#34C759' : (!enabled ? '#8E8E93' : '#FF3B30');
        clearTimeout(hideTimer);
        if (!freeCopyMode && enabled) {
            hideTimer = setTimeout(() => { 
                if (dot && !freeCopyMode && enabled) { 
                    dot.remove(); 
                    dot = null; 
                } 
            }, 3000);
        }
    };

    const showDot = () => {
        if (dot) { 
            clearTimeout(hideTimer);
            dot.style.opacity = '.8'; 
            dot.style.transform = 'scale(1)'; 
            updateDot();
            return; 
        }
        ensureShadow();
        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;';
        shadowRoot.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; }
                else if (freeCopyMode) { freeCopyMode = false; hookAPI(); hookExec(); }
                else { enabled = true; denyCount = 0; restoreAllCopyAPIs(); hookAPI(); hookExec(); }
            } else if (taps === 2) { 
                freeCopyMode = enabled = true; denyCount = allowCount = 0; restoreAllCopyAPIs();
             }
             updateDot(); taps = 0;
         }, 300);
      };
        dot.onclick = handleTap; dot.ontouchstart = e => { e.preventDefault(); handleTap(); };
    };

    const handleAuth = (txt, successCb, failCb) => {
        if (!recordCopy('api')) return failCb?.();
        if (freeCopyMode) return successCb();
        if (!enabled || denyCount >= MAX_DENY) return failCb?.();
        showDot();
        showConfirm('允许复制内容?', txt).then(ok => {
            if (ok) { 
                allowCount++; 
                if (allowCount >= AUTO_FREE_AFTER_ALLOW) freeCopyMode = true;
                if (typeof GM_setClipboard !== 'undefined') {
                    GM_setClipboard(txt);
                } else {
                    successCb(); 
                }
            }
            else { 
                denyCount++; 
                if (denyCount >= MAX_DENY) { enabled = false; hideAllModals(); disableAllCopyAPIs(); }
                failCb?.();
            }
            updateDot();
        });
    };

    const recordCopy = (method) => {
        if (freeCopyMode) return true;
        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; hideAllModals(); updateDot(); copyRecords = []; disableAllCopyAPIs();
            showToast('检测到恶意复制行为,复制功能已临时禁用');
            return false;
        }
        return true;
    };

    const hookAPI = () => {
        if (!navigator.clipboard) return;
        const def = (p, v) => { try { Object.defineProperty(navigator.clipboard, p, { get: () => v, set: n => { if(n !== v) console.log('拦截到 ' + p + ' 修改尝试') }, configurable: true }); } catch (e) { navigator.clipboard[p] = v; } };
        const wWT = t => new Promise((res) => {
            if (freeCopyMode) return origWriteText.call(navigator.clipboard, t).then(res);
            handleAuth(t, () => origWriteText.call(navigator.clipboard, t).then(res), () => res());
        });
        const wW = d => new Promise(async (res) => {
            if (freeCopyMode) return origWrite.call(navigator.clipboard, d).then(res);
            let t = ''; try { t = d[0]?.types.includes('text/plain') ? await (await d[0].getType('text/plain')).text() : ''; } catch (e) {}
            handleAuth(t, () => origWrite.call(navigator.clipboard, d).then(res), () => res());
        });
        def('writeText', wWT);
        if (origWrite) def('write', wW);
    };

    const hookExec = () => {
        const wrapExec = function(cmd, ui, val) {
            if (cmd === 'copy') {
                const sel = window.getSelection().toString();
                if (!sel) return originalExec.apply(document, arguments);
                return handleAuth(sel, () => {
                    const ta = document.createElement('textarea'); ta.value = sel; ta.style.cssText = 'position:fixed;opacity:0;';
                    document.body.appendChild(ta); ta.select(); originalExec.call(document, 'copy'); ta.remove();
                }), false;
            }
            return originalExec.apply(document, arguments);
        };
        try {
            Object.defineProperty(document, 'execCommand', { get: () => wrapExec, set: v => { if(v !== wrapExec) console.log('拦截到 execCommand 修改尝试') }, configurable: true });
        } catch (e) { document.execCommand = wrapExec; }
    };

    const enableContentEdit = () => {
        if (editAbortController) return;
        editAbortController = new AbortController();
        const { signal } = editAbortController;
        let t = null;
        const start = (e) => {
            const el = e.target;
            if (!['P', 'SPAN', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'LI', 'B', 'FONT'].includes(el.tagName)) return;
            t = setTimeout(() => {
                const parent = el.parentNode;
                if (parent) {
                    Array.from(parent.children).forEach(sibling => {
                        if (sibling.tagName === el.tagName) {
                            sibling.style.userSelect = 'text';
                            sibling.style.webkitUserSelect = 'text';
                        }
                    });
                }
                el.setAttribute('contenteditable', 'true');
                el.focus();
                const bg = el.style.backgroundColor;
                el.style.backgroundColor = 'rgba(255, 255, 0, 0.15)';
                const r = document.createRange();
                r.selectNodeContents(el);
                const sel = window.getSelection();
                sel.removeAllRanges();
                sel.addRange(r);
                el.addEventListener('blur', () => {
                    el.removeAttribute('contenteditable');
                    el.style.backgroundColor = bg;
                }, { once: true });
            }, 800);
        };
        const stop = () => { if (t) clearTimeout(t) };
        document.addEventListener('mousedown', start, { capture: true, signal });
        document.addEventListener('mouseup', stop, { capture: true, signal });
        document.addEventListener('touchstart', start, { capture: true, signal, passive: true });
        document.addEventListener('touchend', stop, { capture: true, signal, passive: true });
    };

    const disableContentEdit = () => {
        if (editAbortController) {
            editAbortController.abort();
            editAbortController = null;
        }
    };

    const toggleUnlock = (mode) => {
        const cssId = 'unlock-css-base';
        const oldCss = document.getElementById(cssId);
        if (oldCss) oldCss.remove();
        const evs = ['copy', 'cut', 'contextmenu', 'selectstart', 'mousedown', 'beforecopy', 'dragstart'];
        if (mode !== 1 && mode !== 2 && window._origPreventDefault) {
            Event.prototype.preventDefault = window._origPreventDefault;
            delete window._origPreventDefault;
        }
        if (mode === 0) return;
        const s = document.createElement('style');
        s.id = cssId;
        s.innerHTML = '*{user-select:text!important;-webkit-user-select:text!important;-moz-user-select:text!important;-ms-user-select:text!important;}';
        (document.head || document.documentElement).appendChild(s);
        if (!window._origPreventDefault) {
            window._origPreventDefault = Event.prototype.preventDefault;
            Event.prototype.preventDefault = function() {
                if (evs.includes(this.type)) return;
                return window._origPreventDefault.apply(this, arguments);
            };
        }
        evs.forEach(n => {
            try { Object.defineProperty(document, 'on' + n, { get: () => null, set: () => {}, configurable: true }); } catch (e) {}
            if (mode === 2) {
                document.addEventListener(n, e => {
                    if (enabled || freeCopyMode || editAbortController) e.stopImmediatePropagation();
                }, { capture: true, passive: false });
            }
        });
    };

    const setupFrame = iframe => {
        try {
            const doc = iframe.contentDocument, win = doc?.defaultView, nav = win?.navigator;
            if (!doc || !win) return;
            const origE = doc.execCommand, wrapExec = function(cmd, ui, val) {
                if (cmd === 'copy') {
                    const sel = win.getSelection().toString();
                    handleAuth(sel || '[iframe 自动复制尝试]', () => {
                        const ta = doc.createElement('textarea'); ta.value = sel; ta.style.cssText = 'position:fixed;opacity:0;';
                        doc.body.appendChild(ta); ta.select(); origE.call(doc, 'copy'); ta.remove();
                    }, () => {});
                    return true;
                }
                return origE.apply(doc, arguments);
            };
            const def = (o, p, v, m) => { try { Object.defineProperty(o, p, { get: () => v, set: n => { if(n !== v) console.log(m) }, configurable: true }); } catch (e) { o[p] = v; } };
            def(doc, 'execCommand', wrapExec, 'iframe execCommand locked');
            if (nav?.clipboard) {
                const _wT = nav.clipboard.writeText, _w = nav.clipboard.write;
                const wWT = t => new Promise((res) => handleAuth(t, () => _wT.call(nav.clipboard, t).then(res), () => res()));
                const wW = d => new Promise(async (res) => {
                    let t = ''; try { t = d[0]?.types.includes('text/plain') ? await (await d[0].getType('text/plain')).text() : ''; } catch (e) {}
                    handleAuth(t, () => _w.call(nav.clipboard, d).then(res), () => res());
                });
                def(nav.clipboard, 'writeText', wWT, 'iframe writeText locked');
                if (_w) def(nav.clipboard, 'write', wW, 'iframe write locked');
            }
        } catch (e) {}
    };

    const checkAndFix = () => {
        if (!enabled || freeCopyMode) return;
        try {
            if (navigator.clipboard) {
                const c = navigator.clipboard, t = c.writeText?.toString() || '', w = c.write?.toString() || '';
                if ((origWriteText && !t.includes('handleAuth')) || (origWrite && !w.includes('handleAuth'))) hookAPI();
            }
            if (originalExec && !document.execCommand.toString().includes('handleAuth')) hookExec();
        } catch (e) { console.log('自修复执行失败: 接口可能被页面硬锁定'); }
    };

    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 = () => { 
      const authMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_auth_mode_' + location.host, 0) : 0);
      const unlockMode = (typeof GM_getValue !== 'undefined' ? GM_getValue('clipboard_unlock_mode_' + location.host, 1) : 1);
        if (unlockMode !== 0) toggleUnlock(unlockMode);
        if (authMode === 0) { hookAPI(); hookExec(); enabled = true; freeCopyMode = false; }
        else if (authMode === 2) { restoreAllCopyAPIs(); enabled = true; freeCopyMode = true; }
        else { enabled = false; freeCopyMode = false; disableAllCopyAPIs(); }
    }; init();
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => setTimeout(init, 0));
    else setTimeout(init, 0);
    document.addEventListener('mousedown', checkAndFix, { capture: true, passive: true });
    document.addEventListener('keydown', checkAndFix, { capture: true, passive: true });
    document.addEventListener('touchstart', checkAndFix, { capture: true, passive: true });
})();