// ==UserScript==
// @name CSDN免登录copy
// @version 1.0
// @description CSDN可以不需要登录进行复制
// @author lk
// @match https://blog.csdn.net/*/article/details/*
// @icon https://g.csdnimg.cn/static/logo/favicon32.ico
// @namespace https://greasyfork.org/users/1509322
// ==/UserScript==
(function() {
'use strict';
// 删除页面内所有节点的复制事件限制
function removeAllCopyRestrictions() {
// 获取页面中所有的元素
const allElements = document.querySelectorAll('*');
console.log(`开始处理 ${allElements.length} 个页面元素`);
allElements.forEach(element => {
// 移除可能的复制限制属性
element.removeAttribute('oncopy');
element.removeAttribute('oncontextmenu');
element.removeAttribute('onselectstart');
element.removeAttribute('ondragstart');
element.removeAttribute('oncut');
element.removeAttribute('onpaste');
// 设置允许文本选择的样式
element.style.userSelect = 'text';
element.style.webkitUserSelect = 'text';
element.style.mozUserSelect = 'text';
element.style.msUserSelect = 'text';
// 移除可能的禁用选择类名
element.classList.remove('no-select', 'noselect', 'unselectable');
});
// 特别处理body和html元素
[document.body, document.documentElement].forEach(element => {
if (element) {
element.style.userSelect = 'text';
element.style.webkitUserSelect = 'text';
element.style.mozUserSelect = 'text';
element.style.msUserSelect = 'text';
element.removeAttribute('onselectstart');
element.removeAttribute('oncontextmenu');
}
});
}
// 专门处理code元素,使其可编辑
function enableCodeEditing() {
// 获取页面中所有的 code 标签元素
// querySelectorAll 返回一个包含所有匹配选择器的元素的 NodeList
const codes = document.querySelectorAll("code");
if (codes.length === 0) {
console.log('未找到 code 元素');
return;
}
console.log(`找到 ${codes.length} 个代码块`);
// 遍历所有找到的 code 元素
// 使用 forEach 方法对每个 code 元素进行处理
codes.forEach(code => {
// 将每个 code 元素设置为可编辑状态
// contentEditable = "true" 使元素内容可以被用户编辑和选择
// 这样可以绕过 CSDN 的复制限制,让用户能够正常选择和复制代码
code.contentEditable = "true";
// 移除所有可能阻止复制的事件监听器
// 克隆节点并替换原节点,这样可以移除所有事件监听器
const newCode = code.cloneNode(true);
newCode.contentEditable = "true";
// 设置允许文本选择的样式
newCode.style.userSelect = 'text';
newCode.style.webkitUserSelect = 'text';
newCode.style.mozUserSelect = 'text';
newCode.style.msUserSelect = 'text';
// 移除可能的复制限制属性
newCode.removeAttribute('oncopy');
newCode.removeAttribute('oncontextmenu');
newCode.removeAttribute('onselectstart');
newCode.removeAttribute('ondragstart');
// 替换原节点
if (code.parentNode) {
code.parentNode.replaceChild(newCode, code);
}
console.log(newCode, '已处理的代码块');
});
}
// 移除全局的事件监听器并阻止复制限制
function removeGlobalEventListeners() {
// 阻止页面级别的复制限制事件
const events = ['copy', 'cut', 'paste', 'selectstart', 'contextmenu', 'dragstart', 'mousedown', 'mouseup', 'keydown', 'keyup'];
events.forEach(eventType => {
// 移除所有现有的事件监听器
document.removeEventListener(eventType, function() {}, true);
document.removeEventListener(eventType, function() {}, false);
// 添加新的事件监听器,阻止默认的限制行为
document.addEventListener(eventType, function(e) {
// 阻止事件冒泡和默认行为
e.stopImmediatePropagation();
// 特别处理复制事件,确保不弹出登录框
if (eventType === 'copy' || eventType === 'cut') {
// 允许复制操作继续
return true;
}
// 对于其他事件,也允许继续
return true;
}, true);
});
// 特别处理CSDN的复制拦截机制
// 重写可能被CSDN劫持的方法
try {
const originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
// 如果是复制相关事件,不添加监听器
if (['copy', 'selectstart', 'contextmenu', 'dragstart'].includes(type)) {
console.log(`阻止添加 ${type} 事件监听器`);
return;
}
// 其他事件正常处理
return originalAddEventListener.call(this, type, listener, options);
};
} catch (e) {
console.log('无法重写 addEventListener,使用备选方案');
}
// 重写document.oncopy等属性(使用try-catch避免重定义错误)
try {
Object.defineProperty(document, 'oncopy', {
set: function() {
console.log('阻止设置 document.oncopy');
},
get: function() {
return null;
},
configurable: true
});
} catch (e) {
// 如果无法重定义,直接设置为null
try {
document.oncopy = null;
} catch (e2) {
console.log('无法修改 document.oncopy');
}
}
try {
Object.defineProperty(document, 'onselectstart', {
set: function() {
console.log('阻止设置 document.onselectstart');
},
get: function() {
return null;
},
configurable: true
});
} catch (e) {
try {
document.onselectstart = null;
} catch (e2) {
console.log('无法修改 document.onselectstart');
}
}
try {
Object.defineProperty(document, 'oncontextmenu', {
set: function() {
console.log('阻止设置 document.oncontextmenu');
},
get: function() {
return null;
},
configurable: true
});
} catch (e) {
try {
document.oncontextmenu = null;
} catch (e2) {
console.log('无法修改 document.oncontextmenu');
}
}
// 阻止CSDN可能使用的其他限制方法
try {
if (window.getSelection) {
const originalGetSelection = window.getSelection;
window.getSelection = function() {
const selection = originalGetSelection.call(this);
// 确保选择功能正常工作
return selection;
};
}
} catch (e) {
console.log('无法重写 getSelection 方法');
}
// 重写可能被CSDN用来检测复制的方法
try {
const originalExecCommand = document.execCommand;
document.execCommand = function(commandId, showUI, value) {
if (commandId === 'copy' || commandId === 'cut') {
console.log(`允许执行 ${commandId} 命令`);
return originalExecCommand.call(this, commandId, showUI, value);
}
return originalExecCommand.call(this, commandId, showUI, value);
};
} catch (e) {
console.log('无法重写 execCommand 方法');
}
console.log('已移除全局事件限制并阻止复制拦截');
}
// 等待页面加载完成后执行
function init() {
// 执行所有解除限制的操作
removeAllCopyRestrictions();
removeGlobalEventListeners();
enableCodeEditing();
console.log('CSDN免登录复制脚本已启动');
}
// 如果页面已经加载完成,立即执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();