IMDB Streaming Service

Allows people to watch any movie or TV show directly from the IMDb website

// ==UserScript==
// @name         IMDB Streaming Service
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Allows people to watch any movie or TV show directly from the IMDb website
// @author       Tristan Reeves
// @match        https://www.imdb.com/title/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=imdb.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let currentKeyIndex = 0;

    function getColor() {
        return hexCode[currentKeyIndex].substring(1);
    }

    function rotateKey() {
        currentKeyIndex = (currentKeyIndex + 1) % hexCode.length;
    }

    // Inject Font Awesome CSS
    function injectFontAwesome() {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
        document.head.appendChild(link);
    }

    function getIframe(url) {
        const iframe = document.createElement('iframe');
        iframe.src = url;
        iframe.width = '100%';
        iframe.height = '600';
        iframe.frameBorder = '0';
        iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
        iframe.allowFullscreen = true;
        return iframe;
    }
    const hexCode = ['#3a00944e', '#c9e1059a', '#594b3352', '#3fb62173', '#6afacd8a'];

    function createControlButtons() {
        const container = document.createElement('div');
        container.style.marginTop = '10px';
        container.style.position = 'relative';

        // Previous Episode Button
        const prevButton = document.createElement('button');
        prevButton.innerHTML = '<i class="fas fa-backward"></i>';
        prevButton.title = 'Previous Episode';
        prevButton.style.fontSize = '20px';
        prevButton.style.border = 'none';
        prevButton.style.backgroundColor = 'transparent';
        prevButton.style.cursor = 'pointer';
        prevButton.style.marginRight = '10px';

        // Next Episode Button
        const nextButton = document.createElement('button');
        nextButton.innerHTML = '<i class="fas fa-forward"></i>';
        nextButton.title = 'Next Episode';
        nextButton.style.fontSize = '20px';
        nextButton.style.border = 'none';
        nextButton.style.backgroundColor = 'transparent';
        nextButton.style.cursor = 'pointer';

        container.appendChild(prevButton);
        container.appendChild(nextButton);

        return { container, prevButton, nextButton };
    }

    function createInputFields() {
        const container = document.createElement('div');
        container.style.marginTop = '10px';
        container.style.position = 'relative';

        const seasonLabel = document.createElement('label');
        seasonLabel.textContent = 'Season: ';
        const seasonInput = document.createElement('input');
        seasonInput.type = 'number';
        seasonInput.min = 1; // Keep minimum as 1 but allow any number initially
        seasonLabel.appendChild(seasonInput);

        const episodeLabel = document.createElement('label');
        episodeLabel.textContent = ' Episode: ';
        const episodeInput = document.createElement('input');
        episodeInput.type = 'number';
        episodeInput.min = 1; // Keep minimum as 1 but allow any number initially
        episodeLabel.appendChild(episodeInput);

        const messageContainer = document.createElement('span');
        messageContainer.style.marginLeft = '10px';
        messageContainer.style.transition = 'opacity 2s';
        messageContainer.style.display = 'inline-block';
        messageContainer.style.position = 'absolute';

        container.appendChild(seasonLabel);
        container.appendChild(episodeLabel);
        container.appendChild(messageContainer);

        return { container, seasonInput, episodeInput, messageContainer };
    }

    function showMessage(messageContainer, message) {
        messageContainer.textContent = message;
        messageContainer.style.opacity = '1';
        setTimeout(() => {
            messageContainer.style.opacity = '0';
        }, 3000);
    }

    async function fetchShowDetails(imdbCode) {
        let data;
        try {
            const response = await fetch(`https://www.omdbapi.com/?i=${imdbCode}&apikey=${getColor()}`);
            data = await response.json();
        } catch (error) {
            rotateKey();
            return fetchShowDetails(imdbCode);
        }
        return data;
    }

    async function fetchMaxEpisodesPerSeason(imdbCode, totalSeasons) {
        const maxEpisodesPerSeason = [];
        for (let season = 1; season <= totalSeasons; season++) {
            try {
                const response = await fetch(`https://www.omdbapi.com/?i=${imdbCode}&Season=${season}&apikey=${getColor()}`);
                const seasonData = await response.json();
                maxEpisodesPerSeason[season] = seasonData.Episodes.length;
            } catch (error) {
                rotateKey();
                season--;
            }
        }
        return maxEpisodesPerSeason;
    }

    function saveLastWatched(imdbCode, season, episode) {
        localStorage.setItem(`lastWatched_${imdbCode}`, JSON.stringify({ season, episode }));
    }

    function getLastWatched(imdbCode) {
        const lastWatched = localStorage.getItem(`lastWatched_${imdbCode}`);
        return lastWatched ? JSON.parse(lastWatched) : null;
    }

    // Inject Font Awesome CSS
    injectFontAwesome();

    const currentUrl = window.location.href;
    const idMatch = currentUrl.match(/title\/(tt\d+)\//);
    const imdbCode = idMatch ? idMatch[1] : null;

    if (!imdbCode) { return }

    const videoTypeMeta = document.querySelector('meta[property="og:type"]');
    const videoType = videoTypeMeta ? videoTypeMeta.getAttribute('content') : null;

    if (!videoType) { return }

    let embedUrl = '';

    if (videoType === 'video.movie') {
        embedUrl = `https://multiembed.mov/directstream.php?video_id=${imdbCode}`;
    } else if (videoType === 'video.tv_show') {
        embedUrl = `https://multiembed.mov/directstream.php?video_id=${imdbCode}&s=1&e=1`;
    }

    if (embedUrl) {
        const result = document.evaluate(
            '//*[@id="__next"]/main/div/section[1]/section/div[3]/section/section/div[2]',
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );

        const topDiv = result.singleNodeValue;
        if (topDiv) {
            const iframe = getIframe(embedUrl);
            topDiv.insertAdjacentElement("afterend", iframe);

            if (videoType === 'video.tv_show') {
                // Create control buttons and input fields
                const { container: buttonsContainer, prevButton, nextButton } = createControlButtons();
                iframe.insertAdjacentElement("afterend", buttonsContainer);

                const { container, seasonInput, episodeInput, messageContainer } = createInputFields();
                buttonsContainer.insertAdjacentElement("afterend", container);

                // Load last watched season and episode if available
                const lastWatched = getLastWatched(imdbCode);
                if (lastWatched) {
                    seasonInput.value = lastWatched.season;
                    episodeInput.value = lastWatched.episode;
                    iframe.src = `https://multiembed.mov/directstream.php?video_id=${imdbCode}&s=${lastWatched.season}&e=${lastWatched.episode}`;
                } else {
                    // Set default values if no last watched data is found
                    seasonInput.value = 1;
                    episodeInput.value = 1;
                }

                // Update iframe src and save state when inputs change (immediate effect without validation)
                function updateIframeSrcAndSave() {
                    const season = parseInt(seasonInput.value, 10);
                    const episode = parseInt(episodeInput.value, 10);
                    iframe.src = `https://multiembed.mov/directstream.php?video_id=${imdbCode}&s=${season}&e=${episode}`;
                    saveLastWatched(imdbCode, season, episode);
                }

                // Fetch details and validate inputs
                fetchShowDetails(imdbCode).then(data => {
                    if (data.Type === 'series') {
                        const totalSeasons = parseInt(data.totalSeasons, 10);
                        fetchMaxEpisodesPerSeason(imdbCode, totalSeasons).then(maxEpisodesPerSeason => {
                            // Validate season input
                            seasonInput.addEventListener('input', () => {
                                let season = parseInt(seasonInput.value, 10);
                                if (season < 1) {
                                    season = 1;
                                    showMessage(messageContainer, 'Number of seasons must be at least 1.');
                                } else if (season > totalSeasons) {
                                    season = totalSeasons;
                                    showMessage(messageContainer, `Season cannot exceed ${totalSeasons}.`);
                                }
                                seasonInput.value = season;
                                updateIframeSrcAndSave();
                            });

                            // Validate episode input
                            episodeInput.addEventListener('input', () => {
                                let season = parseInt(seasonInput.value, 10);
                                let episode = parseInt(episodeInput.value, 10);
                                if (episode < 1) {
                                    episode = 1;
                                    showMessage(messageContainer, 'Number of episodes must be at least 1.');
                                } else if (episode > maxEpisodesPerSeason[season]) {
                                    episode = maxEpisodesPerSeason[season];
                                    showMessage(messageContainer, `Season ${season} contains only ${maxEpisodesPerSeason[season]} episodes.`);
                                }
                                episodeInput.value = episode;
                                updateIframeSrcAndSave();
                            });

                            // Button event listeners
                            prevButton.addEventListener('click', () => {
                                let season = parseInt(seasonInput.value, 10);
                                let episode = parseInt(episodeInput.value, 10);
                                if (episode > 1) {
                                    episode--;
                                } else if (season > 1) {
                                    season--;
                                    episode = maxEpisodesPerSeason[season]; // Reset to the first episode of the previous season
                                } else {
                            showMessage(messageContainer, 'This is the first episode of the first season.');
                            return;
                        }
                                if (season < 1) {
                                    season = 1;
                                }
                                if (episode < 1) {
                                    episode = 1;
                                }
                                seasonInput.value = season;
                                episodeInput.value = episode;
                                updateIframeSrcAndSave();
                            });

                            nextButton.addEventListener('click', () => {
                                let season = parseInt(seasonInput.value, 10);
                                let episode = parseInt(episodeInput.value, 10);
                                if (episode < maxEpisodesPerSeason[season]) {
                                    episode++;
                                } else if (season < totalSeasons) {
                                    season++;
                                    episode = 1; // Reset to the first episode of the next season
                                } else {
                            showMessage(messageContainer, 'This is the last episode of the last season.');
                            return;
                        }
                                if (season > totalSeasons) {
                                    season = totalSeasons;
                                }
                                if (episode > maxEpisodesPerSeason[season]) {
                                    episode = maxEpisodesPerSeason[season];
                                }
                                seasonInput.value = season;
                                episodeInput.value = episode;
                                updateIframeSrcAndSave();
                            });
                        });
                    }
                });
            }
        }
    }
})();