Greasy Fork

Greasy Fork is available in English.

Youtube AD Skipper 油管广告拦截跳过

Quickly skip video ads in Youtube. 快速跳过油管中的视频广告。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                  Youtube AD Skipper 油管广告拦截跳过
// @name:zh-CN            Youtube AD Skipper 油管广告拦截跳过
// @namespace             http://tampermonkey.net/
// @version               0.10
// @description           Quickly skip video ads in Youtube. 快速跳过油管中的视频广告。
// @description:zh-CN     Quickly skip video ads in Youtube. 快速跳过油管中的视频广告。
// @icon                  https://www.gstatic.com/youtube/img/branding/favicon/favicon_144x144.png
// @author                gabe
// @license               MIT
// @match                 https://*.youtube.com/*
// @grant                 none
// ==/UserScript==

(function () {
  "use strict";

  const SKIP_BUTTON = ".ytp-ad-skip-button, .ytp-ad-skip-button-modern";
  const AD_OVERLAY = ".ytp-ad-player-overlay, .ytp-ad-survey-player-overlay";
  const AD_INFO =
    ".ytp-ad-player-overlay-instream-info, .ytp-ad-survey-player-overlay-instream-info";

  function log() {
    return console.info("[Youtube AD Skipper]", ...arguments);
  }

  function sleep(delay = 500) {
    return new Promise(function (resolve) {
      const timer = setTimeout(function () {
        clearTimeout(timer);
        resolve();
      }, delay);
    });
  }

  function newTouch(el) {
    const rect = el.getBoundingClientRect();
    const x = (rect.left + rect.right) / 2;
    const y = (rect.top + rect.bottom) / 2;
    return new Touch({
      identifier: Date.now(),
      target: el,
      clientX: x,
      clientY: y,
      screenX: x,
      screenY: y,
      pageX: x + document.body.scrollLeft,
      pageY: y + document.body.scrollTop,
      radiusX: 10.0,
      radiusY: 10.0,
      rotationAngle: 0.0,
      force: 1,
    });
  }

  function newTouchEvent(touch, name) {
    return new TouchEvent(name, {
      cancelable: true,
      bubbles: true,
      touches: [touch],
      targetTouches: [touch],
      changedTouches: [touch],
    });
  }

  function dispatchTouch(el) {
    const touch = newTouch(el);
    el.dispatchEvent(newTouchEvent(touch, "touchstart"));
    // el.dispatchEvent(newTouchEvent(touch, "touchmove"));
    el.dispatchEvent(newTouchEvent(touch, "touchend"));
  }

  async function skipAd(moviePlayer, adOverlay) {
    let i = 0;
    while (++i < 6) {
      if (i > 1) {
        adOverlay = moviePlayer.querySelector(AD_OVERLAY);
        if (!adOverlay) {
          log("skip done!!!");
          return;
        }
      }

      const skipButton = adOverlay.querySelector(SKIP_BUTTON);
      const isMobile = location.hostname === "m.youtube.com";
      if (skipButton) {
        if (isMobile) {
          dispatchTouch(skipButton);
          log("skip touch ->", i);
        } else {
          skipButton.click();
          log("skip click ->", i);
        }
      } else {
        const video = moviePlayer.getElementsByTagName("video")[0];
        video.currentTime = video.duration;
        log("skip play ->", i);
      }

      await sleep();
    }

    log("skip failed...");
  }

  let isBusying = false;
  const pageObserver = new MutationObserver(async function () {
    if (isBusying) {
      return;
    }

    try {
      isBusying = true;

      const moviePlayer = document.getElementById("movie_player");
      if (!moviePlayer) {
        return;
      }

      const adOverlay = moviePlayer.querySelector(AD_OVERLAY);
      if (!adOverlay) {
        return;
      }

      const adInfo = adOverlay.querySelector(AD_INFO);
      log("found ad:", adInfo && adInfo.innerText);
      await skipAd(moviePlayer, adOverlay);
    } catch (err) {
      log("got error:", err.message);
    } finally {
      isBusying = false;
    }
  });

  if (window.self !== window.top) {
    return;
  }

  pageObserver.observe(document.body, {
    childList: true,
    subtree: true,
  });

  log("--- start ---");
})();