|
mcdouglasx (OP)
|
 |
May 21, 2026, 02:57:24 PM Last edit: May 21, 2026, 11:40:19 PM by mcdouglasx Merited by Welsh (5), klarki (3), nutildah (3), Halab (2), Mia Chloe (2), davis196 (1), dkbit98 (1), Ambatman (1) |
|
Since some users may find navigating the forum tedious, citing spam as a problem that prevents them from enjoying the experience as they would like, in an attempt to minimize spam or make their visit more pleasant, I have added a series of filters to a Tampermonkey userscript that allows for various types of post filtering. It includes: 1- Board filters tab: This tab allows you to select which boards will be excluded from the unread and unreadreplies sections. 2- Filtering by posts in order of most/least views and vice versa. 3- Filtering posts according to the number of merits of the OPs (this option is adjustable by us). 4-Keyword Filter: To hide posts that contain certain words in their title, based on a custom list.( new) // ==UserScript== // @name Bitcointalk Board Filter + Priority (Views / Merit) // @version 1.1 // @description Filter topics by board, Control filters global, Keyword filtering. // @author Mcdouglasx // @match https://bitcointalk.org/index.php?action=unreadreplies* // @match https://bitcointalk.org/index.php?action=unread* // @match https://bitcointalk.org/index.php?board=* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @run-at document-idle // ==/UserScript==
(function() { 'use strict';
const boardNames = { 1: "Bitcoin Discussion", 4: "Bitcoin Technical Support", 5: "Marketplace", 6: "Development & Technical Discussion", 7: "Economics", 8: "Trading Discussion", 9: "Off-topic", 10: "Pyccкий (Russian)", 11: "Other languages/locations", 12: "Project Development", 13: "Français", 14: "Mining", 16: "Deutsch (German)", 17: "Chinese students", 18: "Paзнoe", 19: "Юpиcты", 20: "Tpeйдepы", 21: "Maйнepы", 22: "Hoвички", 23: "Бизнec", 24: "Meta", 25: "Obsolete (buying)", 26: "Obsolete (selling)", 27: "Español (Spanish)", 28: "Italiano (Italian)", 29: "Português (Portuguese)", 30: "中文 (Chinese)", 31: "Mercado y Economía", 32: "Hardware y Minería", 33: "Esquina Libre", 34: "Politics & Society", 35: "Biete", 36: "Suche", 37: "Wallet software", 39: "Beginners & Help", 40: "Mining support", 41: "Pools", 42: "Mining software (miners)", 44: "CPU/GPU Bitcoin mining hardware", 45: "Skandinavisk", 46: "Mercato valute", 47: "Discussions générales et utilisation du Bitcoin", 48: "Mining et Hardware", 49: "Place de marché", 50: "Hors-sujet", 51: "Goods", 52: "Services", 53: "Currency exchange", 54: "Wiki, documentation et traduction", 55: "Xaйпы", 56: "Gambling", 57: "Speculation", 59: "Archival", 60: "Mining (Deutsch)", 61: "Trading und Spekulation", 62: "Anfänger und Hilfe", 63: "Projektentwicklung", 64: "Off-Topic (Deutsch)", 65: "Lending", 66: "Кoдepы", 67: "Altcoin Discussion", 69: "Economia & Mercado", 70: "Mineração em Geral", 71: "Games and rounds", 72: "Aльтepнaтивныe кpиптoвaлюты", 73: "Auctions", 74: "Legal", 75: "Computer hardware", 76: "Hardware", 77: "Press", 78: "Securities", 79: "Nederlands (Dutch)", 80: "Markt", 81: "Mining speculation", 82: "한국어 (Korean)", 83: "Scam Accusations", 84: "Service Announcements", 85: "Service Discussion", 86: "Meetups", 87: "Important Announcements", 88: "Long-term offers", 89: "India", 90: "Идeи", 91: "Пoлитикa", 92: "Кopзинa", 93: "Digital goods", 94: "Gokken/lotterijen", 95: "עברית (Hebrew)", 97: "Armory", 98: "Electrum", 99: "MultiBit", 100: "Bitcoin Wallet for Android", 101: "Mercadillo", 102: "Mexico", 103: "Argentina", 104: "España", 105: "Centroamerica y Caribe", 107: "Beni", 108: "Română (Romanian)", 109: "Anunturi importante", 110: "Offtopic", 111: "Market", 112: "Tutoriale", 113: "Bine ai venit!", 114: "Presa", 115: "Mining (Italiano)", 116: "Mining (Nederlands)", 117: "跳蚤市场", 118: "山寨币", 119: "媒体", 120: "Eλληνικά (Greek)", 121: "Mining (India)", 122: "Marketplace (India)", 123: "Regional Languages (India)", 124: "Press & News from India", 125: "Alt Coins (India)", 126: "Buyer/ Seller Reputations (India)", 127: "Off-Topic (India)", 128: "Hoвocти", 129: "Reputation", 130: "Primeros pasos y ayuda", 131: "Primeiros Passos (Iniciantes)", 132: "Alt-Currencies (Italiano)", 133: "Türkçe (Turkish)", 134: "Brasil", 135: "Portugal", 136: "Aγoρά", 137: "Group buys", 138: "BitcoinJ", 139: "Treffen", 140: "Presse ", 141: "Auktionen", 142: "Polski", 143: "Beurzen", 144: "Raduni/Meeting (Italiano)", 145: "Off-Topic (Italiano)", 146: "挖矿", 147: "Alt Coins (Nederlands)", 148: "Off-topic (Nederlands)", 149: "Altcoins (Français)", 150: "Meetings (Nederlands)", 151: "Altcoins (criptomonedas alternativas)", 152: "Altcoins (Deutsch)", 153: "Guide (Italiano)", 155: "Pazar Alanı", 156: "Madencilik", 157: "Alternatif Kripto-Paralar", 158: "Konu Dışı", 159: "Announcements (Altcoins)", 160: "Mining (Altcoins)", 161: "Marketplace (Altcoins)", 162: "Accuse scam/truffe", 163: "Tablica ogłoszeń", 164: "Alternatywne kryptowaluty", 165: "Crittografia e decentralizzazione", 166: "Minerit", 167: "New forum software", 168: "Bitcoin Wiki", 169: "Progetti", 170: "Mercato", 171: "Servizi", 172: "Esercizi commerciali", 173: "Hardware/Mining (Italiano)", 174: "Yeni Başlayanlar & Yardım", 175: "Trading, analisis e speculazione", 176: "Annunci", 177: "Minería de altcoins", 178: "Anunturi Monede Alternative", 179: "Altcoins (Eλληνικά)", 180: "Bitcoin Haberleri", 181: "Criptomoedas Alternativas", 182: "대체코인 Alt Coins (한국어)", 183: "Actualité et News", 184: "Vos sites et projetos", 185: "Paбoтa", 186: "Développement et technique", 187: "Économie et spéculation", 188: "Le Bitcoin et la loi", 189: "Ekonomi", 190: "Servisler", 191: "Bahasa Indonesia (Indonesian)", 192: "Altcoins (Bahasa Indonesia)", 193: "Marketplace (Bahasa Indonesia)", 194: "Mining (Bahasa Indonesia)", 195: "Mining Discussion (Eλληνικά)", 196: "离题万里", 197: "Service Announcements (Altcoins)", 198: "Service Discussion (Altcoins)", 199: "Pools (Altcoins)", 200: "Gambling (Italiano)", 201: "Hrvatski (Croatian)", 202: "Servicios", 203: "Trading y especulación", 204: "Servicios", 205: "Discussioni avanzate e sviluppo", 206: "Desenvolvimento & Discussões Técnicas", 207: "Investor-based games", 208: "Débutants", 209: "Échanges", 210: "Produits et services", 211: "Petites annonces", 212: "Micro Earnings", 217: "Collectibles", 219: "Pilipinas", 220: "Trgovina", 221: "Altcoins (Hrvatski)", 222: "Web Wallets", 223: "Exchanges", 224: "Speculation (Altcoins)", 227: "Investigations", 228: "Gambling discussion", 229: "Proje Geliştirme", 230: "Buluşmalar", 231: "Mycelium", 232: "Fonlar", 234: "Invites & Accounts", 235: "Madencilik (Alternatif Kripto-Paralar)", 236: "Бapaxoлкa", 237: "Oбмeнники", 238: "Bounties (Altcoins)", 239: "Duyurular (Alternatif Kripto-Paralar)", 240: "Tokens (Altcoins)", 241: "العربية (Arabic)", 242: "العملات البديلة (Altcoins)", 243: "Altcoins (Pilipinas)", 246: "Altcoin Announcements (Eλληνικά)", 247: "Altcoin Mining (Eλληνικά)", 248: "Toкeны", 250: "Serious discussion", 251: "Ivory Tower", 252: "日本語 (Japanese)", 253: "إstefsarat wa as'ilat al-mubtadi'in", 254: "Tokens (Español)", 255: "アルトコイン", 256: "Бayнти и aиpдpoпы", 257: "Discutii Servicii", 258: "Annonces", 259: "Altcoins (Monede Alternative)", 260: "Altcoin Announcements (Pilipinas)", 261: "Hardware wallets", 262: "Oбcyждeниe Bitcoin (Russian)", 263: "Nowe kryptowaluty i tokeny", 264: "Tablica ogłoszeń (altcoiny)", 265: "النقاشات", 266: "التعدين", 267: "النقاشات الأخرى", 268: "Pamilihan", 269: "Marktplatz", 270: "Announcements (Deutsch)", 271: "منصات التبادل", 272: "Off-topic (Hrvatski)", 273: "Announcements (Hrvatski)", 274: "Others (Pilipinas)", 275: "Nigeria (Naija)", 276: "Trading dan Spekulasi", 277: "Ekonomi, Politik, dan Budaya", 278: "Topik Lainnya", 279: "Politics and society (Naija)", 280: "Off-topic (Naija)" };
let hiddenSubforums = {}; let blockedKeywords = []; let checkboxes = {}; let isCollapsed = true; let isKeywordCollapsed = true; let priorityMode = 'none'; let meritThreshold = 0; let enableMeritFilter = false; let meritCache = {};
function loadPref() { try { hiddenSubforums = JSON.parse(GM_getValue('hiddenSubforums', '{}')); blockedKeywords = JSON.parse(GM_getValue('blockedKeywords', '[]')); isCollapsed = GM_getValue('isCollapsed', true); isKeywordCollapsed = GM_getValue('isKeywordCollapsed', true); priorityMode = GM_getValue('priorityMode', 'none'); meritThreshold = parseInt(GM_getValue('meritThreshold', 0), 10); enableMeritFilter = GM_getValue('enableMeritFilter', false); meritCache = JSON.parse(GM_getValue('meritCache', '{}'));
const now = Date.now(); for (const uid in meritCache) { if (Object.prototype.hasOwnProperty.call(meritCache, uid) && (now - meritCache[uid].timestamp > 3600000)) { delete meritCache[uid]; } } } catch(e) { console.error("Error", e); } }
function savePref() { GM_setValue('hiddenSubforums', JSON.stringify(hiddenSubforums)); GM_setValue('blockedKeywords', JSON.stringify(blockedKeywords)); GM_setValue('isCollapsed', isCollapsed); GM_setValue('isKeywordCollapsed', isKeywordCollapsed); GM_setValue('priorityMode', priorityMode); GM_setValue('meritThreshold', meritThreshold); GM_setValue('enableMeritFilter', enableMeritFilter); GM_setValue('meritCache', JSON.stringify(meritCache)); }
function getBoardId(row) { const link = row.querySelector('td:nth-child(3) span.smalltext a[href*="board="]'); if (link) { const match = link.href.match(/board=(\d+)/); if (match && match[1]) return parseInt(match[1], 10); } return null; }
function getViews(row) { const viewsCell = row.querySelector('td:nth-child(6)'); if (viewsCell) { return parseInt(viewsCell.textContent.replace(/,/g, '').trim(), 10) || 0; } return 0; }
function getAuthorId(row) { const authorLink = row.querySelector('td:nth-child(4) a'); if (authorLink) { const match = authorLink.href.match(/u=(\d+)/); if (match && match[1]) return parseInt(match[1], 10); } return null; }
function fetchMerit(userId) { return new Promise(function(resolve) { const now = Date.now(); if (meritCache[userId] && (now - meritCache[userId].timestamp < 3600000)) { resolve(meritCache[userId].merit); return; }
const url = 'https://bitcointalk.org/index.php?action=merit;u=' + userId; GM_xmlhttpRequest({ method: 'GET', url: url, window: true, anonymous: false, onload: function(resp) { let merit = 0; if (resp.status === 200) { const html = resp.responseText; const match = html.match(/<p>\s*Merit:\s*([0-9,]+)\s*<\/p>/i) || html.match(/Merit:\s*([0-9,]+)/i); if (match) { merit = parseInt(match[1].replace(/,/g, ''), 10); } } meritCache[userId] = { merit: merit, timestamp: Date.now() }; savePref(); resolve(merit); }, onerror: function(err) { console.error('Error' + userId, err); meritCache[userId] = { merit: 0, timestamp: Date.now() }; savePref(); resolve(0); } }); }); }
async function fetchAllMerits(rows) { const uniqueAuthors = {}; for (let i = 0; i < rows.length; i++) { const uid = getAuthorId(rows[i]); if (uid && !uniqueAuthors[uid]) { uniqueAuthors[uid] = rows[i]; } } const authorIds = Object.keys(uniqueAuthors); if (authorIds.length === 0) return; const promises = []; for (let i = 0; i < authorIds.length; i++) { promises.push(fetchMerit(parseInt(authorIds[i], 10))); } await Promise.all(promises); }
async function runPipeline() { const mainTable = document.querySelector('table.bordercolor[cellspacing="1"][cellpadding="4"]'); if (!mainTable) return; const tbody = mainTable.querySelector('tbody'); if (!tbody) return;
const allRows = Array.from(tbody.querySelectorAll('tr:not(.titlebg)')); if (allRows.length === 0) return;
const isUnreadPage = window.location.search.includes('action=unread');
let currentRows = allRows.filter(row => { // Filter Boards if (isUnreadPage) { const boardId = getBoardId(row); const isBoardHidden = boardId && hiddenSubforums[boardId]; if (isBoardHidden) { row.style.display = 'none'; return false; } } // Filter Keywords const titleLink = row.querySelector('td:nth-child(3) a'); if (titleLink) { const titleText = titleLink.textContent.toLowerCase(); for (let i = 0; i < blockedKeywords.length; i++) { if (titleText.includes(blockedKeywords[i].toLowerCase())) { row.style.display = 'none'; return false; } } }
row.style.display = ''; return true; });
if (enableMeritFilter) { const msgDiv = document.createElement('div'); msgDiv.textContent = 'Verifying OP Merit requirements...'; msgDiv.style.cssText = 'background:#ffffcc;padding:5px;margin:5px 0;border:1px solid #ccc;text-align:center;font-weight:bold;color:#000;'; const priorityPanel = document.querySelector('#priorityFilter'); if (priorityPanel) priorityPanel.parentNode.insertBefore(msgDiv, priorityPanel.nextSibling);
await fetchAllMerits(currentRows); if (msgDiv && msgDiv.parentNode) msgDiv.remove();
currentRows = currentRows.filter(row => { const uid = getAuthorId(row); const userMerit = (uid && meritCache[uid]) ? parseInt(meritCache[uid].merit, 10) : 0; if (userMerit < meritThreshold) { row.style.display = 'none'; return false; } return true; }); }
if (priorityMode === 'views') { currentRows.sort((a, b) => getViews(b) - getViews(a)); } else if (priorityMode === 'views_asc') { currentRows.sort((a, b) => getViews(a) - getViews(b)); }
for (let i = 0; i < currentRows.length; i++) { tbody.appendChild(currentRows[i]); } }
function buildBoardFilterUI() { const container = document.createElement('div'); container.id = 'subforumFilter'; container.style.cssText = 'background:#e9f1f6;padding:8px;margin:10px 0;border:1px solid #c9d2d9;border-radius:3px;color:#000;font-family:Verdana,sans-serif;font-size:13px;box-shadow:0 1px 3px rgba(0,0,0,0.1);';
const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.marginBottom = '5px'; const title = document.createElement('h3'); title.textContent = 'Board Filter'; title.style.cssText = 'margin:0; color:#333; font-size:14px; font-weight:bold;'; const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggleBtn'; toggleBtn.style.cssText = 'padding:4px 8px;background:#3a6ea5;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;font-weight:bold;';
header.appendChild(title); header.appendChild(toggleBtn); container.appendChild(header); const content = document.createElement('div'); content.id = 'filterContent'; content.style.display = 'none'; const grid = document.createElement('div'); grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px;max-height:300px;overflow-y:auto;padding:5px;margin-bottom:10px;border:1px solid #c9d2d9;border-radius:3px;background:#fff;';
const boardEntries = Object.entries(boardNames).sort((a, b) => a[1].localeCompare(b[1])); for (let i = 0; i < boardEntries.length; i++) { const id = boardEntries[i][0]; const name = boardEntries[i][1]; const label = document.createElement('label'); label.style.cssText = 'display:flex;align-items:center;margin-bottom:4px;font-size:12px;color:#333;cursor:pointer;'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.value = id; cb.checked = hiddenSubforums[id] || false; cb.style.marginRight = '6px'; checkboxes[id] = cb; label.appendChild(cb); label.appendChild(document.createTextNode(name)); grid.appendChild(label); } content.appendChild(grid); const btnContainer = document.createElement('div'); btnContainer.style.display = 'flex'; btnContainer.style.gap = '8px';
const applyBtn = document.createElement('button'); applyBtn.textContent = 'Apply Board Changes'; applyBtn.style.cssText = 'padding:6px 12px;background:#3a6ea5;color:white;border:none;border-radius:3px;cursor:pointer;font-size:12px;font-weight:bold;'; applyBtn.addEventListener('click', function() { for (const id in checkboxes) { if (Object.prototype.hasOwnProperty.call(checkboxes, id)) hiddenSubforums[id] = checkboxes[id].checked; } savePref(); runPipeline().catch(err => console.error(err)); });
btnContainer.appendChild(applyBtn); content.appendChild(btnContainer); container.appendChild(content); toggleBtn.addEventListener('click', function() { isCollapsed = !isCollapsed; savePref(); content.style.display = isCollapsed ? 'none' : 'block'; toggleBtn.innerHTML = isCollapsed ? '▶ Show Filter' : '▼ Hide Filter'; }); return container; }
function buildKeywordFilterUI() { const container = document.createElement('div'); container.id = 'keywordFilter'; container.style.cssText = 'background:#f6e9e9;padding:8px;margin:10px 0;border:1px solid #d9c9c9;border-radius:3px;color:#000;font-family:Verdana,sans-serif;font-size:13px;box-shadow:0 1px 3px rgba(0,0,0,0.1);';
const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.marginBottom = '5px'; const title = document.createElement('h3'); title.textContent = 'Keyword Filter'; title.style.cssText = 'margin:0; color:#333; font-size:14px; font-weight:bold;'; const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggleKeywordBtn'; toggleBtn.style.cssText = 'padding:4px 8px;background:#a53a3a;color:white;border:none;border-radius:3px;cursor:pointer;font-size:11px;font-weight:bold;';
header.appendChild(title); header.appendChild(toggleBtn); container.appendChild(header); const content = document.createElement('div'); content.id = 'keywordContent'; content.style.display = 'none';
const inputArea = document.createElement('div'); inputArea.style.marginBottom = '8px'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Add keyword...'; input.style.cssText = 'padding:4px; width:70%;'; const addBtn = document.createElement('button'); addBtn.textContent = 'Add'; addBtn.style.cssText = 'padding:4px 8px; margin-left:5px; cursor:pointer;'; inputArea.appendChild(input); inputArea.appendChild(addBtn); content.appendChild(inputArea);
const tagsContainer = document.createElement('div'); tagsContainer.id = 'tagsContainer'; tagsContainer.style.cssText = 'display:flex; flex-wrap:wrap; gap:5px; margin-top:5px;'; content.appendChild(tagsContainer);
function refreshTags() { tagsContainer.innerHTML = ''; blockedKeywords.forEach((kw, index) => { const tag = document.createElement('span'); tag.style.cssText = 'background:#ddd; padding:2px 6px; border-radius:3px; cursor:pointer; font-size:11px;'; tag.textContent = kw + ' [x]'; tag.onclick = () => { blockedKeywords.splice(index, 1); savePref(); refreshTags(); runPipeline(); }; tagsContainer.appendChild(tag); }); }
addBtn.onclick = () => { if (input.value.trim() !== '') { blockedKeywords.push(input.value.trim()); input.value = ''; savePref(); refreshTags(); runPipeline(); } };
refreshTags(); container.appendChild(content);
toggleBtn.addEventListener('click', function() { isKeywordCollapsed = !isKeywordCollapsed; savePref(); content.style.display = isKeywordCollapsed ? 'none' : 'block'; toggleBtn.innerHTML = isKeywordCollapsed ? '▶ Show Keywords' : '▼ Hide Keywords'; }); return container; }
function buildPriorityUI() { const container = document.createElement('div'); container.id = 'priorityFilter'; container.style.cssText = 'background:#e9f1f6;padding:10px;margin:10px 0;border:1px solid #c9d2d9;border-radius:3px;font-family:Verdana,sans-serif;font-size:13px;color:#000;'; const title = document.createElement('h3'); title.textContent = 'Control filters'; title.style.cssText = 'margin:0 0 10px 0; color:#333; font-size:14px; font-weight:bold;'; const flexBlock = document.createElement('div'); flexBlock.style.cssText = 'display:flex; flex-wrap:wrap; gap:15px; align-items:center;';
const orderWrapper = document.createElement('div'); orderWrapper.innerHTML = '<strong>Sorting Order: </strong>'; const modeSelect = document.createElement('select'); modeSelect.id = 'priorityModeSelect'; modeSelect.style.cssText = 'padding:4px; margin-left:5px; cursor:pointer;'; const noneOpt = document.createElement('option'); noneOpt.value = 'none'; noneOpt.text = 'None (Default Forum Order)'; const viewsOpt = document.createElement('option'); viewsOpt.value = 'views'; viewsOpt.text = 'Most Views First (High → Low)'; const viewsAscOpt = document.createElement('option'); viewsAscOpt.value = 'views_asc'; viewsAscOpt.text = 'Least Views First (Low → High)'; modeSelect.appendChild(noneOpt); modeSelect.appendChild(viewsOpt); modeSelect.appendChild(viewsAscOpt); modeSelect.value = priorityMode; orderWrapper.appendChild(modeSelect);
const meritWrapper = document.createElement('div'); meritWrapper.style.cssText = 'display:flex; align-items:center; gap:6px; border-left:2px solid #ccc; padding-left:15px;'; const meritCheck = document.createElement('input'); meritCheck.type = 'checkbox'; meritCheck.id = 'enableMeritFilterCheck'; meritCheck.checked = enableMeritFilter; meritCheck.style.cursor = 'pointer'; const meritLabel = document.createElement('label'); meritLabel.htmlFor = 'enableMeritFilterCheck'; meritLabel.innerHTML = '<strong>Exclude topics if OP merit is below:</strong>'; const meritInput = document.createElement('input'); meritInput.type = 'number'; meritInput.id = 'meritThresholdInput'; meritInput.value = meritThreshold; meritInput.style.cssText = 'width:65px; padding:3px; text-align:center;'; meritWrapper.appendChild(meritCheck); meritWrapper.appendChild(meritLabel); meritWrapper.appendChild(meritInput);
const applyPriorityBtn = document.createElement('button'); applyPriorityBtn.textContent = 'Apply & Refresh Feed'; applyPriorityBtn.style.cssText = 'padding:6px 14px; background:#2d5580; color:white; border:none; border-radius:3px; cursor:pointer; font-size:12px; font-weight:bold; box-shadow: 0 1px 2px rgba(0,0,0,0.2); margin-left:auto;'; applyPriorityBtn.addEventListener('click', function() { priorityMode = modeSelect.value; enableMeritFilter = meritCheck.checked; let val = parseInt(meritInput.value, 10); if (isNaN(val) || val < 0) val = 0; meritThreshold = val; savePref(); window.location.reload(); });
flexBlock.appendChild(orderWrapper); flexBlock.appendChild(meritWrapper); flexBlock.appendChild(applyPriorityBtn); container.appendChild(title); container.appendChild(flexBlock); return container; }
function init() { loadPref(); const bodyArea = document.getElementById('bodyarea'); if (!bodyArea) return;
const isUnreadPage = window.location.search.includes('action=unread');
if (isUnreadPage) { const boardFilter = buildBoardFilterUI(); bodyArea.insertBefore(boardFilter, bodyArea.firstChild); }
const keywordFilter = buildKeywordFilterUI(); bodyArea.insertBefore(keywordFilter, bodyArea.firstChild);
const priorityFilter = buildPriorityUI(); if (isUnreadPage) { const boardFilterElement = document.getElementById('subforumFilter'); bodyArea.insertBefore(priorityFilter, boardFilterElement.nextSibling); } else { bodyArea.insertBefore(priorityFilter, bodyArea.firstChild); }
const contentDiv = document.getElementById('filterContent'); const toggleBtn = document.getElementById('toggleBtn'); if (contentDiv && toggleBtn) { contentDiv.style.display = isCollapsed ? 'none' : 'block'; toggleBtn.innerHTML = isCollapsed ? '▶ Show Filter' : '▼ Hide Filter'; }
const kwContentDiv = document.getElementById('keywordContent'); const kwToggleBtn = document.getElementById('toggleKeywordBtn'); if (kwContentDiv && kwToggleBtn) { kwContentDiv.style.display = isKeywordCollapsed ? 'none' : 'block'; kwToggleBtn.innerHTML = isKeywordCollapsed ? '▶ Show Keywords' : '▼ Hide Keywords'; }
runPipeline().catch(err => console.error('Pipeline Error:', err)); }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); 
|