复制必弹窗,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或关注我们的公众号极客氢云获取最新地址