您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет кнопку для строгой фильтрации видео. Режим "Только русский" показывает видео с кириллицей. Режим "Только английский" показывает видео с латиницей, но скрывает, если в названии есть кириллица.
// ==UserScript== // @name YouTube: Строгий фильтр по языку (RU/EN) // @namespace http://tampermonkey.net/ // @version 3.0 // @description Добавляет кнопку для строгой фильтрации видео. Режим "Только русский" показывает видео с кириллицей. Режим "Только английский" показывает видео с латиницей, но скрывает, если в названии есть кириллица. // @author torch // @match *://www.youtube.com/results* // @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'; // --- 1. Управление состоянием фильтра --- // 'none', 'russian', 'english' let activeFilterMode = GM_getValue('languageFilterMode', 'none'); let isFilterEnabled = GM_getValue('isFilterEnabled', false); let menuHideTimer; // --- 2. Стили --- GM_addStyle(` #language-filter-container { position: fixed; bottom: 20px; right: 20px; z-index: 10000; } #language-filter-toggle { width: 56px; height: 56px; background-color: #303030; color: #FFFFFF; border: 1px solid #505050; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 8px rgba(0,0,0,0.2); transition: background-color 0.3s; } #language-filter-toggle:hover { background-color: #4d4d4d; } #language-filter-toggle.active { background-color: #007bff; border-color: #0056b3; } #language-filter-toggle.active:hover { background-color: #0056b3; } #language-filter-toggle svg { width: 24px; height: 24px; fill: #FFFFFF; } #language-filter-menu { position: absolute; bottom: 65px; right: 0; background-color: #282828; border: 1px solid #505050; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); padding: 8px; width: 220px; opacity: 0; transform: translateY(10px); transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; } #language-filter-menu.visible { opacity: 1; transform: translateY(0); pointer-events: auto; } .language-option { display: flex; align-items: center; padding: 8px; cursor: pointer; border-radius: 4px; color: #FFFFFF; } .language-option:hover { background-color: #3d3d3d; } .language-option label { cursor: pointer; width: 100%; margin-left: 10px; } .language-option input[type="radio"] { cursor: pointer; } .filter-hidden { display: none !important; } `); // --- 3. Создание HTML-элементов --- const container = document.createElement('div'); container.id = 'language-filter-container'; const toggleButton = document.createElement('button'); toggleButton.id = 'language-filter-toggle'; toggleButton.innerHTML = `<svg viewBox="0 0 24 24"><path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path></svg>`; const menu = document.createElement('div'); menu.id = 'language-filter-menu'; menu.innerHTML = ` <div class="language-option"> <input type="radio" id="filter-mode-russian" name="filter-mode" value="russian"> <label for="filter-mode-russian">Только русский</label> </div> <div class="language-option"> <input type="radio" id="filter-mode-english" name="filter-mode" value="english"> <label for="filter-mode-english">Только английский (без RU)</label> </div> <div class="language-option"> <input type="radio" id="filter-mode-none" name="filter-mode" value="none"> <label for="filter-mode-none">Сбросить (показать все)</label> </div> `; container.appendChild(menu); container.appendChild(toggleButton); document.body.appendChild(container); // --- 4. Логика фильтрации --- const regexPatterns = { russian: /[а-яА-ЯЁё]/, english: /[a-zA-Z]/ }; const applyFilter = () => { if (!isFilterEnabled || activeFilterMode === 'none') { showAllVideos(); return; } const videoSelectors = 'ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-shelf-renderer'; document.querySelectorAll(videoSelectors).forEach(video => { const titleElements = Array.from(video.querySelectorAll('#video-title')); if (titleElements.length === 0) return; // Для полок с видео - проверяем все заголовки const titlesText = titleElements.map(el => el.textContent || '').join(' '); const hasCyrillic = regexPatterns.russian.test(titlesText); const hasLatin = regexPatterns.english.test(titlesText); let shouldHide = false; if (activeFilterMode === 'russian') { if (!hasCyrillic) { shouldHide = true; } } else if (activeFilterMode === 'english') { // Скрываем, если есть русские буквы ИЛИ если нет английских if (hasCyrillic || !hasLatin) { shouldHide = true; } } if (shouldHide) { video.classList.add('filter-hidden'); } else { video.classList.remove('filter-hidden'); } }); }; const showAllVideos = () => { document.querySelectorAll('.filter-hidden').forEach(video => { video.classList.remove('filter-hidden'); }); }; // --- 5. Обновление UI и сохранение состояния --- const updateUIAndSaveState = () => { // Кнопка if (isFilterEnabled && activeFilterMode !== 'none') { toggleButton.classList.add('active'); toggleButton.title = 'Фильтр активен. Наведите для смены режима.'; } else { toggleButton.classList.remove('active'); toggleButton.title = 'Включить фильтр по языку'; } // Радиокнопки document.querySelector(`#filter-mode-${activeFilterMode}`).checked = true; // Сохранение GM_setValue('isFilterEnabled', isFilterEnabled); GM_setValue('languageFilterMode', activeFilterMode); }; const reapplyFilter = () => { if (isFilterEnabled) { applyFilter(); } else { showAllVideos(); } }; // --- 6. Обработчики событий и MutationObserver --- container.addEventListener('mouseenter', () => { clearTimeout(menuHideTimer); menu.classList.add('visible'); }); container.addEventListener('mouseleave', () => { menuHideTimer = setTimeout(() => { menu.classList.remove('visible'); }, 300); }); toggleButton.addEventListener('click', () => { isFilterEnabled = !isFilterEnabled; updateUIAndSaveState(); reapplyFilter(); }); menu.addEventListener('change', (event) => { if (event.target.name === 'filter-mode') { activeFilterMode = event.target.value; // Если выбрали режим, а фильтр был выключен - включаем его if (activeFilterMode !== 'none' && !isFilterEnabled) { isFilterEnabled = true; } // Если выбрали "Сброс", но оставили главный переключатель включенным if(activeFilterMode === 'none' && isFilterEnabled){ isFilterEnabled = false; } updateUIAndSaveState(); reapplyFilter(); } }); const observer = new MutationObserver(() => { if (isFilterEnabled) { setTimeout(applyFilter, 300); } }); observer.observe(document.body, { childList: true, subtree: true }); // --- 7. Первоначальный запуск --- updateUIAndSaveState(); setTimeout(() => { reapplyFilter(); }, 1000); })();