Pump Card Filter Manager

Manage blacklisted and highlighted phrases for pump cards

// ==UserScript==
// @name         Pump Card Filter Manager
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Manage blacklisted and highlighted phrases for pump cards
// @author       bob123
// @match        https://neo.bullx.io/neo-vision
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // Initialize from localStorage or create empty Sets
    let blacklistedPhrases = new Set(JSON.parse(localStorage.getItem('blacklistedPhrases') || '[]'));
    let highlightedPhrases = new Set(JSON.parse(localStorage.getItem('highlightedPhrases') || '[]'));
    let caseSensitive = JSON.parse(localStorage.getItem('caseSensitive') || 'false');
    let managersVisible = JSON.parse(localStorage.getItem('managersVisible') || 'true');

    // Save to localStorage function
    function saveToStorage() {
        localStorage.setItem('blacklistedPhrases', JSON.stringify([...blacklistedPhrases]));
        localStorage.setItem('highlightedPhrases', JSON.stringify([...highlightedPhrases]));
        localStorage.setItem('caseSensitive', JSON.stringify(caseSensitive));
        localStorage.setItem('managersVisible', JSON.stringify(managersVisible));
    }

    const styles = `
        .toggle-managers-btn {
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 10000;
            background: #2d2d2d;
            color: white;
            border: none;
            padding: 5px 10px;
            border-radius: 4px;
            cursor: pointer;
        }
        .toggle-managers-btn:hover {
            background: #404040;
        }
        .filter-manager {
            position: fixed;
            right: 10px;
            background: #2d2d2d;
            padding: 10px;
            border-radius: 8px;
            width: 200px;
            z-index: 9999;
            color: #fff;
            font-family: Arial, sans-serif;
            max-height: 180px; /* Fixed total height */
            display: flex;
            flex-direction: column;
        }
        .blacklist-manager {
            top: 40px;
        }
        .highlight-manager {
            top: 230px;
        }
        .filter-manager h3 {
            margin: 0 0 5px 0;
            color: #fff;
            font-size: 14px;
            flex-shrink: 0;
        }
        .filter-input {
            width: 100%;
            padding: 3px;
            margin-bottom: 5px;
            background: #404040;
            border: 1px solid #555;
            color: #fff;
            border-radius: 4px;
            font-size: 12px;
            flex-shrink: 0;
        }
        .phrases-list {
            flex-grow: 1;
            overflow-y: auto;
            margin-top: 5px;
            padding-right: 5px;
            /* Scrollbar styling */
            scrollbar-width: thin;
            scrollbar-color: #666 #2d2d2d;
        }
        /* WebKit scrollbar styling */
        .phrases-list::-webkit-scrollbar {
            width: 6px;
        }
        .phrases-list::-webkit-scrollbar-track {
            background: #2d2d2d;
            border-radius: 3px;
        }
        .phrases-list::-webkit-scrollbar-thumb {
            background-color: #666;
            border-radius: 3px;
            border: 2px solid #2d2d2d;
        }
        .phrase-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 3px;
            background: #404040;
            margin: 2px 0;
            border-radius: 4px;
            word-break: break-all;
            font-size: 12px;
        }
        .phrase-text {
            flex: 1;
            margin-right: 5px;
        }
        .remove-phrase {
            background: #ff4444;
            color: white;
            border: none;
            border-radius: 3px;
            padding: 1px 4px;
            cursor: pointer;
            flex-shrink: 0;
            font-size: 10px;
        }
        .case-sensitive-toggle {
            display: flex;
            align-items: center;
            margin-bottom: 5px;
            font-size: 11px;
            flex-shrink: 0;
        }
        .case-sensitive-toggle input {
            margin-right: 3px;
        }
        .highlight-manager .phrase-item {
            border-left: 2px solid #ff4444;
        }
        .filter-manager.hidden {
            display: none;
        }
    `;

    function createToggleButton() {
        const button = document.createElement('button');
        button.className = 'toggle-managers-btn';
        button.textContent = 'Toggle Filters';
        button.addEventListener('click', () => {
            managersVisible = !managersVisible;
            document.querySelectorAll('.filter-manager').forEach(manager => {
                manager.classList.toggle('hidden', !managersVisible);
            });
            saveToStorage();
        });
        document.body.appendChild(button);
    }

    function createFilterManager(type) {
        const manager = document.createElement('div');
        manager.className = `filter-manager ${type}-manager`;
        if (!managersVisible) {
            manager.classList.add('hidden');
        }
        const title = type === 'blacklist' ? 'Blacklist Manager' : 'Highlight Manager';
        const placeholder = type === 'blacklist' ? 'Enter phrase to blacklist' : 'Enter phrase to highlight';

        manager.innerHTML = `
            <h3>${title}</h3>
            <div class="case-sensitive-toggle">
                <input type="checkbox" id="${type}CaseSensitiveToggle" ${caseSensitive ? 'checked' : ''}>
                <label for="${type}CaseSensitiveToggle">Case Sensitive</label>
            </div>
            <input type="text" class="filter-input" placeholder="${placeholder}">
            <div class="phrases-list"></div>
        `;
        document.body.appendChild(manager);

        const input = manager.querySelector('.filter-input');
        const phrasesList = manager.querySelector('.phrases-list');
        const caseToggle = manager.querySelector(`#${type}CaseSensitiveToggle`);

        input.addEventListener('keypress', (e) => {
            if (e.key === 'Enter' && input.value.trim()) {
                if (type === 'blacklist') {
                    blacklistedPhrases.add(input.value.trim());
                } else {
                    highlightedPhrases.add(input.value.trim());
                }
                input.value = '';
                updatePhrasesList(type);
                checkAllCards();
                saveToStorage();
            }
        });

        caseToggle.addEventListener('change', (e) => {
            caseSensitive = e.target.checked;
            checkAllCards();
            saveToStorage();
        });

        return manager;
    }

    function updatePhrasesList(type) {
        const phrases = type === 'blacklist' ? blacklistedPhrases : highlightedPhrases;
        const phrasesList = document.querySelector(`.${type}-manager .phrases-list`);
        phrasesList.innerHTML = '';

        phrases.forEach(phrase => {
            const phraseItem = document.createElement('div');
            phraseItem.className = 'phrase-item';
            phraseItem.innerHTML = `
                <span class="phrase-text">"${phrase}"</span>
                <button class="remove-phrase">×</button>
            `;
            phraseItem.querySelector('.remove-phrase').addEventListener('click', () => {
                phrases.delete(phrase);
                updatePhrasesList(type);
                checkAllCards();
                saveToStorage();
            });
            phrasesList.appendChild(phraseItem);
        });
    }

    function checkText(text, phrase) {
        if (caseSensitive) {
            return text.includes(phrase);
        }
        return text.toLowerCase().includes(phrase.toLowerCase());
    }

    function shouldHide(element) {
        const span = element.querySelector('span.font-normal.text-grey-200.overflow-ellipsis.line-clamp-1.text-xs.\\!leading-\\[12px\\]');
        if (!span) return false;

        const text = span.textContent;
        return Array.from(blacklistedPhrases).some(phrase => checkText(text, phrase));
    }

    function shouldHighlight(element) {
        const span = element.querySelector('span.font-normal.text-grey-200.overflow-ellipsis.line-clamp-1.text-xs.\\!leading-\\[12px\\]');
        if (!span) return false;

        const text = span.textContent;
        return Array.from(highlightedPhrases).some(phrase => checkText(text, phrase));
    }

    function processElement(element) {
        if (!element.classList.contains('pump-card') ||
            !element.classList.contains('group') ||
            !element.classList.contains('row-hover') ||
            (!element.classList.contains('bg-grey-900') && !element.classList.contains('bg-grey-850'))) {
            return;
        }

        if (shouldHide(element)) {
            element.style.display = 'none';
        } else {
            element.style.display = '';
            if (shouldHighlight(element)) {
                element.style.border = '2px solid red';
                element.style.borderRadius = '4px';
            } else {
                element.style.border = '';
                element.style.borderRadius = '';
            }
        }
    }

    function checkAllCards() {
        const cards = document.querySelectorAll('.pump-card.group.row-hover');
        cards.forEach(processElement);
    }

    function startObserver() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === 1) {
                        if (node.classList && node.classList.contains('pump-card')) {
                            processElement(node);
                        }
                        const cards = node.querySelectorAll('.pump-card.group.row-hover');
                        cards.forEach(processElement);
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        return observer;
    }

    function init() {
        const styleSheet = document.createElement('style');
        styleSheet.textContent = styles;
        document.head.appendChild(styleSheet);

        createToggleButton();
        createFilterManager('blacklist');
        createFilterManager('highlight');

        // Initialize lists with stored data
        updatePhrasesList('blacklist');
        updatePhrasesList('highlight');

        const observer = startObserver();
        checkAllCards(); // Check existing cards on load

        window.addEventListener('unload', () => {
            observer.disconnect();
        });
    }


    init();
})();