Greasy Fork

YouTube AI广告跳过增强 v2.0

纯本地:自动识别广告、60倍速暴力跳过、静音、AI选择器更新、累计时长统计、无UI、无服务器、无上传。

// ==UserScript==
// @name         YouTube AI广告跳过增强 v2.0
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  纯本地:自动识别广告、60倍速暴力跳过、静音、AI选择器更新、累计时长统计、无UI、无服务器、无上传。
// @author       little fool
// @match        *://www.youtube.com/*
// @match        *://m.youtube.com/*
// @match        *://music.youtube.com/*
// @match        *://www.youtube-nocookie.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      openkey.cloud
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  const API_URL = 'https://openkey.cloud/v1/chat/completions';
  const API_KEY = ['sk-', '1ytLN', 'fSpk5R34n', 'jTF628665', '6331c426cAeCb95E266F8D377'].join('');
  const CACHE_KEY = 'yt_ad_selectors_cache';
  const UPDATE_KEY = 'yt_ad_last_update';
  const SKIP_TIME_KEY = 'yt_ad_total_skip_time';

  const BASE_SELECTORS = [
    '.ytp-ad-module',
    '.ytp-ad-overlay-container',
    '.ytp-ad-player-overlay',
    '.ad-showing .video-ads',
    '#player-ads'
  ];

  let dynamicSelectors = [];
  let lastUpdate = 0;
  let wasAdPlaying = false;
  let previousMuted = null;
  let skipStart = null;
  let refreshAttempts = 0;
  const MAX_REFRESH_ATTEMPTS = 5;
  const REFRESH_INTERVAL = 5000;

  const getVideo = () => document.querySelector('video');
  const isVideoPage = () => location.pathname.startsWith('/watch') || location.pathname.startsWith('/tv') || location.pathname.startsWith('/embed');

  function updateSelectorsViaAI() {
    if (Date.now() - lastUpdate < 3600000) return;
    GM_xmlhttpRequest({
      method: 'POST',
      url: API_URL,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`
      },
      data: JSON.stringify({
        model: 'gpt-4o',
        messages: [{ role: 'user', content: "请提供当前YouTube最新广告CSS选择器数组,JSON格式,仅返回如 ['.ytp-ad-module', ...]" }],
        temperature: 0.3
      }),
      onload: res => {
        try {
          const json = JSON.parse(res.responseText);
          const content = json?.choices?.[0]?.message?.content?.trim().replace(/```json|```/g, '');
          const selectors = JSON.parse(content);
          if (Array.isArray(selectors)) {
            dynamicSelectors = selectors;
            GM_setValue(CACHE_KEY, selectors);
            GM_setValue(UPDATE_KEY, Date.now());
            lastUpdate = Date.now();
            console.log('[AI选择器更新]', selectors);
          }
        } catch (e) {
          console.log('[AI更新失败]', e.message);
        }
      },
      onerror: () => console.log('[AI请求失败]')
    });
  }

  function hideAds() {
    [...BASE_SELECTORS, ...dynamicSelectors].forEach(sel => {
      document.querySelectorAll(sel).forEach(el => {
        el.style.cssText = 'opacity:0.01;pointer-events:none;position:absolute;left:-9999px;top:-9999px;z-index:0';
      });
    });
  }

  function clickSkipButton() {
    const btn = document.querySelector('.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-ad-button');
    if (btn && btn.offsetParent !== null) {
      setTimeout(() => btn.click(), 200 + Math.random() * 300);
    }
  }

  function accelerateAdPlayback() {
    const video = getVideo();
    const isAd = document.querySelector('.ad-showing');
    if (!video) return;

    if (isAd) {
      video.playbackRate = 60;
      if (!wasAdPlaying) {
        previousMuted = video.muted;
        video.muted = true;
        skipStart = Date.now();
        console.log('[广告中] 强制60x加速');
      }
    } else if (wasAdPlaying) {
      video.playbackRate = 1;
      if (previousMuted !== null) video.muted = previousMuted;
      previousMuted = null;

      const duration = Math.floor((Date.now() - skipStart) / 1000);
      const total = GM_getValue(SKIP_TIME_KEY, 0) + duration;
      GM_setValue(SKIP_TIME_KEY, total);
      console.log(`[广告结束] 本次 ${duration}s |累计 ${total}s`);
      skipStart = null;
    }
    wasAdPlaying = !!isAd;
  }

  function detectAdBlockWarning() {
    const signs = [
      '您似乎在使用广告拦截器', '广告支持我们的服务',
      '关闭广告拦截', 'Ad blockers violate YouTube’s Terms'
    ];
    return signs.some(k => document.body.innerText.includes(k));
  }

  function observeAdElements() {
    const observer = new MutationObserver(() => {
      hideAds();
      clickSkipButton();
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  function init() {
    dynamicSelectors = GM_getValue(CACHE_KEY, []);
    lastUpdate = GM_getValue(UPDATE_KEY, 0);

    updateSelectorsViaAI();
    setInterval(updateSelectorsViaAI, 3600000);

    if (isVideoPage()) {
      observeAdElements();

      // ✅ 初始化时立即执行一次核心逻辑,避免延迟
      hideAds();
      clickSkipButton();
      accelerateAdPlayback();

      // 定时处理广告跳过与加速
      setInterval(() => {
        accelerateAdPlayback();
        clickSkipButton();
      }, 500);

      // 侦测广告拦截器警告并自动刷新(最多5次)
      setInterval(() => {
        if (detectAdBlockWarning()) {
          if (refreshAttempts < MAX_REFRESH_ATTEMPTS) {
            refreshAttempts++;
            location.reload();
          }
        }
      }, REFRESH_INTERVAL);
    }
  }

  init();
})();