场景:首页列表、评论、发帖页面。
用法,打开目标用户发的帖子,左上角添加了红色按钮用于 block 用户。
作用:减少看见目标用户的可能。
支持移除 block 用户。

// ==UserScript==
// @name Ld246.com User Blocker (with Comment Filter)
// @namespace http://tampermonkey.net/
// @version 5.1
// @icon https://ld246.com/images/favicon.png
// @description Blocks posts and comments from specified users on ld246.com. Robust handling for dynamic content.
// @author Gemini & Your Name
// @match https://ld246.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURATION & STATE MANAGEMENT ---
let config = {
isEnabled: GM_getValue('isEnabled', true),
blockedUsers: GM_getValue('blockedUsers', [])
};
function saveConfig() {
GM_setValue('isEnabled', config.isEnabled);
GM_setValue('blockedUsers', config.blockedUsers);
}
// --- MENU COMMANDS ---
function toggleManagementPanel() {
const menu = document.getElementById('blocker-management-panel');
if (menu) {
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
}
GM_registerMenuCommand('Manage Blocked Users', toggleManagementPanel);
GM_registerMenuCommand((config.isEnabled ? 'Disable' : 'Enable') + ' User Blocker', () => {
config.isEnabled = !config.isEnabled;
saveConfig();
alert(`User blocker is now ${config.isEnabled ? 'enabled' : 'disabled'}. Page will reload.`);
location.reload();
});
// --- CORE BLOCKING FUNCTIONS ---
/**
* A unified function to find and hide posts and comments from blocked users.
* It uses a data attribute `data-block-checked` to ensure elements are processed only once ("cache" mechanism).
*/
function processPageContent() {
if (!config.isEnabled) return;
// 1. Filter posts on any list page
document.querySelectorAll('.article-list__item:not([data-block-checked])').forEach(post => {
post.dataset.blockChecked = 'true'; // Mark as processed
const userLink = post.querySelector('.article-list__user a:first-child');
if (userLink) {
const username = userLink.href.split('/member/')[1];
if (username && config.blockedUsers.includes(username)) {
post.style.display = 'none';
}
}
});
// 2. Filter comments on article pages
if (window.location.pathname.startsWith('/article/')) {
document.querySelectorAll('.commentList > li:not([data-block-checked])').forEach(comment => {
comment.dataset.blockChecked = 'true'; // Mark as processed
const userLink = comment.querySelector('a[href*="/member/"]');
if (userLink) {
const username = userLink.href.split('/member/')[1];
if (username && config.blockedUsers.includes(username)) {
comment.style.display = 'none';
}
}
});
}
}
/**
* If the article's author is blocked, show a modal overlay.
*/
function handleBlockedAuthor() {
const authorLink = document.querySelector('a[rel="author"]');
if (!authorLink) return;
const username = authorLink.href.split('/member/')[1];
const title = document.title.substring(0, document.title.length - 5);
if (username && config.blockedUsers.includes(username)) {
const modal = document.createElement('div');
modal.id = 'author-blocked-modal';
modal.innerHTML = `
<div class="modal-content">
<h2>关于《${title}》</h2>
<p>该贴主(<b>${username}</b>)已被加入屏蔽列表 </p>
<button id="continue-reading-btn">继续阅读</button>
<button id="back-home-btn">返回上一页</button>
<a href="https://ld246.com/">返回主页</a>
</div>
`;
document.body.appendChild(modal);
document.getElementById('continue-reading-btn').addEventListener('click', () => {
modal.remove();
});
document.getElementById('back-home-btn').addEventListener('click', () => {
history.back();
});
}
}
/**
* Adds a "Block User" button next to the author's name on article pages.
*/
function addBlockButtonToArticlePage() {
setTimeout(() => {
const sideUserDiv = document.querySelector('.article__sideuser');
const authorLink = document.querySelector('a[rel="author"]');
if (!sideUserDiv || !authorLink || sideUserDiv.querySelector('.user-block-btn')) return;
const username = authorLink.href.split('/member/')[1];
if (!username) return;
const blockButton = document.createElement('button');
blockButton.className = 'btn btn--small user-block-btn';
blockButton.style.marginLeft = '10px';
blockButton.style.color = 'white';
blockButton.style.border = 'none';
const updateButtonState = () => {
blockButton.textContent = config.blockedUsers.includes(username) ? 'Unblock User' : 'Block User';
blockButton.style.backgroundColor = config.blockedUsers.includes(username) ? '#5cb85c' : '#d9534f';
};
blockButton.addEventListener('click', () => {
const isCurrentlyBlocked = config.blockedUsers.includes(username);
if (isCurrentlyBlocked) {
config.blockedUsers = config.blockedUsers.filter(u => u !== username);
} else {
config.blockedUsers.push(username);
}
saveConfig();
alert(`User ${username} has been ${isCurrentlyBlocked ? 'unblocked' : 'blocked'}.`);
updateButtonState();
if (!isCurrentlyBlocked) location.reload();
});
updateButtonState();
sideUserDiv.appendChild(blockButton);
}, 500);
}
// --- UI PANEL (Accessed via Menu Command) ---
function createManagementPanel() {
const panel = document.createElement('div');
panel.id = 'blocker-management-panel';
panel.style.display = 'none';
document.body.appendChild(panel);
function renderPanel() {
panel.innerHTML = `
<div class="panel-header">
<h4>Manage Blocked Users</h4>
<span class="close-btn" title="Close">×</span>
</div>
<div class="panel-content">
<h5>Blocked Users List</h5>
<div id="blocked-users-list">
${config.blockedUsers.map(user => `
<div class="blocked-user-item">
<span>${user}</span>
<button data-user="${user}" class="remove-btn">Remove</button>
</div>`).join('') || '<p>No users blocked.</p>'}
</div>
</div>`;
panel.querySelector('.close-btn').addEventListener('click', toggleManagementPanel);
panel.querySelectorAll('.remove-btn').forEach(button => {
button.addEventListener('click', (e) => {
const userToRemove = e.target.getAttribute('data-user');
config.blockedUsers = config.blockedUsers.filter(u => u !== userToRemove);
saveConfig();
renderPanel();
location.reload();
});
});
}
renderPanel();
}
// --- STYLES ---
function addGlobalStyles() {
GM_addStyle(`
/* Management Panel Styles */
#blocker-management-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 350px; background-color: #fff; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); z-index: 2000; border: 1px solid #ddd; font-family: sans-serif; }
#blocker-management-panel .panel-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background-color: #f7f7f7; border-bottom: 1px solid #ddd; }
#blocker-management-panel h4, #blocker-management-panel h5 { margin: 0; padding: 0; }
#blocker-management-panel h5 { margin-bottom: 10px; }
#blocker-management-panel .close-btn { font-size: 24px; cursor: pointer; color: #888; }
#blocker-management-panel .panel-content { padding: 15px; max-height: 400px; overflow-y: auto; }
#blocker-management-panel .blocked-user-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
#blocker-management-panel .remove-btn { background-color: #007bff; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; }
#blocker-management-panel p { font-size: 0.9em; color: #666; }
/* Blocked Author Modal Styles */
#author-blocked-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.75); z-index: 9999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); }
#author-blocked-modal .modal-content { background-color: #fff; padding: 30px 40px; border-radius: 8px; text-align: center; box-shadow: 0 5px 20px rgba(0,0,0,0.4); }
#author-blocked-modal h2 { margin-top: 0; }
#author-blocked-modal button { background-color: #28a745; color: white; border: none; padding: 10px 20px; font-size: 1em; border-radius: 5px; cursor: pointer; margin-top: 15px; margin-right: 10px; }
`);
}
// --- INITIALIZATION ---
function init() {
addGlobalStyles();
createManagementPanel();
if (!config.isEnabled) {
console.log('Ld246 User Blocker is disabled.');
return;
}
// --- Robust Content Filtering Strategy ---
// 1. Initial run for content already on the page.
processPageContent();
// 2. MutationObserver for efficiently handling dynamically added content.
const observer = new MutationObserver(processPageContent);
observer.observe(document.body, { childList: true, subtree: true });
// 3. Polling as a fallback for any content loading method that might bypass the observer.
// This is the "兜底操作" (fallback operation).
setInterval(processPageContent, 750); // Check roughly every 0.75 seconds.
// --- Page-Specific Logic ---
if (window.location.pathname.startsWith('/article/')) {
handleBlockedAuthor();
addBlockButtonToArticlePage();
}
}
// Run the script after the DOM is ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于