YouTube Ad-Bypass

Пропускает и скрывает рекламу на YouTube.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name              YouTube Ad-Bypass
// @namespace         YouTube_Ad-Bypass_Fckoff
// @version           1.26.1
// @description:en    Skips and hides YouTube and YouTube Music ads
// @description:es    Salta y oculta anuncios de YouTube y YouTube Music
// @description:pt    Pula e oculta anuncios do YouTube & YouTube Music
// @description:fr    Passe et masque las publicités YouTube & YouTube Music
// @description:it    Salta e nasconde gli annunci di YouTube & YouTube Music
// @description:de    Überspringe und verberge YouTube-Werbung
// @description:hi    यूट्यूब विज्ञापनों को छोड़ें और छुपाएं (YouTube vigyapanon ko chhodein aur chhupayein)
// @description:zh-CN 跳过并 column 隐藏 YouTube 广告 (Tiàoguò bìng yǐncáng YouTube guǎnggào)
// @description:ja    YouTube 広告をスキップして非表示にします (YouTube kōkoku o sukippu shite hihyōji ni shimasu)
// @description:ru    Пропускает и скрывает рекламу на YouTube.
// @author            WakeUpNeo
// @match             *://www.youtube.com/*
// @match             *://music.youtube.com/*
// @run-at            document-start
// @grant             none
// @license           MIT
// @description Salta y oculta anuncios de YouTube
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Configuration object containing CSS selectors for different types of ad elements.
     */
    const SELECTORS = {
        // Elements that should be visually hidden from the UI
        toHide: [
            '.ytp-ad-message-container',
            'ytd-player-legacy-desktop-watch-ads-renderer',
            'ytd-ad-slot-renderer',
            '#masthead-ad',
            'tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model)',
            '.yt-mealbar-promo-renderer',
            '.video-ads',
            '.ytp-ad-module',
            '.ytp-ad-player-overlay',
            '.ad-showing > video',
            '.ad-interrupting > video',
            'div:has(> div#banner)',
            'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
            'ytd-rich-item-renderer:has(ytd-ad-slot-renderer)',
            'ytmusic-mealbar-promo-renderer',
            'ytd-in-feed-ad-layout-renderer',
            '#player-ads',
            '.ytd-video-masthead-ad-v3-renderer',
            'ytd-ad-selection-preview-renderer',
            '.ytp-ad-image-overlay',
            '#root.yt-chips-search-renderer-header-v2'
        ],
        // Selectors for the main YouTube video player container
        player: [
            '#movie_player',
            '.html5-video-player'
        ],
        // Classes added by YouTube when an ad is active
        adsClasses: [
            '.ad-showing',
            '.ad-interrupting',
            '.ytp-ad-player-overlay'
        ],
        // Selectors for the various "Skip Ad" buttons
        skipButtons: [
            '.ytp-ad-skip-button-modern',
            '.ytp-skip-ad-button',
            '.ytp-ad-skip-button',
            '.ytp-ad-skip-button-slot',
            '.ytp-ad-skip-button-container'
        ]
    };

    /**
     * Convert arrays of selectors into single comma-separated strings for querySelectorAll/matches usage.
     */
    const selectors = Object.fromEntries(
       Object.entries(SELECTORS).map(([key, value]) => [key, value.join(', ')])
    );

    let playerObserver = null;
    let video = null;
    let spinner = null;
    let cued_thumb = null;
    let isAdEnabled = true;

    /**
     * Injects a global <style> tag to hide ad-related elements using CSS.
     * Uses visibility:hidden and 1px size to avoid breaking layout while making ads invisible.
     */
    const injectStyles = () => {
        const style = document.createElement('style');
        style.textContent = `
            ${selectors.toHide} {
                display: flex !important;
                visibility: hidden !important;
                opacity: 0 !important;
                pointer-events: none !important;
                height: 1px !important;
                width: 1px !important;
                overflow: hidden !important;
            }
        `;
        (document.head || document.documentElement).appendChild(style);
    };

    /**
     * Executes the skipping logic: fast-forwards the video to the end and clicks the skip button.
     */
    const skipAction = () => {
        if (spinner){
            spinner.style.display = '';
            cued_thumb.style.display = 'none';
        }
        video.pause();
        video.paused = true;
        if (!video.muted) { video.muted = true; video.volume = 0; }
        if (video.playbackRate !== 2.0) { video.playbackRate = 2.0; }
        if (isFinite(video.duration) && video.duration > 0) {
            video.currentTime = video.duration - 0.1;
        }
        video.play();
    };

    /**
     * Checks if the player is currently showing an ad.
     * @param {HTMLElement} target - The player element to check for ad-related classes.
     */
    const checkAndSkip = (target) => {
       // Re-fetch the video element if it's missing or disconnected from DOM
       if (!video || !video.isConnected) {
            video = document.querySelector('video');
        }
        if (!video) return;

        // If player has ad-related classes, trigger skip; otherwise, reset playback speed
        if (target.matches(selectors.adsClasses)) {
            isAdEnabled = true;
            skipAction();
        } else {
            if (isAdEnabled == true){
                isAdEnabled = false;
                if (spinner){
                    spinner.style.display = 'none';
                    cued_thumb.style.display = 'none';
                }
                // Restore normal speed if the script had previously accelerated it
                if (video.playbackRate > 2) video.playbackRate = 1;
            }
        }
    };

    /**
     * Initializes a MutationObserver to watch for changes in the player's class attribute.
     * This allows the script to react instantly when an ad starts.
     */
    const setupPlayerObserver = () => {
        const player = document.querySelector(selectors.player);

        if (player && !playerObserver) {
            spinner = document.querySelector('.ytp-spinner');
            cued_thumb = document.querySelector('.ytp-cued-thumbnail-overlay');
            playerObserver = new MutationObserver(() => checkAndSkip(player));
            // Monitor class changes which indicate ad transitions
            playerObserver.observe(player, { attributes: true, attributeFilter: ['class'] });

            // Run initial check
            checkAndSkip(player);
        }
    };

    // Event listeners to handle page loads and YouTube's internal navigation (SPA)
    window.addEventListener('load', setupPlayerObserver);
    window.addEventListener('yt-navigate-finish', setupPlayerObserver);

    // Inject CSS as soon as the DOM structure is available
    window.addEventListener('DOMContentLoaded', (event) => {
        injectStyles();
    });

    /**
     * Fallback mechanism: attempts to initialize the observer every 1000ms
     * in case 'load' events fire before the player is ready.
     */
    let retry = 0;
    const fallback = setInterval(() => {
        setupPlayerObserver();
        // Stop retrying if observer is active or after 10 failed attempts
        if (playerObserver || retry > 10) clearInterval(fallback);
        retry++;
    }, 1000);

})();