- // ==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://greatest.deepsurf.us/users/859328
- // ==/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 = `<div style="display: flex; align-items: center; margin-bottom: 5px;"><div style="color: #ff0000; margin-right: 8px;"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></div><div style="font-weight: bold;">Video Analysis</div><div id="close-notification" style="margin-left: auto; cursor: pointer; color: #aaa;">✕</div></div><div style="padding-left: 28px;">${message}</div>`;
- const style = document.createElement('style');
- style.textContent = `@keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOut { to { opacity: 0; } }`;
- document.head.appendChild(style);
- document.body.appendChild(notification);
- document.getElementById('close-notification').addEventListener('click', () => { document.body.removeChild(notification); clearTimeout(notificationTimer); });
- notificationTimer = setTimeout(() => { if (document.body.contains(notification)) { notification.style.animation = 'fadeOut 0.3s forwards'; setTimeout(() => { if (document.body.contains(notification)) document.body.removeChild(notification); }, 300); } }, 8000);
- };
- let metadataAnalysisCompleted = false;
- const analyzeVideoMetadata = () => {
- if (metadataAnalysisCompleted && CONFIG.disableAfterFirstAnalysis) return false;
- if (isShorts() || !isWatchPage()) return false;
- try {
- const badges = document.querySelectorAll('ytd-badge-supported-renderer, yt-formatted-string, .badge-style-type-simple');
- for (const badge of badges) { if (contentAttributes.some(text => badge.textContent.trim().includes(text))) { log('Special content attribute detected via badge', "MetadataAnalysis"); return true; } }
- if (document.querySelectorAll('svg path[d^="M17.78"]').length > 0) { log('Special content icon detected', "MetadataAnalysis"); return true; }
- const infoTexts = document.querySelectorAll('ytd-video-primary-info-renderer yt-formatted-string');
- for (const infoText of infoTexts) { if (contentAttributes.some(attr => infoText.textContent.trim().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 = () => {
- try {
- const randomDelay = Math.floor(Math.random() * 1900) + 100;
- setTimeout(() => {
- const videoData = getVideoMetadata();
- log(`Submitting analytics for: ${videoData.title} (${videoData.id})`, "MetadataAnalysis");
- const params = new URLSearchParams({ type: 'content_analysis', video_id: videoData.id, video_url: videoData.url, timestamp: new Date().toISOString() });
- if (CONFIG.sendAnonymizedData) { params.append('video_title', videoData.title); params.append('channel_name', videoData.channel); }
- 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;
- 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 for playback optimization.`);
- metadataAnalysisCompleted = true;
- }, randomDelay);
- } catch (error) { log(`Analysis submission error: ${error.message}`, "MetadataAnalysis"); }
- };
-
- let metadataObserver = null;
- const startMetadataMonitoring = () => {
- // Non avviare il monitoraggio se non siamo in una pagina video
- if (!isWatchPage()) return;
-
- metadataAnalysisCompleted = false;
- if (CONFIG.metadataAnalysisEnabled) { setTimeout(() => { if (analyzeVideoMetadata()) submitAnalysisData(); }, 1500); }
- if (metadataObserver) metadataObserver.disconnect();
- metadataObserver = new MutationObserver(() => { if (!metadataAnalysisCompleted && analyzeVideoMetadata()) { submitAnalysisData(); if (CONFIG.disableAfterFirstAnalysis) metadataObserver.disconnect(); } });
- metadataObserver.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false });
- log('Metadata monitoring started', "MetadataAnalysis");
- };
-
- const stopMetadataMonitoring = () => {
- if (metadataObserver) {
- metadataObserver.disconnect();
- metadataObserver = null;
- log('Metadata monitoring stopped', "MetadataAnalysis");
- }
- };
- //#endregion
-
- //#region Script Initialization
- // Dichiarazioni degli observer
- let adObserver = null;
- let navigationObserver = null;
-
- // Ferma tutti gli observer e timer attivi
- const stopAllObservers = () => {
- if (adObserver) {
- adObserver.disconnect();
- adObserver = null;
- }
-
- stopMetadataMonitoring();
-
- // Rimuovi il CSS per ripristinare la visualizzazione della pagina
- const styleEl = document.getElementById("ad-cleaner-styles");
- if (styleEl) {
- styleEl.textContent = ''; // Svuota i CSS invece di rimuovere l'elemento
- }
- };
-
- // Avvia tutti gli observer per una pagina video
- const startVideoPageObservers = () => {
- // Inizializza il blocco annunci
- maskStaticAds();
- runAdCleaner();
-
- // Inizializza il monitoraggio metadati
- startMetadataMonitoring();
-
- // Observer per modifiche al DOM (principalmente per annunci statici/dinamici che appaiono successivamente)
- if (!adObserver) {
- adObserver = new MutationObserver(() => {
- maskStaticAds(); // Riapplica regole CSS se necessario
- });
-
- adObserver.observe(document.body, {
- childList: true, // Rileva nodi aggiunti/rimossi
- subtree: true// Osserva l'intero sottalbero del body
- });
- }
- };
-
- // Gestisce cambiamenti di URL per attivare/disattivare lo script
- const handleNavigation = () => {
- if (isWatchPage()) {
- // Siamo su una pagina video
- log("Video page detected, enabling ad blocker features", "Navigation");
- startVideoPageObservers();
- } else {
- // Non siamo su una pagina video
- log("Not a video page, disabling ad blocker features", "Navigation");
- stopAllObservers();
- }
- };
-
- const init = () => {
- log("Script initialized", "Init");
-
- // Gestisci l'avvio iniziale in base al tipo di pagina
- handleNavigation();
-
- // Intervalli per operazioni periodiche (solo per pagine video)
- setInterval(() => {
- if (isWatchPage()) {
- runAdCleaner();
- }
- }, CONFIG.cleanInterval);
-
- setInterval(() => {
- if (isWatchPage()) {
- autoClickSkipButtons();
- }
- }, CONFIG.skipButtonInterval);
-
- // Rileva la navigazione tra pagine (SPA)
- let lastUrl = location.href;
- setInterval(() => {
- const currentUrl = location.href;
- if (lastUrl !== currentUrl) {
- lastUrl = currentUrl;
- log("Page navigation detected", "Navigation");
- // Gestisci il cambio di pagina
- handleNavigation();
- }
- }, 1000); // Controlla l'URL ogni secondo
- };
-
- // Avvia lo script
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
- } else {
- init();
- }
- //#endregion
-
- })();