YouTube Ad Auto-Skipper

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

La data de 23-11-2025. Vezi ultima versiune.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         YouTube Ad Auto-Skipper
// @version      2025.11.24
// @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 
// @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;

  // CSS选择器:只隐藏明确的广告元素
  const CSS_HIDE_SELECTORS = [
    '#player-ads',
    '#masthead-ad',
    '#panels > ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
    '.ytp-ad-overlay-container',
    '.ytp-ad-text-overlay',
    '.ytp-ad-image-overlay',
    '.ytp-paid-content-overlay',
    'ytd-merch-shelf-renderer',
    'ytmusic-mealbar-promo-renderer'
  ];

  const CHECK_DEBOUNCE_MS = 100;
  const INTERVAL_CHECK_MS = 1000; // 1秒检查一次保底
  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 sel = CSS_HIDE_SELECTORS.join(',');
    if (!sel) return;
    const style = document.createElement('style');
    // opacity:0 防检测,pointer-events:none 防止点击穿透拦截
    style.textContent = `${sel}{opacity:0 !important; pointer-events:none !important; z-index:-1 !important;}`;
    document.head ? document.head.appendChild(style) : document.documentElement.appendChild(style);
  }

  // 【关键修复】获取播放器容器,限制查找范围
  function getPlayerContainer() {
    return document.querySelector('#movie_player') || 
           document.querySelector('yt-music-player') || 
           document.body;
  }

  function querySkipButton(container) {
    // 1. 优先查找标准的广告跳过按钮 class
    const highPriorityBtn = container.querySelector('.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-ad-skip-button-slot, .ytp-ad-skip-button-container button');
    if (highPriorityBtn) return highPriorityBtn;

    // 2. 只有找不到标准 class 时,才遍历 button 文本
    // 【关键修复】限制只在 container (播放器) 内部查找,不扫描整个 document
    const buttons = container.querySelectorAll('button'); 
    
    // 转换为数组进行查找
    for (const b of buttons) {
      // 忽略不可见按钮,提高性能并减少误判
      if (b.offsetParent === null) continue;

      const t = (b.getAttribute('aria-label') || b.textContent || '').trim();
      // 【关键修复】移除了 'Skip', '繼續' 等太宽泛的词,只匹配明确的广告词
      if (/skip ad|skip ads|跳过广告|跳過廣告/i.test(t)) {
        return b;
      }
    }
    return null;
  }

  function detectAdContext() {
    const container = getPlayerContainer();
    
    // 只在播放器内部查找元素
    const adShowing = !!container.querySelector('.ad-showing');
    const pieCountdown = !!container.querySelector('.ytp-ad-timed-pie-countdown-container');
    const skipBtn = querySkipButton(container);
    
    const adLikely = adShowing || pieCountdown || !!skipBtn;
    return { adLikely, skipBtn, container };
  }

  function trySoftSkip(ctx) {
    // 1. 点击跳过按钮
    if (ctx.skipBtn) {
      const delay = 300 + Math.random() * 500;
      setTimeout(() => {
        try {
          if (ctx.skipBtn && ctx.skipBtn.click) {
            ctx.skipBtn.click();
            log('Clicked skip button');
            // 点击后立即触发下一次检查(应对连续广告)
            setTimeout(() => scheduleCheck(0), 50);
          }
        } catch (_) {}
      }, delay);
      return true;
    }

    // 2. 强制跳过(加速+静音)
    if (ctx.adLikely) {
      const adVideo = ctx.container.querySelector('video.html5-main-video');
      if (adVideo && !Number.isNaN(adVideo.duration) && adVideo.duration > 0) {
         // 只有当确实是广告时才操作
         // 简单的判断:通常广告视频时间很短,或者有 ad-showing 类
         // 为了安全,我们依赖 detectAdContext 的 adLikely 判断
        try {
          const targetTime = Math.max(0, adVideo.duration - SEEK_EPSILON);
          if (adVideo.currentTime < targetTime) {
              adVideo.muted = true;
              adVideo.playbackRate = 16; 
              adVideo.currentTime = targetTime;
              log('Accelerated ad');
              return true;
          }
        } catch (_) {}
      }
    }
    return false;
  }

  function skipAd() {
    const ctx = detectAdContext();
    if (ctx.adLikely) {
      log('Ad detected');
      const handled = trySoftSkip(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() {
    // 【关键修复】只监视播放器区域,而不是整个 Body
    // 这避免了点击菜单修改 Body 属性时触发脚本,也减少了性能消耗
    const targetNode = document.querySelector('#movie_player') || document.body;
    
    const observer = new MutationObserver((mutations) => {
      // 简单防抖
      scheduleCheck(50);
    });
    
    observer.observe(targetNode, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['class', 'style', 'src'] // 限制监控的属性
    });
  }

  // === 启动 ===
  addCss();
  
  // 尝试等待播放器加载后再启动 Observer
  const waitTimer = setInterval(() => {
      if (document.querySelector('#movie_player') || document.querySelector('video')) {
          clearInterval(waitTimer);
          setupObserver();
          log('Observer attached to player');
      }
  }, 500);

  // 定时保底
  setInterval(() => scheduleCheck(), INTERVAL_CHECK_MS);
  
  // 立即检查一次
  scheduleCheck(1000);
})();