Prime Video Enchantments

Enhancements for Prime Video player: auto Fullscreen, skip intro, skip credits, and more.

// ==UserScript==
// @name         Prime Video Enchantments
// @namespace    http://tampermonkey.net/
// @version      0.2.1
// @description  Enhancements for Prime Video player: auto Fullscreen, skip intro, skip credits, and more.
// @author       JJJ
// @match        https://www.amazon.com/gp/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=amazon.com
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const config = {
        enableSkipRecap: GM_getValue('enableSkipRecap', true),
        enableSkipIntro: GM_getValue('enableSkipIntro', true),
        //enableSkipOutro: GM_getValue('enableSkipOutro', true),
        enableAutoFullscreen: GM_getValue('enableAutoFullscreen', true),
    };

    const selectors = {
        skipRecapButton: 'div.f16im4ho > div > button.fqye4e3.f1ly7q5u.fk9c3ap.fz9ydgy.f1xrlb00.f1hy0e6n.fgbpje3.f1uteees.f1h2a8xb.atvwebplayersdk-skipelement-button.fjgzbz9.fiqc9rt.fg426ew.f1ekwadg',
        skipIntroButton: 'div.f16im4ho > div > button.fqye4e3.f1ly7q5u.fk9c3ap.fz9ydgy.f1xrlb00.f1hy0e6n.fgbpje3.f1uteees.f1h2a8xb.atvwebplayersdk-skipelement-button.fjgzbz9.fiqc9rt.fg426ew.f1ekwadg',
        fullscreenVideo: 'video',
    };

    const buttonState = {
        skipRecap: false,
        skipIntro: false
    };

    function showSettingsDialog() {
        const dialogHTML = `
        <div id="primeVideoEnchantmentsDialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: black; border: 1px solid #ccc; padding: 20px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); z-index: 9999; color: white; width: 300px;">
          <h3 style="margin-top: 0; font-size: 1.2em;">Prime Video Enchantments</h3>
          <br>
          <label style="display: block; margin-bottom: 10px; color: white; font-size: 1em;" title="Automatically skip episode recaps">
            <input type="checkbox" id="enableSkipRecap" ${config.enableSkipRecap ? 'checked' : ''}>
            <span style="color: white;">Skip Recap</span>
          </label>
          <br>
          <label style="display: block; margin-bottom: 10px; color: white; font-size: 1em;" title="Automatically skip the intro of episodes">
            <input type="checkbox" id="enableSkipIntro" ${config.enableSkipIntro ? 'checked' : ''}>
            <span style="color: white;">Skip Intro</span>
          </label>
          <br>
          <label style="display: block; margin-bottom: 10px; color: white; font-size: 1em;" title="Automatically enter fullscreen mode">
            <input type="checkbox" id="enableAutoFullscreen" ${config.enableAutoFullscreen ? 'checked' : ''}>
            <span style="color: white;">Auto Fullscreen</span>
          </label>
          <br>
          <button id="saveSettingsButton" style="padding: 8px 12px; background-color: #0078d4; color: white; border: none; cursor: pointer; font-size: 1em;">Save</button>
          <button id="cancelSettingsButton" style="padding: 8px 12px; background-color: #d41a1a; color: white; border: none; cursor: pointer; margin-left: 10px; font-size: 1em;">Cancel</button>
        </div>
      `;

        const dialogWrapper = document.createElement('div');
        dialogWrapper.innerHTML = dialogHTML;
        document.body.appendChild(dialogWrapper);

        document.getElementById('saveSettingsButton').addEventListener('click', saveAndCloseSettings);
        document.getElementById('cancelSettingsButton').addEventListener('click', closeSettingsDialog);
    }

    function closeSettingsDialog() {
        const dialog = document.getElementById('primeVideoEnchantmentsDialog');
        if (dialog) {
            dialog.remove();
            if (document.fullscreenElement) {
                document.exitFullscreen();
            }
        }
    }

    function saveAndCloseSettings() {
        ['enableSkipRecap', 'enableSkipIntro', 'enableAutoFullscreen'].forEach(setting => {
            config[setting] = document.getElementById(setting).checked;
            GM_setValue(setting, config[setting]);
        });
        closeSettingsDialog();
    }

    function clickButton(selector, buttonType) {
        const button = document.querySelector(selector);
        if (button && !buttonState[buttonType]) {
            button.click();
            buttonState[buttonType] = true;
            console.log(`${buttonType} button clicked`);
        }
    }

    function toggleFullscreen() {
        const videoElement = document.querySelector(selectors.fullscreenVideo);
        if (videoElement) {
            if (!document.fullscreenElement) {
                document.documentElement.requestFullscreen();
            } else {
                document.exitFullscreen();
            }
        }
    }

    function handleSkipActions() {
        try {
            if (config.enableSkipRecap) {
                clickButton(selectors.skipRecapButton, 'skipRecap');
            }

            if (config.enableSkipIntro) {
                clickButton(selectors.skipIntroButton, 'skipIntro');
            }

            if (config.enableAutoFullscreen && !document.fullscreenElement) {
                toggleFullscreen();
            }

            // Reset button states if buttons are not found
            ['skipRecap', 'skipIntro'].forEach(buttonType => {
                if (!document.querySelector(selectors[`${buttonType}Button`])) {
                    buttonState[buttonType] = false;
                }
            });

        } catch (error) {
            console.error('An error occurred:', error);
        }
    }

    const observer = new MutationObserver(handleSkipActions);
    observer.observe(document.body, { childList: true, subtree: true });

    GM_registerMenuCommand('Prime Video Enchantments', showSettingsDialog);

    let isSettingsDialogOpen = false;

    function toggleSettingsDialog() {
        isSettingsDialogOpen = !isSettingsDialogOpen;
        if (isSettingsDialogOpen) {
            showSettingsDialog();
        } else {
            closeSettingsDialog();
        }
    }

    document.addEventListener('keyup', (event) => {
        if (event.key === 'F2') {
            toggleSettingsDialog();
        } else if (event.key === 'Escape') {
            document.exitFullscreen();
        }
    });

    // Auto skip ads
    const timePattern = /(\d?\d:){0,2}\d?\d/;
    const intervalDuration = 200;
    let adBypassed = false;

    setInterval(() => {
        const playerContainer = document.querySelector(".rendererContainer");
        const videoElement = playerContainer ? playerContainer.querySelector('video') : null;
        const skipIndicator = document.querySelector(".atvwebplayersdk-adtimeindicator-text");
        const remainingAdTimeElement = document.querySelector(".atvwebplayersdk-ad-timer-remaining-time");

        if (videoElement && videoElement.currentTime && (remainingAdTimeElement || skipIndicator)) {
            if (!adBypassed) {
                let adDurationElement = remainingAdTimeElement && timePattern.test(remainingAdTimeElement.textContent) ? remainingAdTimeElement :
                    skipIndicator && timePattern.test(skipIndicator.textContent) ? skipIndicator : null;

                if (adDurationElement) {
                    const adDurationParts = adDurationElement.textContent.match(timePattern)[0].split(':');
                    const adDurationSeconds = adDurationParts.reduce((acc, part, index) =>
                        acc + parseInt(part, 10) * Math.pow(60, adDurationParts.length - 1 - index), 0);

                    videoElement.currentTime += adDurationSeconds;
                    adBypassed = true;
                    console.log('=====================\nAD SKIPPED ON PRIME VIDEO\n=====================');
                }
            }
        } else {
            adBypassed = false;
        }
    }, intervalDuration);

})();