YouTube Russian Language and Shorts Filter

Hides all videos except those with Russian characters in the title, and also hides the Shorts shelf. Remembers toggle state.

// ==UserScript==
// @name         YouTube Russian Language and Shorts Filter
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  Hides all videos except those with Russian characters in the title, and also hides the Shorts shelf. Remembers toggle state.
// @description:ru Скрывает все видео, кроме тех, в названии которых есть русские буквы, а также скрывает полку с шортсами. Запоминает состояние кнопки.
// @author       torch
// @match        https://www.youtube.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- SETTINGS ---
    // Load the saved state. If nothing is saved, it defaults to 'true' (enabled).
    let isScriptEnabled = GM_getValue('isRussianFilterEnabled', true);

    // The regular expression to test for any Russian (Cyrillic) characters.
    const russianRegex = /[а-яА-ЯёЁ]/;

    // --- SCRIPT LOGIC ---

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function clickNotInterested(videoElement, titleText) {
        const menuButton = videoElement.querySelector('button[aria-label="Ещё"], button[aria-label="Action menu"]');
        if (!menuButton) return;

        menuButton.click();
        await sleep(200); // Wait for menu to appear

        const menuItems = document.querySelectorAll('ytd-menu-service-item-renderer, yt-list-item-view-model');
        for (const item of menuItems) {
            const textElement = item.querySelector('yt-formatted-string, .yt-core-attributed-string');
            if (textElement && (textElement.textContent.trim() === 'Не интересует' || textElement.textContent.trim() === 'Not interested')) {
                item.click();
                console.log(`[Russian Filter] Clicked "Not interested" for: "${titleText}"`);
                return;
            }
        }
        // Click body to close the menu if the option wasn't found
        document.body.click();
    }

    async function processVideo(videoElement) {
        // Stop if the script is disabled or if this video has already been checked
        if (videoElement.getAttribute('data-processed-russian-filter') === 'true' || !isScriptEnabled) {
            return;
        }
        videoElement.setAttribute('data-processed-russian-filter', 'true');

        const titleLink = videoElement.querySelector('a#video-title, .yt-lockup-metadata-view-model-wiz__title');
        if (!titleLink) return;

        const titleText = (titleLink.getAttribute('title') || titleLink.textContent).trim();

        // *** CORE LOGIC CHANGE ***
        // Test if the title contains any Russian characters using the regular expression.
        const hasRussianChars = russianRegex.test(titleText);

        if (hasRussianChars) {
            // If the title has Russian characters, keep it and highlight it.
            console.log(`[Russian Filter] KEEPING: "${titleText}"`);
            videoElement.style.outline = '2px solid limegreen';
            return;
        }

        // Otherwise, hide the video.
        console.log(`[Russian Filter] HIDING: "${titleText}"`);
        videoElement.style.display = 'none';
        clickNotInterested(videoElement, titleText);
    }

    /**
     * Finds and hides the YouTube Shorts shelf.
     */
    function hideShortsShelf() {
        if (!isScriptEnabled) return;

        const shelves = document.querySelectorAll('ytd-rich-shelf-renderer');
        shelves.forEach(shelf => {
            const titleElement = shelf.querySelector('#title.style-scope.ytd-rich-shelf-renderer');
            if (titleElement && titleElement.textContent.trim() === 'Shorts') {
                if (shelf.style.display !== 'none') {
                    console.log('[Russian Filter] HIDING: Shorts shelf');
                    shelf.style.display = 'none';
                    // Mark it as processed to easily find and restore it later
                    shelf.setAttribute('data-processed-shorts-filter', 'true');
                }
            }
        });
    }


    function processAllVisibleVideos() {
        if (!isScriptEnabled) return;
        console.log('[Russian Filter] Processing visible items...');
        // Hide Shorts shelf first
        hideShortsShelf();
        // Then process individual videos
        document.querySelectorAll('ytd-rich-item-renderer:not([data-processed-russian-filter]), ytd-video-renderer:not([data-processed-russian-filter]), ytd-grid-video-renderer:not([data-processed-russian-filter]), ytd-compact-video-renderer:not([data-processed-russian-filter])').forEach(processVideo);
    }

    // This observer watches for new videos being loaded on the page (e.g., infinite scroll)
    const observer = new MutationObserver(() => {
        if (!isScriptEnabled) return;
        processAllVisibleVideos();
    });

    // --- UI ELEMENTS ---
    function createToggleButton() {
        const button = document.createElement('button');
        button.id = 'russian-filter-toggle';
        document.body.appendChild(button);

        button.addEventListener('click', () => {
            isScriptEnabled = !isScriptEnabled;
            // Save the new state to memory so it persists across page loads
            GM_setValue('isRussianFilterEnabled', isScriptEnabled);

            updateButtonState(button);

            if (isScriptEnabled) {
                // If turning on, process all videos immediately.
                processAllVisibleVideos();
            } else {
                // If turning off, un-hide all previously hidden videos and shelves.
                 document.querySelectorAll('[data-processed-russian-filter]').forEach(el => {
                    if (el.style.display === 'none') {
                        // Reset the display property to its default value
                        el.style.display = '';
                    }
                    el.style.outline = 'none'; // Remove the green outline
                });
                // Also un-hide the shorts shelf
                document.querySelectorAll('[data-processed-shorts-filter]').forEach(el => {
                    el.style.display = '';
                });
            }
        });
        updateButtonState(button);
    }

    function updateButtonState(button) {
        button.textContent = isScriptEnabled ? 'Filter: ON' : 'Filter: OFF';
        button.className = isScriptEnabled ? 'enabled' : 'disabled';
    }

    GM_addStyle(`
        #russian-filter-toggle { position: fixed; bottom: 20px; left: 20px; z-index: 9999; padding: 10px 15px; border-radius: 8px; border: none; color: white; font-size: 14px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 8px rgba(0,0,0,0.2); transition: background-color 0.3s, transform 0.2s; }
        #russian-filter-toggle.enabled { background-color: #1a73e8; }
        #russian-filter-toggle.disabled { background-color: #d93025; }
        #russian-filter-toggle:hover { transform: scale(1.05); }
    `);

    // --- STARTUP ---
    function startObserver() {
        const targetNode = document.querySelector('ytd-app');
        if (targetNode) {
            console.log('[Russian Filter] Script started. Observer activated.');
            observer.observe(targetNode, { childList: true, subtree: true });
            createToggleButton();
            if (isScriptEnabled) {
                // Give the page a moment to load initial content before the first run.
                setTimeout(processAllVisibleVideos, 1500);
            }
        } else {
            // If the main app element isn't ready, wait and try again.
            setTimeout(startObserver, 1000);
        }
    }

    startObserver();

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。