// ==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
// ==/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
})();