Auto hide next up card for Amazon Prime Video

Auto hide next up card for Amazon Prime Video.

当前为 2024-01-07 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Auto hide next up card for Amazon Prime Video
// @namespace    http://tampermonkey.net/
// @version      2.1.1
// @description  Auto hide next up card for Amazon Prime Video.
// @author       ryo-fujinone
// @match        https://*.amazon.co.jp/*
// @match        https://*.amazon.com/*
// @match        https://*.amazon.ae/*
// @match        https://*.amazon.co.uk/*
// @match        https://*.amazon.it/*
// @match        https://*.amazon.in/*
// @match        https://*.amazon.eg/*
// @match        https://*.amazon.com.au/*
// @match        https://*.amazon.nl/*
// @match        https://*.amazon.ca/*
// @match        https://*.amazon.sa/*
// @match        https://*.amazon.sg/*
// @match        https://*.amazon.se/*
// @match        https://*.amazon.es/*
// @match        https://*.amazon.de/*
// @match        https://*.amazon.com.tr/*
// @match        https://*.amazon.com.br/*
// @match        https://*.amazon.fr/*
// @match        https://*.amazon.com.be/*
// @match        https://*.amazon.pl/*
// @match        https://*.amazon.com.mx/*
// @match        https://*.amazon.cn/*
// @match        https://*.primevideo.com/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    const observeConfig = { childList: true, subtree: true };

    const getDefaultOptions = () => {
        return {
            hideSkipIntroBtn: true,
            hideNextup: true,
            temporarilyDisableOverlay: true,
            hideRating: true,
            scriptVersion: "2.1.1",
        };
    };

    const getScriptInfo = () => {
        let scriptInfo = {
            scriptType: "unknown",
            scriptVersion: getDefaultOptions().scriptVersion,
        };

        // user script
        /**
         * When using optional chaining with window.GM_info in tampermonkey,
         * it sometimes became undefined for some reason, so I implemented it using try-catch.
         */
        try {
            const gmVer = window.GM_info.script.version;
            if (!isNaN(parseFloat(gmVer))) {
                scriptInfo = {
                    scriptType: "user-script",
                    scriptVersion: gmVer,
                };
                return scriptInfo;
            }
        } catch (e) {
            // console.log(e);
        }

        // chrome extension
        try {
            const chromeExtVer = chrome?.runtime?.getManifest()?.version;
            if (!isNaN(parseFloat(chromeExtVer))) {
                scriptInfo = {
                    scriptType: "chrome-extension",
                    scriptVersion: chromeExtVer,
                };
                return scriptInfo;
            }
        } catch (e) {
            // console.log(e);
        }

        // unknown
        return scriptInfo;
    };

    const addStyle = (css) => {
        const style = document.createElement("style");
        style.textContent = css;
        document.head.appendChild(style);
    };

    const saveDefaultOptions = () => {
        const jsonStr = JSON.stringify(getDefaultOptions());
        localStorage.setItem("nextup-ext", jsonStr);
    };

    const getOptions = () => {
        const jsonStr = localStorage.getItem("nextup-ext");
        if (!jsonStr) {
            saveDefaultOptions();
            return getDefaultOptions();
        }
        return JSON.parse(jsonStr);
    };

    const saveOptions = (_newOptions = {}) => {
        const options = getOptions();
        const newOptions = {
            ...options,
            ..._newOptions,
        };
        const jsonStr = JSON.stringify(newOptions);
        localStorage.setItem("nextup-ext", jsonStr);
    };

    const updateOptionVersion = (scriptInfo) => {
        const options = getOptions();
        if (options.scriptVersion === scriptInfo.scriptVersion) {
            return;
        }

        const defaultOptions = getDefaultOptions();
        const mergedOptions = {
            ...defaultOptions,
            ...options,
            scriptVersion: scriptInfo.scriptVersion,
        };
        const mergedOptionsKeys = Object.keys(mergedOptions);
        const newOptions = mergedOptionsKeys.reduce((obj, key) => {
            if (Object.hasOwn(defaultOptions, key)) {
                obj[key] = mergedOptions[key];
            }
            return obj;
        }, {});
        const jsonStr = JSON.stringify(newOptions);
        localStorage.setItem("nextup-ext", jsonStr);
    };

    const createOptionMessages = () => {
        const jaMessages = {
            hideSkipIntroBtn: "イントロスキップボタンを非表示にする",
            hideNextup: "Next upを非表示にする",
            temporarilyDisableOverlay:
                "非表示ボタンの自動クリック時に5秒間オーバーレイ表示を無効にする",
            hideRating: "レーティング(推奨対象年齢)を非表示にする",
            close: "閉じる",
        };
        const enMessages = {
            hideSkipIntroBtn: "Hide skip intro button",
            hideNextup: "Hide next up card",
            temporarilyDisableOverlay:
                "Disable overlay for 5 seconds when auto-clicking hide button",
            hideRating: "Hide rating",
            close: "Close",
        };
        return /ja|ja-JP/.test(window.navigator.language)
            ? jaMessages
            : enMessages;
    };

    const getOptionDialog = () =>
        document.querySelector(".nextup-ext-opt-dialog");

    const playVideo = () => {
        const video = document.querySelector(".webPlayerElement video");
        if (!video) {
            return;
        }
        if (video.paused) {
            video.play();
        }
    };

    const pauseVideo = () => {
        const video = document.querySelector(".webPlayerElement video");
        if (!video) {
            return;
        }
        if (!video.paused) {
            video.pause();
        }
    };

    const funcWhenDialogIsClosed = () => {
        playVideo();
    };

    const closeDialogWhenClickedOutside = (e) => {
        if (e.target.classList.contains("nextup-ext-opt-dialog")) {
            e.target.close();
            funcWhenDialogIsClosed();
            document.removeEventListener(
                "click",
                closeDialogWhenClickedOutside
            );
        }
    };

    const funcWhenOpeningDialog = () => {
        pauseVideo();
        setTimeout(() => {
            document.addEventListener("click", closeDialogWhenClickedOutside);
        }, 1000);
    };

    const createOptionDialog = () => {
        if (getOptionDialog()) {
            return;
        }

        const messages = createOptionMessages();
        const options = getOptions();

        const dialogHtmlStr = `
        <dialog class="nextup-ext-opt-dialog">
        <div class="dialog-inner">
           <label>
              <input type="checkbox" id="hide-skip-intro-btn" name="hide-skip-intro-btn" ${
                  options.hideSkipIntroBtn ? "checked" : ""
              } />
              <p>${messages.hideSkipIntroBtn}</p>
           </label>
           <label>
              <input type="checkbox" id="hide-nextup" name="hide-nextup" ${
                  options.hideNextup ? "checked" : ""
              } />
              <p>${messages.hideNextup}</p>
           </label>
           <label>
              <input type="checkbox" id="temporarily-disable-overlay" name="temporarily-disable-overlay" ${
                  options.temporarilyDisableOverlay ? "checked" : ""
              } />
              <p>${messages.temporarilyDisableOverlay}</p>
           </label>
           <label>
              <input type="checkbox" id="hide-rationg" name="hide-rationg" ${
                  options.hideRating ? "checked" : ""
              } />
              <p>${messages.hideRating}</p>
           </label>
           <div>
              <button id="nextup-ext-opt-dialog-close">${
                  messages.close
              }</button>
           </div>
        </div>
        </dialog>
        `;
        document.body.insertAdjacentHTML("beforeend", dialogHtmlStr);

        const css = [
            ".nextup-ext-opt-dialog {width: 370px; padding: 0;}",
            ".dialog-inner {padding: 14px;}",
            ".nextup-ext-opt-dialog label {display: inline;}",
            ".nextup-ext-opt-dialog label input {float: left;}",
            ".nextup-ext-opt-dialog label p {float: left; margin-bottom: 5px; width: calc(100% - 24px);}",
            ".nextup-ext-opt-dialog label:last-of-type p {margin-bottom: 12px;}",
            ".nextup-ext-opt-dialog div:has(#nextup-ext-opt-dialog-close):not(.dialog-inner) {text-align: center;}",
            "#nextup-ext-opt-dialog-close {border-color: black; border: solid 1px; background-color: #EEE}",
            "#nextup-ext-opt-dialog-close:hover {background-color: #DDD}",
        ];
        addStyle(css.join(""));

        const optDialog = getOptionDialog();
        optDialog.addEventListener(
            "click",
            (e) => {
                const idName = e.target.id;
                if (idName === "") {
                    return;
                }

                switch (idName) {
                    case "hide-skip-intro-btn":
                        saveOptions({ hideSkipIntroBtn: e.target.checked });
                        break;
                    case "hide-nextup":
                        saveOptions({ hideNextup: e.target.checked });
                        break;
                    case "temporarily-disable-overlay":
                        saveOptions({
                            temporarilyDisableOverlay: e.target.checked,
                        });
                        break;
                    case "hide-rationg":
                        saveOptions({ hideRating: e.target.checked });
                        break;
                    case "nextup-ext-opt-dialog-close":
                        optDialog.close();
                        funcWhenDialogIsClosed();
                        break;
                    default:
                        break;
                }
            },
            true
        );
    };

    const openOptionDialog = () => {
        createOptionDialog();
        const optDialog = getOptionDialog();
        funcWhenOpeningDialog();
        optDialog.showModal();
    };

    const openOptionDialogWithKeyboard = () => {
        new MutationObserver((_, _observer) => {
            const webPlayerContainer = document.querySelector(
                ".webPlayerContainer"
            );
            if (!webPlayerContainer) {
                return;
            }

            _observer.disconnect();

            createOptionDialog();
            document.body.addEventListener("keydown", (e) => {
                if (e.altKey && e.code === "KeyP") {
                    const optDialog = getOptionDialog();
                    if (optDialog.hasAttribute("open")) {
                        optDialog.close();
                        funcWhenDialogIsClosed();
                    } else {
                        funcWhenOpeningDialog();
                        optDialog.showModal();
                    }
                }
            });
        }).observe(document, observeConfig);
    };

    const createOptionBtn = () => {
        new MutationObserver((_, _observer) => {
            if (document.querySelector(".nextup-ext-opt-btn-container")) {
                return;
            }

            const btnsContainer = document.querySelector(
                ".atvwebplayersdk-hideabletopbuttons-container"
            );
            if (!btnsContainer) {
                return;
            }

            _observer.disconnect();

            const optContainer = btnsContainer.querySelector(
                ".atvwebplayersdk-options-wrapper span div:has(.atvwebplayersdk-optionsmenu-button)"
            );
            const clone = optContainer.cloneNode(true);
            clone.classList.add("nextup-ext-opt-btn-container");
            btnsContainer
                .querySelector("div:has(.atvwebplayersdk-options-wrapper)")
                .appendChild(clone);

            const cloneOptBtn = clone.querySelector(
                ".atvwebplayersdk-optionsmenu-button"
            );
            cloneOptBtn.classList.remove("atvwebplayersdk-optionsmenu-button");
            cloneOptBtn.classList.add("nextup-ext-opt-btn");

            const cloneOptBtnImg = cloneOptBtn.querySelector("img");
            cloneOptBtnImg.style.filter =
                "sepia(100%) saturate(2000%) hue-rotate(120deg)";

            const cloneTooltip = clone.querySelector("button + div div");
            cloneTooltip.textContent = "Option - Auto hide next up card";

            cloneOptBtn.addEventListener("click", (_) => {
                openOptionDialog();
            });
        }).observe(document, observeConfig);
    };

    const hideSkipIntroBtn = (options) => {
        if (!options.hideSkipIntroBtn) {
            return;
        }
        const css = [
            ".atvwebplayersdk-skipelement-button {display: none !important;}",
        ];
        addStyle(css.join(""));
    };

    const temporarilyDisableOverlay = (options, delay = "5000") => {
        if (!options.temporarilyDisableOverlay) {
            return;
        }
        const overlaysWrapper = document.querySelector(
            ".atvwebplayersdk-overlays-wrapper"
        );
        if (!overlaysWrapper) {
            return;
        }
        overlaysWrapper.style.display = "none";
        setTimeout(() => {
            overlaysWrapper.style.display = "";
        }, delay);
    };

    const autoHideNextup = (options) => {
        if (!options.hideNextup) {
            return;
        }
        new MutationObserver((_, outerObserver) => {
            const wrapper = document.querySelector(
                ".atvwebplayersdk-nextupcard-wrapper"
            );
            if (!wrapper) {
                return;
            }

            outerObserver.disconnect();

            new MutationObserver((_) => {
                wrapper.style.display = "none";
                const hideButton = wrapper.querySelector(
                    ".atvwebplayersdk-nextupcardhide-button"
                );
                if (hideButton) {
                    // Temporarily disable the overlay because it will be displayed by executing click().
                    temporarilyDisableOverlay(options, "5000");
                    hideButton.click();
                }
            }).observe(wrapper, observeConfig);
        }).observe(document, observeConfig);
    };

    const hideRatingText = (options) => {
        if (!options.hideRating) {
            return;
        }
        const css = [
            ".atvwebplayersdk-rating-text {display: none !important;}",
            ".atvwebplayersdk-ratingdescriptor-text {display: none !important;}",
        ];
        addStyle(css.join(""));

        // Hide the overlays that appear in the top center and top left when viewing ratings.
        new MutationObserver((_, _observer) => {
            const ratingDesc = document.querySelector(
                ".atvwebplayersdk-ratingdescriptor-text"
            );
            if (!ratingDesc) {
                return;
            }

            _observer.disconnect();

            const parent = ratingDesc.parentNode.parentNode;
            if (parent.childNodes.length !== 3) {
                return;
            }
            if (
                !Array.from(parent.childNodes).every(
                    (child) => child.tagName === "DIV"
                )
            ) {
                return;
            }

            for (const child of parent.childNodes) {
                if (
                    child.querySelector(
                        ".atvwebplayersdk-ratingdescriptor-text"
                    )
                ) {
                    continue;
                }

                if (child.childNodes.length === 0 && child.textContent === "") {
                    child.style.display = "none";
                    continue;
                }

                if (
                    child.childNodes.length === 1 &&
                    child.childNodes[0].childNodes.length === 0 &&
                    child.childNodes[0].textContent === ""
                ) {
                    child.style.display = "none";
                    continue;
                }
            }
        }).observe(document, observeConfig);
    };

    const main = () => {
        if (!localStorage.getItem("nextup-ext")) {
            saveDefaultOptions();
        }

        const scriptInfo = getScriptInfo();
        updateOptionVersion(scriptInfo);

        createOptionBtn();
        openOptionDialogWithKeyboard();

        const options = getOptions();
        hideSkipIntroBtn(options);
        autoHideNextup(options);
        hideRatingText(options);
    };

    main();
})();