// ==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 // @downloadURL none // ==/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 = `

AutoPlay Settings

`; 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", "*"); }); } }); }