Greasy Fork is available in English.
解除网页复制限制,支持多种反复制技术
// ==UserScript==
// @name 复制限制解除器
// @namespace https://chat.deepseek.com/a/chat/s/762d1db8-7364-4668-a56e-2549eb5c8662?rev=552
// @version 1.1.0
// @description 解除网页复制限制,支持多种反复制技术
// @author DeepSeek
// @match *://*/*
// @run-at document-start
// @grant unsafeWindow
// @grant GM_addStyle
// @license GPL-3.0
// ==/UserScript==
(function() {
'use strict';
// ==================== 配置 ====================
const CONFIG = {
debug: false,
aggressiveMode: false, // 激进模式,hook更多API
removeCopyListeners: true,
restoreSelection: true,
forceUserSelect: true,
replaceNativeCopy: false,
};
// ==================== 工具函数 ====================
function log(...args) {
if (CONFIG.debug) {
console.log('[ForceCopy]', ...args);
}
}
function warn(...args) {
console.warn('[ForceCopy]', ...args);
}
// ==================== CSS注入 ====================
if (CONFIG.forceUserSelect) {
const css = `
* {
user-select: text !important;
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
}
input, textarea, [contenteditable="true"] {
user-select: auto !important;
}
`;
if (typeof GM_addStyle !== "undefined") {
GM_addStyle(css);
} else {
const style = document.createElement('style');
style.textContent = css;
document.documentElement.appendChild(style);
}
log('CSS样式已注入');
}
// ==================== 核心Hook类 ====================
class CopyEnforcer {
constructor() {
this.hookedEvents = new WeakMap();
this.originalAddEventListener = EventTarget.prototype.addEventListener;
this.originalRemoveEventListener = EventTarget.prototype.removeEventListener;
this.hookAddEventListener();
this.hookRemoveEventListener();
this.hookShadowDOM();
this.setupSelectionProtection();
this.setupCopyProtection();
log('复制强制执行器已初始化');
}
// ==================== EventListener Hook ====================
hookAddEventListener() {
const self = this;
EventTarget.prototype.addEventListener = function(type, listener, options) {
// 记录被hook的事件
if (typeof listener === 'function') {
if (!self.hookedEvents.has(this)) {
self.hookedEvents.set(this, new Map());
}
const eventMap = self.hookedEvents.get(this);
if (!eventMap.has(type)) {
eventMap.set(type, []);
}
eventMap.get(type).push({ listener, options });
}
// 阻止复制相关事件的默认行为阻止
const copyBlockingEvents = [
'copy', 'cut', 'selectstart', 'selectionchange',
'mousedown', 'mouseup', 'click', 'dragstart',
'contextmenu', 'keydown', 'keyup', 'keypress'
];
if (CONFIG.removeCopyListeners && copyBlockingEvents.includes(type.toLowerCase())) {
// 检查是否是阻止复制的事件处理器
const listenerStr = listener.toString().toLowerCase();
const blockingKeywords = [
'preventdefault', 'returnfalse', 'stopimmediatepropagation',
'stoppropagation', 'select', 'selection', 'copy', 'clipboard'
];
if (blockingKeywords.some(keyword => listenerStr.includes(keyword))) {
log(`阻止了可能妨碍复制的${type}事件监听器`, listener);
// 包装监听器以允许默认行为
const wrappedListener = function(e) {
try {
// 对于某些事件,始终允许默认行为
if (['copy', 'cut', 'selectstart', 'dragstart'].includes(e.type)) {
e.stopImmediatePropagation = () => {
log(`阻止了stopImmediatePropagation: ${e.type}`);
};
e.stopPropagation = () => {
log(`阻止了stopPropagation: ${e.type}`);
};
if (e.type === 'copy' || e.type === 'cut') {
// 确保复制事件能正常工作
setTimeout(() => {
self.restoreClipboardIfNeeded();
}, 10);
}
}
return listener.call(this, e);
} catch (err) {
log(`事件监听器执行错误: ${err.message}`);
}
};
// 复制原始属性
Object.defineProperties(wrappedListener, {
name: { value: listener.name || 'wrappedListener' },
toString: { value: () => listener.toString() }
});
return self.originalAddEventListener.call(this, type, wrappedListener, options);
}
}
return self.originalAddEventListener.call(this, type, listener, options);
};
}
hookRemoveEventListener() {
const self = this;
EventTarget.prototype.removeEventListener = function(type, listener, options) {
// 清理记录
if (self.hookedEvents.has(this)) {
const eventMap = self.hookedEvents.get(this);
if (eventMap && eventMap.has(type)) {
const listeners = eventMap.get(type);
const index = listeners.findIndex(l => l.listener === listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
return self.originalRemoveEventListener.call(this, type, listener, options);
};
}
// ==================== Shadow DOM Hook ====================
hookShadowDOM() {
if (!Element.prototype.attachShadow) return;
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(options) {
const shadowRoot = originalAttachShadow.call(this, options);
// Hook shadow root内部的事件监听
const originalShadowAdd = shadowRoot.addEventListener;
shadowRoot.addEventListener = function(type, listener, options) {
return this.ownerDocument.defaultView.EventTarget.prototype.addEventListener.call(this, type, listener, options);
};
// 为shadow root添加CSS
setTimeout(() => {
const style = document.createElement('style');
style.textContent = `
* {
user-select: text !important;
-webkit-user-select: text !important;
}
`;
shadowRoot.appendChild(style);
}, 100);
return shadowRoot;
};
log('Shadow DOM API已hook');
}
// ==================== 选择保护 ====================
setupSelectionProtection() {
if (!CONFIG.restoreSelection) return;
// 保护getSelection
const originalGetSelection = window.getSelection;
if (originalGetSelection) {
window.getSelection = function() {
const selection = originalGetSelection.call(window);
if (selection && selection.rangeCount > 0) {
try {
// 确保选择范围有效
const range = selection.getRangeAt(0);
if (range && range.cloneContents) {
return selection;
}
} catch (e) {
log('选择范围恢复失败:', e);
}
}
return selection;
};
}
// 保护selection相关事件
document.addEventListener('selectstart', (e) => {
e.stopImmediatePropagation();
log('selectstart事件已允许');
}, true);
document.addEventListener('selectionchange', (e) => {
log('selectionchange事件触发');
}, true);
}
// ==================== 复制保护 ====================
setupCopyProtection() {
if (!CONFIG.replaceNativeCopy) return;
// 拦截复制事件
document.addEventListener('copy', (e) => {
log('复制事件触发');
e.stopImmediatePropagation();
// 获取当前选择
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
try {
// 设置剪贴板数据
if (e.clipboardData) {
e.clipboardData.setData('text/plain', selection.toString());
e.clipboardData.setData('text/html', this.getSelectedHTML(selection));
}
log('复制内容已设置:', selection.toString().substring(0, 100));
} catch (err) {
warn('设置剪贴板数据失败:', err);
}
}
e.preventDefault();
return false;
}, true);
document.addEventListener('cut', (e) => {
log('剪切事件触发');
e.stopImmediatePropagation();
e.preventDefault();
}, true);
}
// ==================== 辅助方法 ====================
getSelectedHTML(selection) {
if (!selection || selection.rangeCount === 0) return '';
try {
const range = selection.getRangeAt(0);
const container = document.createElement('div');
container.appendChild(range.cloneContents());
return container.innerHTML;
} catch (e) {
return selection.toString();
}
}
restoreClipboardIfNeeded() {
setTimeout(() => {
try {
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
navigator.clipboard.writeText(selection.toString()).then(() => {
log('剪贴板已恢复');
}).catch(err => {
log('剪贴板恢复失败:', err);
});
}
} catch (e) {
// 忽略错误
}
}, 50);
}
// ==================== 清理函数 ====================
cleanup() {
EventTarget.prototype.addEventListener = this.originalAddEventListener;
EventTarget.prototype.removeEventListener = this.originalRemoveEventListener;
log('清理完成');
}
}
// ==================== 激进模式Hook ====================
if (CONFIG.aggressiveMode) {
// Hook Object.defineProperty,防止通过设置oncopy等属性阻止复制
const originalDefineProperty = Object.defineProperty;
Object.defineProperty = function(obj, prop, descriptor) {
if (prop && typeof prop === 'string') {
const lowerProp = prop.toLowerCase();
if (lowerProp.includes('copy') || lowerProp.includes('select') || lowerProp.includes('drag')) {
log(`拦截了defineProperty: ${prop}`, obj);
// 允许设置但记录
}
}
return originalDefineProperty.call(this, obj, prop, descriptor);
};
// Hook setAttribute
const originalSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
if (name && typeof name === 'string') {
const lowerName = name.toLowerCase();
if (lowerName.includes('onselect') || lowerName.includes('oncopy') ||
lowerName.includes('oncut') || lowerName.includes('ondrag')) {
log(`拦截了setAttribute: ${name}`, this);
return; // 直接阻止设置
}
}
return originalSetAttribute.call(this, name, value);
};
}
// ==================== 初始化 ====================
// 等待DOM准备就绪
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.copyEnforcer = new CopyEnforcer();
});
} else {
window.copyEnforcer = new CopyEnforcer();
}
// ==================== 全局辅助函数 ====================
// 提供手动复制函数
window.forceCopy = function() {
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
try {
document.execCommand('copy');
log('手动复制执行成功');
return true;
} catch (e) {
warn('手动复制失败:', e);
return false;
}
}
return false;
};
// 提供清理函数
window.cleanupForceCopy = function() {
if (window.copyEnforcer) {
window.copyEnforcer.cleanup();
delete window.copyEnforcer;
}
};
log('强制复制脚本已加载');
})();