Prime Video Enhancer

Enhanced Prime Video experience with X-ray hiding, ad skipping, cursor management, and more

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Prime Video Enhancer
// @namespace    https://github.com/bernardopg
// @version      2.0
// @description  Enhanced Prime Video experience with X-ray hiding, ad skipping, cursor management, and more
// @author       bernardopg
// @icon         https://www.primevideo.com/favicon.ico
// @match        https://www.primevideo.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // Configuration
  const CONFIG = {
    logging: false, // Set to true for debugging
    adSkip: {
      tries: 3,
      delay: 1500,
      selectors: [
        ".adSkipButton.skippable",
        '[data-testid="skip-ad-button"]',
        ".atvwebplayersdk-skipelements-button",
      ],
    },
    xray: {
      selectors: [
        ".xrayQuickView",
        '[data-testid="x-ray-panel"]',
        ".dv-player-fullscreen .xrayQuickView",
      ],
    },
    cursor: {
      hideDelay: 3000,
      playerSelectors: [
        ".webPlayerUIContainer",
        '[data-testid="video-player"]',
        ".dv-player-fullscreen",
      ],
    },
  };

  // Utility functions
  const Utils = {
    log: function (message, type = "info") {
      if (!CONFIG.logging) return;
      const prefix = "[Prime Video Enhancer]";
      console[type](`${prefix} ${message}`);
    },

    isElementVisible: function (element) {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    },

    waitForElement: function (selector, timeout = 10000) {
      return new Promise((resolve) => {
        const element = document.querySelector(selector);
        if (element) {
          resolve(element);
          return;
        }

        const observer = new MutationObserver(() => {
          const element = document.querySelector(selector);
          if (element) {
            observer.disconnect();
            resolve(element);
          }
        });

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

        setTimeout(() => {
          observer.disconnect();
          resolve(null);
        }, timeout);
      });
    },

    debounce: function (func, wait) {
      let timeout;
      return function executedFunction(...args) {
        const later = () => {
          clearTimeout(timeout);
          func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
      };
    },
  };

  // X-ray Panel Manager
  const XrayManager = {
    styleInjected: false,

    init: function () {
      this.injectStyles();
      this.observeXrayElements();
      Utils.log("X-ray manager initialized");
    },

    injectStyles: function () {
      if (this.styleInjected) return;

      const style = document.createElement("style");
      style.type = "text/css";
      style.id = "prime-video-enhancer-xray";

      const css = `
                ${CONFIG.xray.selectors.join(", ")} {
                    visibility: hidden !important;
                    opacity: 0 !important;
                    pointer-events: none !important;
                }
            `;

      style.textContent = css;
      document.head.appendChild(style);
      this.styleInjected = true;
      Utils.log("X-ray hiding styles injected");
    },

    observeXrayElements: function () {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              CONFIG.xray.selectors.forEach((selector) => {
                if (node.matches && node.matches(selector)) {
                  this.hideElement(node);
                }
                const elements =
                  node.querySelectorAll && node.querySelectorAll(selector);
                if (elements) {
                  elements.forEach((el) => this.hideElement(el));
                }
              });
            }
          });
        });
      });

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

    hideElement: function (element) {
      if (element) {
        element.style.setProperty("visibility", "hidden", "important");
        element.style.setProperty("opacity", "0", "important");
        element.style.setProperty("pointer-events", "none", "important");
        Utils.log("X-ray element hidden");
      }
    },
  };

  // Ad Skipper
  const AdSkipper = {
    init: function () {
      this.hookFetch();
      this.observeAds();
      Utils.log("Ad skipper initialized");
    },

    hookFetch: function () {
      const originalFetch = window.fetch;
      if (typeof originalFetch !== "function") return;

      window.fetch = function (...args) {
        const result = originalFetch.apply(this, args);
        result
          .then(() => {
            AdSkipper.checkForAds();
          })
          .catch(() => {
            // Silently handle fetch errors
          });
        return result;
      };
    },

    observeAds: function () {
      const observer = new MutationObserver(
        Utils.debounce(() => {
          this.checkForAds();
        }, 500)
      );

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

    checkForAds: function () {
      CONFIG.adSkip.selectors.forEach((selector) => {
        const button = document.querySelector(selector);
        if (button && Utils.isElementVisible(button)) {
          this.skipAd(button);
        }
      });
    },

    skipAd: function (button) {
      try {
        button.click();
        Utils.log("Ad skipped successfully");
      } catch (error) {
        Utils.log(`Failed to skip ad: ${error.message}`, "error");
      }
    },
  };

  // Cursor Manager
  const CursorManager = {
    hideTimeout: null,
    isPlayerActive: false,

    init: function () {
      this.observePlayer();
      Utils.log("Cursor manager initialized");
    },

    observePlayer: function () {
      const observer = new MutationObserver(() => {
        this.setupCursorHiding();
      });

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

      this.setupCursorHiding();
    },

    setupCursorHiding: function () {
      CONFIG.cursor.playerSelectors.forEach((selector) => {
        const player = document.querySelector(selector);
        if (player && !player.dataset.cursorSetup) {
          this.attachCursorEvents(player);
          player.dataset.cursorSetup = "true";
        }
      });
    },

    attachCursorEvents: function (player) {
      const videoElement = player.querySelector("video");

      player.addEventListener("mouseenter", () => {
        this.isPlayerActive = true;
        this.scheduleCursorHide(player);
      });

      player.addEventListener("mouseleave", () => {
        this.isPlayerActive = false;
        this.showCursor(player);
      });

      player.addEventListener("mousemove", () => {
        if (this.isPlayerActive) {
          this.showCursor(player);
          this.scheduleCursorHide(player);
        }
      });

      // Show cursor when video is paused
      if (videoElement) {
        videoElement.addEventListener("pause", () => {
          this.showCursor(player);
        });
      }
    },

    scheduleCursorHide: function (player) {
      clearTimeout(this.hideTimeout);
      this.hideTimeout = setTimeout(() => {
        if (this.isPlayerActive) {
          this.hideCursor(player);
        }
      }, CONFIG.cursor.hideDelay);
    },

    hideCursor: function (player) {
      player.style.cursor = "none";
      Utils.log("Cursor hidden");
    },

    showCursor: function (player) {
      clearTimeout(this.hideTimeout);
      player.style.cursor = "default";
    },
  };

  // Quality Manager (New Feature)
  const QualityManager = {
    init: function () {
      this.addKeyboardShortcuts();
      Utils.log("Quality manager initialized");
    },

    addKeyboardShortcuts: function () {
      document.addEventListener("keydown", (event) => {
        // Only trigger on video player pages
        if (!document.querySelector("video")) return;

        switch (event.key.toLowerCase()) {
          case "h":
            if (event.ctrlKey) {
              event.preventDefault();
              this.openQualitySettings();
            }
            break;
          case "f":
            if (event.ctrlKey) {
              event.preventDefault();
              this.toggleFullscreen();
            }
            break;
        }
      });
    },

    openQualitySettings: function () {
      const settingsButton =
        document.querySelector('[data-testid="settings-button"]') ||
        document.querySelector(".atvwebplayersdk-settings-button");
      if (settingsButton) {
        settingsButton.click();
        Utils.log("Quality settings opened");
      }
    },

    toggleFullscreen: function () {
      const fullscreenButton =
        document.querySelector('[data-testid="fullscreen-button"]') ||
        document.querySelector(".atvwebplayersdk-fullscreen-button");
      if (fullscreenButton) {
        fullscreenButton.click();
        Utils.log("Fullscreen toggled");
      }
    },
  };

  // Auto-play Manager (New Feature)
  const AutoPlayManager = {
    init: function () {
      this.handleNextEpisode();
      Utils.log("Auto-play manager initialized");
    },

    handleNextEpisode: function () {
      const observer = new MutationObserver(() => {
        const nextButton =
          document.querySelector('[data-testid="next-episode-button"]') ||
          document.querySelector(".nextupcard-button");

        if (nextButton && Utils.isElementVisible(nextButton)) {
          setTimeout(() => {
            if (nextButton.parentNode) {
              nextButton.click();
              Utils.log("Auto-played next episode");
            }
          }, 2000); // Wait 2 seconds before auto-clicking
        }
      });

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

  // Main initialization
  function initialize() {
    Utils.log("Prime Video Enhancer starting...");

    // Wait for the page to be ready
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", startEnhancements);
    } else {
      startEnhancements();
    }
  }

  function startEnhancements() {
    try {
      XrayManager.init();
      AdSkipper.init();
      CursorManager.init();
      QualityManager.init();
      AutoPlayManager.init();

      Utils.log("All enhancements initialized successfully");
    } catch (error) {
      Utils.log(`Initialization error: ${error.message}`, "error");
    }
  }

  // Start the enhancer
  initialize();
})();