YouTube Ad-Master

解决Trusted Types与Class构造报错,优化广告跳过逻辑

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         YouTube Ad-Master
// @namespace    https://github.com/tientq64/userscripts
// @version      9.9.8.3
// @description  解决Trusted Types与Class构造报错,优化广告跳过逻辑
// @author       tientq64 + Gemini + Copilot
// @match        https://www.youtube.com/*
// @match        https://m.youtube.com/*
// @match        https://music.youtube.com/*
// @exclude      https://studio.youtube.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // --- 1. 解决 Trusted Types 策略错误 (修复图2/图3报错) ---
    if (window.trustedTypes && window.trustedTypes.createPolicy) {
        if (!window.trustedTypes.defaultPolicy) {
            window.trustedTypes.createPolicy('default', {
                createHTML: (string) => string,
                createScriptURL: (string) => string,
                createScript: (string) => string,
            });
        }
    }

    let state = {
        savedVolume: 1,
        savedMuted: false,
        isAdActive: false,
        volumeLocked: false
    };

    // --- 2. 安全点击与错误捕获 (修复图4 Class构造报错) ---
    const safeClick = (el) => {
        if (!el) return;
        try {
            // 优先尝试标准点击
            el.click();
            // 辅助模拟指针事件
            ['pointerdown', 'mousedown', 'mouseup'].forEach(name => {
                el.dispatchEvent(new MouseEvent(name, {
                    bubbles: true,
                    cancelable: true,
                    view: window,
                    buttons: 1
                }));
            });
        } catch (e) {
            // 捕获并忽略来自受保护组件(如 sl-popup)的构造错误
            console.debug('Skip error during click:', e.message);
        }
    };

    // --- 3. 深度 Shadow DOM 探测器 ---
    const findButtonsRecursive = (root) => {
        const selectors = [
            'button[id^="skip-button"]',
            '.ytp-ad-skip-button',
            '.ytp-ad-skip-button-modern',
            '[aria-label*="跳过"]',
            '[aria-label*="Skip"]',
            ".ytp-ad-overlay-close-button",
        ];
        let found = [];
        try {
            selectors.forEach(s => root.querySelectorAll(s).forEach(el => found.push(el)));
            root.querySelectorAll('*').forEach(el => {
                if (el.shadowRoot) found = found.concat(findButtonsRecursive(el.shadowRoot));
            });
        } catch (e) {}
        return found;
    };

    // --- 4. 动态透明度控制 ---
    const toggleAdVisibility = (isAd) => {
        const player = document.querySelector('#movie_player');
        if (!player) return;
        if (isAd) {
            player.style.setProperty('opacity', '0', 'important');
            player.style.setProperty('filter', 'brightness(0)', 'important');
            player.style.setProperty('pointer-events', 'none', 'important');
        } else {
            player.style.removeProperty('opacity');
            player.style.removeProperty('filter');
            player.style.removeProperty('pointer-events');
        }
    };

    // --- 5. 核心跳过引擎 ---
    const runSkipEngine = () => {
        const video = document.querySelector('video.html5-main-video');
        const moviePlayer = document.querySelector('#movie_player');

        const adShowing = moviePlayer && (
            moviePlayer.classList.contains('ad-showing') ||
            moviePlayer.classList.contains('ad-interrupting') ||
            document.querySelector('.ytp-ad-player-overlay')
        );

        if (adShowing && video) {
            if (!state.isAdActive) {
                state.savedVolume = video.volume;
                state.savedMuted = video.muted;
                state.isAdActive = true;
                state.volumeLocked = true;
                toggleAdVisibility(true);
            }
            video.muted = true;
            video.playbackRate = 16.0;

            // 自动点击所有发现的跳过按钮
            findButtonsRecursive(document).forEach(btn => safeClick(btn));

            // 调用内部 API
            if (moviePlayer && typeof moviePlayer.skipAd === 'function') {
                try { moviePlayer.skipAd(); } catch(e) {}
            }
        } else if (video && state.isAdActive) {
            state.isAdActive = false;
            video.playbackRate = 1.0;
            toggleAdVisibility(false);
            setTimeout(() => {
                if (state.volumeLocked && video) {
                    video.volume = state.savedVolume;
                    video.muted = state.savedMuted;
                }
            }, 200);
            state.volumeLocked = false;
        }
    };

    // --- 6. 稳健的 UI 净化 ---
    const injectStyles = () => {
        const styleId = 'yt-master-transparent-css';
        if (document.getElementById(styleId)) return;

        const style = document.createElement('style');
        style.id = styleId;
        style.textContent = `
            .ytp-ad-player-overlay, .ytp-ad-module, ytd-ad-slot-renderer,
            #masthead-ad, ytd-banner-promo-renderer {
                opacity: 0 !important;
                pointer-events: none !important;
            }
            yt-upsell-dialog-renderer, #pigeon-messaging-container { display: none !important; }
        `;

        // 确保在适当的时机插入,不干扰初始化
        const target = document.head || document.documentElement;
        if (target) {
            target.appendChild(style);
        }
    };

    // --- 7. 循环与监听 ---
    const tick = () => {
        runSkipEngine();
        requestAnimationFrame(tick);
    };

    // 初始执行
    injectStyles();
    requestAnimationFrame(tick);

    // 辅助功能:每秒清理弹窗和错误层
    setInterval(() => {
        // 自动关闭“不用了”等弹窗
        const dismiss = document.querySelectorAll('#dismiss-button, [aria-label*="thanks"], [aria-label*="不用了"]');
        dismiss.forEach(btn => safeClick(btn));

        // 刷新播放错误层
        if (document.querySelector('.yt-playability-error-supported-renderers')) {
            location.reload();
        }
    }, 1500);

    window.addEventListener('yt-navigate-finish', () => {
        state.isAdActive = false;
        state.volumeLocked = false;
        toggleAdVisibility(false);
    });
})();