SubsPlease Episode 01 Highlighter ✨ (PRECISE)

Color-coded episode highlighting (different colors per show) with click-to-scroll! 🎯🌈

// ==UserScript==
// @name         SubsPlease Episode 01 Highlighter ✨ (PRECISE)
// @namespace    http://tampermonkey.net/
// @version      9.0
// @description  Color-coded episode highlighting (different colors per show) with click-to-scroll! 🎯🌈
// @author       Zotikus1001
// @license      MIT
// @match        https://subsplease.org/*
// @grant        none
// @run-at       document-end
// ==/UserScript==


(function() {
    'use strict';

    // 🚫 Only run on main page - exit immediately if on show pages
    const currentURL = window.location.href;
    if (currentURL.includes('/shows/') ||
        currentURL.includes('/schedule/') ||
        currentURL.includes('/xdcc/') ||
        !currentURL.match(/https:\/\/subsplease\.org\/?$/)) {
        return; // Exit completely - no initialization, no elements, nothing
    }

    // 🎨 Dark-mode-friendly styles for highlighting episode 01s
    const styles = `
        .episode-01-highlight {
            color: #000 !important;
            border-radius: 6px !important;
            border: 2px solid #00ffff !important;
            transform: scale(1.02) !important;
            transition: all 0.3s ease !important;
            position: relative !important;
            z-index: 1000 !important;
            margin: 2px !important;
            font-weight: bold !important;
            animation: neon-pulse 2s ease-in-out infinite alternate !important;
        }

        .episode-01-highlight:hover {
            transform: scale(1.05) !important;
            background: #00ffff !important;
            box-shadow: 0 0 25px rgba(0, 255, 255, 1), inset 0 0 15px rgba(0, 255, 136, 0.5) !important;
            border-color: #00ff88 !important;
        }

        .episode-01-highlight::before {
            content: "🆕 NEW!" !important;
            position: absolute !important;
            top: -12px !important;
            right: -8px !important;
            background: #ff4757 !important;
            color: white !important;
            padding: 2px 6px !important;
            border-radius: 8px !important;
            font-size: 9px !important;
            font-weight: bold !important;
            animation: bounce 1.5s infinite !important;
            box-shadow: 0 0 10px rgba(255, 71, 87, 0.8) !important;
            z-index: 1001 !important;
            white-space: nowrap !important;
            border: 1px solid #fff !important;
        }

        /* 📺 Episode 02 Highlight - Dimmer, different colors per show */
        .episode-02-highlight {
            border-radius: 6px !important;
            border: 1px solid currentColor !important;
            box-shadow: 0 0 8px currentColor !important;
            transform: scale(1.01) !important;
            transition: all 0.3s ease !important;
            position: relative !important;
            z-index: 999 !important;
            margin: 2px !important;
            font-weight: bold !important;
            opacity: 0.7 !important;
            color: #000 !important;
        }

        /* 📺 Episode 03 Highlight - Even Dimmer */
        .episode-03-highlight {
            border-radius: 4px !important;
            border: 1px solid currentColor !important;
            box-shadow: 0 0 5px currentColor !important;
            transform: scale(1.005) !important;
            transition: all 0.3s ease !important;
            position: relative !important;
            z-index: 998 !important;
            margin: 2px !important;
            font-weight: normal !important;
            opacity: 0.5 !important;
            color: #000 !important;
        }

        /* 📺 Episode 04+ Highlight - Very Subtle */
        .episode-other-highlight {
            border-radius: 3px !important;
            border: 1px solid currentColor !important;
            box-shadow: 0 0 3px currentColor !important;
            transition: all 0.3s ease !important;
            position: relative !important;
            z-index: 997 !important;
            margin: 2px !important;
            font-weight: normal !important;
            opacity: 0.3 !important;
            color: #000 !important;
        }

        .episode-02-highlight:hover,
        .episode-03-highlight:hover,
        .episode-other-highlight:hover {
            transform: scale(1.02) !important;
            filter: brightness(1.1) !important;
        }

        @keyframes neon-pulse {
            0% {
                box-shadow: 0 0 15px rgba(0, 255, 136, 0.8), inset 0 0 10px rgba(0, 255, 255, 0.3);
                background: #00ff88;
            }
            100% {
                box-shadow: 0 0 25px rgba(0, 255, 136, 1), inset 0 0 15px rgba(0, 255, 255, 0.5);
                background: #00ffaa;
            }
        }

        @keyframes bounce {
            0%, 100% { transform: translateY(0px) rotate(0deg); }
            25% { transform: translateY(-2px) rotate(1deg); }
            50% { transform: translateY(-4px) rotate(0deg); }
            75% { transform: translateY(-2px) rotate(-1deg); }
        }

        .episode-counter {
            position: fixed !important;
            top: 20px !important;
            right: 20px !important;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
            color: white !important;
            padding: 10px 15px !important;
            border-radius: 25px !important;
            font-weight: bold !important;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important;
            z-index: 10000 !important;
            animation: counter-glow 2s infinite alternate !important;
            font-family: Arial, sans-serif !important;
        }

        @keyframes counter-glow {
            0% { box-shadow: 0 4px 15px rgba(0,0,0,0.2); }
            100% { box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4); }
        }

        .found-list {
            position: fixed !important;
            top: 70px !important;
            right: 20px !important;
            width: 400px !important;
            max-height: 500px !important;
            background: rgba(0,0,0,0.9) !important;
            color: white !important;
            padding: 15px !important;
            border-radius: 10px !important;
            font-size: 13px !important;
            overflow-y: auto !important;
            z-index: 10001 !important;
            font-family: Arial, sans-serif !important;
            border: 1px solid rgba(0, 255, 136, 0.3) !important;
        }

        .found-list h3 {
            margin: 0 0 12px 0 !important;
            color: #4ecdc4 !important;
            font-size: 14px !important;
        }

        .found-entry {
            margin-bottom: 5px !important;
            padding: 8px 10px !important;
            background: rgba(255,255,255,0.1) !important;
            border-radius: 5px !important;
            font-size: 12px !important;
            cursor: pointer !important;
            transition: all 0.2s ease !important;
            line-height: 1.3 !important;
        }

        .found-entry:hover {
            background: rgba(0, 255, 136, 0.3) !important;
            transform: translateX(3px) !important;
            box-shadow: 0 2px 8px rgba(0, 255, 136, 0.4) !important;
        }

        .debug-toggle {
            position: fixed !important;
            bottom: 20px !important;
            right: 20px !important;
            background: rgba(0,0,0,0.8) !important;
            color: white !important;
            padding: 8px 15px !important;
            border-radius: 20px !important;
            font-size: 11px !important;
            cursor: pointer !important;
            z-index: 10002 !important;
            font-family: Arial, sans-serif !important;
            border: 1px solid rgba(255,255,255,0.3) !important;
            transition: all 0.2s ease !important;
            white-space: nowrap !important;
        }

        .debug-toggle:hover {
            background: rgba(0, 255, 136, 0.2) !important;
            border-color: #00ff88 !important;
            transform: scale(1.05) !important;
        }

        .debug-toggle.debug-on {
            background: rgba(255, 71, 87, 0.8) !important;
            border-color: #ff4757 !important;
        }

        /* 🖼️ Fix image hover z-index to appear above episode names */
        .preview-image,
        [id*="preview"],
        [class*="preview"],
        [class*="tooltip"],
        [class*="hover"],
        img[src*="subsplease"],
        img[src*="preview"],
        .image-preview,
        .hover-image {
            z-index: 10003 !important;
        }
    `;

    let scanCount = 0;
    let foundEpisodes = [];
    let showsWithEp01 = new Set(); // Track shows that have episode 01
    let debugMode = false;

    // 🎨 Function to generate unique colors for different shows
    function getShowColor(showName) {
        // Improved hash function for better distribution
        let hash = 0;
        for (let i = 0; i < showName.length; i++) {
            const char = showName.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32-bit integer
        }

        // More distinct color palette - very different colors that are easy to distinguish
        const colors = [
            '#ff4757', // Red
            '#3742fa', // Blue
            '#2ed573', // Green
            '#ffa502', // Orange
            '#9c88ff', // Purple
            '#00d2d3', // Cyan
            '#ff6348', // Coral
            '#dda0dd', // Plum
            '#ffb8b8', // Pink
            '#78e08f', // Light Green
            '#60a3bc', // Steel Blue
            '#f8b500', // Amber
            '#e74c3c', // Crimson
            '#8e44ad', // Dark Purple
            '#16a085', // Teal
            '#f39c12', // Dark Orange
            '#2980b9', // Strong Blue
            '#27ae60', // Emerald
            '#e67e22', // Carrot
            '#c0392b', // Dark Red
            '#9b59b6', // Amethyst
            '#1abc9c', // Turquoise
            '#f1c40f', // Yellow
            '#34495e', // Dark Gray
            '#95a5a6', // Silver
        ];

        // Add secondary hash to reduce collisions
        let secondHash = 0;
        for (let i = 0; i < showName.length; i++) {
            secondHash += showName.charCodeAt(i) * (i + 1);
        }

        const combinedHash = Math.abs(hash + secondHash);
        return colors[combinedHash % colors.length];
    }

    // 🔧 Debug logging function
    function debugLog(...args) {
        if (debugMode) {
            console.log(...args);
        }
    }

    // 🔧 Create debug toggle button
    function createDebugToggle() {
        let toggle = document.querySelector('.debug-toggle');

        if (!toggle) {
            toggle = document.createElement('div');
            toggle.className = 'debug-toggle';
            toggle.title = 'Toggle console logging for SubsPlease Episode 01 Highlighter script';
            document.body.appendChild(toggle);

            toggle.addEventListener('click', function() {
                debugMode = !debugMode;
                updateDebugToggle();

                // Show user-friendly message about what was toggled
                if (debugMode) {
                    console.log('🔧 SubsPlease Episode 01 Highlighter: Console logging ENABLED');
                    console.log('📝 You will now see detailed scan information in the console');
                } else {
                    console.log('🔧 SubsPlease Episode 01 Highlighter: Console logging DISABLED');
                    console.log('🔇 Script will run silently (no console spam)');
                }
            });
        }

        updateDebugToggle();
    }

    // 🔧 Update debug toggle appearance
    function updateDebugToggle() {
        const toggle = document.querySelector('.debug-toggle');
        if (toggle) {
            toggle.textContent = debugMode ?
                '🔧 Script Debug: ON' :
                '🔧 Script Debug: OFF';
            toggle.className = `debug-toggle ${debugMode ? 'debug-on' : ''}`;
            toggle.title = debugMode ?
                'Click to disable console logging for Episode 01 Highlighter' :
                'Click to enable console logging for Episode 01 Highlighter';
        }
    }

    // 📊 Function to update episode counter
    function updateEpisodeCounter(count) {
        let counter = document.querySelector('.episode-counter');

        if (!counter) {
            counter = document.createElement('div');
            counter.className = 'episode-counter';
            document.body.appendChild(counter);
        }

        // Only count episode 01s for the counter
        const ep01Count = foundEpisodes.length;
        counter.innerHTML = `🆕 Episode 01s: ${ep01Count}`;
    }

    // 📋 Function to update found episodes list
    function updateFoundList(episodes) {
        let list = document.querySelector('.found-list');

        if (!list) {
            list = document.createElement('div');
            list.className = 'found-list';
            document.body.appendChild(list);
        }

        const uniqueEpisodes = [...new Set(episodes)]; // Remove duplicates

        list.innerHTML = `
            <h3>🆕 Episode 01s Found</h3>
            ${uniqueEpisodes.length === 0 ? '<div>No episode 01s found yet...</div>' :
              uniqueEpisodes.map((ep, index) => `<div class="found-entry" data-episode="${ep}">🎯 ${ep}</div>`).join('')}
        `;

        // 🎯 Add click handlers to scroll to episodes
        const entries = list.querySelectorAll('.found-entry');
        entries.forEach(entry => {
            entry.addEventListener('click', function() {
                const episodeName = this.getAttribute('data-episode');
                scrollToEpisode(episodeName);
            });
        });
    }

    // 📍 Function to scroll to a specific episode
    function scrollToEpisode(episodeName) {
        debugLog(`🎯 Scrolling to episode: "${episodeName}"`);

        // Find the episode link that matches the name (any highlight type)
        const episodeLinks = document.querySelectorAll('#releases-table a.episode-01-highlight, #releases-table a.episode-02-highlight, #releases-table a.episode-03-highlight, #releases-table a.episode-other-highlight');

        for (let link of episodeLinks) {
            const linkText = link.textContent.trim();
            if (linkText === episodeName) {
                // Scroll to the element with smooth animation
                link.scrollIntoView({
                    behavior: 'smooth',
                    block: 'center',
                    inline: 'nearest'
                });

                // Add temporary highlight flash
                flashElement(link);

                debugLog(`✅ Scrolled to: "${episodeName}"`);
                return;
            }
        }

        debugLog(`❌ Episode not found: "${episodeName}"`);
    }

    // ✨ Function to flash highlight an element
    function flashElement(element) {
        const originalBoxShadow = element.style.boxShadow;

        // Add intense flash
        element.style.boxShadow = '0 0 30px #fff, 0 0 60px #00ffff, 0 0 90px #00ff88';
        element.style.transform = 'scale(1.1)';

        // Return to normal after animation
        setTimeout(() => {
            element.style.boxShadow = originalBoxShadow;
            element.style.transform = 'scale(1.02)';
        }, 800);
    }

    // 🎯 ENHANCED function to highlight episode 01s AND subsequent episodes of same shows
    function highlightEpisode01s() {
        scanCount++;
        debugLog(`🔍 === ENHANCED SCAN ${scanCount} ===`);

        let newEpisode01Count = 0;
        let newFoundEpisodes = [];
        let newShowsWithEp01 = new Set();

        // 🎯 Get all episode title links
        const episodeTitleLinks = document.querySelectorAll('#releases-table a');
        debugLog(`🔍 Found ${episodeTitleLinks.length} episode title links`);

        // 📍 FIRST PASS: Find episode 01s and track show names
        episodeTitleLinks.forEach((link, index) => {
            const text = link.textContent || link.innerText || '';

            // Skip quality links and short text
            if (/^\d+p$/.test(text.trim()) ||
                text.trim() === 'XDCC' ||
                text.trim() === 'New!' ||
                text.length < 5) {
                return;
            }

            // Look for episode 01 patterns
            const episode01Patterns = [
                /—\s*01(?:\s|$)/i,         // "— 01" with em dash (main pattern)
                /-\s*01(?:\s|$)/i,         // "- 01" with regular dash
                /\s01(?:\s|$)/i,           // " 01" at end or followed by space
            ];

            const matchedPattern = episode01Patterns.find(pattern => pattern.test(text));

            if (matchedPattern && !link.classList.contains('episode-01-highlight')) {
                link.classList.add('episode-01-highlight');
                newEpisode01Count++;
                newFoundEpisodes.push(text.trim());

                // Extract show name (everything before the episode number)
                const showName = text.replace(/\s*[—-]\s*01.*$/i, '').trim();
                newShowsWithEp01.add(showName);
                showsWithEp01.add(showName);

                // Apply show-specific color for episode 01 too
                const showColor = getShowColor(showName);
                link.style.setProperty('background-color', showColor, 'important');
                link.style.setProperty('border-color', '#00ffff', 'important');  // Keep cyan border for episode 01s
                link.style.setProperty('box-shadow', `0 0 15px ${showColor}, inset 0 0 10px rgba(0, 255, 255, 0.3)`, 'important');

                debugLog(`🆕 FOUND Episode 01: "${text}" (Show: "${showName}", Color: ${showColor})`);

                // Add click handler
                link.addEventListener('click', function() {
                    debugLog('🆕 Clicked on Episode 01:', text);
                });
            }
        });

        // 📍 SECOND PASS: Find subsequent episodes for shows with episode 01
        let subsequentEpisodesCount = 0;
        episodeTitleLinks.forEach((link, index) => {
            const text = link.textContent || link.innerText || '';

            // Skip if already highlighted or is quality/short text
            if (link.classList.contains('episode-01-highlight') ||
                link.classList.contains('episode-02-highlight') ||
                link.classList.contains('episode-03-highlight') ||
                link.classList.contains('episode-other-highlight') ||
                /^\d+p$/.test(text.trim()) ||
                text.trim() === 'XDCC' ||
                text.trim() === 'New!' ||
                text.length < 5) {
                return;
            }

            // Look for other episode patterns (02, 03, 04, etc.)
            const otherEpisodePattern = /(.+?)\s*[—-]\s*(\d{2,3})(?:\s|$)/i;
            const match = text.match(otherEpisodePattern);

            if (match) {
                const showName = match[1].trim();
                const episodeNum = match[2];

                // Check if this show has episode 01 present
                if (showsWithEp01.has(showName)) {
                    const epNum = parseInt(episodeNum);
                    let highlightClass = '';

                    if (epNum === 2) {
                        highlightClass = 'episode-02-highlight';
                    } else if (epNum === 3) {
                        highlightClass = 'episode-03-highlight';
                    } else if (epNum >= 4) {
                        highlightClass = 'episode-other-highlight';
                    }

                    if (highlightClass) {
                        link.classList.add(highlightClass);

                        // Apply show-specific color
                        const showColor = getShowColor(showName);
                        link.style.setProperty('background-color', showColor, 'important');
                        link.style.setProperty('border-color', showColor, 'important');
                        link.style.setProperty('box-shadow', `0 0 ${highlightClass.includes('02') ? '8' : highlightClass.includes('03') ? '5' : '3'}px ${showColor}`, 'important');

                        subsequentEpisodesCount++;

                        debugLog(`📺 FOUND Episode ${episodeNum}: "${text}" (Show: "${showName}", Color: ${showColor})`);

                        // Add click handler
                        link.addEventListener('click', function() {
                            debugLog(`📺 Clicked on Episode ${episodeNum}:`, text);
                        });
                    }
                }
            }
        });

        // Update found episodes list (only episode 01s)
        foundEpisodes = [...foundEpisodes, ...newFoundEpisodes];
        updateFoundList(foundEpisodes);
        updateEpisodeCounter(foundEpisodes.length);

        debugLog(`🔍 Scan ${scanCount}: Found ${newEpisode01Count} NEW episode 01s, ${subsequentEpisodesCount} subsequent episodes (Total episode 01s: ${foundEpisodes.length})`);

        if (newFoundEpisodes.length > 0) {
            debugLog('🆕 New Episode 01s found:', newFoundEpisodes);
        }
    }

    // 🕵️ Monitor XHR requests to see when data loads
    const originalXHR = window.XMLHttpRequest;
    window.XMLHttpRequest = function() {
        const xhr = new originalXHR();
        const originalOpen = xhr.open;

        xhr.open = function(method, url) {
            if (url.includes('api/?f=latest')) {
                debugLog('🌐 Latest releases API call detected');
                xhr.addEventListener('load', function() {
                    debugLog('🌐 Latest releases API response received - scanning in 1 second...');
                    setTimeout(highlightEpisode01s, 1000);
                });
            }
            return originalOpen.apply(this, arguments);
        };

        return xhr;
    };

    // 🚀 Initialize the script
    function init() {
        debugLog('🌟 SubsPlease Episode 01 Highlighter PRECISE VERSION starting...');

        // Add styles to the page
        const styleSheet = document.createElement('style');
        styleSheet.textContent = styles;
        document.head.appendChild(styleSheet);

        // Create debug toggle
        createDebugToggle();

        // Initial scans with delays to catch dynamic content
        setTimeout(highlightEpisode01s, 1000);
        setTimeout(highlightEpisode01s, 3000);
        setTimeout(highlightEpisode01s, 5000);

        // 👀 MutationObserver for table changes
        const observer = new MutationObserver(function(mutations) {
            let tableChanged = false;

            mutations.forEach(function(mutation) {
                if (mutation.target.id === 'releases-table' ||
                    mutation.target.closest('#releases-table')) {
                    tableChanged = true;
                }
            });

            if (tableChanged) {
                debugLog('🔄 Table content changed - scanning...');
                setTimeout(highlightEpisode01s, 300);
            }
        });

        // Observe the releases table specifically
        const table = document.querySelector('#releases-table');
        if (table) {
            observer.observe(table, {
                childList: true,
                subtree: true
            });
            debugLog('📊 Table monitoring enabled');
        }

        // 🔄 Periodic scanning (less frequent)
        setInterval(highlightEpisode01s, 10000);

        // 🎯 Monitor "See more" button
        function monitorSeeMoreButton() {
            const seeMoreButton = document.querySelector('#latest-load-more');
            if (seeMoreButton && !seeMoreButton.hasAttribute('data-monitored')) {
                seeMoreButton.setAttribute('data-monitored', 'true');
                seeMoreButton.addEventListener('click', function() {
                    debugLog('🔄 "See more" clicked - will scan in 2 seconds...');
                    setTimeout(highlightEpisode01s, 2000);
                });
                debugLog('📱 "See more" button monitoring enabled');
            }
        }

        setTimeout(monitorSeeMoreButton, 1000);

        debugLog('🌟 SubsPlease Episode 01 Highlighter PRECISE VERSION activated!');
    }

    // 🎬 Start the script
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    window.addEventListener('load', function() {
        setTimeout(highlightEpisode01s, 2000);
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址