Greasy Fork

YouTube AI广告稳定自愈版 v2.0.0 (优化更新版)

跳过广告、过滤字幕、黑屏提示、AI自愈、本地缓存、自动识别广告拦截警告并重新加载、支持首页广告屏蔽!完全本地运行、无设置面板。优化:改进AI响应解析、异步处理、性能优化、错误处理增强、代码结构化。

// ==UserScript==
// @name         YouTube AI广告稳定自愈版 v2.0.0 (优化更新版)
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  跳过广告、过滤字幕、黑屏提示、AI自愈、本地缓存、自动识别广告拦截警告并重新加载、支持首页广告屏蔽!完全本地运行、无设置面板。优化:改进AI响应解析、异步处理、性能优化、错误处理增强、代码结构化。
// @author       little fool (optimized by Grok)
// @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 HISTORY_KEY = 'yt_ad_selectors_history';
  const UPDATE_KEY = 'yt_ad_last_update';

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

  let dynamicSelectors = new Set();
  let lastUpdate = 0;
  let previousMuted = null;
  let wasAdPlaying = false;
  let refreshAttempts = 0;

  const MAX_REFRESH_ATTEMPTS = 5;
  const REFRESH_INTERVAL = 5000;
  const UPDATE_INTERVAL = 3600000; // 1 hour

  const log = (...args) => console.log('[YT净化 v2.0.0]', ...args);

  function getVideo() {
    return document.querySelector('video') || document.querySelector('ytd-player video') || document.querySelector('#movie_player video');
  }

  function isVideoPage() {
    return location.pathname.startsWith('/watch') ||
      location.pathname.startsWith('/tv') ||
      location.pathname.startsWith('/embed');
  }

  function isHomePage() {
    return location.pathname === '/';
  }

  async function updateSelectorsViaAI() {
    if (Date.now() - lastUpdate < UPDATE_INTERVAL) return;
    try {
      const response = await new Promise((resolve, reject) => {
        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: resolve,
          onerror: reject
        });
      });

      const reply = JSON.parse(response.responseText);
      let content = reply.choices[0].message.content.trim().replace(/```json|```/g, '');
      const selectors = JSON.parse(content);

      if (Array.isArray(selectors) && selectors.length > 0) {
        dynamicSelectors = new Set(selectors);
        GM_setValue(CACHE_KEY, Array.from(dynamicSelectors));
        GM_setValue(UPDATE_KEY, Date.now());
        lastUpdate = Date.now();

        let history = GM_getValue(HISTORY_KEY, []);
        history.unshift(Array.from(dynamicSelectors));
        if (history.length > 10) history = history.slice(0, 10);
        GM_setValue(HISTORY_KEY, history);

        log('[AI更新成功]', Array.from(dynamicSelectors));
      } else {
        log('[AI格式错误]', content);
        fallbackSelectors();
      }
    } catch (e) {
      log('[AI请求/解析失败]', e);
      fallbackSelectors();
    }
  }

  function fallbackSelectors() {
    const history = GM_getValue(HISTORY_KEY, []);
    if (history.length > 0) {
      dynamicSelectors = new Set(history[0]);
      log('[容灾] 使用历史缓存', Array.from(dynamicSelectors));
    } else {
      dynamicSelectors = new Set();
      log('[严重容灾] 无历史,仅用默认选择器');
    }
  }

  function hideAds() {
    const allSelectors = new Set([...BASE_SELECTORS, ...dynamicSelectors]);
    allSelectors.forEach(sel => {
      document.querySelectorAll(sel).forEach(el => {
        if (el.style.opacity !== '0.01') {
          Object.assign(el.style, {
            opacity: '0.01',
            pointerEvents: 'none',
            position: 'absolute',
            left: '-9999px',
            top: '-9999px',
            zIndex: '0'
          });
        }
      });
    });
  }

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

  function accelerateAdPlayback() {
    const video = getVideo();
    if (!video) return;

    const ad = document.querySelector('.ad-showing');
    if (ad) {
      if (video.playbackRate !== 16) video.playbackRate = 16;
      if (!wasAdPlaying) {
        previousMuted = video.muted;
        video.muted = true;
        log('[广告中] 倍速+静音');
      }
    } else {
      if (video.playbackRate !== 1) video.playbackRate = 1;
      if (wasAdPlaying && previousMuted !== null) {
        video.muted = previousMuted;
        previousMuted = null;
        log('[广告结束] 恢复音量');
      }
    }
    wasAdPlaying = !!ad;
    if (!ad) refreshAttempts = 0;
  }

  function removeAdSubtitles() {
    const keywords = ['广告', '贊助', '推广', 'Sponsored', 'Sponsor'];
    document.querySelectorAll('.ytp-caption-segment').forEach(el => {
      if (keywords.some(k => el.textContent.includes(k)) && el.style.display !== 'none') {
        el.style.display = 'none';
        el.textContent = '';
      }
    });
  }

  function detectAdBlackScreen() {
    const video = getVideo();
    const isAd = document.querySelector('.ad-showing');
    const isBlack = video && video.readyState >= 2 && video.videoWidth === 0;
    const stuck = isAd && video?.currentTime < 2;
    if ((isAd && isBlack) || stuck) {
      showTip('yt-ad-wait-tip', '⏳ 正在跳过广告,请稍候...');
    } else {
      removeTip('yt-ad-wait-tip');
    }
  }

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

  function showTip(id, message) {
    let tip = document.getElementById(id);
    if (!tip) {
      tip = document.createElement('div');
      tip.id = id;
      Object.assign(tip.style, {
        position: 'fixed',
        top: id.includes('wait') ? '20px' : '60px',
        left: '50%',
        transform: 'translateX(-50%)',
        backgroundColor: id.includes('wait') ? '#111' : '#c00',
        color: '#fff',
        padding: '10px 16px',
        borderRadius: '8px',
        zIndex: '9999',
        fontSize: '14px',
        fontWeight: 'bold',
        boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
        opacity: '0.95'
      });
      document.body.appendChild(tip);
    }
    if (tip.innerText !== message) tip.innerText = message;
  }

  function removeTip(id) {
    const el = document.getElementById(id);
    if (el) el.remove();
  }

  function hideHomepageAds() {
    const keywords = ['广告', '推广', 'Sponsored', '赞助'];
    document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer').forEach(item => {
      const text = item.innerText;
      if (keywords.some(k => text.includes(k)) && item.style.display !== 'none') {
        item.style.display = 'none';
      }
    });
  }

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

  async function init() {
    const cached = GM_getValue(CACHE_KEY, []);
    dynamicSelectors = new Set(cached);
    lastUpdate = GM_getValue(UPDATE_KEY, 0);

    await updateSelectorsViaAI();
    setInterval(updateSelectorsViaAI, UPDATE_INTERVAL);

    if (isVideoPage()) {
      observeAdElements();
      setInterval(detectAdBlackScreen, 1000);
      setInterval(() => {
        if (detectAdBlockWarning()) {
          if (refreshAttempts < MAX_REFRESH_ATTEMPTS) {
            refreshAttempts++;
            showTip('yt-adblock-refresh-tip', `⚠️ 检测到广告拦截器提示,正在重新加载页面(第 ${refreshAttempts} 次)`);
            setTimeout(() => location.href = location.href, 800);
          } else {
            removeTip('yt-adblock-refresh-tip');
          }
        } else {
          removeTip('yt-adblock-refresh-tip');
        }
      }, REFRESH_INTERVAL);
    }

    if (isHomePage()) {
      setInterval(hideHomepageAds, 2000);
    }
  }

  init();
})();