NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name V2EX Topic Filter // @namespace http://tampermonkey.net/ // @version 0.1 // @description Filter V2EX topics based on keywords // @copyright 2025 // @license MIT // @author Itachy // @match https://v2ex.com/* // @match https://www.v2ex.com/* // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; // Default settings const defaultKeywords = []; // Get or initialize keywords list let keywords = GM_getValue('v2exFilterKeywords', defaultKeywords); // Create settings panel function createSettingsPanel() { const panel = document.createElement('div'); panel.style.cssText = ` position: fixed; top: 20px; right: 20px; background: white; padding: 15px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; display: none; `; panel.innerHTML = ` <h3 style="margin: 0 0 10px 0">V2EX Topic Filter Settings</h3> <p style="margin: 5px 0">Keywords (one per line):</p> <textarea id="v2exKeywords" style="width: 200px; height: 100px; margin: 5px 0">${keywords.join('\n')}</textarea> <br> <button id="v2exSaveSettings" style="margin: 5px 0">Save</button> `; document.body.appendChild(panel); return panel; } // Create toggle button function createToggleButton() { const button = document.createElement('button'); button.textContent = '⚙️ Filter Settings'; button.style.cssText = ` position: fixed; top: 10px; right: 10px; z-index: 9998; padding: 5px 10px; border-radius: 3px; background: #fff; border: 1px solid #ccc; cursor: pointer; `; document.body.appendChild(button); return button; } // Filter topics function filterTopics() { const topics = document.querySelectorAll('.cell.item'); topics.forEach(topic => { const title = topic.querySelector('.topic-link')?.textContent.toLowerCase() || ''; const shouldHide = keywords.some(keyword => keyword && title.includes(keyword.toLowerCase()) ); if (shouldHide) { topic.style.display = 'none'; } }); } // Initialize UI const settingsPanel = createSettingsPanel(); const toggleButton = createToggleButton(); // Add event listeners toggleButton.addEventListener('click', () => { settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none'; }); document.getElementById('v2exSaveSettings').addEventListener('click', () => { const newKeywords = document.getElementById('v2exKeywords').value .split('\n') .map(k => k.trim()) .filter(k => k); keywords = newKeywords; GM_setValue('v2exFilterKeywords', newKeywords); // Re-filter topics with new keywords filterTopics(); // Hide panel after saving settingsPanel.style.display = 'none'; }); // Initial filtering filterTopics(); // Handle dynamic content loading (if V2EX uses AJAX) const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.addedNodes.length) { filterTopics(); break; } } }); // Start observing the main content area const mainContent = document.querySelector('#Main'); if (mainContent) { observer.observe(mainContent, { childList: true, subtree: true }); } })();