- // ==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.31.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.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');
- this.video = player.querySelector('video.html5-main-video');
-
- if (hasAd && this.video) {
- await this.handleVideoAd();
- }
-
- 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');
- }
- }
- }
-
- 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'];
-
- 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-overlay-slot', '.ytp-ad-text-overlay'].join(',');
-
- document.querySelectorAll(overlayAdSelectors).forEach(ad => (ad.style.display = 'none'));
- }
-
- 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);
- }
- }
-
- new YouTubeAdSkipper();
- })();