动画疯自动播放广告

自动同意年龄提示,到达时间后自动跳过广告(内置播放器广告和两种 google iframe 广告),自动播放下一集,并注册一些快捷键(详见最下方的更新日志)

当前为 2024-12-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name               Auto play ads on ani.gamer.com.tw
// @name:zh-CN         动画疯自动播放广告
// @name:zh-TW         動畫瘋自動播放廣告
// @namespace          ling921
// @version            0.5.0
// @description        Agree to age prompt, auto skip ads when time is up, auto play next video, and register some keyboard shortcuts (see the release notes below for details)
// @description:zh-CN  自动同意年龄提示,到达时间后自动跳过广告(内置播放器广告和两种 google iframe 广告),自动播放下一集,并注册一些快捷键(详见最下方的更新日志)
// @description:zh-TW  自動同意年齡提示,到達時間後自動跳過廣告(內置播放器廣告和兩種 google iframe 廣告),自動播放下一集,並註冊一些快捷鍵(詳見最下方的更新日誌)
// @author             ling921
// @match              https://ani.gamer.com.tw/animeVideo.php*
// @match              https://*.safeframe.googlesyndication.com/*
// @match              https://imasdk.googleapis.com/*
// @icon               http://gamer.com.tw/favicon.ico
// @grant              none
// @run-at             document-idle
// @tag                video
// @tag                anime
// @tag                utilities
// @license            MIT
// ==/UserScript==

/**
 * Global variable to store video player
 * @type {HTMLVideoElement}
 */
var videoPlayer;

(function () {
  "use strict";

  // Handle top level window
  if (window === window.top) {
    videoPlayer = document.querySelector("#ani_video_html5_api");
    if (videoPlayer) {
      console.log("Add event listener to videoPlayer");
      // Auto unmute video player
      videoPlayer.addEventListener("loadstart", () => {
        videoPlayer.muted = false;
      });
      // Auto play next video
      videoPlayer.addEventListener("ended", () => {
        const nextButton = document.querySelector(".vjs-next-button");
        if (nextButton) {
          console.log("Auto play next video");
          nextButton.click();
        }
      });
    }

    // Register keyboard shortcuts
    registerKeyboardShortcuts(document);

    // Define observer to execute functions when DOM changes
    const observer = new MutationObserver(() => {
      agreeAgePrompt();
      handleVideoPlayerAds();
      ensureShortcutTitles();
    });

    // Start observing the body for changes
    observer.observe(document.body, { childList: true, subtree: true });
  }
  // Handle iframe window
  else {
    if (window.location.href.includes("safeframe.googlesyndication.com")) {
      const observer = new MutationObserver(() => {
        handleIframeAds(document);
      });
      observer.observe(document.body, { childList: true, subtree: true });
    } else if (window.location.href.includes("imasdk.googleapis.com")) {
      const observer = new MutationObserver(() => {
        handleIframeAds2(document);
      });
      observer.observe(document.body, { childList: true, subtree: true });
    }
  }
})();

/**
 * Register keyboard shortcuts
 * @param {Document} doc - The document
 */
function registerKeyboardShortcuts(doc) {
  doc.addEventListener("keydown", (event) => {
    // Ignore input fields event propagation
    if (
      event.target.tagName === "INPUT" ||
      event.target.tagName === "TEXTAREA" ||
      event.target.isContentEditable
    ) {
      return;
    }

    if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
      /**
       * Get the document of the event target
       * @type {Document}
       */
      const _doc = event.target.ownerDocument || doc;

      // P pause or play
      if (event.key === "p") {
        const playButton = _doc.querySelector(".vjs-play-control");
        if (playButton) {
          console.log("Pause or play");
          playButton.click();
        } else {
          console.log("No play button found");
        }
      }
      // [ goes to previous video
      else if (event.key === "[") {
        const prevButton = _doc.querySelector(".vjs-pre-button");
        if (prevButton) {
          console.log("Go to previous video");
          prevButton.click();
        } else {
          console.log("No previous video button found");
        }
      }
      // ] goes to next video
      else if (event.key === "]") {
        const nextButton = _doc.querySelector(".vjs-next-button");
        if (nextButton) {
          console.log("Go to next video");
          nextButton.click();
        } else {
          console.log("No next video button found");
        }
      }
      // D enable or disable danmu
      else if (event.key === "d") {
        const danmuButton = _doc.querySelector(
          ".vjs-danmu-button .vjs-menu-button"
        );
        if (danmuButton) {
          console.log("Enable or disable danmu");
          danmuButton.click();
        } else {
          console.log("No danmu button found");
        }
      }
      // T enter or exit theater mode
      else if (event.key === "t") {
        const theaterButton = _doc.querySelector(".vjs-indent-button");
        if (theaterButton) {
          console.log("Enter or exit theater mode");
          theaterButton.click();
        } else {
          console.log("No theater button found");
        }
      }
      // F enter or exit fullscreen
      else if (event.key === "f") {
        const fullscreenButton = _doc.querySelector(".vjs-fullscreen-control");
        if (fullscreenButton) {
          console.log("Enter or exit fullscreen");
          fullscreenButton.click();
        } else {
          console.log("No fullscreen button found");
        }
      }
      // Video player control
      else if (!event.target.closest("video-js")) {
        const dispatchEvent = (eventType) => {
          videoPlayer.dispatchEvent(
            new KeyboardEvent(eventType, {
              key: event.key,
              code: event.code,
              keyCode: event.keyCode,
              which: event.which,
              bubbles: true,
              cancelable: true,
              composed: true,
              isTrusted: true,
            })
          );
        };

        // ↑ video volume up
        if (event.key === "ArrowUp") {
          if (videoPlayer) {
            if (videoPlayer.volume < 1) {
              event.preventDefault();
              console.log("Dispatch video volume up");
              dispatchEvent("keydown");
            }
          } else {
            console.log("No video player found");
          }
        }
        // ↓ video volume down
        else if (event.key === "ArrowDown") {
          if (videoPlayer) {
            if (videoPlayer.volume > 0) {
              event.preventDefault();
              console.log("Dispatch video volume down");
              dispatchEvent("keydown");
            }
          } else {
            console.log("No video player found");
          }
        }
        // ← video backward
        else if (event.key === "ArrowLeft") {
          if (videoPlayer) {
            if (videoPlayer.currentTime > 0) {
              event.preventDefault();
              console.log("Dispatch video backward");
              dispatchEvent("keydown");
            }
          } else {
            console.log("No video player found");
          }
        }
        // → video forward
        else if (event.key === "ArrowRight") {
          if (videoPlayer) {
            if (videoPlayer.currentTime < videoPlayer.duration) {
              event.preventDefault();
              console.log("Dispatch video forward");
              dispatchEvent("keydown");
            }
          } else {
            console.log("No video player found");
          }
        }
      }
    }
  });
}

/**
 * Agree to age prompt
 */
function agreeAgePrompt() {
  const agePrompt = document.querySelector("button.choose-btn-agree#adult");
  if (agePrompt) {
    console.log("Agree to age prompt");
    agePrompt.click();
  }
}

/**
 * Handle ads in video player
 */
function handleVideoPlayerAds() {
  const skipButton = document.querySelector("#adSkipButton");
  if (skipButton) {
    if (skipButton.classList.contains("enable")) {
      console.log("Ads time is up, skip ads (#adSkipButton)");
      skipButton.click();
    } else {
      videoPlayer.muted = true;
    }
  }

  const skipButton2 = document.querySelector(".nativeAD-skip-button.enable");
  if (skipButton2 && !skipButton2.classList.contains("vjs-hidden")) {
    console.log("Ads time is up, skip ads (.nativeAD-skip-button)");
    skipButton2.click();
  }
}

/**
 * Ensure shortcut titles
 */
function ensureShortcutTitles() {
  /**
   * Ensure title ends with text
   * @param {Element|null} element - The element
   * @param {string} text - The text
   */
  function ensureTitleEndsWith(element, text) {
    if (!element) {
      return;
    }
    const title = element.getAttribute("title");
    if (!title) {
      element.setAttribute("title", text);
    } else if (!title.endsWith(text)) {
      element.setAttribute("title", title + " " + text);
    }
  }

  // Play button
  ensureTitleEndsWith(document.querySelector(".vjs-play-control"), "(P)");
  // Previous button
  ensureTitleEndsWith(document.querySelector(".vjs-pre-button"), "([)");
  // Next button
  ensureTitleEndsWith(document.querySelector(".vjs-next-button"), "(])");
  // Danmu button
  ensureTitleEndsWith(document.querySelector(".vjs-danmu-button"), "(D)");
  // Theater button
  ensureTitleEndsWith(document.querySelector(".vjs-indent-button"), "(T)");
  // Fullscreen button
  ensureTitleEndsWith(document.querySelector(".vjs-fullscreen-control"), "(F)");
}

/**
 * Handle ads in iframe
 * @param {Document} doc - The iframe document
 */
function handleIframeAds(doc) {
  const adsCountDown = doc.querySelector("#count-down-text");
  if (adsCountDown) {
    const dismissDialog = () => {
      const dismissButton = doc.querySelector("#card #dismiss-button-element");
      if (dismissButton) {
        if (dismissButton.style.display !== "none") {
          console.log("Dismiss dialog Ad");
          dismissButton.click();
        } else {
          console.log("Dismiss button is hidden, waiting for it to appear...");
        }
      } else {
        console.log("Dismiss button is not found");
      }
    };
    if (adsCountDown.offsetParent === null) {
      dismissDialog();
    } else if (adsCountDown.textContent === "1 秒後即可獲得獎勵") {
      setTimeout(dismissDialog, 1000);
    }
  }
}

/**
 * Handle ads in iframe
 * @param {Document} doc - The iframe document
 */
function handleIframeAds2(doc) {
  const skipButton = doc.querySelector('[aria-label="Skip Ad"]');
  if (skipButton) {
    if (skipButton.textContent === "Skip Ad") {
      console.log('Click Skip Ad ([aria-label="Skip Ad"])');
      skipButton.click();
    } else {
      videoPlayer.muted = true;
    }
  }
}

// Release notes

// 2024-12-18 version 0.5.0
// - 添加自動播放下一集
// - 完善頁面快捷鍵相關按鈕的 title 屬性

// 2024-12-16 version 0.4.0
// - 規範版本號

// 2024-12-16 version 0.3
// - 註冊快捷鍵 ↑ ↓ ← → 分別控制音量、時間軸
// - 註冊快捷鍵 D 控制彈幕

// 2024-12-15 version 0.2
// - 添加標籤 video, anime, utilities

// 2024-12-14 version 0.1
// - 自動同意年齡確認
// - 廣告倒數結束結束自動跳過廣告
// - 播放廣告時靜音,播放影片時取消靜音
// - 註冊快捷鍵 [ 和 ] 分別跳到上一個和下一個視頻
// - 註冊快速鍵 P 暫停或播放
// - 註冊快速鍵 T 進入或退出劇院模式
// - 註冊快捷鍵 F 進入或退出全螢幕