您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Filter catalog threads using regex patterns
当前为
// ==UserScript== // @name 8chan Catalog Filter // @version 1.2 // @description Filter catalog threads using regex patterns // @match *://8chan.moe/*/catalog.* // @grant none // @license MIT // @namespace https://greasyfork.org/users/1459581 // ==/UserScript== (function() { 'use strict'; // Initial configuration - can be modified by the user through the dashboard let config = { filters: [ { pattern: /Arknights|AKG/i, // Example regex pattern action: 'setTop' // 'setTop' or 'remove' } // More filters can be added by the user ] }; // Load saved configuration from localStorage if available function loadConfig() { const savedConfig = localStorage.getItem('8chanCatalogFilterConfig'); if (savedConfig) { try { const parsedConfig = JSON.parse(savedConfig); // Convert string patterns back to RegExp objects parsedConfig.filters = parsedConfig.filters.map(filter => ({ pattern: new RegExp(filter.patternText, filter.flags), patternText: filter.patternText, // Store the raw text pattern action: filter.action })); config = parsedConfig; } catch (e) { console.error('Failed to load saved filters:', e); } } } // Save configuration to localStorage function saveConfig() { // Convert RegExp objects to a serializable format const serializedConfig = { filters: config.filters.map(filter => ({ patternText: filter.patternText || filter.pattern.source, flags: filter.pattern.flags, action: filter.action })) }; localStorage.setItem('8chanCatalogFilterConfig', JSON.stringify(serializedConfig)); } // Create and inject the filter dashboard function createDashboard() { const toolsDiv = document.getElementById('divTools'); if (!toolsDiv) return; // Create container for the filter dashboard const dashboardContainer = document.createElement('div'); dashboardContainer.id = 'filterDashboard'; dashboardContainer.style.marginBottom = '10px'; // Create the dashboard controls const dashboardControls = document.createElement('div'); dashboardControls.className = 'catalogLabel'; dashboardControls.innerHTML = ` <span style="font-weight: bold;">Filters:</span> <button id="showFilterManager" class="catalogLabel" style="margin-left: 5px;">Manage Filters</button> <button id="applyFilters" class="catalogLabel" style="margin-left: 5px;">Apply Filters</button> <span id="activeFiltersCount" style="margin-left: 5px;">(${config.filters.length} active)</span> `; // Create the filter manager panel (initially hidden) const filterManager = document.createElement('div'); filterManager.id = 'filterManagerPanel'; filterManager.style.display = 'none'; filterManager.style.border = '1px solid #ccc'; filterManager.style.padding = '10px'; filterManager.style.marginTop = '5px'; updateFilterManagerContent(filterManager); // Add everything to the dashboard container dashboardContainer.appendChild(dashboardControls); dashboardContainer.appendChild(filterManager); // Insert dashboard before the search box const searchDiv = toolsDiv.querySelector('div[style="float: right; margin-top: 6px;"]'); if (searchDiv) { toolsDiv.insertBefore(dashboardContainer, searchDiv); } else { toolsDiv.appendChild(dashboardContainer); } // Add event listeners document.getElementById('showFilterManager').addEventListener('click', function() { const panel = document.getElementById('filterManagerPanel'); panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; }); document.getElementById('applyFilters').addEventListener('click', function() { processCatalog(); }); } function updateFilterManagerContent(filterManager) { let content = ` <div style="margin-bottom: 10px;"> <h4 style="margin: 0 0 5px 0;">Current Filters</h4> <table style="width: 100%; border-collapse: collapse;"> <thead> <tr> <th style="text-align: left; padding: 2px 5px;">Pattern</th> <th style="text-align: left; padding: 2px 5px;">Action</th> <th style="text-align: center; padding: 2px 5px;">Remove</th> </tr> </thead> <tbody id="filtersTableBody"> `; config.filters.forEach((filter, index) => { content += ` <tr> <td style="padding: 2px 5px;">${filter.patternText || filter.pattern.source}</td> <td style="padding: 2px 5px;">${filter.action}</td> <td style="text-align: center; padding: 2px 5px;"> <button class="removeFilterBtn" data-index="${index}" style="cursor: pointer;">X</button> </td> </tr> `; }); content += ` </tbody> </table> </div> <div style="margin-bottom: 10px;"> <h4 style="margin: 0 0 5px 0;">Add New Filter</h4> <div style="display: flex; gap: 5px; align-items: center;"> <input type="text" id="newFilterPattern" placeholder="Regex pattern (e.g. anime|manga)" style="flex-grow: 1;"> <label style="white-space: nowrap;"> <input type="checkbox" id="caseInsensitive" checked> Case insensitive </label> <select id="newFilterAction"> <option value="setTop">Move to Top</option> <option value="remove">Hide</option> </select> <button id="addNewFilter" style="cursor: pointer;">Add</button> </div> </div> `; filterManager.innerHTML = content; // Add event listeners after updating content setTimeout(() => { // Remove filter buttons document.querySelectorAll('.removeFilterBtn').forEach(btn => { btn.addEventListener('click', function() { const index = parseInt(this.dataset.index); config.filters.splice(index, 1); saveConfig(); updateFilterManagerContent(filterManager); updateActiveFiltersCount(); }); }); // Add new filter button document.getElementById('addNewFilter').addEventListener('click', function() { const patternInput = document.getElementById('newFilterPattern'); const caseInsensitive = document.getElementById('caseInsensitive').checked; const actionSelect = document.getElementById('newFilterAction'); if (patternInput.value.trim()) { try { const patternText = patternInput.value.trim(); const flags = caseInsensitive ? 'i' : ''; const newFilter = { pattern: new RegExp(patternText, flags), patternText: patternText, // Store the raw text action: actionSelect.value }; config.filters.push(newFilter); saveConfig(); updateFilterManagerContent(filterManager); updateActiveFiltersCount(); patternInput.value = ''; } catch (e) { alert('Invalid regex pattern: ' + e.message); } } }); }, 0); } function updateActiveFiltersCount() { const countElement = document.getElementById('activeFiltersCount'); if (countElement) { countElement.textContent = `(${config.filters.length} active)`; } } function processCatalog() { const catalogDiv = document.getElementById('divThreads'); if (!catalogDiv) return; // Reset all cells visibility first const allCells = Array.from(catalogDiv.querySelectorAll('.catalogCell')); allCells.forEach(cell => { cell.style.display = ''; cell.style.order = ''; }); // Apply filters const cells = Array.from(catalogDiv.querySelectorAll('.catalogCell')); const matchedCells = []; cells.forEach(cell => { const subject = cell.querySelector('.labelSubject')?.textContent || ''; const message = cell.querySelector('.divMessage')?.textContent || ''; const text = `${subject} ${message}`; config.filters.forEach(filter => { if (filter.pattern.test(text)) { if (filter.action === 'remove') { cell.style.display = 'none'; } else if (filter.action === 'setTop') { matchedCells.push(cell); cell.style.order = -1; } } }); }); // Bring matched cells to top matchedCells.reverse().forEach(cell => { catalogDiv.insertBefore(cell, catalogDiv.firstChild); }); } // Initialize the script function init() { loadConfig(); createDashboard(); processCatalog(); // Optional: Add mutation observer to handle dynamic updates const observer = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.type === 'childList' && (mutation.target.id === 'divThreads' || mutation.target.classList.contains('catalogCell'))) { processCatalog(); break; } } }); const threadsDiv = document.getElementById('divThreads'); if (threadsDiv) { observer.observe(threadsDiv, { childList: true, subtree: true }); } } // Wait for page to load completely if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();