Greasy Fork

Greasy Fork is available in English.

RafBlocker Enhanced

Blocks all YouTube ads (video, banner, preview) and suppresses adblocker warnings without skipping playlist videos.

当前为 2025-10-26 提交的版本,查看 最新版本

// ==UserScript==
// @name         RafBlocker Enhanced
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Blocks all YouTube ads (video, banner, preview) and suppresses adblocker warnings without skipping playlist videos.
// @author       Raf
// @match        *://www.youtube.com/*
// @match        *://m.youtube.com/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

/*
 * MIT License
 *
 * Copyright (c) 2025 Raf
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

(function() {
    'use strict';

    // --- Configuration and Selectors ---
    const AD_SELECTORS = [
        '.ad-container',
        '.video-ads',
        '.ytp-ad-module',
        '.ytp-ad-image-overlay',
        '.ytp-ad-overlay-container',
        '.ytp-ce-element',
        '.ytp-ad-text',
        '.ytp-ad-preview-container',
        '.ytp-ad-skip-button-container',
        '.ytp-ad-banner-container',
        '.sparkles-light-cta', // Promotional banners
        '.ytd-companion-slot-renderer',
        '.ytd-action-companion-ad-renderer',
        '.ytd-in-feed-ad-renderer'
    ].join(', ');

    const SKIP_BUTTON_SELECTOR = '.ytp-ad-skip-button, .ytp-skip-ad-button, .ytp-ad-skip-button-modern';
    const WARNING_MODAL_SELECTOR = [
        'ytd-popup-container',
        'tp-yt-paper-dialog',
        '.ytp-adblock-warning',
        '.yt-spec-general-modal',
        '#dialog',
        '.ytd-consent-bump-v2-lightbox'
    ].join(', ');

    const DISMISS_BUTTON_SELECTOR = [
        'ytd-button-renderer button',
        '.ytp-adblock-warning-dismiss',
        '.yt-spec-button-shape-next--call-to-action',
        'button[aria-label*="Dismiss"]',
        'button[aria-label*="Close"]'
    ].join(', ');

    // --- Utility Functions ---

    // Remove elements matching the selector
    function removeElements(selector) {
        document.querySelectorAll(selector).forEach(element => {
            if (element) {
                element.remove();
            }
        });
    }

    // Check if the current video is an ad
    function isAdPlaying() {
        return document.querySelector('.ad-showing, .ad-interrupting, .ytp-ad-player-overlay') !== null;
    }

    // Skip video ads without affecting playlist videos
    function skipVideoAd() {
        const skipButton = document.querySelector(SKIP_BUTTON_SELECTOR);
        if (skipButton) {
            skipButton.click();
            return true;
        }

        const videoPlayer = document.querySelector('video');
        if (videoPlayer && isAdPlaying()) {
            videoPlayer.currentTime = videoPlayer.duration || Number.MAX_SAFE_INTEGER;
            return true;
        }
        return false;
    }

    // Suppress adblocker warnings
    function suppressAdBlockWarning() {
        // Inject CSS to hide modals and restore scrolling
        let style = document.getElementById('RafBlockerStyle');
        if (!style) {
            style = document.createElement('style');
            style.id = 'RafBlockerStyle';
            style.type = 'text/css';
            document.head.appendChild(style);
        }

        style.innerHTML = `
            ${WARNING_MODAL_SELECTOR}, .ytp-adblock-warning, .ytp-consent-bump {
                display: none !important;
                visibility: hidden !important;
                pointer-events: none !important;
            }
            body, html {
                overflow: auto !important;
            }
            .ytp-ad-player-overlay, .ytp-ad-overlay-slot {
                display: none !important;
            }
        `;

        // Click dismiss buttons
        document.querySelectorAll(DISMISS_BUTTON_SELECTOR).forEach(button => {
            if (button.innerText.toUpperCase().includes('DISMISS') || 
                button.innerText.toUpperCase().includes('CLOSE') || 
                button.getAttribute('aria-label')?.toUpperCase().includes('DISMISS') ||
                button.getAttribute('aria-label')?.toUpperCase().includes('CLOSE')) {
                button.click();
            }
        });

        // Manipulate player state to bypass ad checks
        const player = document.querySelector('#movie_player');
        if (player && player.getPlayerState) {
            const state = player.getPlayerState();
            if (state === 2 && isAdPlaying()) { // Paused due to ad
                player.playVideo();
            }
        }

        // Override YouTube's ad-related properties
        if (window.ytplayer?.config?.args) {
            window.ytplayer.config.args.advideo = 0;
            window.ytplayer.config.args.is_ad = false;
            window.ytplayer.config.args.ad_flags = 0;
            window.ytplayer.config.args.ad_module = '';
        }

        // Mock ad-related functions to return empty or safe values
        const originalGet = Object.getOwnPropertyDescriptor;
        const originalDefine = Object.defineProperty;
        const mockProperties = ['adPlacements', 'adSlots', 'isAdPlaying', 'adsEnabled'];
        mockProperties.forEach(prop => {
            if (window.ytplayer) {
                originalDefine(window.ytplayer, prop, {
                    get: () => false,
                    configurable: true
                });
            }
        });
    }

    // --- Main Blocking Logic ---
    function blockAdsAndWarnings() {
        // Suppress adblock warnings first
        suppressAdBlockWarning();

        // Remove all ad-related elements
        removeElements(AD_SELECTORS);

        // Skip video ads only if an ad is detected
        if (isAdPlaying()) {
            skipVideoAd();
        }
    }

    // --- Playlist Protection ---
    function protectPlaylist() {
        const playlist = document.querySelector('ytd-playlist-panel-renderer');
        if (playlist) {
            // Ensure the player doesn't skip videos due to ad detection
            const player = document.querySelector('#movie_player');
            if (player && player.getPlaylistId) {
                const playlistId = player.getPlaylistId();
                if (playlistId && isAdPlaying()) {
                    // Prevent skipping by ensuring ad checks don't interfere
                    player.playVideo();
                }
            }
        }
    }

    // --- Observer and Interval ---
    const observer = new MutationObserver(() => {
        blockAdsAndWarnings();
        protectPlaylist();
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // Run every 100ms for aggressive ad removal
    setInterval(() => {
        blockAdsAndWarnings();
        protectPlaylist();
    }, 100);

    // Initial run
    blockAdsAndWarnings();
    protectPlaylist();
})();