YouTube Saltar anuncios automáticamente

Salta automáticamente los anuncios en videos de YouTube

// ==UserScript==
// @name              YouTube Bỏ qua quảng cáo video tự động
// @name:en           YouTube Auto Ad Skipper
// @name:vi           YouTube Bỏ qua quảng cáo video tự động
// @name:zh-cn        YouTube 自动跳过广告
// @name:zh-tw        YouTube 自動跳過廣告
// @name:ja           YouTube 広告自動スキップ
// @name:ko           YouTube 자동 광고 건너뛰기
// @name:es           YouTube Saltar anuncios automáticamente
// @name:ru           YouTube Автоматический пропуск рекламы
// @name:id           YouTube Lewati Iklan Otomatis
// @name:hi           YouTube स्वचालित विज्ञापन स्किपर
// @namespace         http://tampermonkey.net/
// @version           2024.12.23.3
// @description       Tự động bỏ qua quảng cáo trên YouTube
// @description:en    Automatically skip ads on YouTube videos
// @description:vi    Tự động bỏ qua quảng cáo trên YouTube
// @description:zh-cn 自动跳过 YouTube 视频广告
// @description:zh-tw 自動跳過 YouTube 影片廣告
// @description:ja    YouTube動画の広告を自動的にスキップ
// @description:ko    YouTube 동영상의 광고를 자동으로 건너뛰기
// @description:es    Salta automáticamente los anuncios en videos de YouTube
// @description:ru    Автоматически пропускает рекламу в видео на YouTube
// @description:id    Otomatis melewati iklan di video YouTube
// @description:hi    YouTube वीडियो में विज्ञापनों को स्वचालित रूप से छोड़ें
// @author            RenjiYuusei
// @license           MIT
// @icon              https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @match             https://*.youtube.com/*
// @grant            GM_addStyle
// @grant            GM_getValue
// @grant            GM_setValue
// @run-at           document-start
// @compatible chrome
// @compatible firefox
// @compatible edge
// ==/UserScript==

(function () {
	'use strict';

	const CONFIG = {
		allowedReloadPage: true, // allow reload page
		dontReloadWhileBusy: true, // don't reload page while busy
		maxScrollThreshold: 200, // max scroll threshold
		adSkipDelay: 500, // add delay 500ms to boost speed skip ads
		maxPlaybackRate: 16, // max playback rate
		maxSkipAttempts: 10, // increase skip ads attempts
		autoMuteAds: true, // auto mute ads
		hideAllAds: true, // hide all ads
	};

	class YouTubeAdSkipper {
		constructor() {
			this.video = null;
			this.currentVideoTime = 0;
			this.isTabBlurred = false;
			this.skipAttempts = 0;
			this.maxSkipAttempts = CONFIG.maxSkipAttempts;
			this.lastSkipTime = 0;
			this.init();
		}

		init() {
			this.setupEventListeners();
			this.setupMutationObserver();
			if (CONFIG.hideAllAds) {
				this.addCSSHideAds();
			}
			this.skipAd();

			// add interval to check ads continuously
			setInterval(() => this.skipAd(), 1000);
		}

		setupEventListeners() {
			window.addEventListener('blur', () => (this.isTabBlurred = true));
			window.addEventListener('focus', () => {
				this.isTabBlurred = false;
				this.skipAd(); // check ads when focus again
			});

			document.addEventListener(
				'timeupdate',
				e => {
					if (e.target.matches('video.html5-main-video')) {
						this.currentVideoTime = e.target.currentTime;
					}
				},
				true
			);

			// add event for load new video
			document.addEventListener('yt-navigate-finish', () => this.skipAd());
		}

		setupMutationObserver() {
			const observer = new MutationObserver(() => this.skipAd());
			observer.observe(document.body, {
				attributes: true,
				attributeFilter: ['class', 'src', 'style'],
				childList: true,
				subtree: true,
			});
		}

		async skipAd() {
			if (window.location.pathname.startsWith('/shorts/')) return;

			const player = document.querySelector('#movie_player');
			if (!player) return;

			const hasAd = player.classList.contains('ad-showing');
			this.video = player.querySelector('video.html5-main-video');

			if (hasAd && this.video) {
				await this.handleVideoAd();
			}

			this.removeAdBlockerWarnings();
			this.removeShortVideoAds();
			this.removeOverlayAds();
		}

		async handleVideoAd() {
			// avoid processing too many times in a short time
			const now = Date.now();
			if (now - this.lastSkipTime < 100) return;
			this.lastSkipTime = now;

			const skipButtons = document.querySelectorAll(`
                .ytp-skip-ad-button,
                .ytp-ad-skip-button,
                .ytp-ad-skip-button-modern,
                .ytp-ad-survey-answer-button,
                .ytp-ad-skip-button-container button
            `);

			skipButtons.forEach(button => {
				if (button && button.offsetParent !== null) {
					button.click();
					button.remove();
				}
			});

			if (this.video.src) {
				this.video.currentTime = 9999;
				this.video.playbackRate = CONFIG.maxPlaybackRate;
			}

			if (CONFIG.autoMuteAds) {
				this.video.muted = true;
				this.video.volume = 0;
			}

			if (this.skipAttempts < this.maxSkipAttempts) {
				this.skipAttempts++;
				await new Promise(resolve => setTimeout(resolve, CONFIG.adSkipDelay));
				this.skipAd();
			}
		}

		removeAdBlockerWarnings() {
			const warnings = ['tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model)', '.yt-playability-error-supported-renderers:has(.ytd-enforcement-message-view-model)', 'ytd-enforcement-message-view-model', '.ytd-popup-container'];

			warnings.forEach(selector => {
				const warning = document.querySelector(selector);
				if (warning) {
					if (selector.includes('playability-error') && this.checkCanReloadPage()) {
						this.reloadPage();
					}
					warning.remove();
				}
			});
		}

		removeShortVideoAds() {
			document
				.querySelectorAll(
					`
                ytd-reel-video-renderer:has(.ytd-ad-slot-renderer),
                ytd-in-feed-ad-layout-renderer,
                ytd-promoted-video-renderer
            `
				)
				.forEach(ad => ad.remove());
		}

		removeOverlayAds() {
			document
				.querySelectorAll(
					`
                .ytp-ad-overlay-container,
                .ytp-ad-overlay-slot,
                .ytp-ad-text-overlay
            `
				)
				.forEach(ad => (ad.style.display = 'none'));
		}

		checkCanReloadPage() {
			if (!CONFIG.allowedReloadPage) return false;
			if (!CONFIG.dontReloadWhileBusy) return true;
			if (document.activeElement?.matches('input, textarea, select')) return false;
			if (document.documentElement.scrollTop > CONFIG.maxScrollThreshold) return false;
			if (this.isTabBlurred) return false;
			return true;
		}

		reloadPage() {
			const params = new URLSearchParams(location.search);
			if (this.currentVideoTime > 0) {
				params.set('t', Math.floor(this.currentVideoTime) + 's');
			}
			location.replace(`${location.origin}${location.pathname}?${params.toString()}`);
		}

		addCSSHideAds() {
			const styles = `
                #player-ads,
                #masthead-ad,
                ytd-ad-slot-renderer,
                ytd-rich-item-renderer:has(.ytd-ad-slot-renderer),
                ytd-rich-section-renderer:has(.ytd-statement-banner-renderer),
                ytd-reel-video-renderer:has(.ytd-ad-slot-renderer),
                tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model),
                tp-yt-paper-dialog:has(> ytd-checkbox-survey-renderer),
                .ytp-suggested-action,
                .yt-mealbar-promo-renderer,
                ytmusic-mealbar-promo-renderer,
                ytmusic-statement-banner-renderer,
                .ytd-display-ad-renderer,
                .ytd-statement-banner-renderer,
                .ytd-in-feed-ad-layout-renderer,
                .ytp-ad-overlay-container,
                .ytp-ad-text-overlay,
                ytd-promoted-sparkles-web-renderer,
                ytd-promoted-video-renderer,
                .ytd-banner-promo-renderer,
                .ytd-video-masthead-ad-v3-renderer,
                .ytd-primetime-promo-renderer {
                    display: none !important;
                }
                
                .ytp-ad-skip-button-slot,
                .ytp-ad-preview-slot,
                .ytp-ad-message-slot {
                    display: none !important;
                }
            `;
			GM_addStyle(styles);
		}
	}

	new YouTubeAdSkipper();
})();