您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hides posts and quoted text from a defined list of users [quotes supported]
// ==UserScript== // @license MIT // @name fokse's d2jsp post blocker (posts + quotes) // @author Fokse // @description Hides posts and quoted text from a defined list of users [quotes supported] // @namespace jsppostblocker // @include https://forums.d2jsp.org/topic.php?t=*&f=* // @include https://forums.d2jsp.org/topic.php?t=* // @include https://forums.d2jsp.org/post.php // @require http://code.jquery.com/jquery-latest.js // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @version 1.23 // ==/UserScript== /** OPTIONS **/ const QUOTE_REDACTION_MODE = 'hide'; // 'hide' or 'stars' /** STORAGE KEYS **/ const IDS_KEY = 'fokse_post_blocker_userlist'; // existing: array of userId strings const NAMES_KEY = 'fokse_post_blocker_usernames'; // new: array of username strings /** UTILS **/ const getArr = (k) => { const v = GM_getValue(k); return Array.isArray(v) ? v : []; }; const setArr = (k, arr) => GM_setValue(k, Array.from(new Set(arr))); // dedupe const norm = (s) => (s || '').replace(/\s+/g, ' ').trim().toLowerCase(); if (!Array.isArray(GM_getValue(IDS_KEY))) GM_setValue(IDS_KEY, []); if (!Array.isArray(GM_getValue(NAMES_KEY))) GM_setValue(NAMES_KEY, []); function placeholderHtml(txt) { return `<center><b><span style="color:#d65a5a;">${txt}</span></b></center>`; } function redactTextWithStars($root) { // Replace visible text characters with * $root.find('*').addBack().contents().each(function() { if (this.nodeType === 3) { // text node this.nodeValue = this.nodeValue.replace(/\S/g, '*'); } }); // Optionally remove media inside quotes $root.find('img, video, iframe').remove(); } function parseQuoteNameFromHeaderText(t) { // Tries common patterns: // "Quote (UserName @ Sep 5 2025 02:39pm)" // "Quote (UserName)" // "Quote by UserName" t = (t || '').trim(); let m = t.match(/^Quote\s*\(([^@)]+)\s*@/i) || t.match(/^Quote\s*\(([^)]+)\)/i) || t.match(/^Quote\s+by\s+([^,]+)\b/i); return m ? m[1].trim() : null; } // SIMPLE QUOTE BLOCKER - runs every 2 seconds function blockQuotes() { const blockedNames = getArr(NAMES_KEY).map(norm); if (blockedNames.length === 0) return; // Find all quote1 elements and check if next sibling is quote2 $('.quote1').each(function() { const quoteHeader = $(this).text(); const username = parseQuoteNameFromHeaderText(quoteHeader); if (username && blockedNames.includes(norm(username))) { const $quoteContent = $(this).next('.quote2'); if ($quoteContent.length && !$quoteContent.hasClass('blocked-quote')) { $quoteContent.addClass('blocked-quote'); if (QUOTE_REDACTION_MODE === 'stars') { redactTextWithStars($quoteContent); } else { $quoteContent.html(placeholderHtml('Quote from blocked user hidden')); } } } }); } function hideOrRedactQuote($headerEl, blockedNameSet) { // Try to find the body immediately after the header; cover multiple class names const bodySel = '.qmsg, .qp, .q1p, .quote-msg, .qbody'; let $body = $headerEl.nextAll(bodySel).first(); // Fallback: if no known body element, use the next sibling block element if (!$body.length) { const $n = $headerEl.next(); if ($n.length) $body = $n; } if ($body && $body.length) { if (QUOTE_REDACTION_MODE === 'stars') { redactTextWithStars($body); } else { $body.html(placeholderHtml('Quote from blocked user hidden')); } return true; } // Final fallback: collapse entire quote container if it looks like one const $container = $headerEl.closest('.quote, .q1, .qbox, .qtbox'); if ($container.length) { $container.html(placeholderHtml('Quote from blocked user hidden')); return true; } return false; } function processQuotes() { const blockedNames = getArr(NAMES_KEY).map(norm); const blockedNameSet = new Set(blockedNames); if (!blockedNames.length) return; // Likely quote header selectors on d2jsp (cover variants) const headerSel = '.qheader, .qt, .quote .top, .q1w, .quote > .top, .quote .qheader'; // Pass 1: obvious headers $(headerSel).each(function() { const name = parseQuoteNameFromHeaderText($(this).text()); if (name && blockedNameSet.has(norm(name))) { hideOrRedactQuote($(this), blockedNameSet); } }); // Pass 2: generic heuristic — a single-line block starting with "Quote (" $('div, td').filter(function () { const txt = $(this).clone().children().remove().end().text().trim(); return /^Quote\s*\(/i.test(txt) && $(this).children().length === 0; }).each(function() { const name = parseQuoteNameFromHeaderText($(this).text()); if (name && blockedNameSet.has(norm(name))) { hideOrRedactQuote($(this), blockedNameSet); } }); } function processPosts() { let blockedIds = getArr(IDS_KEY); let blockedNames = getArr(NAMES_KEY); $('body > form > dl').each(function() { const $userLink = $('.pU > div > a', this); const href = $userLink.attr('href') || ''; if (!href.includes('user.php?i=')) return; const userId = href.split('=').pop(); const userName = $userLink.text().trim(); // If ID is blocked, ensure the name is also stored (backfill) if (~blockedIds.indexOf(userId) && userName && !~blockedNames.map(norm).indexOf(norm(userName))) { blockedNames.push(userName); setArr(NAMES_KEY, blockedNames); } if (~blockedIds.indexOf(userId)) { $('dd > div > div.bc1.upc > div.desc.cl.rc > div.fR.links', this) .prepend(`<b><a href="#" class="blockPost" action="unblock" userId="${userId}" userName="${userName.replace(/"/g,'"')}">Unblock Posts</a></b>`); $('dd > div > div.bc1.upc > .sig', this).hide(); $('.pU', this).children().eq(1).hide(); $('div.bts', this).html(placeholderHtml('Post from that user is hidden')); } else { $('dd > div > div.bc1.upc > div.desc.cl.rc > div.fR.links', this) .prepend(`<b><a href="#" class="blockPost" action="block" userId="${userId}" userName="${userName.replace(/"/g,'"')}">Block Posts</a></b>`); } }); // Click handler for block/unblock $('.blockPost').off('click').on('click', function(e){ e.preventDefault(); let blockedIds = getArr(IDS_KEY); let blockedNames = getArr(NAMES_KEY); const userId = $(this).attr('userId'); const userName = ($(this).attr('userName') || '').trim(); if ($(this).attr('action') === 'block') { if (!~blockedIds.indexOf(userId)) blockedIds.push(userId); if (userName && !~blockedNames.map(norm).indexOf(norm(userName))) blockedNames.push(userName); } else { blockedIds = blockedIds.filter(id => id !== userId); if (userName) { const n = norm(userName); blockedNames = blockedNames.filter(x => norm(x) !== n); } } setArr(IDS_KEY, blockedIds); setArr(NAMES_KEY, blockedNames); location.reload(); }); } function parsePage(){ processPosts(); processQuotes(); // NEW: Simple quote blocker that runs once immediately blockQuotes(); // Handle dynamically added content (e.g., quick replies) const mo = new MutationObserver(() => { processQuotes(); blockQuotes(); }); mo.observe(document.body, { childList: true, subtree: true }); } parsePage();