Greasy Fork

Greasy Fork is available in English.

YouTube Bỏ qua quảng cáo video tự động

Tự động bỏ qua quảng cáo trên YouTube

当前为 2025-01-02 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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           2025.01.02.1
// @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
// @compatible safari
// ==/UserScript==

const DEBUG = false;

function log(...args) {
	if (DEBUG) {
		console.log('[YouTube Ad Skipper]:', ...args);
	}
}

(function () {
	'use strict';

	const DEFAULT_CONFIG = {
		allowedReloadPage: true,
		dontReloadWhileBusy: true,
		maxScrollThreshold: 200,
		adSkipDelay: 300,
		maxPlaybackRate: 16,
		maxSkipAttempts: 15,
		autoMuteAds: true,
		hideAllAds: true,
		checkInterval: 500,
		minSkipInterval: 50,
	};

	class YouTubeAdSkipper {
		constructor() {
			this.video = null;
			this.currentVideoTime = 0;
			this.isTabBlurred = false;
			this.skipAttempts = 0;
			this.maxSkipAttempts = DEFAULT_CONFIG.maxSkipAttempts;
			this.lastSkipTime = 0;
			this.config = DEFAULT_CONFIG;
			this.errorCount = 0;
			this.maxErrors = 3;
			this.debounceTimeout = null;
			this.recoveryAttempts = 0;
			this.maxRecoveryAttempts = 3;
			this.recoveryTimeout = null;
			this.init();
		}

		init() {
			try {
				this.loadConfig();
				this.setupEventListeners();
				this.setupMutationObserver();

				if (this.config.hideAllAds) {
					this.addCSSHideAds();
				}

				this.skipAd();
				this.startAdCheckInterval();
			} catch (error) {
				log('Error during initialization:', error);
			}
		}

		loadConfig() {
			try {
				const savedConfig = GM_getValue('adSkipperConfig');
				if (savedConfig) {
					this.config = { ...DEFAULT_CONFIG, ...savedConfig };
				}
			} catch (error) {
				log('Error when read config, restore to default:', error);
				this.config = DEFAULT_CONFIG;
				this.saveConfig();
			}
		}

		saveConfig() {
			try {
				GM_setValue('adSkipperConfig', this.config);
			} catch (error) {
				log('Error when save config:', error);
			}
		}

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

			document.addEventListener('timeupdate', this.handleTimeUpdate.bind(this), true);

			document.addEventListener('yt-navigate-finish', () => {
				this.skipAttempts = 0;
				this.skipAd();
			});

			document.addEventListener(
				'pause',
				() => {
					if (this.video && this.video.paused) {
						log('Video is paused, try to play...');
						setTimeout(() => {
							this.video.play().catch(error => {
								log('Cannot play video automatically:', error);
							});
						}, 500);
					}
				},
				true
			);
		}

		handleTimeUpdate(e) {
			if (e.target.matches('video.html5-main-video')) {
				this.currentVideoTime = e.target.currentTime;
			}
		}

		setupMutationObserver() {
			const observer = new MutationObserver(() => {
				if (this.isTabBlurred) return;

				clearTimeout(this.debounceTimeout);
				this.debounceTimeout = setTimeout(() => {
					this.skipAd();
				}, 100);
			});

			// Wait until document.body exists
			const observeBody = () => {
				if (document.body) {
					observer.observe(document.body, {
						attributes: true,
						attributeFilter: ['class', 'src', 'style'],
						childList: true,
						subtree: true,
					});
				} else {
					// Try again after 50ms if body does not exist
					setTimeout(observeBody, 50);
				}
			};

			observeBody();
		}

		startAdCheckInterval() {
			setInterval(() => {
				if (!this.isTabBlurred) {
					this.skipAd();
				}
			}, this.config.checkInterval);
		}

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

				const player = document.querySelector('#movie_player');
				if (!player) {
					log('Not found player');
					return;
				}

				const hasAd = player.classList.contains('ad-showing') || document.querySelector('.video-ads') !== null;

				this.video = player.querySelector('video.html5-main-video');

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

				this.removeAdBlockerWarnings();
				this.removeShortVideoAds();
				this.removeOverlayAds();
			} catch (error) {
				this.errorCount++;
				log('Error when skip ads:', error);
				if (this.errorCount >= this.maxErrors) {
					log('Exceeded the maximum number of retry attempts, trying to recover...');
					await this.attemptRecovery();
				}
			}
		}

		async handleVideoAd() {
			const now = Date.now();
			if (now - this.lastSkipTime < this.config.minSkipInterval) return;
			this.lastSkipTime = now;

			this.clickSkipButtons();

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

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

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

		clickSkipButtons() {
			const skipButtonSelectors = ['.ytp-skip-ad-button', '.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.ytp-ad-survey-answer-button', '.ytp-ad-skip-button-container button', '[class*="skip-button"]', '[class*="skipButton"]', '.videoAdUiSkipButton', '.ytp-ad-preview-container button'];

			skipButtonSelectors.forEach(selector => {
				const buttons = document.querySelectorAll(selector);
				buttons.forEach(button => {
					if (button && button.offsetParent !== null) {
						button.click();
						button.remove();
					}
				});
			});
		}

		removeAdBlockerWarnings() {
			const warningSelectors = ['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'];

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

		removeShortVideoAds() {
			const shortAdSelectors = ['ytd-reel-video-renderer:has(.ytd-ad-slot-renderer)', 'ytd-in-feed-ad-layout-renderer', 'ytd-promoted-video-renderer'].join(',');

			document.querySelectorAll(shortAdSelectors).forEach(ad => ad.remove());
		}

		removeOverlayAds() {
			const overlayAdSelectors = ['.ytp-ad-overlay-container', '.ytp-ad-text-overlay', '.ytp-ad-overlay-slot', 'div[id^="ad-overlay"]', '.video-ads', '.ytp-ad-overlay-image', '.ytp-ad-text-overlay-container'].join(',');

			document.querySelectorAll(overlayAdSelectors).forEach(ad => {
				ad.style.display = 'none';
				ad.remove();
			});
		}

		checkCanReloadPage() {
			if (!this.config.allowedReloadPage) return false;
			if (!this.config.dontReloadWhileBusy) return true;
			if (document.activeElement?.matches('input, textarea, select')) return false;
			if (document.documentElement.scrollTop > this.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,
                .ytp-ad-skip-button-slot,
                .ytp-ad-preview-slot,
                .ytp-ad-message-slot {
                    display: none !important;
                }
            `;
			GM_addStyle(styles);
		}

		handlePrerollAds() {
			const prerollContainer = document.querySelector('.ytp-ad-preview-container');
			if (prerollContainer) {
				this.video.currentTime = this.video.duration || 9999;
				this.video.playbackRate = this.config.maxPlaybackRate;
				prerollContainer.remove();
			}
		}

		async attemptRecovery() {
			if (this.recoveryAttempts >= this.maxRecoveryAttempts) {
				log('Exceeded the maximum number of recovery attempts');
				return;
			}

			this.recoveryAttempts++;
			log(`Try to recovery attempt ${this.recoveryAttempts}...`);

			clearTimeout(this.recoveryTimeout);
			this.recoveryTimeout = setTimeout(() => {
				this.errorCount = 0;
				this.skipAttempts = 0;
				this.init();
				this.recoveryAttempts = 0;
			}, 5000 * this.recoveryAttempts);
		}
	}

	new YouTubeAdSkipper();
})();