Greasy Fork

Greasy Fork is available in English.

s.to autoplay

Autoplay für SerienStream.to

当前为 2025-01-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         s.to autoplay
// @namespace    https://github.com/zaheer-exe
// @version      9.5
// @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() {
    // Pattern matching helpers
    function isAdScriptUrl(url) {
        if (typeof url !== 'string') return false;
        // Allow player-related URLs
        if (url.includes('/hls2-c/') ||
            url.includes('/mp4-proxy/') ||
            url.includes('.m3u8') ||
            url.includes('.mp4') ||
            url.includes('plyr') ||
            url.includes('hls.min.js') ||
            url.includes('plyr.min.js') ||
            url.includes('bootstrap.bundle.min.js')) {
            return false;
        }
        // Match common ad domains and patterns
        return /synonymdetected|delivery-node-.*key=|robertordercharacter|ads-twitter|imasdk|\/ads?\/|\/pop\.|popunder|\.adb\.|doubleclick|adsystem|adserv|\/prebid|bidder|auction/i.test(url);
    }

    function isSuspiciousFormOrInput(element) {
        // Skip player elements
        if (element.id === 'voe-player' ||
            element.classList.contains('plyr') ||
            element.closest('.plyr')) {
            return false;
        }
        // Check for randomly generated patterns
        return (element.className && /^[a-f0-9]{8,}$/.test(element.className)) ||
               (element.name && /^[a-f0-9]{16,}$/.test(element.name)) ||
               (element.id && /^[a-f0-9]{16,}$/.test(element.id));
    }

    function isSuspiciousFunction(fn) {
        if (typeof fn !== 'function') return false;
        const fnStr = fn.toString().toLowerCase();

        // Allow player-related functions
        if (fnStr.includes('plyr') ||
            fnStr.includes('hls') ||
            fnStr.includes('video') ||
            fnStr.includes('player')) {
            return false;
        }

        return (
            fnStr.includes('window.open') ||
            fnStr.includes('popup') ||
            (fnStr.includes('appendchild') && fnStr.includes('script')) ||
            (fnStr.includes('input') && fnStr.includes('hidden') && !fnStr.includes('player')) ||
            /function\s+[a-f0-9]{16,}/i.test(fnStr)
        );
    }

    // Override window.open
    const originalWindowOpen = window.open;
    window.open = function(url, ...args) {
        if (isAdScriptUrl(url)) {
            console.log('Blocked popup:', url);
            return {
                focus: function() {},
                closed: false
            };
        }
        return originalWindowOpen.call(this, url, ...args);
    };

    // Override createElement
    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' && isAdScriptUrl(value)) {
                    console.log('Blocked script src:', value);
                    return;
                }
                return originalSetAttribute.call(this, name, value);
            };
        }
        return element;
    };

    // Override XMLHttpRequest
    const originalXHROpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, ...args) {
        if (isAdScriptUrl(url)) {
            console.log('Blocked XHR:', url);
            throw new Error('Blocked XHR');
        }
        return originalXHROpen.call(this, method, url, ...args);
    };

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

    // Clean up suspicious elements
    function cleanupElements() {
        // Remove suspicious forms and inputs
        const elements = document.querySelectorAll('form, input[type="hidden"]');
        elements.forEach(element => {
            if (isSuspiciousFormOrInput(element)) {
                console.log('Removed suspicious element:', element.tagName, element.className || element.name);
                element.remove();
            }
        });
    }

    // Override timer functions
    const originalSetTimeout = window.setTimeout;
    const originalSetInterval = window.setInterval;
    const wrapTimerFunction = (originalFunc) => {
        return function(callback, delay, ...args) {
            if (typeof callback === 'function') {
                const callbackString = callback.toString().toLowerCase();
                // Allow player-related timeouts
                if (callbackString.includes('plyr') ||
                    callbackString.includes('hls') ||
                    callbackString.includes('video') ||
                    callbackString.includes('player')) {
                    return originalFunc.call(this, callback, delay, ...args);
                }
                // Block suspicious timeouts
                if (isSuspiciousFunction(callback)) {
                    console.log('Blocked suspicious timer function');
                    return;
                }
            }
            return originalFunc.call(this, callback, delay, ...args);
        };
    };
    window.setTimeout = wrapTimerFunction(originalSetTimeout);
    window.setInterval = wrapTimerFunction(originalSetInterval);

    // Initial cleanup
    cleanupElements();

    // Set interval for periodic cleanup
    setInterval(cleanupElements, 500);
}

////////// 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:url']");
if (checkIfVoe && checkIfVoe.content.includes("voe")) {
    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", "*");
            });
        }
    });
}