Greasy Fork is available in English.
保护用户剪贴板内容,允许或禁止网站污染剪贴板。
// ==UserScript==
// @name 请不要污染我的剪贴板
// @namespace Violentmonkey Scripts
// @version 1.0
// @description 保护用户剪贴板内容,允许或禁止网站污染剪贴板。
// @match *://*/*
// @grant GM_notification
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// @license MIT
/* jshint esversion: 8 */
// ==/UserScript==
(function() {
'use strict';
const PERMISSION_KEY = 'CLIPBOARD_PERMISSIONS';
let permissions = GM_getValue(PERMISSION_KEY, {});
// 创建权限管理菜单
GM_registerMenuCommand("剪贴板权限管理", showPermissionManager);
/**************** 核心拦截逻辑 ****************/
// 修复1:正确处理传统execCommand
const originalExecCommand = document.execCommand.bind(document);
document.execCommand = function(command, showUI, value) {
if (/^(copy|cut|paste)$/i.test(command)) {
const allowed = checkPermissionSync(
'execCommand:' + command,
`网站试图通过document.execCommand执行【${command.toUpperCase()}】操作`
);
return allowed ? originalExecCommand(command, showUI, value) : false;
}
return originalExecCommand(command, showUI, value);
};
// 修复2:正确处理现代Clipboard API
if (navigator.clipboard) {
// 写入拦截
const originalWrite = navigator.clipboard.writeText.bind(navigator.clipboard);
navigator.clipboard.writeText = async function(data) {
const allowed = await checkPermissionAsync(
'clipboard:write',
`网站试图写入剪贴板内容:${truncateText(data)}`
);
return allowed ? originalWrite(data) : Promise.reject(new DOMException('Not allowed', 'NotAllowedError'));
};
// 读取拦截
const originalRead = navigator.clipboard.readText.bind(navigator.clipboard);
navigator.clipboard.readText = async function() {
const allowed = await checkPermissionAsync(
'clipboard:read',
'网站试图读取剪贴板内容'
);
return allowed ? originalRead() : Promise.reject(new DOMException('Not allowed', 'NotAllowedError'));
};
}
// 修复3:优化事件监听劫持
const originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (['copy', 'cut', 'paste'].includes(type)) {
const wrappedListener = (e) => {
const operation = type === 'paste' ? '读取' : '写入';
const allowed = checkPermissionSync(
'event:' + type,
`网站通过【${type}】事件进行剪贴板${operation}`
);
if (allowed) return listener(e);
e.preventDefault();
e.stopImmediatePropagation();
};
return originalAddEventListener.call(this, type, wrappedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
/**************** 工具函数 ****************/
// 同步权限检查(用于同步API)
function checkPermissionSync(permissionId, message) {
const domain = window.location.hostname;
const fullKey = `${domain}:${permissionId}`;
// 检查已保存的权限
if (permissions[fullKey] !== undefined) {
return permissions[fullKey];
}
// 交互式询问
const choice = confirm(`${message}\n\n是否允许本次操作?\n(确定=允许,取消=拒绝)`);
const remember = confirm('是否永久记住此选择?');
if (remember) {
permissions[fullKey] = choice;
GM_setValue(PERMISSION_KEY, permissions);
}
return choice;
}
// 异步权限检查(用于异步API)
async function checkPermissionAsync(permissionId, message) {
return new Promise(resolve => {
resolve(checkPermissionSync(permissionId, message));
});
}
function showPermissionManager() {
let msg = '已保存的剪贴板权限:\n';
Object.keys(permissions).forEach(k => {
msg += `\n${k.replace(':', ' → ')}: ${permissions[k] ? '✓' : '×'}`;
});
alert(msg + '\n\n提示:在弹窗中选择"记住"可永久保存设置');
}
function truncateText(text, max = 50) {
return text.length > max ? text.substring(0, max) + '...' : text;
}
})();