Greasy Fork is available in English.
在非选词复制时显示图标提示用户以防止未经授权的自动复制,脚本菜单还加入了禁止写入剪贴板功能
// ==UserScript==
// @name 防止未经授权的自动复制
// @version 32
// @description 在非选词复制时显示图标提示用户以防止未经授权的自动复制,脚本菜单还加入了禁止写入剪贴板功能
// @author DeepSeek
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @run-at document-start
// @namespace http://greasyfork.icu/users/452911
// ==/UserScript==
(function() {
'use strict';
const domain = window.location.hostname;
let isEnabledClipboard = GM_getValue(domain + '_clipboard', false);
let isEnabledAutoCopy = GM_getValue(domain + '_autocopy', true);
GM_registerMenuCommand((isEnabledClipboard ? '允许' : '禁止') + '写入剪贴板', () => {
isEnabledClipboard = !isEnabledClipboard;
GM_setValue(domain + '_clipboard', isEnabledClipboard);
location.reload();
});
GM_registerMenuCommand((isEnabledAutoCopy ? '禁用' : '启用') + '复制监听', () => {
isEnabledAutoCopy = !isEnabledAutoCopy;
GM_setValue(domain + '_autocopy', isEnabledAutoCopy);
location.reload();
});
if (isEnabledClipboard) {
['execCommand', 'writeText', 'write'].forEach(method => {
const target = method === 'execCommand' ? document : navigator.clipboard;
Object.defineProperty(target, method, {
value: () => {},
writable: false,
configurable: false
});
});
}
if (!isEnabledAutoCopy) return;
let redDot = null;
let pendingCopyText = '';
let timeoutId = null;
let isUserSelection = false;
let isInitialized = false;
function createRedDot() {
if (redDot) return redDot;
const dot = document.createElement('div');
dot.innerHTML = `
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
<path d="M4 14h.01"></path>
<path d="M20 14h.01"></path>
<path d="M4 18h.01"></path>
<path d="M20 18h.01"></path>
</svg>
`;
// 现代化的毛玻璃 + 渐变 + 呼吸光 + 微波纹
dot.style.cssText = `
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%) scale(0);
width: 64px;
height: 64px;
background: linear-gradient(135deg, rgba(255, 59, 92, 0.95), rgba(255, 20, 80, 0.95));
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
z-index: 2147483647;
box-shadow: 0 10px 30px rgba(255, 40, 100, 0.4),
0 0 0 8px rgba(255, 60, 100, 0.15);
backdrop-filter: blur(12px);
border: 3px solid rgba(255,255,255,0.25);
opacity: 0;
pointer-events: none;
transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
`;
// 呼吸光环动画
const pulse = document.createElement('div');
pulse.style.cssText = `
position: absolute;
inset: -12px;
border-radius: 50%;
border: 2px solid rgba(255, 80, 120, 0.4);
animation: pulseRing 2.5s ease-out infinite;
pointer-events: none;
`;
dot.appendChild(pulse);
// 鼠标悬停放大 + 高亮
dot.addEventListener('mouseenter', () => {
dot.style.transform = 'translateY(-50%) scale(1.15)';
dot.style.boxShadow = '0 15px 40px rgba(255, 40, 100, 0.6), 0 0 0 12px rgba(255, 80, 120, 0.3)';
});
dot.addEventListener('mouseleave', () => {
dot.style.transform = 'translateY(-50%) scale(1)';
dot.style.boxShadow = '0 10px 30px rgba(255, 40, 100, 0.4), 0 0 0 8px rgba(255, 60, 100, 0.15)';
});
dot.addEventListener('click', handleRedDotClick);
redDot = dot;
return dot;
}
function ensureRedDot() {
if (!redDot && document.body) {
createRedDot();
document.body.appendChild(redDot);
}
return redDot;
}
function showRedDot() {
const dot = ensureRedDot();
if (dot) {
dot.style.display = 'flex';
dot.style.opacity = '1';
dot.style.transform = 'translateY(-50%) scale(1)';
dot.style.pointerEvents = 'auto';
clearTimeout(timeoutId);
timeoutId = setTimeout(hideRedDot, 6000);
}
}
function hideRedDot() {
if (redDot) {
redDot.style.opacity = '0';
redDot.style.transform = 'translateY(-50%) scale(0)';
redDot.style.pointerEvents = 'none';
setTimeout(() => {
if (redDot) redDot.style.display = 'none';
}, 450);
}
pendingCopyText = '';
clearTimeout(timeoutId);
timeoutId = null;
}
function handleRedDotClick() {
if (!pendingCopyText) return;
const promptText = `检测到复制请求,是否允许复制以下内容?\n\n"${pendingCopyText.substring(0, 100)}${pendingCopyText.length > 100 ? '...' : ''}"`;
const allowCopy = confirm(promptText);
if (allowCopy && typeof GM_setClipboard === "function") {
GM_setClipboard(pendingCopyText);
}
hideRedDot();
}
// 以下所有函数完全保持原逻辑不变
function setupSelectionTracking() {
document.addEventListener('selectionchange', function() {
const selection = window.getSelection();
if (selection && selection.toString().length > 0) {
isUserSelection = true;
}
});
}
function setupClipboardInterception() {
if (navigator.clipboard && navigator.clipboard.writeText) {
const originalWriteText = navigator.clipboard.writeText;
Object.defineProperty(navigator.clipboard, 'writeText', {
value: function(text) {
if (isUserSelection) {
isUserSelection = false;
return originalWriteText.call(navigator.clipboard, text);
}
if (!text || text.trim().length === 0) return Promise.resolve();
pendingCopyText = text;
showRedDot();
return Promise.resolve();
},
writable: true,
configurable: true,
});
}
}
function setupExecCommandInterception() {
const originalExecCommand = document.execCommand;
if (originalExecCommand) {
Object.defineProperty(document, 'execCommand', {
value: function(command) {
if (command === 'copy') {
const selection = window.getSelection();
if (selection && selection.toString().length > 0) {
if (isUserSelection) {
isUserSelection = false;
return originalExecCommand.apply(document, arguments);
}
pendingCopyText = selection.toString();
showRedDot();
return true;
}
}
return originalExecCommand.apply(document, arguments);
},
writable: true,
configurable: true,
});
}
}
function setupCopyEventInterception() {
document.addEventListener('copy', function(e) {
const selection = window.getSelection();
if (selection && selection.toString().length > 0) {
if (isUserSelection) {
isUserSelection = false;
return;
}
pendingCopyText = selection.toString();
showRedDot();
e.preventDefault();
e.stopPropagation();
}
}, true);
}
function setupTouchEvents() {
let startX = null;
let startY = null;
document.addEventListener('touchstart', e => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; });
document.addEventListener('touchmove', e => {
if (startX === null || startY === null) return;
const diffX = Math.abs(e.touches[0].clientX - startX);
const diffY = Math.abs(e.touches[0].clientY - startY);
if ((diffX > 10 || diffY > 10) && redDot && redDot.style.opacity === '1') {
hideRedDot();
}
});
}
function initUI() {
if (isInitialized) return;
ensureRedDot();
setupTouchEvents();
isInitialized = true;
}
function setupInterceptions() {
setupSelectionTracking();
setupClipboardInterception();
setupExecCommandInterception();
setupCopyEventInterception();
}
function initialize() {
setupInterceptions();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initUI);
} else {
initUI();
}
}
// 添加呼吸光环动画
const style = document.createElement('style');
style.textContent = `
@keyframes pulseRing {
0% { transform: scale(0.8); opacity: 0.6; }
70% { transform: scale(1.4); opacity: 0; }
100% { transform: scale(1.6); opacity: 0; }
}
`;
document.head.appendChild(style);
initialize();
})();