NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
/* jshint esversion: 8 */
// ==UserScript==
// @name CSDN免登录复制脚本
// @namespace http://tampermonkey.net/
// @version 7.0
// @description 破解CSDN登录限制,自由复制代码和文本,保持格式整洁
// @author louk78
// @license MIT
// @match *://*.csdn.net/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// ========== 工具函数 ==========
const cleanText = (text) => {
// 移除开头和结尾的空行,并将连续多个空行压缩为最多两个
return text.replace(/^\s*\n/, '').replace(/\n{3,}/g, '\n\n').trim();
};
// 安全移除元素(带白名单检查)
const safeRemove = (el) => {
if (!el || !el.parentNode) return;
// 白名单:某些包含“login”但不是遮罩的元素(如用户信息)
const whiteList = [
'user-login-status',
'login-info',
'user-login'
];
const id = el.id || '';
const cls = el.className || '';
if (whiteList.some(key => id.includes(key) || cls.includes(key))) {
return;
}
el.remove();
};
const nukeLoginElements = () => {
const killSelectors = [
'.passport-login-container',
'.login-mark',
'.signin',
'.login-box',
'.login-modal',
'.modal-login',
'.passport-login-box',
'.hljs-signin',
'.meiqia-chat',
'#passportbox',
'.csdn-side-toolbar',
'.toolbar-login',
'.unlogin-box',
'.hide-article-box',
'.article_content_unlogin',
'.needslogin',
'.content-not-login',
'.read-soon',
'.blur-content',
'.blur-box',
'.blur-mask',
'.login-blur',
'.hljs-button', // 原复制按钮(禁用状态)
'.btn-copy[disabled]',
'.code-snippet-btn[disabled]',
'.copy-btn-disabled',
'.mask',
'.modal-backdrop',
'.overlay',
'.fixed-layer'
];
killSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(safeRemove);
});
// 移除固定定位的高zIndex遮罩
document.querySelectorAll('*').forEach(el => {
try {
const style = window.getComputedStyle(el);
if (style.position === 'fixed' && parseInt(style.zIndex) > 1000) {
safeRemove(el);
}
} catch (e) {}
});
// 恢复页面滚动
document.body.style.overflow = 'auto';
document.documentElement.style.overflow = 'auto';
document.body.classList.remove('no-scroll', 'overflow-hidden');
};
// ========== 2. 注入最小必要样式 ==========
const injectCoreStyles = () => {
const coreCSS = `
/* 解除选择限制(仅作用于正文和代码区域) */
#content_views, #article_content, .article_content, .blog-content-box,
.htmledit_views, .content_views, pre, code, .hljs, .prettyprint {
user-select: auto !important;
-webkit-user-select: auto !important;
-moz-user-select: auto !important;
-ms-user-select: auto !important;
pointer-events: auto !important;
}
/* 移除模糊效果 */
* {
filter: none !important;
-webkit-filter: none !important;
}
/* 自定义复制按钮样式 */
.nuclear-copy-btn {
position: absolute;
top: 8px;
right: 8px;
padding: 4px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-family: sans-serif;
z-index: 9999;
opacity: 0.7;
transition: opacity 0.2s;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.nuclear-copy-btn:hover {
opacity: 1;
}
.nuclear-copy-btn.copied {
background: #2196F3;
}
pre {
position: relative !important;
padding-top: 35px !important; /* 为按钮留出空间 */
}
`;
const style = document.createElement('style');
style.id = 'nuclear-csdn-styles';
style.textContent = coreCSS;
if (!document.getElementById('nuclear-csdn-styles')) {
document.head.appendChild(style);
}
};
// ========== 3. 劫持事件,允许复制 ==========
const hijackEvents = () => {
// 阻止网站禁止复制的所有事件
const blockEvents = ['copy', 'cut', 'selectstart', 'contextmenu'];
blockEvents.forEach(eventType => {
document.addEventListener(eventType, (e) => {
e.stopPropagation();
// 允许默认行为
}, true);
window.addEventListener(eventType, (e) => {
e.stopPropagation();
}, true);
});
// 覆盖事件属性
Object.defineProperties(document, {
oncopy: { value: null, writable: false },
oncut: { value: null, writable: false },
onselectstart: { value: null, writable: false }
});
};
// ========== 4. 增强复制按钮:提取干净代码 ==========
const addCleanCopyButtons = () => {
// 为每个pre添加复制按钮
document.querySelectorAll('pre').forEach((pre) => {
if (pre.querySelector('.nuclear-copy-btn')) return; // 避免重复
pre.style.position = 'relative';
pre.style.paddingTop = '35px';
const btn = document.createElement('button');
btn.className = 'nuclear-copy-btn';
btn.textContent = '复制代码';
btn.addEventListener('click', async (e) => {
e.stopPropagation();
// 获取代码元素
let codeEl = pre.querySelector('code');
if (!codeEl) codeEl = pre; // 如果没有code标签,直接用pre
// 克隆节点以移除可能存在的按钮干扰
const clone = codeEl.cloneNode(true);
// 移除克隆节点内的任何按钮
clone.querySelectorAll('.nuclear-copy-btn, .hljs-button, .btn-copy').forEach(el => el.remove());
// 提取文本:使用innerText保留格式
let codeText = clone.innerText || clone.textContent;
codeText = cleanText(codeText);
try {
await navigator.clipboard.writeText(codeText);
btn.textContent = '已复制';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = '复制代码';
btn.classList.remove('copied');
}, 1500);
} catch (err) {
// 降级方案
const textarea = document.createElement('textarea');
textarea.value = codeText;
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
btn.textContent = '已复制(降级)';
setTimeout(() => {
btn.textContent = '复制代码';
}, 1500);
}
});
pre.appendChild(btn);
});
// 为独立的code块(无pre)添加包裹和按钮
document.querySelectorAll('code:not(pre code)').forEach(code => {
if (code.closest('.nuclear-copy-wrapper')) return;
const wrapper = document.createElement('div');
wrapper.className = 'nuclear-copy-wrapper';
wrapper.style.position = 'relative';
wrapper.style.margin = '10px 0';
wrapper.style.padding = '15px';
wrapper.style.background = '#f6f8fa';
wrapper.style.borderRadius = '5px';
code.parentNode.insertBefore(wrapper, code);
wrapper.appendChild(code);
const btn = document.createElement('button');
btn.className = 'nuclear-copy-btn';
btn.textContent = '复制代码';
btn.style.position = 'absolute';
btn.style.top = '5px';
btn.style.right = '5px';
btn.addEventListener('click', async () => {
const text = cleanText(code.innerText || code.textContent);
await navigator.clipboard.writeText(text);
btn.textContent = '已复制';
setTimeout(() => {
btn.textContent = '复制代码';
}, 1500);
});
wrapper.appendChild(btn);
});
};
// ========== 5. 清理普通文本复制格式 ==========
const enhancePlainCopy = () => {
document.addEventListener('copy', (e) => {
// 如果用户是通过复制按钮复制,这里不处理(按钮已处理)
// 如果是用户直接选择文本复制,可以清理HTML
setTimeout(() => {
// 无法直接修改剪贴板内容,但可以尝试通过设置自定义数据?实际上copy事件中可以修改
// 这里我们不做强制修改,因为用户可能想保留格式。如果需要可以在这里干预。
// 可选:如果选中区域包含代码块,可以尝试提取纯文本。
}, 0);
}, true);
};
// ========== 6. 监控动态内容 ==========
const startMonitoring = () => {
const observer = new MutationObserver(() => {
nukeLoginElements();
addCleanCopyButtons();
});
observer.observe(document.body, { childList: true, subtree: true });
};
// ========== 7. 反检测(精简) ==========
const antiDetection = () => {
Object.defineProperty(navigator, 'webdriver', { get: () => false });
// 可选:屏蔽控制台中与脚本相关的日志
const originalLog = console.log;
console.log = function(...args) {
if (args[0] && typeof args[0] === 'string' && args[0].includes('Tampermonkey')) return;
originalLog.apply(console, args);
};
};
// ========== 8. 网络请求拦截(保留,但不影响复制格式) ==========
const interceptNetwork = () => {
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
if (typeof url === 'string' && /login|signin|passport|user\/status/i.test(url)) {
return Promise.reject(new Error('Blocked'));
}
return originalFetch.apply(this, args);
};
const originalXHR = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(...args) {
const url = args[1];
if (typeof url === 'string' && /login|signin|passport|user\/status/i.test(url)) {
args[1] = 'about:blank';
}
return originalXHR.apply(this, args);
};
};
// ========== 主启动函数 ==========
const main = () => {
nukeLoginElements();
injectCoreStyles();
hijackEvents();
antiDetection();
interceptNetwork();
// 延迟执行以确保DOM就绪
setTimeout(() => {
addCleanCopyButtons();
startMonitoring();
}, 500);
setTimeout(addCleanCopyButtons, 1500);
setInterval(addCleanCopyButtons, 5000); // 定期检查新代码块
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();