YouTube Grid Auto-Scroll & Search (Ultra Optimized - Instant)

Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, stop button, and animated border highlighting. Improved robustness and user experience.

目前为 2025-03-02 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube Grid Auto-Scroll & Search (Ultra Optimized - Instant)
// @match        https://www.youtube.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @version      2.2
// @description  Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, stop button, and animated border highlighting.  Improved robustness and user experience.
// @author       Your Name (with further optimization)
// @license      MIT
// @namespace    https://gf.qytechs.cn/users/1435316
// ==/UserScript==

(function() {
    'use strict';

    let targetText = "";
    let searchBox;
    let isSearching = false; // Renamed for clarity: are we *actively* searching
    let searchInput;
    let searchButton;
    let stopButton;
    let observer;
    let searchTimeout;  // Added: Timer to prevent infinite loops.
    const SEARCH_TIMEOUT_MS = 20000; // 20 seconds.  Adjust as needed.
    const SCROLL_DELAY_MS = 750;

    GM_addStyle(`
        /* Existing CSS from previous versions */
         #floating-search-box {
            background-color: #222;
            padding: 5px;
            border: 1px solid #444;
            border-radius: 5px;
            display: flex;
            align-items: center;
            margin-left: 10px;
        }
        #floating-search-box input[type="text"] {
            background-color: #333;
            color: #fff;
            border: 1px solid #555;
            padding: 3px 5px;
            border-radius: 3px;
            margin-right: 5px;
            width: 200px;
            height: 30px;
        }
        #floating-search-box input[type="text"]:focus {
            outline: none;
            border-color: #065fd4;
        }
        #floating-search-box button {
            background-color: #065fd4;
            color: white;
            border: none;
            padding: 3px 8px;
            border-radius: 3px;
            cursor: pointer;
            height: 30px;
        }
        #floating-search-box button:hover {
            background-color: #0549a8;
        }
        #floating-search-box button:focus {
            outline: none;
        }

        #stop-search-button { /* Style for the stop button */
            background-color: #aa0000; /* Red color */
        }
        #stop-search-button:hover {
             background-color: #800000;
        }


        .highlighted-text {
            position: relative; /* Needed for the border to be positioned correctly */
            z-index: 1;       /* Ensure the border is on top of other elements */
        }

        /* Creates the animated border effect */
        .highlighted-text::before {
          content: '';
          position: absolute;
          top: -2px;
          left: -2px;
          right: -2px;
          bottom: -2px;
          border: 2px solid transparent;  /* Transparent border to start */
          border-radius: 8px;          /* Rounded corners */
          background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); /* Rainbow gradient */
          background-size: 400% 400%;  /* Make the gradient larger than the element */
          animation: gradientAnimation 5s ease infinite; /* Animate the background position */
          z-index: -1;                /* Behind the content */
        }
        /* Keyframes for the gradient animation */
        @keyframes gradientAnimation {
          0% {
            background-position: 0% 50%;
          }
          50% {
            background-position: 100% 50%;
          }
          100% {
            background-position: 0% 50%;
          }
        }
       /* Style for the error message */
        #search-error-message {
            color: red;
            font-weight: bold;
            padding: 5px;
            position: fixed; /* Fixed position */
            top: 50px;          /* Position below the masthead (adjust as needed)*/
            left: 50%;
            transform: translateX(-50%); /* Center horizontally */
            background-color: rgba(0, 0, 0, 0.8); /* Semi-transparent black */
            color: white;
            border-radius: 5px;
            z-index: 10000; /* Ensure it's on top */
            display: none;   /* Initially hidden */
         }
    `);


    // --- Create the Search Box ---
    function createSearchBox() {
        searchBox = document.createElement('div');
        searchBox.id = 'floating-search-box';

        searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.placeholder = 'Search to scroll...';
        searchInput.value = GM_getValue('lastSearchTerm', ''); // Load last search

        searchButton = document.createElement('button');
        searchButton.textContent = 'Search';
        searchButton.addEventListener('click', searchAndScroll);

        stopButton = document.createElement('button');
        stopButton.textContent = 'Stop';
        stopButton.id = 'stop-search-button';
        stopButton.addEventListener('click', stopSearch);

        searchBox.appendChild(searchInput);
        searchBox.appendChild(searchButton);
        searchBox.appendChild(stopButton);

        const mastheadEnd = document.querySelector('#end.ytd-masthead');
        const buttonsContainer = document.querySelector('#end #buttons');

       if (mastheadEnd) {
           if(buttonsContainer){
                mastheadEnd.insertBefore(searchBox, buttonsContainer);
            } else{
                mastheadEnd.appendChild(searchBox);
            }
        } else {
            console.error("Could not find the YouTube masthead's end element.");
            // More visible error:  Create and show the error message.
            showErrorMessage("Could not find the YouTube masthead. Search box placed at top of page.");
            document.body.appendChild(searchBox);  //Still add the box.
        }
         // Trigger search on load if text is present
         if (searchInput.value.trim() !== "") {
             searchAndScroll();
         }
    }

    // --- Show Error Message ---
   function showErrorMessage(message) {
        let errorDiv = document.getElementById('search-error-message');
        if (!errorDiv) {
            errorDiv = document.createElement('div');
            errorDiv.id = 'search-error-message';
            document.body.appendChild(errorDiv);
        }
        errorDiv.textContent = message;
        errorDiv.style.display = 'block'; // Make it visible

        // Hide the message after a few seconds
        setTimeout(() => {
           errorDiv.style.display = 'none';
        }, 5000);
    }


    // --- Stop Search Function ---
    function stopSearch() {
        if (observer) {
            observer.disconnect();
        }
        isSearching = false;
        clearTimeout(searchTimeout); // Clear the timeout if stop is pressed.
         const prevHighlighted = document.querySelector('.highlighted-text');
          if (prevHighlighted) {
            prevHighlighted.classList.remove('highlighted-text');
             prevHighlighted.style.position = '';
        }
    }


    // --- Optimized Search and Scroll Function ---
    function searchAndScroll() {
    if (isSearching) return;
    isSearching = true;
    clearTimeout(searchTimeout); // Clear any existing timeout

    if (observer) {
        observer.disconnect();
    }

    targetText = searchInput.value.trim().toLowerCase();
    if (!targetText) {
        isSearching = false;
        return;
    }

    GM_setValue('lastSearchTerm', targetText);

    const prevHighlighted = document.querySelector('.highlighted-text');
    if (prevHighlighted) {
        prevHighlighted.classList.remove('highlighted-text');
         prevHighlighted.style.position = '';
    }

    observer = new IntersectionObserver((entries) => {
        for (const entry of entries) {
            if (entry.isIntersecting) {
                const titleElement = entry.target.querySelector('#video-title');
                if (titleElement && titleElement.textContent.toLowerCase().includes(targetText)) {
                    entry.target.scrollIntoView({ behavior: 'auto', block: 'center' });
                    entry.target.classList.add('highlighted-text');
                    observer.disconnect();
                    isSearching = false;
                    clearTimeout(searchTimeout); // Clear timeout on success
                    return;
                }
            }
        }
    });

    document.querySelectorAll('ytd-rich-grid-media').forEach(item => {
        observer.observe(item);
    });

    // Set a timeout to prevent infinite searching
    searchTimeout = setTimeout(() => {
        stopSearch();
        showErrorMessage("Search timed out. No matching elements found.");
    }, SEARCH_TIMEOUT_MS);

    setTimeout(() => {
        if (!document.querySelector('.highlighted-text')) {
            window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'auto' });
            setTimeout(() => {
                if (!isSearching) return;  // Check again in case stop was pressed
                isSearching = false;
                searchAndScroll(); // Recursive call
            }, SCROLL_DELAY_MS);
        } else {
             isSearching = false;
         }

     }, 100);

}
    // --- Initialization ---
    createSearchBox();

})();

QingJ © 2025

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