YouTube Ad Auto-Skipper (VoidMuser)

Prioritize "Soft Skip" (Mute+Seek/Button), fallback to "Reload" with cooldown; uses MutationObserver; reduces detection risk. / 优先“软跳过”(按钮/静音+跳尾),失败时执行带冷却的“重载”;使用 MutationObserver + 兜底定时器;降低被风控概率。

目前為 2025-11-23 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Ad Auto-Skipper (VoidMuser)
// @version      2025.11.23.1
// @match        https://www.youtube.com/*
// @match        https://m.youtube.com/*
// @match        https://music.youtube.com/*
// @exclude      https://studio.youtube.com/*
// @grant        none
// @license      MIT
// @noframes
// @run-at       document-idle
// @namespace
// @namespace 
// @description Prioritize "Soft Skip" (Mute+Seek/Button), fallback to "Reload" with cooldown; uses MutationObserver; reduces detection risk. / 优先“软跳过”(按钮/静音+跳尾),失败时执行带冷却的“重载”;使用 MutationObserver + 兜底定时器;降低被风控概率。
// ==/UserScript==

(function () {
  'use strict';

  const DEBUG = false;

  // 轮询节流
  const CHECK_DEBOUNCE_MS = 100;
  const INTERVAL_CHECK_MS = 800;
  const SEEK_EPSILON = 0.1;

  const state = {
    checkTimer: null
  };

  const log = (...args) => { if (DEBUG) console.log('[ASYA]', ...args); };

  const isMobile = location.hostname === 'm.youtube.com';
  const isMusic  = location.hostname === 'music.youtube.com';

  // 只隐藏明确的广告相关容器,避免动到全局弹窗/菜单的宿主
  function addCss() {
    const style = document.createElement('style');
    style.textContent = `
      /* 广告容器:做透明处理,但不乱改 z-index,避免影响布局堆叠 */
      #player-ads,
      #panels > ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"],
      .ytp-featured-product,
      .ytp-ad-overlay-container,
      .ytp-ad-text-overlay,
      .ytp-ad-image-overlay,
      .ytp-paid-content-overlay,
      .yt-mealbar-promo-renderer,
      ytd-merch-shelf-renderer,
      ytmusic-mealbar-promo-renderer,
      ytmusic-statement-banner-renderer {
        opacity: 0 !important;
      }

      /* 叠在视频上面的广告层,禁用指针事件,防止挡住点击 */
      .ytp-ad-overlay-container,
      .ytp-ad-text-overlay,
      .ytp-ad-image-overlay,
      .ytp-paid-content-overlay {
        pointer-events: none !important;
      }
    `;
    (document.head || document.documentElement).appendChild(style);
  }

  function querySkipButton() {
    const byClass = document.querySelector(
      '.ytp-ad-skip-button, ' +
      '.ytp-ad-skip-button-modern, ' +
      '.ytp-ad-skip-button-slot, ' +
      '.ytp-ad-skip-button-container button'
    );
    if (byClass) return byClass;

    const btn = [...document.querySelectorAll('button')].find(b => {
      const t = (b.getAttribute('aria-label') || b.textContent || '').trim();
      return /skip ad|skip ads|跳过|跳過|繼續|Skip/i.test(t);
    });
    return btn || null;
  }

  function detectAdContext() {
    const adShowing    = !!document.querySelector('.ad-showing');
    const pieCountdown = !!document.querySelector('.ytp-ad-timed-pie-countdown-container');
    const survey       = !!document.querySelector('.ytp-ad-survey-questions');
    const skipBtn      = querySkipButton();
    const adLikely     = adShowing || pieCountdown || survey || !!skipBtn;
    return { adShowing, pieCountdown, survey, skipBtn, adLikely };
  }

  function getPlayers() {
    const moviePlayerEl = document.querySelector('#movie_player') || null;
    let playerEl = null;
    let player   = null;

    if (isMobile || isMusic) {
      playerEl = moviePlayerEl;
      player   = moviePlayerEl;
    } else {
      const ytd = document.querySelector('#ytd-player');
      playerEl = ytd || moviePlayerEl || null;
      if (ytd && typeof ytd.getPlayer === 'function') {
        try { player = ytd.getPlayer(); } catch (_) {}
      }
      if (!player && moviePlayerEl) player = moviePlayerEl;
    }
    return { moviePlayerEl, playerEl, player };
  }

  function trySoftSkip(players, ctx) {
    // 1. 如有跳过按钮,模拟人类点击
    if (ctx.skipBtn) {
      const delay = 300 + Math.random() * 500; // 300~800ms
      setTimeout(() => {
        try {
          if (ctx.skipBtn && ctx.skipBtn.click) {
            ctx.skipBtn.click();
            log('Human-like clicked skip button after', delay, 'ms');

            // 点击后稍微再检查一次,处理连播广告
            setTimeout(() => {
              scheduleCheck(0);
            }, 50);
          }
        } catch (e) {
          log('Click failed', e);
        }
      }, delay);
      return true;
    }

    // 2. 无按钮时:静音 + 16x + 跳至尾部(仅在判断为广告时执行)
    if (ctx.adLikely) {
      const adVideo = document.querySelector('video.html5-main-video');
      if (adVideo && !Number.isNaN(adVideo.duration) && adVideo.duration > 0) {
        try {
          const targetTime = Math.max(0, adVideo.duration - SEEK_EPSILON);
          if (adVideo.currentTime < targetTime) {
            adVideo.muted = true;
            adVideo.playbackRate = 16;
            adVideo.currentTime = targetTime;
            log('Muted + 16x + seek to end');
            return true;
          }
        } catch (_) {}
      }
    }

    return false;
  }

  function skipAd() {
    const ctx = detectAdContext();

    if (ctx.adLikely) {
      log('Ad detected. Context:', ctx);
      const players = getPlayers();
      const handled = trySoftSkip(players, ctx);

      if (handled) {
        // 处理过后,再过一段时间确认是否还有下一段广告
        scheduleCheck(1000);
      }
    }
  }

  // 调度器:防抖
  function scheduleCheck(delayMs = CHECK_DEBOUNCE_MS) {
    if (state.checkTimer) clearTimeout(state.checkTimer);
    state.checkTimer = setTimeout(() => {
      skipAd();
      state.checkTimer = null;
    }, delayMs);
  }

  // 观察广告状态变化
  function setupObserver() {
    const observer = new MutationObserver(mutations => {
      for (const m of mutations) {
        const el = m.target;
        if (
          el &&
          el.classList &&
          (el.classList.contains('ad-showing') ||
           el.classList.contains('ytp-ad-skip-button-container'))
        ) {
          scheduleCheck(0);
          return;
        }
      }
    });

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

  // === 入口 ===
  addCss();
  setupObserver();
  scheduleCheck(0);

  // 心跳轮询,兜底
  setInterval(() => scheduleCheck(), INTERVAL_CHECK_MS);

  log('Script loaded (Fixed Version)');
})();