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 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 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)');
})();