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 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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)');
})();