// ==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();
})();