s.to autoplay

Autoplay für SerienStream.to

// ==UserScript==
// @name         s.to autoplay
// @namespace    https://github.com/zaheer-exe
// @version      9.3
// @description  Autoplay für SerienStream.to
// @author       zaheer-exe
// @match        https://s.to/*
// @match        https://serienstream.to/*
// @match        https://aniworld.to/*
// @match        https://voe.sx/*
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        none
// @run-at       document-start
// @icon         https://www.google.com/s2/favicons?sz=64&domain=s.to
// @license      Apache License
// ==/UserScript==

function adblock() {
    function hasKey(url) {
        // Check for 'key' parameter
        const keyPattern = /[?&]key=[^&]+/i;
        return keyPattern.test(url);
    }

    // Override window.open
    const originalWindowOpen = window.open;
    window.open = function(url, ...args) {
        if (typeof url === 'string' && hasKey(url)) {
            console.log('Blocked popup with key:', url, window.location.href);
            return null;
        }
        return originalWindowOpen.call(this, url, ...args);
    };

    // Override createElement to prevent script injection
    const originalCreateElement = document.createElement;
    document.createElement = function(tagName) {
        const element = originalCreateElement.call(document, tagName);
        if (tagName.toLowerCase() === 'script') {
            const originalSetAttribute = element.setAttribute;
            element.setAttribute = function(name, value) {
                if (name === 'src' && hasKey(value)) {
                    console.log('Blocked script src with key:', value);
                    return;
                }
                return originalSetAttribute.call(this, name, value);
            };
        }
        return element;
    };

    // Intercept and block certain XMLHttpRequests
    const originalXHROpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, ...args) {
        if (hasKey(url)) {
            console.log('Blocked XHR with key:', url);
            throw new Error('Blocked XHR');
        }
        return originalXHROpen.call(this, method, url, ...args);
    };

    // Block fetch requests with keys
    const originalFetch = window.fetch;
    window.fetch = function(input, init) {
        if (typeof input === 'string' && hasKey(input)) {
            console.log('Blocked fetch with key:', input);
            return Promise.reject(new Error('Blocked fetch'));
        }
        return originalFetch.call(this, input, init);
    };

    // Remove suspicious hidden inputs periodically
    const removeHiddenInputs = () => {
        const hiddenInputs = document.querySelectorAll('input[type="hidden"]');
        hiddenInputs.forEach(input => {
            if (input.name && input.name.length > 30 && /^[a-f0-9]{32}$/.test(input.name)) {
                console.log('Removed suspicious hidden input:', input.name);
                input.remove();
            }
        });
    };

    // Prevent timer-based function execution
    const originalSetTimeout = window.setTimeout;
    const originalSetInterval = window.setInterval;

    const wrapTimerFunction = (originalFunc) => {
        return function(callback, delay, ...args) {
            if (typeof callback === 'function') {
                const callbackString = callback.toString();
                if (callbackString.includes('append') && callbackString.includes('input')) {
                    console.log('Blocked suspicious timer function');
                    return;
                }
                // Wrap the callback to remove hidden inputs after execution
                const wrappedCallback = function() {
                    callback.apply(this, arguments);
                    removeHiddenInputs();
                };
                return originalFunc.call(this, wrappedCallback, delay, ...args);
            }
            return originalFunc.call(this, callback, delay, ...args);
        };
    };

    window.setTimeout = wrapTimerFunction(originalSetTimeout);
    window.setInterval = wrapTimerFunction(originalSetInterval);

    // Initial cleanup
    removeHiddenInputs();

    // Periodically check and remove suspicious elements
    setInterval(removeHiddenInputs, 1000);
};

////////// LISTER ////////////////////////////////////////////////////////////////
const sToHosts = ['s.to', 'aniworld.to', 'serienstream.to'];
if (sToHosts.includes(new URL(window.location.href).hostname)) {
    let isAutoPlayed = false;

    function nextEpisode() {
        const currentLang = document.querySelector("img.selectedLanguage").dataset.langKey;
        console.log("S: current lang ", currentLang);
        const episodeMenuCurrentElem = document.querySelector('li a.active[href*="episode"]');
        const nextEpisodeUrl = episodeMenuCurrentElem.parentElement.nextElementSibling.querySelector('a');

        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET", nextEpisodeUrl, false);
        xmlHttp.send(null);

        let temp = document.createElement('div');
        temp.innerHTML = xmlHttp.responseText;
        let url = temp.querySelector('li[data-lang-key="' + currentLang + '"] .watchEpisode .icon.VOE').parentElement.href;
        let title = temp.querySelector(".hosterSiteTitle").innerHTML;

        let streamIframe = document.querySelector(".inSiteWebStream iframe");
        streamIframe.src = url;

        document.querySelector(".hosterSiteTitle").innerHTML = title;
        document.querySelector(".breadCrumbMenu").innerHTML = temp.querySelector(".breadCrumbMenu").innerHTML;
        episodeMenuCurrentElem.classList.remove('active');
        nextEpisodeUrl.classList.add('active');
        window.history.pushState("", "", nextEpisodeUrl.href);

        if (!isAutoPlayed) {
            disableElements();
            isAutoPlayed = true;
        }
    }



    function disableElements() {
        const style = document.createElement('style');
        style.textContent = `
        .changeLanguage, li[data-link-target] {
            opacity: 0.3;
            cursor: none;
        }

        .changeLanguageBox, li[data-link-target] > .generateInlinePlayer {
            pointer-events: none;
        }

        .tooltip {
            position: absolute;
            background-color: #333; /* Dark background for contrast */
            color: #fff; /* White text for contrast */
            padding: 10px; /* Padding for better appearance */
            border-radius: 5px; /* Rounded corners */
            font-size: 14px; /* Font size for readability */
            line-height: 1.4; /* Line height for better text readability */
            white-space: nowrap; /* Prevent text wrapping */
            display: none; /* Hidden by default */
            z-index: 9999; /* Ensure it appears above other content */
            pointer-events: none; /* Ensure it doesn't interfere with interactions */
            transform: translate(-50%, -50%); /* Center tooltip on the cursor */
        }
    `;
        document.head.appendChild(style);

        const tooltip = document.createElement('div');
        tooltip.className = 'tooltip';
        tooltip.textContent = 'Autoplay enabled. Refresh/Reload Page to change settings.';
        document.body.appendChild(tooltip);

        function updateTooltipPosition(event) {
            tooltip.style.left = `${event.pageX}px`;
            tooltip.style.top = `${event.pageY}px`;
        }

        function showTooltip(event) {
            tooltip.style.display = 'block';
            updateTooltipPosition(event);
        }

        function hideTooltip() {
            tooltip.style.display = 'none';
        }

        const elements = document.querySelectorAll('.changeLanguage, li[data-link-target]');
        elements.forEach(element => {
            element.addEventListener('mouseenter', showTooltip);
            element.addEventListener('mousemove', updateTooltipPosition);
            element.addEventListener('mouseleave', hideTooltip);
        });
    }

    function autoPlaySettings() {
        const style = document.createElement('style');
        style.textContent = `
        .autoplay-settings-container {
            border: 1px solid #3a3a3a;
            border-radius: 8px;
            margin-top: 15px;
            padding: 15px;
            background-color: #18181b;
            font-family: Arial, sans-serif;
            color: #ffffff;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }
        .autoplay-settings-container h2 {
            margin: 0 0 15px 0;
            color: #8257e6;
            font-size: 16px;
            font-weight: bold;
        }
        .settings-row {
            display: flex;
            justify-content: space-between;
        }
        .settings-column {
            width: 48%;
        }
        .setting-group {
            margin-bottom: 10px;
        }
        .setting-group label {
            display: block;
            margin-bottom: 5px;
            font-size: 14px;
            color: #a0a0a0;
        }
        .setting-group input {
            width: 100%;
            padding: 8px;
            border: 1px solid #3a3a3a;
            border-radius: 4px;
            font-size: 14px;
            background-color: #27272a;
            color: #ffffff;
            transition: border-color 0.3s, box-shadow 0.3s;
        }
        .setting-group input:focus {
            outline: none;
            border-color: #8257e6;
            box-shadow: 0 0 5px rgba(130, 87, 230, 0.5);
        }
        .setting-group input::placeholder {
            color: #6b7280;
        }
    `;
        document.head.appendChild(style);

        const container = document.createElement("div");
        container.classList.add("autoplay-settings-container");
        container.innerHTML = `
        <h2>AutoPlay Settings</h2>
        <div class="settings-row">
            <div class="settings-column">
                <div class="setting-group">
                    <label for="skip-intro">Skip intro (seconds)</label>
                    <input id="skip-intro" type="number" min="0" placeholder="e.g., 30">
                </div>
                <div class="setting-group">
                    <label for="skip-outro">Skip outro (seconds)</label>
                    <input id="skip-outro" type="number" min="0" placeholder="e.g., 60">
                </div>
            </div>
            <!-- <div class="settings-column">
                <div class="setting-group">
                    <label for="still-here">"Are you still watching?" popup (minutes of inactivity)</label>
                    <input id="still-here" type="number" min="0" placeholder="e.g., 120">
                </div>
            </div> -->
        </div>
    `;
        document.querySelector(".hosterSiteDirectNav").appendChild(container);

        let key = new URL(window.location.href).pathname.split("/");
        key = key[1] + "/" + key[2] + "/" + key[3];
        const seasonData = JSON.parse(localStorage.getItem(key) || "{\"skip-intro\": 0, \"skip-outro\": 0, \"still-here\": 0}");

        container.querySelector("#skip-intro").value = seasonData["skip-intro"];
        container.querySelector("#skip-outro").value = seasonData["skip-outro"];

        // container.querySelector("#still-here").value = seasonData["still-here"];

        ['skip-intro', 'skip-outro'].forEach(id => {
            document.getElementById(id).addEventListener("change", (e) => {
                const value = e.target.value;
                console.log(`${e.target.id} changed to: ${value}`);
                seasonData[e.target.id] = value;
                localStorage.setItem(key, JSON.stringify(seasonData));
            });
        });
    }



    function showTooltip(e) {
        const tooltip = document.getElementById('autoplay-tooltip');
        tooltip.style.display = 'block';
        tooltip.style.left = e.pageX + 10 + 'px';
        tooltip.style.top = e.pageY + 10 + 'px';
    }

    function hideTooltip() {
        const tooltip = document.getElementById('autoplay-tooltip');
        tooltip.style.display = 'none';
    }

    function handleConfig() {
        let key = new URL(window.location.href).pathname.split("/");
        key = key[1] + "/" + key[2] + "/" + key[3];
        const seasonData = JSON.parse(localStorage.getItem(key));
        if (seasonData) {
            if (seasonData["skip-intro"] > 0) {
                console.log("S: sending post data for skip-intro");
                document.querySelector('.inSiteWebStream iframe').contentWindow.postMessage("autoplay$skip-intro$" + seasonData["skip-intro"], "*");
            }
            if (seasonData["skip-outro"] > 0) {
                console.log("S: sending post data for skip-outro");
                document.querySelector('.inSiteWebStream iframe').contentWindow.postMessage("autoplay$skip-outro$" + seasonData["skip-outro"], "*");
            }
        }
    }

    window.addEventListener("message", (event) => {
        if(typeof event.data === "string" && event.data.startsWith("autoplay")) {
            const parsed = event.data.split("$");
            console.log("event", parsed);
            switch (parsed[1]) {
                case "url": {
                    console.log("S: url");
                    let streamIframe = document.querySelector(".inSiteWebStream iframe");
                    if (streamIframe.src.includes("/redirect")) {
                        document.querySelector(".inSiteWebStream iframe").src = parsed[2];

                        document.querySelector(".inSiteWebStream iframe").addEventListener("load", () => {
                            handleConfig();
                        });
                    }
                    break;
                }
                case "end": {
                    console.log("S: ended");
                    nextEpisode();
                    break;
                }
                case "fullscreen": { // fullscreen workaround
                    if (document.fullscreenElement === document.querySelector(".inSiteWebStream iframe")) {
                        document.exitFullscreen()
                    } else {
                        document.querySelector(".inSiteWebStream iframe").requestFullscreen();
                    }
                }
            }
        }
    }, false);

    window.addEventListener("load", () => {
        autoPlaySettings();
        document.querySelector(".inSiteWebStream iframe").allow = document.querySelector(".inSiteWebStream iframe").allow + "; autoplay"
    });
}

////////// HOSTER ////////////////////////////////////////////////////////////////
let checkIfVoe = document.querySelector("head > meta[name='og:sitename']");
if (checkIfVoe && checkIfVoe.content == "VOE: Video Hosting Platform & Online Cloud Storage") {
    adblock();
    window.addEventListener("load", () => {
        console.log("VOE: loaded");

        window.parent.postMessage("autoplay$url$" + window.location.href, '*');
        document.querySelector("video").play();

        let ended = false;
        document.querySelector("video").addEventListener("ended", () => {
            if (!ended) {
                console.log("VEO: ended");
                ended = true;
                window.parent.postMessage("autoplay$end", "*");
            }
        });

        // sometimes the "ended" event does not fire.. idk why. needs a workaround:
        window.setInterval(() => {
            let video = document.querySelector("video");
            if (video.currentTime + 0.5 >= video.duration && !ended) {
                console.log("VEO: ended workaround");
                ended = true;
                window.parent.postMessage("autoplay$end", "*");
            }
        }, 500);


        function handleSkipIntro(introTime) {
            let video = document.querySelector("video");

            if (video.currentTime >= introTime) {
                console.log("Video is already past the intro or user interacted. No skipping necessary.");
                return;
            }
            video.currentTime = parseInt(introTime);
        }

        function handleSkipOutro(outroTime) {
            let video = document.querySelector("video");

            const checkOutro = () => {
                if (video.duration - video.currentTime <= outroTime && !ended) {
                    console.log("Skipping outro", video.duration - video.currentTime, outroTime, video.duration - video.currentTime <= outroTime);
                    ended = true;
                    window.parent.postMessage("autoplay$end", "*");
                    video.removeEventListener("timeupdate", checkOutro);
                }
            };

            video.addEventListener("timeupdate", checkOutro);
        }

        window.addEventListener("message", (e) => {
            let video = document.querySelector("video");
            if(typeof e.data === "string" && e.data.startsWith("autoplay")) {
                const parsed = e.data.split("$");
                console.log("event", parsed);
                switch (parsed[1]) {
                    case "skip-intro": {
                        handleSkipIntro(parseInt(parsed[2]));
                        break;
                    }
                    case "skip-outro": {
                        handleSkipOutro(parseInt(parsed[2]));
                        break;
                    }
                    case "pause": {
                        video.pause();
                        break;
                    }
                }
            }
        });

        // fullscreen workaround
        if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
            let oldButton = document.querySelector("[data-plyr='fullscreen']");
            let target = oldButton.parentElement;
            let fsButton = oldButton.cloneNode(true);
            oldButton.remove();
            target.appendChild(fsButton);

            fsButton.addEventListener("click", () => {
                window.parent.postMessage("autoplay$fullscreen", "*");
            });
        }
    });
}