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          6.1.4
// @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
// @require         https://update.greasyfork.org/scripts/519877/1497523/UserScript%20Compatibility%20Library.js
// @run-at          document-start
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        skipAds: GM_getValue('skipAds', true),
        hideAds: GM_getValue('hideAds', true),
        muteAds: GM_getValue('muteAds', true),
        bypassAdBlock: GM_getValue('bypassAdBlock', true)
    };

    const saveConfig = () => {
        Object.keys(CONFIG).forEach(key => {
            GM_setValue(key, CONFIG[key]);
        });
    };

    const AdDetector = {
        selectors: {
            skippable: [
                '.ytp-ad-skip-button',
                '.ytp-ad-skip-button-modern',
                '.videoAdUiSkipButton',
                '.ytp-ad-overlay-close-button',
                '.ytp-ad-text-overlay-skip-button',
                '.ytp-ad-skip-button-container',
                '.ytp-ad-preview-container'
            ],
            hideable: [
                '.ad-showing',
                '.ytp-ad-module',
                'ytd-ad-slot-renderer',
                '#masthead-ad',
                '.video-ads',
                '.ytp-ad-overlay-container',
                'ytd-promoted-sparkles-web-renderer',
                'ytd-promoted-video-renderer',
                '.ytd-watch-next-secondary-results-renderer.ytd-item-section-renderer',
                'tp-yt-paper-dialog[role="dialog"]'
            ],
            adBlockDetection: [
                'ytd-enforcement-message-view-model',
                '#error-page',
                '#dialog.ytd-popup-container'
            ]
        },

        isAdPlaying(video) {
            return document.querySelector('.ad-showing') !== null ||
                   (video?.src && video.src.includes('/ads/')) ||
                   document.querySelector('.ytp-ad-player-overlay') !== null;
        }
    };

    class AdSkipper {
        constructor() {
            this.originalVolume = null;
            this.observer = null;
            this.retryCount = 0;
            this.maxRetries = 5;
        }

        initialize() {
            this.setupObservers();
            this.checkForAds();
            this.bypassAdBlockDetection();

            document.addEventListener('DOMContentLoaded', () => {
                this.applyCriticalStyles();
            });

            setInterval(() => {
                this.checkForAds();
                this.bypassAdBlockDetection();
            }, 1000);
        }

        applyCriticalStyles() {
            const styles = `
                .ad-showing video { display: none !important; }
                .video-ads { display: none !important; }
                .ytp-ad-overlay-container { display: none !important; }
                .ytp-ad-message-container { display: none !important; }
                ytd-promoted-sparkles-web-renderer { display: none !important; }
                ytd-promoted-video-renderer { display: none !important; }
                tp-yt-paper-dialog[role="dialog"] { display: none !important; }
                ytd-enforcement-message-view-model { display: none !important; }
                #error-page { display: none !important; }
                #dialog.ytd-popup-container { display: none !important; }
            `;
            GM_addStyle(styles);
        }

        bypassAdBlockDetection() {
            if (!CONFIG.bypassAdBlock) return;

            AdDetector.selectors.adBlockDetection.forEach(selector => {
                const elements = document.querySelectorAll(selector);
                elements.forEach(element => element?.remove());
            });

            const video = document.querySelector('video');
            if (video?.paused) {
                video.play().catch(() => {});
            }
        }

        async skipAd() {
            const video = document.querySelector('video');
            if (!video) return;

            try {
                if (await this.clickSkipButton() || await this.skipVideoAd(video)) {
                    if (CONFIG.muteAds) {
                        this.muteAd(video);
                    }
                    this.retryCount = 0;
                    video.style.display = 'block';
                    video.style.visibility = 'visible';
                } else if (this.retryCount < this.maxRetries) {
                    this.retryCount++;
                    setTimeout(() => this.skipAd(), 500);
                }
            } catch {
                // Bỏ qua lỗi nếu có
            }
        }

        async clickSkipButton() {
            for (const selector of AdDetector.selectors.skippable) {
                const buttons = document.querySelectorAll(selector);
                for (const button of buttons) {
                    if (button?.offsetParent !== null) {
                        try {
                            button.click();
                            return true;
                        } catch {
                            continue;
                        }
                    }
                }
            }
            return false;
        }

        async skipVideoAd(video) {
            if (AdDetector.isAdPlaying(video)) {
                try {
                    video.currentTime = video.duration || 0;
                    video.playbackRate = 16;
                    if (video.paused) {
                        await video.play().catch(() => {});
                    }
                    return true;
                } catch {
                    return false;
                }
            }
            return false;
        }

        muteAd(video) {
            if (this.originalVolume === null) {
                this.originalVolume = video.volume;
            }
            try {
                video.muted = true;
                video.volume = 0;
                const muteButton = document.querySelector('.ytp-mute-button');
                if (muteButton && !video.muted) {
                    muteButton.click();
                }
            } catch {
                // Bỏ qua lỗi nếu có
            }
        }

        setupObservers() {
            this.observer?.disconnect();

            this.observer = new MutationObserver(() => {
                requestAnimationFrame(() => {
                    this.checkForAds();
                    this.bypassAdBlockDetection();
                });
            });

            this.observer.observe(document.body, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['class', 'src', 'style']
            });
        }

        checkForAds() {
            if (CONFIG.skipAds) {
                this.skipAd();
            }

            if (CONFIG.hideAds) {
                this.hideAds();
            }
        }

        hideAds() {
            AdDetector.selectors.hideable.forEach(selector => {
                document.querySelectorAll(selector).forEach(el => {
                    try {
                        el?.parentNode && el.remove();
                    } catch {
                        // Bỏ qua lỗi nếu có
                    }
                });
            });
        }

        destroy() {
            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }
            saveConfig();
        }
    }

    const init = () => {
        try {
            const adSkipper = new AdSkipper();
            adSkipper.initialize();

            window.addEventListener('unload', () => {
                adSkipper.destroy();
            });

        } catch {
            setTimeout(init, 1000);
        }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();