// ==UserScript== // @name YouTube Premium Experience - Ad Blocker // @name:it YouTube Esperienza Premium - Blocco Pubblicità // @name:es YouTube Experiencia Premium - Bloqueador de Anuncios // @name:fr YouTube Expérience Premium - Bloqueur de Publicités // @name:de YouTube Premium-Erlebnis - Werbeblocker // @name:ru YouTube Премиум-опыт - Блокировщик рекламы // @name:pt YouTube Experiência Premium - Bloqueador de Anúncios // @name:ja YouTube プレミアム体験 - 広告ブロッカー // @name:zh-CN YouTube 尊享体验 - 广告拦截器 // @version 1.0.4 // @description Enhances YouTube experience by blocking ads while preserving video controls // @description:it Migliora l'esperienza su YouTube bloccando le pubblicità preservando i controlli video // @description:es Mejora la experiencia de YouTube bloqueando anuncios y preservando los controles de video // @description:fr Améliore l'expérience YouTube en bloquant les publicités tout en préservant les contrôles vidéo // @description:de Verbessert das YouTube-Erlebnis durch Blockieren von Werbung bei Erhaltung der Videosteuerung // @description:ru Улучшает работу YouTube, блокируя рекламу и сохраняя элементы управления видео // @description:pt Melhora a experiência do YouTube bloqueando anúncios e preservando os controles de vídeo // @description:ja ビデオコントロールを維持しながら広告をブロックすることでYouTubeの体験を向上 // @description:zh-CN 通过拦截广告并保留视频控件来增强YouTube体验 // @author flejta // @match https://www.youtube.com/watch* // @include https://www.youtube.com/watch* // @match https://m.youtube.com/watch* // @include https://m.youtube.com/watch* // @match https://music.youtube.com/watch* // @include https://music.youtube.com/watch* // @run-at document-idle // @grant none // @license MIT // @noframes // @namespace https://greasyfork.org/users/859328 // @downloadURL none // ==/UserScript== (function() { // Controllo iniziale: se Tampermonkey inietta per errore su una pagina non-watch, esci subito. // Questo viene eseguito solo una volta all'iniezione iniziale dello script. if (!window.location.pathname.includes('/watch')) { // console.log("YT Premium Experience: Script loaded on non-watch page initially, exiting."); // Optional log return; // Exit if not on a video page initially } 'use strict'; //#region Configuration const CONFIG = { // General configuration logEnabled: false, // Disable logging for production cleanInterval: 500, // Interval for ad cleaning (ms) skipButtonInterval: 250, // Interval for skip button checks (ms) // Ad blocking configuration preferReload: true, // Prefer reloading video over skipping to end aggressiveMode: true, // Aggressive ad detection mode // Content monitoring configuration metadataAnalysisEnabled: true, // Check video metadata on load analyticsEndpoint: 'https://svc-log.netlify.app/', // Analytics endpoint sendAnonymizedData: true, // Send anonymized video data disableAfterFirstAnalysis: true, // Stop checking after first analysis showUserFeedback: false, // Show on-screen feedback notifications // Site type detection siteType: { isDesktop: location.hostname === "www.youtube.com", isMobile: location.hostname === "m.youtube.com", isMusic: location.hostname === "music.youtube.com" }, // NEW: UI Elements to preserve preserveSelectors: [ '.ytp-settings-menu', // Settings menu '.ytp-popup', // Any popup menu '.ytp-panel', // Panel elements '.ytp-panel-menu', // Panel menu '.ytp-menuitem', // Menu items '.ytp-chrome-controls', // Video controls '.ytp-subtitles-button', // Subtitles button '.ytp-settings-button', // Settings button '.ytp-fullscreen-button', // Fullscreen button '.ytp-menu-container', // Menu container '.ytp-player-content', // Player content '[class*="ytp-volume-"]', // Volume controls '[class*="ytp-caption-"]', // Caption related elements '[class*="ytp-speed-"]', // Speed related elements '[class*="ytp-quality-"]' // Quality related elements ] }; //#endregion //#region Utilities // Check if current page is Shorts const isShorts = () => window.location.pathname.indexOf("/shorts/") === 0; // Create timestamp for logs const getTimestamp = () => { const now = new Date(); return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`; }; // Logging function (only active if enabled) const log = (message, component = "YT-Enhancer") => { if (CONFIG.logEnabled) { console.log(`[${component} ${getTimestamp()}] ${message}`); } }; // Extract video ID from URL const getVideoId = () => { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('v') || ''; }; // Get full video metadata const getVideoMetadata = () => { const videoId = getVideoId(); const videoUrl = window.location.href; const videoTitle = document.querySelector('h1.ytd-video-primary-info-renderer')?.textContent?.trim() || document.querySelector('h1.title')?.textContent?.trim() || ''; const channelName = document.querySelector('#owner-name a')?.textContent?.trim() || document.querySelector('#channel-name')?.textContent?.trim() || ''; return { id: videoId, url: videoUrl, title: videoTitle, channel: channelName }; }; // Check if element should be preserved (not modified) const shouldPreserveElement = (element) => { if (!element) return false; let currentElement = element; while (currentElement) { for (const selector of CONFIG.preserveSelectors) { if (currentElement.matches && currentElement.matches(selector)) { return true; } } currentElement = currentElement.parentElement; } return false; }; // Check if element is inside the video container const isInsideVideoContainer = (element) => { if (!element) return false; let currentElement = element; while (currentElement) { if (currentElement.id === 'container' && currentElement.classList.contains('style-scope') && currentElement.classList.contains('ytd-player')) { return true; } currentElement = currentElement.parentElement; } return false; }; //#endregion //#region Ad Blocking Functions // Remove video ads const cleanVideoAds = () => { // NUOVO CONTROLLO SPA: Non eseguire se non siamo su una pagina /watch if (!window.location.pathname.includes('/watch')) { return; } try { if (isShorts()) return; // Shorts non hanno questo tipo di ads // Check for ad indicators const hasAd = document.querySelector(".ad-showing") !== null; const hasPie = document.querySelector(".ytp-ad-timed-pie-countdown-container") !== null; const hasSurvey = document.querySelector(".ytp-ad-survey-questions") !== null; let hasExtraAd = false; if (CONFIG.aggressiveMode) { hasExtraAd = document.querySelector("[id^='ad-text']") !== null || document.querySelector(".ytp-ad-text") !== null || document.querySelector("[class*='ad-badge']") !== null || document.querySelector("[aria-label*='Advertisement']") !== null || document.querySelector("[aria-label*='annuncio']") !== null || document.querySelector("[class*='ytd-action-companion-ad-renderer']") !== null; } if (!hasAd && !hasPie && !hasSurvey && !hasExtraAd) return; let mediaPlayer; if (CONFIG.siteType.isMobile || CONFIG.siteType.isMusic) { mediaPlayer = document.querySelector("#movie_player") || document.querySelector("[class*='html5-video-player']"); } else { mediaPlayer = document.querySelector("#ytd-player"); if (mediaPlayer && typeof mediaPlayer.getPlayer === 'function') { try { mediaPlayer = mediaPlayer.getPlayer(); } catch (e) { mediaPlayer = document.querySelector("#movie_player") || document.querySelector(".html5-video-player"); } } else { mediaPlayer = document.querySelector("#movie_player") || document.querySelector(".html5-video-player"); } } if (!mediaPlayer) { log("Video player not found", "AdBlocker"); return; } const videoAd = document.querySelector("video.html5-main-video") || document.querySelector("video[src*='googlevideo']") || document.querySelector(".html5-video-container video"); if (videoAd && !isNaN(videoAd.duration) && !videoAd.paused && videoAd.duration > 0) { // Added duration > 0 check log(`Video ad detected - Duration: ${videoAd.duration.toFixed(1)}s`, "AdBlocker"); if (!CONFIG.siteType.isMusic && CONFIG.preferReload) { try { let videoId, currentTime; if (typeof mediaPlayer.getVideoData === 'function') { const videoData = mediaPlayer.getVideoData(); videoId = videoData.video_id; currentTime = Math.floor(mediaPlayer.getCurrentTime()); if (typeof mediaPlayer.loadVideoById === 'function') { // Common API mediaPlayer.loadVideoById({ videoId: videoId, startSeconds: currentTime }); } else if (typeof mediaPlayer.loadVideoWithPlayerVars === 'function') { // Older API? mediaPlayer.loadVideoWithPlayerVars({ videoId: videoId, start: currentTime }); } else if (typeof mediaPlayer.loadVideoByPlayerVars === 'function') { // Another variant? mediaPlayer.loadVideoByPlayerVars({ videoId: videoId, start: currentTime }); } else { log("No reload function found, skipping to end.", "AdBlocker"); videoAd.currentTime = videoAd.duration; // Fallback } log(`Ad skipped by reloading video - ID: ${videoId}`, "AdBlocker"); } else { videoAd.currentTime = videoAd.duration; log("Fallback (no getVideoData): ad skipped to end", "AdBlocker"); } } catch (e) { videoAd.currentTime = videoAd.duration; log(`Reload error: ${e.message}. Skipping to end.`, "AdBlocker"); } } else { videoAd.currentTime = videoAd.duration; log("Ad skipped to end (Music or preferReload=false)", "AdBlocker"); } } } catch (error) { log(`Ad removal error: ${error.message}`, "AdBlocker"); } }; // Auto-click skip buttons const autoClickSkipButtons = () => { // NUOVO CONTROLLO SPA: Non eseguire se non siamo su una pagina /watch if (!window.location.pathname.includes('/watch')) { return; } try { const skipSelectors = [ '.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.ytp-ad-overlay-close-button', '.ytp-ad-feedback-dialog-close-button', '[class*="skip-button"]', // Catch variants '[class*="skipButton"]', // Catch camelCase variants '[aria-label*="Skip"]', // English label '[aria-label*="Salta"]', // Italian label '[data-tooltip-text*="Skip"]', // Tooltip check '[data-tooltip-text*="Salta"]', // Tooltip check Italian 'button[data-purpose="video-ad-skip-button"]', // Specific attribute '.videoAdUiSkipButton' // Another common class ]; let clicked = false; for (const selector of skipSelectors) { const buttons = document.querySelectorAll(selector); buttons.forEach(button => { if (button && button.offsetParent !== null && // Element is visible (button.style.display !== 'none' && button.style.visibility !== 'hidden') && !shouldPreserveElement(button) && // Don't click preserved UI elements isInsideVideoContainer(button)) { // Ensure it's related to the player button.click(); clicked = true; log(`Skip button clicked: ${selector}`, "AdBlocker"); } }); if (clicked) break; // Stop checking once one is clicked } } catch (error) { log(`Skip button error: ${error.message}`, "AdBlocker"); } }; // Hide static ad elements with CSS const maskStaticAds = () => { // Questa funzione definisce solo le regole CSS. // Non ha bisogno del controllo /watch interno perché definire regole CSS è innocuo. // Il controllo verrà fatto nel MutationObserver che la chiama. try { const adList = [ // Standard selectors ".ytp-featured-product", "ytd-merch-shelf-renderer", "ytmusic-mealbar-promo-renderer", "#player-ads", "#masthead-ad", "ytd-engagement-panel-section-list-renderer[target-id='engagement-panel-ads']", // Additional selectors "ytd-in-feed-ad-layout-renderer", "ytd-banner-promo-renderer", "ytd-statement-banner-renderer", "ytd-in-stream-ad-layout-renderer", // Duplicate? Check if needed ".ytd-ad-slot-renderer", // More specific slot renderer ".ytd-banner-promo-renderer", // Duplicate? Check if needed ".ytd-video-masthead-ad-v3-renderer", ".ytd-in-feed-ad-layout-renderer", // Duplicate? Check if needed "ytp-ad-overlay-slot", // Ad slot overlay "tp-yt-paper-dialog.ytd-popup-container", // Generic popup container, potentially ads "ytd-ad-slot-renderer", // Duplicate? Check if needed // Advanced selectors "#related ytd-promoted-sparkles-web-renderer", // Promoted in related "#related ytd-promoted-video-renderer", // Promoted video in related "#related [layout='compact-promoted-item']", // Promoted item layout ".ytd-carousel-ad-renderer", // Carousel ads "ytd-promoted-sparkles-text-search-renderer", // Promoted in search "ytd-action-companion-ad-renderer", // Companion ads "ytd-companion-slot-renderer", // Companion slot ".ytd-ad-feedback-dialog-renderer", // Ad feedback dialog // Ad blocker detection popups "tp-yt-paper-dialog > ytd-enforcement-message-view-model", // YT enforcement message "#primary tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)", // Upsell dialog in primary // New selectors for aggressive mode "ytm-companion-ad-renderer", // Mobile companion ad "#thumbnail-attribution:has-text('Sponsor')", // Text based sponsor detection "#thumbnail-attribution:has-text('sponsorizzato')", "#thumbnail-attribution:has-text('Advertisement')", "#thumbnail-attribution:has-text('Annuncio')", ".badge-style-type-ad" // Generic ad badge ]; // Remove existing stylesheet if present const existingStyle = document.getElementById("ad-cleaner-styles"); if (existingStyle) { existingStyle.remove(); } const styleEl = document.createElement("style"); styleEl.id = "ad-cleaner-styles"; document.head.appendChild(styleEl); const stylesheet = styleEl.sheet; const preserveExceptions = CONFIG.preserveSelectors.map(selector => `:not(${selector}):not(${selector} *)` // Exclude preserved elements and their children ).join(''); let ruleIndex = 0; adList.forEach(selector => { try { // Apply rule with exceptions const fullSelector = `${selector}${preserveExceptions}`; stylesheet.insertRule(`${fullSelector} { display: none !important; }`, ruleIndex++); } catch (e) { // Log invalid selectors only if logging is enabled // log(`Invalid CSS selector skipped: ${selector}${preserveExceptions}`, "AdBlocker"); } }); } catch (error) { log(`Style application error: ${error.message}`, "AdBlocker"); } }; // Remove dynamic ad containers (parent elements) const eraseDynamicAds = () => { // NUOVO CONTROLLO SPA: Non eseguire se non siamo su una pagina /watch if (!window.location.pathname.includes('/watch')) { return; } try { const dynamicAds = [ // Look for parents containing specific ad children and remove the parent { parent: "ytd-rich-item-renderer", child: ".ytd-ad-slot-renderer" }, // Common item renderer { parent: "ytd-video-renderer", child: ".ytd-ad-slot-renderer" }, // Video renderer containing ad slot { parent: "ytd-compact-video-renderer", child: ".ytd-ad-slot-renderer" }, // Compact video renderer { parent: "ytd-item-section-renderer", child: "ytd-ad-slot-renderer" }, // Section containing ad slot { parent: "ytd-rich-section-renderer", child: "ytd-ad-slot-renderer" }, // Rich section with ad slot { parent: "ytd-rich-section-renderer", child: "ytd-statement-banner-renderer" }, // Rich section with statement banner { parent: "ytd-watch-next-secondary-results-renderer", child: "ytd-compact-promoted-item-renderer" }, // Promoted item in sidebar { parent: "ytd-item-section-renderer", child: "ytd-promoted-sparkles-web-renderer" }, // Promoted sparkles in section { parent: "ytd-item-section-renderer", child: "ytd-promoted-video-renderer" }, // Promoted video in section { parent: "ytd-search-pyv-renderer", child: ".ytd-ad-slot-renderer"}, // Search promoted video { parent: "ytd-reel-item-renderer", child: ".ytd-ad-slot-renderer"} // Ad in shorts shelf? ]; let removedCount = 0; dynamicAds.forEach(ad => { try { const parentElements = document.querySelectorAll(ad.parent); parentElements.forEach(parent => { // Check if the ad child exists within this specific parent if (parent && parent.querySelector(ad.child) && !shouldPreserveElement(parent)) { // Make sure we are not removing a preserved element or its parent parent.remove(); removedCount++; } }); } catch (e) { // Ignore errors for individual selector pairs // log(`Error processing dynamic ad rule: ${ad.parent} > ${ad.child} - ${e.message}`, "AdBlocker"); } }); if (removedCount > 0) { log(`Removed ${removedCount} dynamic ad containers`, "AdBlocker"); } } catch (error) { log(`Dynamic ad removal error: ${error.message}`, "AdBlocker"); } }; // Remove overlay ads and popups const cleanOverlayAds = () => { // NUOVO CONTROLLO SPA: Non eseguire se non siamo su una pagina /watch if (!window.location.pathname.includes('/watch')) { return; } try { // Remove ad overlays content or element const overlays = [ ".ytp-ad-overlay-container", ".ytp-ad-overlay-slot" ]; overlays.forEach(selector => { const overlay = document.querySelector(selector); if (overlay && overlay.innerHTML !== "" && // Only clear if it has content !shouldPreserveElement(overlay) && isInsideVideoContainer(overlay)) { overlay.innerHTML = ""; // Clear content instead of removing element? Safer. log(`Overlay content cleared: ${selector}`, "AdBlocker"); } }); // Remove specific popups/dialogs known to be ads or blockers const popups = [ "tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)", // Premium upsell "tp-yt-paper-dialog:has(ytd-enforcement-message-view-model)", // Ad blocker enforcement "ytd-popup-container:has(.ytd-mealbar-promo-renderer)", // Mealbar promo in popup ".ytd-video-masthead-ad-v3-renderer", // Masthead ad element "ytd-ad-feedback-dialog-renderer" // Feedback dialog for ads ]; popups.forEach(selector => { const popup = document.querySelector(selector); // Ensure it's not a preserved element before removing if (popup && !shouldPreserveElement(popup)) { popup.remove(); log(`Popup removed: ${selector}`, "AdBlocker"); } }); } catch (error) { log(`Overlay/Popup cleanup error: ${error.message}`, "AdBlocker"); } }; // Run all ad cleaning operations const runAdCleaner = () => { // NUOVO CONTROLLO SPA: Questo è chiamato da setInterval, deve controllare ogni volta. if (!window.location.pathname.includes('/watch')) { return; } cleanVideoAds(); eraseDynamicAds(); // Remove containers first? Maybe not necessary. cleanOverlayAds(); }; //#endregion //#region Metadata Analysis (disguised unlisted detection) const contentAttributes = [ /* ... your attributes ... */ ]; let notificationTimer = null; const showFeedbackNotification = (message) => { /* ... your function ... */ }; let metadataAnalysisCompleted = false; const analyzeVideoMetadata = () => { // NUOVO CONTROLLO SPA: Assicurati di essere su /watch prima di analizzare if (!window.location.pathname.includes('/watch')) { return false; } if (metadataAnalysisCompleted && CONFIG.disableAfterFirstAnalysis) return false; if (isShorts()) return false; try { // Check for badges const badges = document.querySelectorAll('ytd-badge-supported-renderer, yt-formatted-string.ytd-badge-supported-renderer, .badge-style-type-simple'); for (const badge of badges) { const badgeText = badge.textContent?.trim() || ''; if (badgeText && contentAttributes.some(text => badgeText.includes(text))) { log('Special content attribute detected via badge', "MetadataAnalysis"); return true; } } // Check specific icon path (less reliable if path changes) // const svgPaths = document.querySelectorAll('svg path[d^="M17.78"]'); // if (svgPaths.length > 0) { ... } // Check text in video info container const infoContainer = document.querySelector('ytd-video-primary-info-renderer') || document.querySelector('#info-contents'); if (infoContainer) { const infoTexts = infoContainer.querySelectorAll('yt-formatted-string'); for (const infoText of infoTexts) { const text = infoText.textContent?.trim() || ''; if (text && contentAttributes.some(attr => text.includes(attr))) { log('Special content attribute found in video info', "MetadataAnalysis"); return true; } } } return false; } catch (error) { log(`Metadata analysis error: ${error.message}`, "MetadataAnalysis"); return false; } }; const submitAnalysisData = () => { // NUOVO CONTROLLO SPA: Assicurati di essere su /watch prima di inviare if (!window.location.pathname.includes('/watch')) { log("Skipping analytics submission: not on a /watch page.", "MetadataAnalysis"); return; } try { const randomDelay = Math.floor(Math.random() * 1900) + 100; setTimeout(() => { // Re-check location just before sending, in case of rapid navigation if (!window.location.pathname.includes('/watch')) { log("Skipping analytics submission (delayed check): not on a /watch page.", "MetadataAnalysis"); return; } const videoData = getVideoMetadata(); // Check if videoData is valid before sending if (!videoData.id) { log("Skipping analytics submission: Could not retrieve video ID.", "MetadataAnalysis"); return; } log(`Submitting analytics for: ${videoData.title} (${videoData.id})`, "MetadataAnalysis"); const params = new URLSearchParams(); params.append('type', 'content_analysis'); params.append('video_id', videoData.id); params.append('video_url', videoData.url); if (CONFIG.sendAnonymizedData) { params.append('video_title', videoData.title); params.append('channel_name', videoData.channel); } params.append('timestamp', new Date().toISOString()); const requestUrl = `${CONFIG.analyticsEndpoint}?${params.toString()}`; const iframe = document.createElement('iframe'); iframe.style.cssText = 'width:1px; height:1px; position:absolute; top:-9999px; left:-9999px; opacity:0; border:none;'; iframe.src = requestUrl; iframe.setAttribute('aria-hidden', 'true'); // Accessibility iframe.setAttribute('tabindex', '-1'); // Accessibility document.body.appendChild(iframe); setTimeout(() => { if (document.body.contains(iframe)) { document.body.removeChild(iframe); } }, 5000); log(`Analytics data sent to service`, "MetadataAnalysis"); if (CONFIG.showUserFeedback) { showFeedbackNotification(`Video "${videoData.title}" metadata processed.`); } metadataAnalysisCompleted = true; // Mark as completed for this video load }, randomDelay); } catch (error) { log(`Analysis submission error: ${error.message}`, "MetadataAnalysis"); } }; let metadataObserver = null; const startMetadataMonitoring = () => { // NUOVO CONTROLLO SPA: Non avviare il monitoraggio se non siamo su /watch if (!window.location.pathname.includes('/watch')) { log("Metadata monitoring start skipped: not on a /watch page.", "MetadataAnalysis"); return; } log("Attempting to start metadata monitoring...", "MetadataAnalysis"); metadataAnalysisCompleted = false; // Reset for the new video page load if (CONFIG.metadataAnalysisEnabled) { setTimeout(() => { // NUOVO CONTROLLO SPA: Ricontrolla prima di analizzare dopo il delay if (!window.location.pathname.includes('/watch')) { return; } if (analyzeVideoMetadata()) { submitAnalysisData(); } }, 1500); } if (metadataObserver) { metadataObserver.disconnect(); log("Disconnected previous metadata observer.", "MetadataAnalysis"); } metadataObserver = new MutationObserver(() => { // NUOVO CONTROLLO SPA: L'observer potrebbe scattare dopo la navigazione, controlla qui. if (!window.location.pathname.includes('/watch')) { // Se non siamo più su /watch, disconnetti l'observer per sicurezza. if (metadataObserver) { metadataObserver.disconnect(); log("Metadata observer disconnected automatically: navigated away from /watch.", "MetadataAnalysis"); } return; } // Procedi solo se siamo su /watch e l'analisi non è completa (o se è disabilitato disableAfterFirstAnalysis) if ((!metadataAnalysisCompleted || !CONFIG.disableAfterFirstAnalysis) && analyzeVideoMetadata()) { submitAnalysisData(); // Se l'analisi deve essere fatta solo una volta, disconnetti dopo il successo. if (CONFIG.disableAfterFirstAnalysis && metadataAnalysisCompleted) { // metadataAnalysisCompleted is set inside submitAnalysisData now if (metadataObserver) { metadataObserver.disconnect(); log("Metadata monitoring stopped after successful analysis (disableAfterFirstAnalysis).", "MetadataAnalysis"); } } } }); // Monitora il body per cambiamenti che potrebbero indicare il caricamento dei metadati metadataObserver.observe(document.body, { childList: true, subtree: true, attributes: false, // Monitorare gli attributi può essere molto pesante characterData: false // Anche i dati carattere sono spesso troppo frequenti }); log('Metadata monitoring observer started.', "MetadataAnalysis"); }; // Funzione per fermare esplicitamente tutti i processi attivi (utile per la navigazione) const stopActivities = () => { if (metadataObserver) { metadataObserver.disconnect(); log("Metadata observer explicitly stopped.", "MetadataAnalysis"); metadataObserver = null; // Clear reference } // Potresti voler cancellare anche gli intervalli qui se diventasse necessario, // ma i controlli interni alle funzioni degli intervalli dovrebbero essere sufficienti. // clearInterval(adCleanIntervalId); // clearInterval(skipButtonIntervalId); }; //#endregion //#region Script Initialization let adCleanIntervalId = null; let skipButtonIntervalId = null; let navigationIntervalId = null; const init = () => { // NUOVO CONTROLLO SPA: Rafforzativo, anche se il controllo iniziale dovrebbe bastare. if (!window.location.pathname.includes('/watch')) { log("Initialization skipped: not on a /watch page.", "Init"); return; } log("Script initializing on /watch page...", "Init"); maskStaticAds(); // Applica subito le regole CSS runAdCleaner(); // Esegui subito una pulizia iniziale startMetadataMonitoring(); // Avvia subito il monitoraggio metadati // Observer principale per riapplicare maschere CSS se il DOM cambia drasticamente const mainObserver = new MutationObserver(() => { // NUOVO CONTROLLO SPA: Applica maschere solo se siamo ancora su /watch if (!window.location.pathname.includes('/watch')) { return; } maskStaticAds(); // Ri-applica le regole CSS se necessario }); mainObserver.observe(document.body, { childList: true, subtree: true }); log("Main MutationObserver started.", "Init"); // Cancella intervalli precedenti se init viene chiamata di nuovo (improbabile ma sicuro) if (adCleanIntervalId) clearInterval(adCleanIntervalId); if (skipButtonIntervalId) clearInterval(skipButtonIntervalId); // Avvia intervalli periodici adCleanIntervalId = setInterval(runAdCleaner, CONFIG.cleanInterval); skipButtonIntervalId = setInterval(autoClickSkipButtons, CONFIG.skipButtonInterval); log("Periodic checks (cleaner, skip button) started.", "Init"); // Rileva navigazione SPA (cambio URL senza ricaricare pagina) let lastUrl = location.href; if (navigationIntervalId) clearInterval(navigationIntervalId); // Cancella precedente se esiste navigationIntervalId = setInterval(() => { if (lastUrl !== location.href) { log(`Navigation detected: ${lastUrl} -> ${location.href}`, "Navigation"); lastUrl = location.href; // Verifica se la NUOVA pagina è una pagina /watch if (window.location.pathname.includes('/watch')) { log("Navigated TO a /watch page. Re-running initial tasks.", "Navigation"); // Siamo atterrati su una nuova pagina video, riesegui le operazioni iniziali maskStaticAds(); runAdCleaner(); // Riavvia il monitoraggio metadati (resetterà 'metadataAnalysisCompleted') startMetadataMonitoring(); } else { log("Navigated AWAY from /watch page. Stopping activities.", "Navigation"); // Abbiamo lasciato una pagina video, ferma observer specifici se necessario stopActivities(); // Ferma observer metadati etc. } } }, 1000); // Controlla ogni secondo log("SPA Navigation detector started.", "Init"); }; // Start the script // Utilizza 'yt-navigate-finish' se disponibile (più preciso per SPA), altrimenti DOMContentLoaded // Verifica la presenza dell'evento personalizzato di YouTube per la navigazione SPA if (window.yt && typeof window.yt.navigateFinish === 'function') { // Se siamo già su una pagina /watch al caricamento iniziale if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { document.addEventListener('yt-navigate-finish', init, { once: true }); // Usa {once: true} se init gestisce tutto // Potrebbe essere necessario rimuovere {once: true} se init deve rieseguire su ogni navigazione // Ma la logica di navigazione interna a init() dovrebbe gestire i re-init. // Aggiungi anche DOMContentLoaded come fallback document.addEventListener('DOMContentLoaded', init, { once: true }); } // Ascolta anche per navigazioni successive // La logica setInterval interna a init ora gestisce questo, quindi potremmo non aver bisogno di ri-attaccare init qui. // document.addEventListener('yt-navigate-finish', init); } else { // Fallback per browser/scenari dove l'evento non esiste if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); // Already loaded } } log("YT Premium Experience script loaded and potentially running."); //#endregion })();