您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Combines "YouTube Grid Auto-Scroll & Search" for finding videos in grids/feeds with "YouTube Channel Full Video Preloader" for loading all videos on a channel page and opening them.
- // ==UserScript==
- // @name YouTube Supercharged: Grid Search & Channel Preloader
- // @namespace https://greasyfork.org/users/1435316
- // @version 3.4
- // @description Combines "YouTube Grid Auto-Scroll & Search" for finding videos in grids/feeds with "YouTube Channel Full Video Preloader" for loading all videos on a channel page and opening them.
- // @author Your Name & AI Assistant
- // @match https://www.youtube.com/*
- // @grant GM_addStyle
- // @grant GM_openInTab
- // @run-at document-idle
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- // --- Variables for YouTube Grid Auto-Scroll & Search ---
- let targetText = "";
- let searchBox;
- let isSearching = false;
- let searchInput;
- let searchButton;
- let stopButton;
- let prevButton;
- let nextButton;
- let scrollContinuationTimeout;
- let overallSearchTimeout;
- const SEARCH_TIMEOUT_MS = 20000;
- const SCROLL_DELAY_MS = 750;
- const MAX_SEARCH_LENGTH = 255;
- let highlightedElements = [];
- let currentHighlightIndex = -1;
- let lastScrollHeight = 0;
- let hasScrolledToResultThisSession = false;
- // --- Variables for YouTube Channel Full Video Preloader ---
- let preloadButtonPreloader; // Renamed to avoid any potential abstract conflict
- let isLoadingStatePreloader = false; // Renamed for clarity
- // --- Combined Styles ---
- GM_addStyle(`
- /* Styles for YouTube Grid Auto-Scroll & Search */
- #floating-search-box {
- background-color: #222;
- padding: 5px;
- border: 1px solid #444;
- border-radius: 5px;
- display: flex;
- align-items: center;
- margin-left: 10px;
- }
- @media (max-width: 768px) {
- #floating-search-box input[type="text"] {
- width: 150px;
- }
- }
- #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 {
- background-color: #aa0000;
- }
- #stop-search-button:hover {
- background-color: #800000;
- }
- #prev-result-button, #next-result-button {
- background-color: #444;
- color: white;
- margin: 0 3px;
- }
- #prev-result-button:hover, #next-result-button:hover {
- background-color: #555;
- }
- .highlighted-text {
- position: relative;
- z-index: 1;
- }
- .highlighted-text::before {
- content: '';
- position: absolute;
- top: -2px;
- left: -2px;
- right: -2px;
- bottom: -2px;
- border: 2px solid transparent;
- border-radius: 8px;
- background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet);
- background-size: 400% 400%;
- animation: gradientAnimation 5s ease infinite;
- z-index: -1;
- }
- @keyframes gradientAnimation {
- 0% { background-position: 0% 50%; }
- 50% { background-position: 100% 50%; }
- 100% { background-position: 0% 50%; }
- }
- #search-error-message {
- color: red;
- font-weight: bold;
- padding: 5px;
- position: fixed;
- top: 50px;
- left: 50%;
- transform: translateX(-50%);
- background-color: rgba(0, 0, 0, 0.8);
- color: white;
- border-radius: 5px;
- z-index: 10000;
- display: none;
- }
- #search-no-results-message {
- color: #aaa;
- padding: 5px;
- position: fixed;
- top: 50px;
- left: 50%;
- transform: translateX(-50%);
- background-color: rgba(0, 0, 0, 0.8);
- border-radius: 5px;
- z-index: 10000;
- display: none;
- }
- /* Styles for YouTube Channel Full Video Preloader */
- #yt-channel-full-preload-button {
- position: fixed;
- bottom: 70px;
- right: 20px;
- z-index: 9999; /* Slightly lower than search messages */
- padding: 12px 20px;
- background-color: #007bff;
- color: white;
- border: none;
- border-radius: 5px;
- cursor: pointer;
- font-size: 14px;
- box-shadow: 0px 4px 8px rgba(0,0,0,0.3);
- transition: background-color 0.3s ease, opacity 0.3s ease;
- }
- #yt-channel-full-preload-button:hover {
- background-color: #0056b3;
- }
- #yt-channel-full-preload-button:disabled {
- background-color: #AAAAAA;
- cursor: not-allowed;
- opacity: 0.7;
- }
- `);
- // --- Functions for YouTube Grid Auto-Scroll & Search ---
- function ensureSearchBoxAttached() {
- if (searchBox && !document.body.contains(searchBox)) {
- console.log("YouTube Grid Auto-Scroll: Search box detached, attempting to re-attach.");
- 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("YouTube Grid Auto-Scroll: Could not find masthead to re-attach search box.");
- if (!document.body.contains(searchBox)) {
- document.body.insertBefore(searchBox, document.body.firstChild);
- }
- }
- }
- }
- function createSearchBox() {
- if (document.getElementById('floating-search-box')) return; // Already exists
- searchBox = document.createElement('div');
- searchBox.id = 'floating-search-box';
- searchBox.setAttribute('role', 'search');
- searchInput = document.createElement('input');
- searchInput.type = 'text';
- searchInput.placeholder = 'Поиск для прокрутки...';
- searchInput.value = '';
- searchInput.setAttribute('aria-label', 'Search within YouTube grid');
- searchInput.maxLength = MAX_SEARCH_LENGTH;
- searchButton = document.createElement('button');
- searchButton.textContent = 'Поиск';
- searchButton.addEventListener('click', () => {
- stopSearch();
- isSearching = true;
- hasScrolledToResultThisSession = false;
- currentHighlightIndex = -1;
- searchAndScroll();
- });
- searchButton.setAttribute('aria-label', 'Start search');
- prevButton = document.createElement('button');
- prevButton.textContent = 'Пред.';
- prevButton.id = 'prev-result-button';
- prevButton.addEventListener('click', () => navigateResults(-1));
- prevButton.setAttribute('aria-label', 'Previous result');
- prevButton.disabled = true;
- nextButton = document.createElement('button');
- nextButton.textContent = 'След.';
- nextButton.id = 'next-result-button';
- nextButton.addEventListener('click', () => navigateResults(1));
- nextButton.disabled = true;
- stopButton = document.createElement('button');
- stopButton.textContent = 'Стоп';
- stopButton.id = 'stop-search-button';
- stopButton.addEventListener('click', stopSearch);
- stopButton.setAttribute('aria-label', 'Stop search');
- searchBox.appendChild(searchInput);
- searchBox.appendChild(searchButton);
- searchBox.appendChild(prevButton);
- searchBox.appendChild(nextButton);
- 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 for search box.");
- showErrorMessageGridSearch("Не удалось найти шапку YouTube. Блок поиска размещен вверху страницы.");
- document.body.insertBefore(searchBox, document.body.firstChild);
- }
- }
- function showErrorMessageGridSearch(message) { // Renamed to avoid conflict if another part used same name
- 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';
- setTimeout(() => { if(errorDiv) errorDiv.style.display = 'none'; }, 5000);
- }
- function showNoResultsMessageGridSearch() { // Renamed
- let noResultsDiv = document.getElementById('search-no-results-message');
- if (!noResultsDiv) {
- noResultsDiv = document.createElement('div');
- noResultsDiv.id = 'search-no-results-message';
- noResultsDiv.textContent = "Совпадений не найдено.";
- document.body.appendChild(noResultsDiv);
- }
- noResultsDiv.style.display = 'block';
- setTimeout(() => { if(noResultsDiv) noResultsDiv.style.display = 'none'; }, 5000);
- }
- function stopSearch() {
- isSearching = false;
- clearTimeout(scrollContinuationTimeout);
- clearTimeout(overallSearchTimeout);
- currentHighlightIndex = -1;
- highlightedElements = [];
- document.querySelectorAll('.highlighted-text').forEach(el => {
- el.classList.remove('highlighted-text');
- });
- updateNavButtons();
- }
- function navigateResults(direction) {
- if (highlightedElements.length === 0) return;
- currentHighlightIndex += direction;
- if (currentHighlightIndex < 0) currentHighlightIndex = highlightedElements.length - 1;
- else if (currentHighlightIndex >= highlightedElements.length) currentHighlightIndex = 0;
- if (highlightedElements[currentHighlightIndex]) {
- highlightedElements[currentHighlightIndex].scrollIntoView({ behavior: 'auto', block: 'center' });
- hasScrolledToResultThisSession = true;
- }
- updateNavButtons();
- }
- function updateNavButtons() {
- if (prevButton && nextButton) { // Ensure buttons are created
- prevButton.disabled = highlightedElements.length <= 1;
- nextButton.disabled = highlightedElements.length <= 1;
- }
- }
- function searchAndScroll() {
- if (searchBox && !document.body.contains(searchBox)) {
- console.warn("YouTube Grid Auto-Scroll: Search box is not in the document. Stopping script operations.");
- showErrorMessageGridSearch("UI поиска потерян. Пожалуйста, перезагрузите страницу или попробуйте переустановить скрипт.");
- stopSearch();
- return;
- }
- if (!isSearching) {
- clearTimeout(scrollContinuationTimeout);
- clearTimeout(overallSearchTimeout);
- return;
- }
- clearTimeout(scrollContinuationTimeout);
- targetText = searchInput.value.trim().toLowerCase();
- if (!targetText) {
- stopSearch();
- return;
- }
- clearTimeout(overallSearchTimeout);
- overallSearchTimeout = setTimeout(() => {
- if (isSearching) {
- showErrorMessageGridSearch("Поиск прерван по таймауту.");
- stopSearch();
- }
- }, SEARCH_TIMEOUT_MS);
- document.querySelectorAll('.highlighted-text').forEach(el => {
- el.classList.remove('highlighted-text');
- });
- const mediaElements = Array.from(document.querySelectorAll('ytd-rich-grid-media:not([style*="display: none"])'));
- let newlyFoundHighlightedElements = [];
- for (let i = 0; i < mediaElements.length; i++) {
- const titleElement = mediaElements[i].querySelector('#video-title');
- if (titleElement && titleElement.textContent.toLowerCase().includes(targetText)) {
- mediaElements[i].classList.add('highlighted-text');
- newlyFoundHighlightedElements.push(mediaElements[i]);
- }
- }
- highlightedElements = newlyFoundHighlightedElements;
- updateNavButtons();
- let elementToScrollTo = null;
- let newActiveHighlightIndex = -1;
- if (highlightedElements.length > 0) {
- if (currentHighlightIndex === -1 || currentHighlightIndex >= highlightedElements.length) {
- newActiveHighlightIndex = 0;
- } else {
- newActiveHighlightIndex = (currentHighlightIndex + 1) % highlightedElements.length;
- }
- elementToScrollTo = highlightedElements[newActiveHighlightIndex];
- }
- if (elementToScrollTo) {
- elementToScrollTo.scrollIntoView({ behavior: 'auto', block: 'center' });
- currentHighlightIndex = newActiveHighlightIndex;
- hasScrolledToResultThisSession = true;
- isSearching = false;
- clearTimeout(overallSearchTimeout);
- } else {
- if (!isSearching) {
- clearTimeout(overallSearchTimeout);
- return;
- }
- lastScrollHeight = document.documentElement.scrollHeight;
- window.scrollTo({ top: lastScrollHeight, behavior: 'auto' });
- scrollContinuationTimeout = setTimeout(() => {
- if (!isSearching) return;
- if (document.documentElement.scrollHeight === lastScrollHeight) {
- const searchWasActiveBeforeStop = isSearching;
- stopSearch();
- if (searchWasActiveBeforeStop && !hasScrolledToResultThisSession && targetText) {
- showNoResultsMessageGridSearch();
- }
- } else {
- searchAndScroll();
- }
- }, SCROLL_DELAY_MS);
- }
- }
- document.addEventListener('visibilitychange', function() {
- if (!document.hidden) {
- ensureSearchBoxAttached();
- if (isSearching || (searchInput && searchInput.value.trim() && currentHighlightIndex !== -1) ) {
- const wasSearchingBeforeVisibilityChange = isSearching;
- const currentSearchTermInBox = searchInput ? searchInput.value : "";
- if (wasSearchingBeforeVisibilityChange) {
- console.log("YouTube Grid Auto-Scroll: Tab became visible during an active search. Restarting search process.");
- stopSearch();
- if (searchInput) searchInput.value = currentSearchTermInBox;
- if (currentSearchTermInBox.trim()) {
- isSearching = true;
- hasScrolledToResultThisSession = false;
- currentHighlightIndex = -1;
- searchAndScroll();
- }
- }
- }
- }
- });
- // --- Functions for YouTube Channel Full Video Preloader ---
- function createPreloadButtonPreloader() {
- if (document.getElementById('yt-channel-full-preload-button')) {
- return; // Button already exists
- }
- preloadButtonPreloader = document.createElement('button');
- preloadButtonPreloader.textContent = 'Загрузить ВСЕ видео (со скроллом)';
- preloadButtonPreloader.id = 'yt-channel-full-preload-button';
- preloadButtonPreloader.addEventListener('click', startFullPreloadPreloader);
- document.body.appendChild(preloadButtonPreloader);
- }
- async function scrollToBottomPreloader() {
- return new Promise(resolve => {
- preloadButtonPreloader.disabled = true;
- isLoadingStatePreloader = true;
- preloadButtonPreloader.textContent = 'Прокрутка... (0 видео)';
- let lastHeight = 0;
- let currentHeight = document.documentElement.scrollHeight;
- let consecutiveNoChange = 0;
- const maxConsecutiveNoChange = 5;
- let videosFound = 0;
- const scrollInterval = setInterval(() => {
- videosFound = document.querySelectorAll('ytd-rich-item-renderer a#video-title-link').length;
- preloadButtonPreloader.textContent = `Прокрутка... (${videosFound} видео)`;
- window.scrollTo(0, document.documentElement.scrollHeight);
- lastHeight = currentHeight;
- currentHeight = document.documentElement.scrollHeight;
- if (lastHeight === currentHeight) {
- consecutiveNoChange++;
- if (consecutiveNoChange >= maxConsecutiveNoChange) {
- clearInterval(scrollInterval);
- preloadButtonPreloader.textContent = `Прокрутка завершена (${videosFound} видео)`;
- isLoadingStatePreloader = false;
- setTimeout(resolve, 1000);
- }
- } else {
- consecutiveNoChange = 0;
- }
- }, 1000);
- });
- }
- async function startFullPreloadPreloader() {
- if (isLoadingStatePreloader) {
- alert("Процесс уже запущен.");
- return;
- }
- const startScroll = confirm("Скрипт начнет прокручивать страницу вниз до конца, чтобы загрузить все видео. Это может занять некоторое время. Продолжить?");
- if (!startScroll) {
- return;
- }
- await scrollToBottomPreloader();
- preloadButtonPreloader.disabled = false;
- const videoItems = document.querySelectorAll('ytd-rich-item-renderer');
- let videoLinks = [];
- videoItems.forEach(item => {
- const linkElement = item.querySelector('a#video-title-link');
- if (linkElement && linkElement.href) {
- videoLinks.push(linkElement.href);
- }
- });
- if (videoLinks.length === 0) {
- alert('Видео для предзагрузки не найдены на этой странице.');
- preloadButtonPreloader.textContent = 'Загрузить ВСЕ видео (со скроллом)';
- return;
- }
- const confirmation = confirm(`Найдено ${videoLinks.length} видео. Хотите открыть их все в фоновых вкладках? Это может занять некоторое время и потребовать много ресурсов.`);
- if (confirmation) {
- preloadButtonPreloader.disabled = true;
- preloadButtonPreloader.textContent = `Открытие... (0/${videoLinks.length})`;
- let openedCount = 0;
- videoLinks.forEach((url, index) => {
- setTimeout(() => {
- GM_openInTab(url, { active: false, insert: true });
- openedCount++;
- preloadButtonPreloader.textContent = `Открытие... (${openedCount}/${videoLinks.length})`;
- if (openedCount === videoLinks.length) {
- alert('Все ссылки на видео отправлены на открытие в фоновых вкладках.');
- preloadButtonPreloader.disabled = false;
- preloadButtonPreloader.textContent = 'Загрузить ВСЕ видео (со скроллом)';
- }
- }, index * 300);
- });
- } else {
- preloadButtonPreloader.textContent = 'Загрузить ВСЕ видео (со скроллом)';
- }
- }
- function initOrReinitPreloaderButton() {
- const oldButton = document.getElementById('yt-channel-full-preload-button');
- if (oldButton) {
- oldButton.remove();
- }
- // Check if on a /videos page
- if (window.location.pathname.endsWith('/videos') || /\/@.+\/videos/.test(window.location.pathname)) {
- // Delay to ensure page elements are settled, especially on SPA navigation
- setTimeout(createPreloadButtonPreloader, 1500); // Adjusted delay
- }
- }
- // --- Initialization ---
- // Initialize Grid Search UI
- createSearchBox();
- // Initialize Preloader Button (conditionally)
- initOrReinitPreloaderButton();
- // MutationObserver for SPA navigation to handle Preloader Button visibility
- let lastUrlPreloader = location.href;
- new MutationObserver(() => {
- const url = location.href;
- if (url !== lastUrlPreloader) {
- lastUrlPreloader = url;
- isLoadingStatePreloader = false; // Reset preloader's specific loading state
- initOrReinitPreloaderButton(); // Check and add/remove preloader button
- }
- }).observe(document, {subtree: true, childList: true});
- })();