自動跳過 YouTube 廣告

幾乎立即自動跳過 YouTube 廣告。刪除廣告攔截器警告彈出視窗。

目前為 2025-02-17 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name               Auto Skip YouTube Ads
// @name:ar            التخطي التلقائي لإعلانات YouTube
// @name:es            Saltar Automáticamente Anuncios De YouTube
// @name:hi            YouTube विज्ञापन स्वचालित रूप से छोड़ें
// @name:id            Lewati Otomatis Iklan YouTube
// @name:ja            YouTube 広告を自動スキップ
// @name:ko            YouTube 광고 자동 건너뛰기
// @name:pt-BR         Pular Automaticamente Anúncios Do YouTube
// @name:ru            Автоматический Пропуск Рекламы На YouTube
// @name:vi            Tự Động Bỏ Qua Quảng Cáo YouTube
// @name:zh-CN         自动跳过 YouTube 广告
// @name:zh-TW         自動跳過 YouTube 廣告
// @namespace          https://github.com/tientq64/userscripts
// @version            7.0.0
// @description        Automatically skip YouTube ads almost instantly. Remove the ad blocker warning pop-up.
// @description:ar     تخطي إعلانات YouTube تلقائيًا في الحال. إزالة النافذة المنبثقة لتحذير مانع الإعلانات.
// @description:es     Omite automáticamente los anuncios de YouTube casi al instante. Elimina la ventana emergente de advertencia del bloqueador de anuncios.
// @description:hi     YouTube विज्ञापनों को लगभग तुरंत ही स्वचालित रूप से छोड़ दें। विज्ञापन अवरोधक चेतावनी पॉप-अप हटाएँ।
// @description:id     Lewati iklan YouTube secara otomatis hampir seketika. Hapus pop-up peringatan pemblokir iklan.
// @description:ja     YouTube 広告をほぼ瞬時に自動的にスキップします。広告ブロッカーの警告ポップアップを削除します。
// @description:ko     YouTube 광고를 거의 즉시 자동으로 건너뜁니다. 광고 차단 경고 팝업을 제거합니다.
// @description:pt-BR  Pule automaticamente os anúncios do YouTube quase instantaneamente. Remova o pop-up de aviso do bloqueador de anúncios.
// @description:ru     Автоматически пропускайте рекламу YouTube почти мгновенно. Уберите всплывающее предупреждение о блокировщике рекламы.
// @description:vi     Tự động bỏ qua quảng cáo YouTube gần như ngay lập tức. Loại bỏ cửa sổ bật lên cảnh báo trình chặn quảng cáo.
// @description:zh-CN  几乎立即自动跳过 YouTube 广告。删除广告拦截器警告弹出窗口。
// @description:zh-TW  幾乎立即自動跳過 YouTube 廣告。刪除廣告攔截器警告彈出視窗。
// @author             tientq64
// @icon               https://cdn-icons-png.flaticon.com/64/2504/2504965.png
// @match              https://www.youtube.com/*
// @match              https://m.youtube.com/*
// @match              https://music.youtube.com/*
// @grant              none
// @license            MIT
// @compatible         firefox
// @compatible         chrome
// @compatible         opera
// @compatible         safari
// @compatible         edge
// @noframes
// @homepage           https://github.com/tientq64/userscripts/tree/main/scripts/Auto-Skip-YouTube-Ads
// ==/UserScript==

/**
 * Skip ads. Remove ad elements.
 */
function skipAd() {
    removeAdElements()

    video = null
    fineScrubber = document.querySelector('.ytp-fine-scrubbing')

    // Check if the current URL is a YouTube Shorts URL and exit the function if true.
    if (window.location.pathname.startsWith('/shorts/')) return

    const moviePlayer = document.querySelector('#movie_player')

    if (moviePlayer) {
        hasAd = moviePlayer.classList.contains('ad-showing')
        video = moviePlayer.querySelector('video.html5-main-video')
    }

    if (hasAd) {
        const skipButton = document.querySelector(`
            .ytp-skip-ad-button,
            .ytp-ad-skip-button,
            .ytp-ad-skip-button-modern,
            .ytp-ad-survey-answer-button
        `)
        // Click the skip ad button if available.
        if (skipButton) {
            skipButton.click()
            skipButton.remove()
        }
        // Otherwise, fast forward to the end of the ad video.
        // Use `9999` instead of `video.duration` to avoid errors when `duration` is not a number.
        else if (video && video.src) {
            video.currentTime = 9999
        }
    }

    if (video) {
        video.addEventListener('pause', handleVideoPause)
        video.addEventListener('pointerup', allowPauseVideo)
        video.addEventListener('timeupdate', handleVideoTimeUpdate)
    }

    // Timed pie countdown ad.
    const pieCountdown = document.querySelector('.ytp-ad-timed-pie-countdown-container')
    if (pieCountdown) {
        pieCountdown.remove()
        replaceCurrentVideo()
    }

    // Handle when ad blocker warning appears inside video player.
    const adBlockerWarningInner = document.querySelector(
        '.yt-playability-error-supported-renderers'
    )
    if (adBlockerWarningInner) {
        adBlockerWarningInner.remove()
        document.addEventListener('yt-navigate-finish', handleYouTubeNavigateFinish)
        replaceCurrentVideo()
    }

    // Video play/pause button.
    const playButton = document.querySelector(
        'button.ytp-play-button, button.player-control-play-pause-icon'
    )
    if (playButton) {
        playButton.addEventListener('click', allowPauseVideo)
    }
}

function queryHasSelector(selector, hasSelector, element = document) {
    const el = element.querySelector(selector)
    if (el === null) return null
    const hasEl = el.querySelector(hasSelector)
    if (hasEl === null) return null
    return el
}

function queryHasSelectorAll(selector, hasSelector, element = document) {
    const els = element.querySelectorAll(selector)
    const result = []
    for (const el of els) {
        const hasEl = el.querySelector(hasSelector)
        if (hasEl === null) continue
        result.push(el)
    }
    return result
}

function getCurrentVideoId() {
    const params = new URLSearchParams(location.search)
    const videoId = params.get('v')
    return videoId
}

/**
 * Check if the user is focused on the input.
 */
function checkEnteringInput() {
    if (document.activeElement === null) {
        return false
    }
    return document.activeElement.matches('input, textarea, select')
}

/**
 * Temporarily allows the video to be paused, for a short period of time.
 */
function allowPauseVideo() {
    pausedByUser = true
    window.clearTimeout(allowPauseVideoTimeoutId)
    allowPauseVideoTimeoutId = window.setTimeout(disallowPauseVideo, 500)
}

/**
 * Pausing the video is not allowed. The purpose is to prevent video from being paused,
 * against the behavior of pausing video when YouTube ad blocking warning dialog appears.
 * Unless certain conditions, such as pausing by user, etc.
 */
function disallowPauseVideo() {
    pausedByUser = false
    window.clearTimeout(allowPauseVideoTimeoutId)
}

function handleWindowBlur() {
    isTabBlurred = true
}

function handleWindowFocus() {
    isTabBlurred = false
}

/**
 * Handle when video is paused. If certain conditions are not met, it will continue
 * playing. Returning early in this function means the video should be paused as it should
 * be.
 */
function handleVideoPause() {
    if (isYouTubeMusic) return

    // If it was stopped by the user, it's ok, let the video pause as it should, and exit the function.
    if (pausedByUser) {
        disallowPauseVideo()
        return
    }

    // The video will pause normally if the tab is not focused. This is to allow for pausing the video via the media controller (of the browser or operating system), etc.
    // Note: While this also gives YouTube the opportunity to pause videos to annoy users, it's an acceptable trade-off.
    if (document.hidden) return
    if (isTabBlurred) return

    if (fineScrubber && fineScrubber.style.display !== 'none') return
    if (video === null) return
    if (video.duration - video.currentTime < 0.1) return

    // This is YouTube's disruptive behavior towards users, so the video should continue to play as normal.
    video.play()
}

function handleVideoTimeUpdate() {
    if (hasAd || video === null) return
    currentVideoTime = video.currentTime
}

/**
 * Handle both keyboard press or release events.
 */
function handleWindowKeyDownAndKeyUp(event) {
    if (isYouTubeMusic) return
    if (checkEnteringInput()) return
    const code = event.code
    if (event.type === 'keydown') {
        if (code === 'KeyK' || code === 'MediaPlayPause') {
            allowPauseVideo()
        }
    } else {
        if (code === 'Space') {
            allowPauseVideo()
        }
    }
}

function handleYouTubeNavigateFinish() {
    currentVideoTime = 0
    replaceCurrentVideo()
}

async function replaceCurrentVideo() {
    const start = Math.floor(currentVideoTime)
    for (let i = 0; i < 16; i++) {
        await waitFor(500)
        const videoId = getCurrentVideoId()
        if (!videoId || !video || video.src) continue
        if (isYouTubeMobile) {
            const player = document.querySelector('#movie_player')
            if (!player) continue
            player.loadVideoByPlayerVars({ videoId, start })
        } else {
            const player = document.querySelector('#ytd-player')
            if (!player) continue
            player.loadVideoWithPlayerVars({ videoId, start })
        }
    }
}

function waitFor(millis) {
    return new Promise((resolve) => {
        window.setTimeout(resolve, millis)
    })
}

/**
 * Add CSS hides some ad elements on the page.
 */
function addCss() {
    const hideCssSelector = [
        // Ad banner in the upper right corner, above the video playlist.
        '#player-ads',

        // Masthead ad on home page.
        '#masthead-ad',

        'ytd-ad-slot-renderer',

        // Ad blocker warning inside the player.
        'yt-playability-error-supported-renderers#error-screen',

        '.ytp-suggested-action',
        '.yt-mealbar-promo-renderer',

        // YouTube Music Premium trial promotion dialog, bottom left corner.
        'ytmusic-mealbar-promo-renderer',

        // YouTube Music Premium trial promotion banner on home page.
        'ytmusic-statement-banner-renderer'
    ].join(',')
    const css = `
        #ytd-player { visibility: visible !important; }
        ${hideCssSelector} { display: none !important; }
    `
    const style = document.createElement('style')
    style.textContent = css
    document.head.appendChild(style)
}

/**
 * Remove ad elements using javascript because these selectors require the use of the CSS
 * `:has` selector which is not supported in older browser versions.
 */
function removeAdElements() {
    const adSelectors = [
        // Ad banner in the upper right corner, above the video playlist.
        ['#panels', 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]'],

        // Temporarily comment out this selector to fix issue [#265124](https://greasyfork.org/en/scripts/498197-auto-skip-youtube-ads/discussions/265124).
        // ['#panels', 'ytd-ads-engagement-panel-content-renderer'],

        // Sponsored ad video items on home page.
        // ['ytd-rich-item-renderer', '.ytd-ad-slot-renderer'],

        // ['ytd-rich-section-renderer', '.ytd-statement-banner-renderer'],

        // Ad videos on YouTube Short.
        ['ytd-reel-video-renderer', '.ytd-ad-slot-renderer'],

        // Ad blocker warning dialog.
        ['tp-yt-paper-dialog', '#feedback.ytd-enforcement-message-view-model']

        // Survey dialog on home page, located at bottom right.
        // ['tp-yt-paper-dialog', ':scope > ytd-checkbox-survey-renderer'],

        // Survey to rate suggested content, located at bottom right.
        // ['tp-yt-paper-dialog', ':scope > ytd-single-option-survey-renderer']
    ]
    for (const adSelector of adSelectors) {
        const adEls = queryHasSelectorAll(adSelector[0], adSelector[1])
        for (const adEl of adEls) {
            adEl.remove()
        }
    }
}

/**
 * Is it YouTube mobile version.
 */
const isYouTubeMobile = location.hostname === 'm.youtube.com'

/**
 * Is the current page YouTube Music.
 */
const isYouTubeMusic = location.hostname === 'music.youtube.com'

/**
 * Current video element.
 */
let video = null

let fineScrubber = null
let hasAd = false
let currentVideoTime = 0

/**
 * Is the video paused by the user, not paused by YouTube's ad blocker warning dialog.
 */
let pausedByUser = false

/**
 * Is the current tab blurred.
 */
let isTabBlurred = false

let allowPauseVideoTimeoutId = 0

// Observe DOM changes to detect ads.
if (window.MutationObserver) {
    const observer = new MutationObserver(skipAd)
    observer.observe(document.body, {
        attributes: true,
        attributeFilter: ['class', 'src'],
        childList: true,
        subtree: true
    })
}
// If DOM observation is not supported. Detect ads every 500ms (2 times per second).
else {
    window.setInterval(skipAd, 500)
}

window.addEventListener('blur', handleWindowBlur)
window.addEventListener('focus', handleWindowFocus)
window.addEventListener('keydown', handleWindowKeyDownAndKeyUp)
window.addEventListener('keyup', handleWindowKeyDownAndKeyUp)

addCss()
skipAd()