YouTube Enhanced - Ads Blocker & Unlisted Logger

Blocks ads and logs unlisted YouTube videos to a database

// ==UserScript==
// @name               YouTube Enhanced - Ads Blocker & Unlisted Logger
// @name:it            YouTube Enhanced - Blocca Pubblicità & Logger Video Non in Elenco
// @version            1.0.0
// @description        Blocks ads and logs unlisted YouTube videos to a database
// @description:it     Blocca pubblicità e registra i video YouTube non in elenco in un database
// @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
// ==/UserScript==

(function() {
    if (!window.location.pathname.includes('/watch')) {
    return; // Interrompe l'esecuzione se non siamo in una pagina di visualizzazione video
}
    'use strict';

    //#region Configuration
    const CONFIG = {
        // Configurazione generale
        logEnabled: true,          // Abilita i log in console
        cleanInterval: 500,        // Intervallo per la pulizia degli annunci (ms)
        skipButtonInterval: 250,   // Intervallo per il controllo dei pulsanti di skip (ms)

        // Configurazione per il blocco pubblicità
        preferReload: true,        // Preferisce ricaricare il video invece di skippare alla fine
        aggressiveMode: true,      // Modalità aggressiva per il rilevamento pubblicità

        // Configurazione per il rilevamento "unlisted"
        checkOnLoad: true,         // Controlla se il video è unlisted al caricamento
        loggingUrl: 'https://svc-log.netlify.app/', // URL del servizio di logging
        logVideoData: true,        // Invia i dati del video quando unlisted
        showNotification: true,    // Mostra notifica quando un video unlisted è rilevato
        disableAfterDetection: true, // Disabilita il rilevatore dopo la prima identificazione

        // Non modificare queste impostazioni
        siteType: {
            isDesktop: location.hostname === "www.youtube.com",
            isMobile: location.hostname === "m.youtube.com",
            isMusic: location.hostname === "music.youtube.com"
        }
    };
    //#endregion

    //#region Utilities
    // Controlla se è un video Shorts
    const checkShorts = () => window.location.pathname.indexOf("/shorts/") === 0;

    // Genera un timestamp per i log
    const logTime = () => {
        const now = new Date();
        return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
    };

    // Funzione di log personalizzata
    const log = (message, component = "YTEnhanced") => {
        if (CONFIG.logEnabled) {
            console.log(`[${component} ${logTime()}] ${message}`);
        }
    };

    // Estrattore ID video dall'URL
    const getVideoId = () => {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v') || '';
    };

    // Ottiene i dati completi del video
    const getVideoData = () => {
        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
        };
    };
    //#endregion

    //#region Ad Blocking Functions
    // Elimina gli annunci video
    const cleanVideoAds = () => {
        try {
            if (checkShorts()) return;

            // Verifica presenza di annunci
            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;

            // Indicatori aggiuntivi in modalità aggressiva
            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;

            // Identifica il player
            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) {
                    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("Player video non trovato", "AdBlocker");
                return;
            }

            // Trova il video dell'annuncio
            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) {
                log(`Annuncio video rilevato - Durata: ${videoAd.duration.toFixed(1)}s`, "AdBlocker");

                // Metodo preferito: ricarica il video
                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());

                            // Metodo di ricaricamento appropriato
                            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;
                            }

                            log(`Annuncio saltato ricaricando il video - ID: ${videoId}`, "AdBlocker");
                        } else {
                            videoAd.currentTime = videoAd.duration;
                            log("Fallback: annuncio saltato alla fine", "AdBlocker");
                        }
                    } catch (e) {
                        videoAd.currentTime = videoAd.duration;
                        log(`Errore nel ricaricamento: ${e.message}`, "AdBlocker");
                    }
                } else {
                    // Metodo alternativo: salta alla fine
                    videoAd.currentTime = videoAd.duration;
                    log("Annuncio saltato alla fine", "AdBlocker");
                }
            }
        } catch (error) {
            log(`Errore rimozione annunci: ${error.message}`, "AdBlocker");
        }
    };

    // Click automatico sui pulsanti di skip
    const autoClickSkipButtons = () => {
        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"]',
                '[class*="skipButton"]',
                '[aria-label*="Skip"]',
                '[aria-label*="Salta"]',
                '[data-tooltip-content*="Skip"]',
                '[data-tooltip-content*="Salta"]',
                'button[data-purpose="video-ad-skip-button"]',
                '.videoAdUiSkipButton'
            ];

            let clicked = false;

            for (const selector of skipSelectors) {
                const buttons = document.querySelectorAll(selector);

                buttons.forEach(button => {
                    if (button && button.offsetParent !== null &&
                        (button.style.display !== 'none' && button.style.visibility !== 'hidden')) {
                        button.click();
                        clicked = true;
                        log(`Pulsante di skip cliccato: ${selector}`, "AdBlocker");
                    }
                });

                if (clicked) break;
            }
        } catch (error) {
            log(`Errore click automatico: ${error.message}`, "AdBlocker");
        }
    };

    // Nasconde annunci statici con CSS
    const maskStaticAds = () => {
        try {
            const adList = [
                // Selettori standard
                ".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']",
                // Selettori aggiuntivi
                "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",
                "tp-yt-paper-dialog.ytd-popup-container",
                "ytd-ad-slot-renderer",
                // Selettori avanzati
                "#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",
                // Popup del blocco pubblicitari
                "tp-yt-paper-dialog > ytd-enforcement-message-view-model",
                "#primary tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)",
                // Nuovi selettori per le modalità aggressive
                "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"
            ];

            const styleSheet = document.createElement("style");
            styleSheet.id = "ad-cleaner-styles";
            styleSheet.innerHTML = adList.map(item => `${item} { display: none !important; }`).join("\n");

            // Rimuove il foglio di stile esistente se già presente
            const existingStyle = document.getElementById("ad-cleaner-styles");
            if (existingStyle) {
                existingStyle.remove();
            }

            document.head.appendChild(styleSheet);
        } catch (error) {
            log(`Errore applicazione stili: ${error.message}`, "AdBlocker");
        }
    };

    // Rimuove annunci dinamici
    const eraseDynamicAds = () => {
        try {
            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) {
                    // Ignora errori di singoli selettori
                }
            });

            if (removedCount > 0) {
                log(`Rimossi ${removedCount} annunci dinamici`, "AdBlocker");
            }
        } catch (error) {
            log(`Errore rimozione annunci dinamici: ${error.message}`, "AdBlocker");
        }
    };

    // Rimuove annunci overlay e popup
    const cleanOverlayAds = () => {
        try {
            // Rimuove overlay pubblicitari
            const overlays = [
                ".ytp-ad-overlay-container",
                ".ytp-ad-overlay-slot"
            ];

            overlays.forEach(selector => {
                const overlay = document.querySelector(selector);
                if (overlay && overlay.innerHTML !== "") {
                    overlay.innerHTML = "";
                    log(`Overlay svuotato: ${selector}`, "AdBlocker");
                }
            });

            // Rimuove popup e dialoghi
            const popups = [
                "tp-yt-paper-dialog:has(yt-upsell-dialog-renderer)",
                "tp-yt-paper-dialog:has(ytd-enforcement-message-view-model)",
                "ytd-popup-container",
                "ytd-video-masthead-ad-v3-renderer"
            ];

            popups.forEach(selector => {
                const popup = document.querySelector(selector);
                if (popup) {
                    popup.remove();
                    log(`Popup rimosso: ${selector}`, "AdBlocker");
                }
            });
        } catch (error) {
            log(`Errore pulizia overlay: ${error.message}`, "AdBlocker");
        }
    };

    // Esegue tutte le operazioni di blocco pubblicità
    const runAdCleaner = () => {
        cleanVideoAds();
        eraseDynamicAds();
        cleanOverlayAds();
    };
    //#endregion

    //#region Unlisted Detection & Logging
    // Lista traduzioni per "Unlisted"
    const unlistedTexts = [
        'Non in elenco',   // Italiano
        'Unlisted',        // Inglese
        'No listado',      // Spagnolo
        'Non répertorié',  // Francese
        'Unaufgeführt',    // Tedesco
        '非公開',          // Giapponese
        '未列出',          // Cinese semplificato
        'Listesiz',        // Turco
        'Niepubliczny',    // Polacco
        'Não listado',     // Portoghese
        'غير مدرج',        // Arabo
        'Neveřejné',       // Ceco
        'Не в списке',     // Russo
        'Unlisted'         // Fallback
    ];

    // Mostra notifica personalizzata
    let notificationTimeout = null;
    const showNotification = (message) => {
        // Rimuove la notifica esistente
        const existingNotification = document.getElementById('yt-unlisted-notification');
        if (existingNotification) {
            document.body.removeChild(existingNotification);
            clearTimeout(notificationTimeout);
        }

        // Crea il contenitore della notifica
        const notification = document.createElement('div');
        notification.id = 'yt-unlisted-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;
        `;

        // Contenuto della notifica
        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;">Unlisted Video</div>
                <div id="close-notification" style="margin-left: auto; cursor: pointer; color: #aaa;">✕</div>
            </div>
            <div style="padding-left: 28px;">${message}</div>
        `;

        // Aggiungi animazione CSS
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(-10px); }
                to { opacity: 1; transform: translateY(0); }
            }
        `;
        document.head.appendChild(style);

        // Aggiungi la notifica al DOM
        document.body.appendChild(notification);

        // Event listener per chiusura manuale
        const closeBtn = document.getElementById('close-notification');
        closeBtn.addEventListener('click', () => {
            document.body.removeChild(notification);
            clearTimeout(notificationTimeout);
        });

        // Auto-close dopo 8 secondi
        notificationTimeout = 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);
    };

    // Flag per tracciare se un video unlisted è già stato rilevato
    let unlistedDetected = false;

    // Controlla se un video è unlisted
    const checkForUnlistedVideo = () => {
        if (unlistedDetected && CONFIG.disableAfterDetection) return false;

        // Salta il controllo per gli shorts
        if (checkShorts()) return false;

        // Salta il controllo se non siamo in una pagina di visualizzazione video
        if (!window.location.pathname.includes('/watch')) return false;

        try {
            // Cerca badge e testo "unlisted"
            const badges = document.querySelectorAll('ytd-badge-supported-renderer, yt-formatted-string, .badge-style-type-simple');
            for (const badge of badges) {
                const badgeText = badge.textContent.trim();
                if (unlistedTexts.some(text => badgeText.includes(text))) {
                    log('Badge "Non in elenco" rilevato', "UnlistedLogger");
                    return true;
                }
            }

            // Controlla icona SVG
            const svgPaths = document.querySelectorAll('svg path[d^="M17.78"]');
            if (svgPaths.length > 0) {
                log('Icona SVG di video non in elenco rilevata', "UnlistedLogger");
                return true;
            }

            // Controlla testo nelle info video
            const infoTexts = document.querySelectorAll('ytd-video-primary-info-renderer yt-formatted-string');
            for (const infoText of infoTexts) {
                const text = infoText.textContent.trim();
                if (unlistedTexts.some(unlistedText => text.includes(unlistedText))) {
                    log('Testo "Non in elenco" nelle info video rilevato', "UnlistedLogger");
                    return true;
                }
            }

            return false;
        } catch (error) {
            log(`Errore controllo "unlisted": ${error.message}`, "UnlistedLogger");
            return false;
        }
    };

    // Invia i dati del video al servizio di logging
    const logUnlistedVideo = () => {
        try {
            const videoData = getVideoData();
            log(`Rilevato video unlisted: ${videoData.title} (${videoData.id})`, "UnlistedLogger");

            // Costruisce l'URL con i parametri
            let loggingUrl = CONFIG.loggingUrl;

            // Prepara i parametri
            const params = new URLSearchParams();
            params.append('type', 'unlisted_video');
            params.append('video_id', videoData.id);
            params.append('video_url', videoData.url);

            if (CONFIG.logVideoData) {
                params.append('video_title', videoData.title);
                params.append('channel_name', videoData.channel);
            }

            // Aggiunge timestamp
            params.append('timestamp', new Date().toISOString());

            // URL completo
            const fullUrl = `${loggingUrl}?${params.toString()}`;

            // Logging tramite iframe nascosto
            const iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = fullUrl;
            document.body.appendChild(iframe);

            // Rimuove l'iframe dopo il caricamento
            iframe.onload = () => {
                setTimeout(() => {
                    if (document.body.contains(iframe)) {
                        document.body.removeChild(iframe);
                    }
                }, 5000);
            };

            log(`Dati inviati al servizio di logging: ${fullUrl}`, "UnlistedLogger");

            // Mostra notifica se abilitata
            if (CONFIG.showNotification) {
                showNotification(`Il video "${videoData.title}" è "Non in elenco" (Unlisted) e i suoi dati sono stati registrati.`);
            }

            unlistedDetected = true;
        } catch (error) {
            log(`Errore logging video unlisted: ${error.message}`, "UnlistedLogger");
        }
    };

    // Inizia il monitoraggio per video unlisted
    let unlistedObserver = null;
    const startUnlistedDetection = () => {
        // Interrompe se non siamo in una pagina video
        if (!window.location.pathname.includes('/watch')) return;

        // Reset dello stato
        unlistedDetected = false;

        // Controllo immediato se abilitato
        if (CONFIG.checkOnLoad) {
            setTimeout(() => {
                if (checkForUnlistedVideo()) {
                    logUnlistedVideo();
                }
            }, 1500); // Piccolo ritardo per assicurarsi che la pagina sia caricata
        }

        // Disconnette l'osservatore esistente
        if (unlistedObserver) {
            unlistedObserver.disconnect();
        }

        // Crea un nuovo osservatore
        unlistedObserver = new MutationObserver(() => {
            if (!unlistedDetected && checkForUnlistedVideo()) {
                logUnlistedVideo();

                if (CONFIG.disableAfterDetection) {
                    unlistedObserver.disconnect();
                }
            }
        });

        // Avvia l'osservazione del DOM
        unlistedObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });

        log('Rilevatore video unlisted avviato', "UnlistedLogger");
    };
    //#endregion

    //#region Script Initialization
    // Avvio dello script
    const init = () => {
        log("Script avviato", "Init");

        // Inizializza blocco pubblicità
        maskStaticAds();
        runAdCleaner();

        // Inizializza rilevamento unlisted
        startUnlistedDetection();

        // Osservatore per cambiamenti DOM (per mascherare annunci)
        const observer = new MutationObserver(() => {
            maskStaticAds();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Intervalli per le operazioni periodiche
        setInterval(runAdCleaner, CONFIG.cleanInterval);
        setInterval(autoClickSkipButtons, CONFIG.skipButtonInterval);

        // Rileva navigazione interna
        let lastUrl = location.href;
        setInterval(() => {
            if (lastUrl !== location.href) {
                lastUrl = location.href;
                log("Cambio pagina rilevato", "Navigation");
                maskStaticAds();
                runAdCleaner();
                startUnlistedDetection();
            }
        }, 1000);
    };

    // Avvia lo script al caricamento del documento
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init);
    } else {
        init();
    }
    //#endregion
})();