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