// ==UserScript==
// @name 屏蔽C站和NGA的傻逼-调试
// @namespace http://tampermonkey.net/
// @version 0.4
// @description 屏蔽C站和NGA论坛中的指定用户,同时删除签名区块,支持一键屏蔽用户
// @author forthejiong
// @match *://*.chiphell.com/*
// @match *://chiphell.com/*
// @match *://ngabbs.com/*
// @match *://*.nga.cn/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
// 屏蔽用户配置
// 将用户列表作为变量直接定义,GM_getValue的键名使用字符串
const CHIPHELL_BLOCK_KEY = 'chiphellBlockUsers';
const NGA_BLOCK_UIDS_KEY = 'ngaBlockUids';
const NGA_BLOCK_USERNAMES_KEY = 'ngaBlockUsernames';
// 在这里直接添加/删除用户。修改这里保存,可生效。
const defaultChiphellBlockUsers = [
"destroypeter",
"kthlon",
"normanlu",
"YsHaNg",
"raoshine",
// 在这里添加/删除 Chiphell 用户名,一行一个
"new_user_1",
"new_user_2"
];
const defaultNgaBlockUids = [
64542480,
// 在这里添加/删除 NGA 的 UID,一行一个
//建议使用UID避免用户修改名字之类的逃逸,建议手动添加或者把存储器的复制过来
6680916,
10086
];
const defaultNgaBlockUsernames = [
"张三",
// 在这里添加/删除 NGA 的用户名,一行一个。“修改请注意引号和逗号格式”
"李四",
"王五"
];
// 初始化屏蔽列表:从 Tampermonkey 存储中获取,如果没有则使用默认值
let chiphellBlockUsers = GM_getValue(CHIPHELL_BLOCK_KEY, defaultChiphellBlockUsers);
let ngaBlockUids = GM_getValue(NGA_BLOCK_UIDS_KEY, defaultNgaBlockUids).map(uid => Number(uid));
let ngaBlockUsernames = GM_getValue(NGA_BLOCK_USERNAMES_KEY, defaultNgaBlockUsernames);
// 从URL中提取UID(兼容相对/绝对URL)
function getUidFromUrl(url) {
try {
const fullUrl = new URL(url, window.location.origin);
const uid = fullUrl.searchParams.get('uid');
return uid ? Number(uid) : null;
} catch (e) {
console.error(`解析UID失败: ${url}`, e);
return null;
}
}
// 检查用户是否应被屏蔽(增加类型校验)
function shouldHideUser(site, identifier) {
if (site === 'chiphell') {
return typeof identifier === 'string' && chiphellBlockUsers.includes(identifier);
} else if (site === 'nga' && typeof identifier === 'object') {
const { uid, username } = identifier;
return typeof uid === 'number' && ngaBlockUids.includes(uid) || typeof username === 'string' && ngaBlockUsernames.includes(username);
}
return false;
}
// 移除指定的DOM元素(增加安全校验,避免误删核心结构)
function removeElement(element, reason) {
// 安全校验:排除页面核心容器(根据NGA/Chiphell结构调整)
const safeTagNames = ['TR', 'TBODY', 'DIV', 'TABLE'];
const forbiddenIds = ['mainContent', 'postList', 'topicList'];
const forbiddenClasses = ['forum-main', 'topic-container'];
if (!element || !element.parentNode || !safeTagNames.includes(element.tagName) || forbiddenIds.includes(element.id) || Array.from(element.classList).some(cls => forbiddenClasses.includes(cls))) {
console.warn(`[元素安全校验] 跳过删除非目标元素: ${element?.id || element?.tagName}`, reason);
return false;
}
element.parentNode.removeChild(element);
console.log(`[元素清理] 已移除: ${reason} (元素: ${element.id || element.tagName})`);
return true;
}
// 删除id以postsign开头的div元素(增加选择器精确性)
function removePostsignDivs() {
// 精确匹配:仅删除class含"postsign"且id以"postsign"开头的div(避免误删其他元素)
const postsignDivs = document.querySelectorAll('div[id^="postsign"][class*="postsign"]');
postsignDivs.forEach(div => {
removeElement(div, `签名区块 (ID: ${div.id})`);
});
}
// 屏蔽指定用户的发言(优化选择器,避免重复操作)
function hideTargetPosts() {
const currentHost = window.location.hostname;
// 处理Chiphell论坛(优化选择器,匹配首页和帖子页)
if (currentHost.includes('chiphell.com')) {
// 1. 处理首页/列表页的帖子(匹配tbody中的帖子行)
const threadRows = document.querySelectorAll('tbody[id^="normalthread_"] tr:has(td.by cite a):not([data-processed])');
threadRows.forEach(row => {
row.setAttribute('data-processed', 'true');
const userLink = row.querySelector('td.by cite a');
if (!userLink) return;
const username = userLink.textContent.trim();
if (shouldHideUser('chiphell', username)) {
// 移除整个帖子行(包含标题、作者等信息的整行)
removeElement(row, `Chiphell帖子列表 (用户: ${username})`);
}
});
// 2. 处理帖子详情页的回复(匹配帖子内容容器)
const postContainers = document.querySelectorAll('div[id^="post_"]:has(a.xw1[href^="space-uid-"]):not([data-processed])');
postContainers.forEach(container => {
container.setAttribute('data-processed', 'true');
const userLink = container.querySelector('a.xw1[href^="space-uid-"]');
if (!userLink) return;
const username = userLink.textContent.trim();
if (shouldHideUser('chiphell', username)) {
removeElement(container, `Chiphell帖子详情 (用户: ${username}, 容器ID: ${container.id})`);
}
});
}
// 处理NGA论坛(分场景精确匹配,避免误删)
else if (currentHost.includes('nga.cn')) {
// 1. 处理回复帖(仅匹配.postrow行,标记已处理)
const replyUserLinks = document.querySelectorAll('tr.postrow a.author[href*="uid="]:not([data-processed])');
replyUserLinks.forEach(link => {
link.setAttribute('data-processed', 'true');
const uid = getUidFromUrl(link.href);
const username = link.textContent.trim();
if (uid && shouldHideUser('nga', { uid, username })) {
const postContainer = link.closest('tr.postrow');
if (postContainer) {
removeElement(postContainer, `NGA回复 (用户: ${username}, UID: ${uid})`);
}
}
});
// 2. 处理主题帖(仅匹配含.c3列的tbody,标记已处理)
const topicUserLinks = document.querySelectorAll('tbody td.c3 a.author[href*="uid="]:not([data-processed])');
topicUserLinks.forEach(link => {
link.setAttribute('data-processed', 'true');
const uid = getUidFromUrl(link.href);
const username = link.textContent.trim();
if (uid && shouldHideUser('nga', { uid, username })) {
const topicContainer = link.closest('tbody');
if (topicContainer) {
removeElement(topicContainer, `NGA主题帖 (用户: ${username}, UID: ${uid})`);
}
}
});
}
}
// 添加一键屏蔽按钮(避免重复添加,优化样式)
function addBlockButtons() {
const currentHost = window.location.hostname;
if (!currentHost.includes('nga.cn')) return;
// 仅匹配未添加按钮的用户链接
const userLinks = document.querySelectorAll('a.author[href*="uid="]:not([data-has-block-btn])');
userLinks.forEach(link => {
link.setAttribute('data-has-block-btn', 'true');
const uid = getUidFromUrl(link.href);
if (!uid) return;
// 创建屏蔽按钮(避免inline样式冲突,用GM_addStyle)
const blockBtn = document.createElement('a');
blockBtn.href = 'javascript:void(0)';
blockBtn.textContent = '屏蔽';
blockBtn.className = 'nga-block-button';
blockBtn.dataset.uid = uid;
// 按钮点击事件(优化逻辑,避免重复添加)
blockBtn.addEventListener('click', () => {
const targetUid = Number(blockBtn.dataset.uid);
if (ngaBlockUids.includes(targetUid)) {
alert(`该用户(UID: ${targetUid})已在屏蔽列表中`);
return;
}
if (confirm(`确定要屏蔽 UID: ${targetUid} 吗?`)) {
ngaBlockUids.push(targetUid);
GM_setValue(NGA_BLOCK_UIDS_KEY, ngaBlockUids);
console.log(`[一键屏蔽] 已添加 UID: ${targetUid} 到屏蔽列表`);
window.location.reload();
}
});
link.after(blockBtn);
});
// 用GM_addStyle添加样式,避免inline样式冲突
GM_addStyle(`
.nga-block-button {
font-size: 10px;
color: #ff5722;
margin-left: 5px;
cursor: pointer;
text-decoration: underline;
padding: 0 2px;
border: none;
background: transparent;
}
.nga-block-button:hover {
color: #e64a19;
}
`);
}
// 核心执行函数(增加防抖,避免重复执行)
let runScriptDebounce;
function runScript() {
// 防抖:50ms内只执行一次,避免频繁触发
clearTimeout(runScriptDebounce);
runScriptDebounce = setTimeout(() => {
removePostsignDivs();
hideTargetPosts();
addBlockButtons();
}, 50);
}
// 初始执行:等待DOM完全加载后再执行(关键修复)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runScript);
} else {
runScript();
}
// 优化MutationObserver:缩小监听范围,减少触发频率
const observer = new MutationObserver((mutations) => {
// 仅当新增节点是元素节点时才执行(过滤文本/注释节点)
const hasUsefulNodes = mutations.some(mut => Array.from(mut.addedNodes).some(node => node.nodeType === 1));
if (hasUsefulNodes) {
runScript();
}
});
// 监听范围优化:仅监听帖子列表容器(根据NGA/Chiphell结构调整)
const targetContainer = document.querySelector('#postList, #topicList, .forum-main, #threadlist') || document.body;
observer.observe(targetContainer, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
// 页面卸载时断开监听,避免内存泄漏
window.addEventListener('beforeunload', () => {
observer.disconnect();
});
})();