CSDN文库阅读全文,去除VIP登录遮挡,解锁鼠标复制文本功能(日志输出到隐藏容器)
// ==UserScript==
// @name CSDN文库免vip付费阅读全文丨解锁复制限制
// @namespace http://tampermonkey.net/
// @version 2025-08-09
// @description CSDN文库阅读全文,去除VIP登录遮挡,解锁鼠标复制文本功能(日志输出到隐藏容器)
// @author You
// @match *://*.csdn.net/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ------------------------------ 日志容器初始化(明确输出到指定容器) ------------------------------
// 创建隐藏的日志容器(样式确保不干扰页面布局)
const logContainer = document.createElement('div');
logContainer.setAttribute('id', 'csdn-unlock-log-container'); // 添加唯一ID便于调试
logContainer.style.cssText = `
position: fixed;
top: 0px; /* 超出屏幕顶部(隐藏) */
left: 0px; /* 超出屏幕左侧(隐藏) */
width: 300px;
max-height: 400px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.8);
color: #0f0; /* 绿色文字(高对比度) */
font-family: monospace;
font-size: 12px;
padding: 10px;
border-radius: 4px;
z-index: 999999;
pointer-events: none; /* 禁止鼠标交互 */
display: none; /* 强制显示(调试时可改为none隐藏) */ /*更改此选项可显示调试*/
`;
document.body.appendChild(logContainer);
// ------------------------------ 日志函数(明确输出到容器) ------------------------------
let lastLogTime = 0;
const LOG_INTERVAL = 200; // 缩短间隔至200ms(平衡实时性与性能)
const log = (msg) => {
const now = Date.now();
// if (now - lastLogTime < LOG_INTERVAL) return; // 防抖:避免频繁写入
lastLogTime = now;
const logEntry = document.createElement('div');
logEntry.style.cssText = `
color: #0f0;
font-size: 12px;
margin-bottom: 4px;
line-height: 1.4;
`;
logEntry.textContent = `[${new Date().toLocaleTimeString([], { hour12: false })}] [CSDN解锁] ${msg}`;
logContainer.appendChild(logEntry);
// 自动滚动到最新日志(容器可见时)
if (logContainer.offsetWidth > 0 && logContainer.offsetHeight > 0) {
logContainer.scrollTop = logContainer.scrollHeight;
}
};
// ------------------------------ 核心功能函数(优化:避免重复操作) ------------------------------
// 移除遮罩层(添加已处理标记)
const removeMask = () => {
log('开始移除遮罩层');
const maskSelectors = ['.open', '.vip', '.mask-layer', '.pay-mask', '#mainBox > main > div.hide-article-box.hide-article-pos.text-center'];
maskSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
if (!el.dataset.processed) {
el.remove();
el.dataset.processed = 'true';
log(`已移除遮罩层: ${selector}`);
}
});
});
log('遮罩层移除完成');
};
// 展开受限内容(添加已处理标记)
const expandContent = () => {
log('开始展开文章内容');
const contentSelectors = [
'.article-box .cont.first-show', // 主内容容器(通用)
'.read-content .main-cont', // 备用选择器1
'.csdn-tracking-statistics', // 备用选择器2
'.article-container .content',
'#article_content'// 备用选择器3
];
let articleContainer = null;
for (const selector of contentSelectors) {
articleContainer = document.querySelector(selector);
if (articleContainer) {
if (!articleContainer.dataset.expanded) {
articleContainer.style.maxHeight = 'none';
articleContainer.style.height = 'auto';
articleContainer.style.overflow = 'visible';
articleContainer.dataset.expanded = 'true';
log(`已展开文章内容: ${selector}`);
}
break;
}
}
if (!articleContainer) {
log('警告:未找到文章内容容器,当前选择器可能失效');
}
};
// 彻底解锁复制功能(优化:避免重复拦截)
const unlockCopy = () => {
log('开始解锁复制功能');
// 1. 移除oncopy事件监听器(仅执行一次)
if (!document.body.dataset.copyHandlerRemoved) {
document.body.oncopy = null;
document.querySelectorAll('*').forEach(el => {
el.oncopy = null;
});
document.body.dataset.copyHandlerRemoved = 'true';
log('已移除oncopy事件监听器');
}
// 2. 拦截addEventListener绑定的copy事件(仅执行一次)
if (!EventTarget.prototype.originalAddEventListener) {
EventTarget.prototype.originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (type !== 'copy') {
this.originalAddEventListener(type, listener, options);
}
};
log('已拦截addEventListener绑定的copy事件');
}
// 3. 启用用户选择(仅修改未处理的元素)
document.querySelectorAll('*').forEach(el => {
if (!el.dataset.userSelectEnabled) {
el.style.userSelect = 'auto';
el.style.webkitUserSelect = 'auto';
el.style.mozUserSelect = 'auto';
el.style.msUserSelect = 'auto';
el.dataset.userSelectEnabled = 'true';
}
});
log('已启用用户选择');
// 4. 解除contenteditable限制(仅处理未处理的元素)
document.querySelectorAll('[contenteditable="false"]').forEach(el => {
if (!el.dataset.contentEditableFixed) {
el.setAttribute('contenteditable', 'true');
el.dataset.contentEditableFixed = 'true';
}
});
log('已解除contenteditable限制');
log('复制功能解锁完成');
};
// ------------------------------ DOM 监听与初始化 ------------------------------
// 使用MutationObserver监听核心区域变化(减少触发频率)
const initObserver = () => {
// 仅监听文章内容区域(避免无关DOM变化触发)
const TARGET_SELECTOR = '#article_content';
const targetElement = document.querySelector(TARGET_SELECTOR);
if (!targetElement) {
log('警告:未找到核心内容区域,延迟初始化Observer');
setTimeout(initObserver, 1000); // 延迟重试
return;
}
const observer = new MutationObserver((mutations) => {
let hasRelevantChanges = false;
mutations.forEach(mutation => {
// 仅处理核心区域的子节点变化
if (mutation.target.closest(TARGET_SELECTOR)) {
hasRelevantChanges = true;
}
});
if (hasRelevantChanges) {
log('检测到核心区域DOM变化,重新执行核心逻辑');
removeMask();
expandContent();
unlockCopy();
}
});
observer.observe(targetElement, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
log('MutationObserver初始化完成(监听核心区域)');
};
// 页面加载完成后执行初始化
const onPageLoad = () => {
log('===== 脚本启动 =====');
removeMask();
expandContent();
unlockCopy();
initObserver(); // 初始化DOM监听
};
// 启动脚本
onPageLoad();
})();