s.to autoplay

Autoplay für SerienStream.to

  1. // ==UserScript==
  2. // @name s.to autoplay
  3. // @namespace https://github.com/zaheer-exe
  4. // @version 9.6
  5. // @description Autoplay für SerienStream.to
  6. // @author zaheer-exe
  7. // @match https://s.to/*
  8. // @match https://serienstream.to/*
  9. // @match https://aniworld.to/*
  10. // @match https://voe.sx/*
  11. // @match *://*/*
  12. // @grant GM_xmlhttpRequest
  13. // @grant none
  14. // @run-at document-start
  15. // @icon https://www.google.com/s2/favicons?sz=64&domain=s.to
  16. // @license Apache License
  17. // ==/UserScript==
  18.  
  19. ////////// LISTER ////////////////////////////////////////////////////////////////
  20. const sToHosts = ['s.to', 'aniworld.to', 'serienstream.to'];
  21. if (sToHosts.includes(new URL(window.location.href).hostname)) {
  22. let isAutoPlayed = false;
  23.  
  24. function nextEpisode() {
  25. const currentLang = document.querySelector("img.selectedLanguage").dataset.langKey;
  26. console.log("S: current lang ", currentLang);
  27. const episodeMenuCurrentElem = document.querySelector('li a.active[href*="episode"]');
  28. const nextEpisodeUrl = episodeMenuCurrentElem.parentElement.nextElementSibling.querySelector('a');
  29.  
  30. var xmlHttp = new XMLHttpRequest();
  31. xmlHttp.open("GET", nextEpisodeUrl, false);
  32. xmlHttp.send(null);
  33.  
  34. let temp = document.createElement('div');
  35. temp.innerHTML = xmlHttp.responseText;
  36. let url = temp.querySelector('li[data-lang-key="' + currentLang + '"] .watchEpisode .icon.VOE').parentElement.href;
  37. let title = temp.querySelector(".hosterSiteTitle").innerHTML;
  38.  
  39. let streamIframe = document.querySelector(".inSiteWebStream iframe");
  40. streamIframe.src = url;
  41.  
  42. document.querySelector(".hosterSiteTitle").innerHTML = title;
  43. document.querySelector(".breadCrumbMenu").innerHTML = temp.querySelector(".breadCrumbMenu").innerHTML;
  44. episodeMenuCurrentElem.classList.remove('active');
  45. nextEpisodeUrl.classList.add('active');
  46. window.history.pushState("", "", nextEpisodeUrl.href);
  47.  
  48. if (!isAutoPlayed) {
  49. disableElements();
  50. isAutoPlayed = true;
  51. }
  52. }
  53.  
  54.  
  55.  
  56. function disableElements() {
  57. const style = document.createElement('style');
  58. style.textContent = `
  59. .changeLanguage, li[data-link-target] {
  60. opacity: 0.3;
  61. cursor: none;
  62. }
  63.  
  64. .changeLanguageBox, li[data-link-target] > .generateInlinePlayer {
  65. pointer-events: none;
  66. }
  67.  
  68. .tooltip {
  69. position: absolute;
  70. background-color: #333; /* Dark background for contrast */
  71. color: #fff; /* White text for contrast */
  72. padding: 10px; /* Padding for better appearance */
  73. border-radius: 5px; /* Rounded corners */
  74. font-size: 14px; /* Font size for readability */
  75. line-height: 1.4; /* Line height for better text readability */
  76. white-space: nowrap; /* Prevent text wrapping */
  77. display: none; /* Hidden by default */
  78. z-index: 9999; /* Ensure it appears above other content */
  79. pointer-events: none; /* Ensure it doesn't interfere with interactions */
  80. transform: translate(-50%, -50%); /* Center tooltip on the cursor */
  81. }
  82. `;
  83. document.head.appendChild(style);
  84.  
  85. const tooltip = document.createElement('div');
  86. tooltip.className = 'tooltip';
  87. tooltip.textContent = 'Autoplay enabled. Refresh/Reload Page to change settings.';
  88. document.body.appendChild(tooltip);
  89.  
  90. function updateTooltipPosition(event) {
  91. tooltip.style.left = `${event.pageX}px`;
  92. tooltip.style.top = `${event.pageY}px`;
  93. }
  94.  
  95. function showTooltip(event) {
  96. tooltip.style.display = 'block';
  97. updateTooltipPosition(event);
  98. }
  99.  
  100. function hideTooltip() {
  101. tooltip.style.display = 'none';
  102. }
  103.  
  104. const elements = document.querySelectorAll('.changeLanguage, li[data-link-target]');
  105. elements.forEach(element => {
  106. element.addEventListener('mouseenter', showTooltip);
  107. element.addEventListener('mousemove', updateTooltipPosition);
  108. element.addEventListener('mouseleave', hideTooltip);
  109. });
  110. }
  111.  
  112. function autoPlaySettings() {
  113. const style = document.createElement('style');
  114. style.textContent = `
  115. .autoplay-settings-container {
  116. border: 1px solid #3a3a3a;
  117. border-radius: 8px;
  118. margin-top: 15px;
  119. padding: 15px;
  120. background-color: #18181b;
  121. font-family: Arial, sans-serif;
  122. color: #ffffff;
  123. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  124. }
  125. .autoplay-settings-container h2 {
  126. margin: 0 0 15px 0;
  127. color: #8257e6;
  128. font-size: 16px;
  129. font-weight: bold;
  130. }
  131. .settings-row {
  132. display: flex;
  133. justify-content: space-between;
  134. }
  135. .settings-column {
  136. width: 48%;
  137. }
  138. .setting-group {
  139. margin-bottom: 10px;
  140. }
  141. .setting-group label {
  142. display: block;
  143. margin-bottom: 5px;
  144. font-size: 14px;
  145. color: #a0a0a0;
  146. }
  147. .setting-group input {
  148. width: 100%;
  149. padding: 8px;
  150. border: 1px solid #3a3a3a;
  151. border-radius: 4px;
  152. font-size: 14px;
  153. background-color: #27272a;
  154. color: #ffffff;
  155. transition: border-color 0.3s, box-shadow 0.3s;
  156. }
  157. .setting-group input:focus {
  158. outline: none;
  159. border-color: #8257e6;
  160. box-shadow: 0 0 5px rgba(130, 87, 230, 0.5);
  161. }
  162. .setting-group input::placeholder {
  163. color: #6b7280;
  164. }
  165. `;
  166. document.head.appendChild(style);
  167.  
  168. const container = document.createElement("div");
  169. container.classList.add("autoplay-settings-container");
  170. container.innerHTML = `
  171. <h2>AutoPlay Settings</h2>
  172. <div class="settings-row">
  173. <div class="settings-column">
  174. <div class="setting-group">
  175. <label for="skip-intro">Skip intro (seconds)</label>
  176. <input id="skip-intro" type="number" min="0" placeholder="e.g., 30">
  177. </div>
  178. <div class="setting-group">
  179. <label for="skip-outro">Skip outro (seconds)</label>
  180. <input id="skip-outro" type="number" min="0" placeholder="e.g., 60">
  181. </div>
  182. </div>
  183. <!-- <div class="settings-column">
  184. <div class="setting-group">
  185. <label for="still-here">"Are you still watching?" popup (minutes of inactivity)</label>
  186. <input id="still-here" type="number" min="0" placeholder="e.g., 120">
  187. </div>
  188. </div> -->
  189. </div>
  190. `;
  191. document.querySelector(".hosterSiteDirectNav").appendChild(container);
  192.  
  193. let key = new URL(window.location.href).pathname.split("/");
  194. key = key[1] + "/" + key[2] + "/" + key[3];
  195. const seasonData = JSON.parse(localStorage.getItem(key) || "{\"skip-intro\": 0, \"skip-outro\": 0, \"still-here\": 0}");
  196.  
  197. container.querySelector("#skip-intro").value = seasonData["skip-intro"];
  198. container.querySelector("#skip-outro").value = seasonData["skip-outro"];
  199.  
  200. // container.querySelector("#still-here").value = seasonData["still-here"];
  201.  
  202. ['skip-intro', 'skip-outro'].forEach(id => {
  203. document.getElementById(id).addEventListener("change", (e) => {
  204. const value = e.target.value;
  205. console.log(`${e.target.id} changed to: ${value}`);
  206. seasonData[e.target.id] = value;
  207. localStorage.setItem(key, JSON.stringify(seasonData));
  208. });
  209. });
  210. }
  211.  
  212.  
  213.  
  214. function showTooltip(e) {
  215. const tooltip = document.getElementById('autoplay-tooltip');
  216. tooltip.style.display = 'block';
  217. tooltip.style.left = e.pageX + 10 + 'px';
  218. tooltip.style.top = e.pageY + 10 + 'px';
  219. }
  220.  
  221. function hideTooltip() {
  222. const tooltip = document.getElementById('autoplay-tooltip');
  223. tooltip.style.display = 'none';
  224. }
  225.  
  226. function handleConfig() {
  227. let key = new URL(window.location.href).pathname.split("/");
  228. key = key[1] + "/" + key[2] + "/" + key[3];
  229. const seasonData = JSON.parse(localStorage.getItem(key));
  230. if (seasonData) {
  231. if (seasonData["skip-intro"] > 0) {
  232. console.log("S: sending post data for skip-intro");
  233. document.querySelector('.inSiteWebStream iframe').contentWindow.postMessage("autoplay$skip-intro$" + seasonData["skip-intro"], "*");
  234. }
  235. if (seasonData["skip-outro"] > 0) {
  236. console.log("S: sending post data for skip-outro");
  237. document.querySelector('.inSiteWebStream iframe').contentWindow.postMessage("autoplay$skip-outro$" + seasonData["skip-outro"], "*");
  238. }
  239. }
  240. }
  241.  
  242. window.addEventListener("message", (event) => {
  243. if(typeof event.data === "string" && event.data.startsWith("autoplay")) {
  244. const parsed = event.data.split("$");
  245. console.log("event", parsed);
  246. switch (parsed[1]) {
  247. case "url": {
  248. console.log("S: url");
  249. let streamIframe = document.querySelector(".inSiteWebStream iframe");
  250. if (streamIframe.src.includes("/redirect")) {
  251. document.querySelector(".inSiteWebStream iframe").src = parsed[2];
  252.  
  253. document.querySelector(".inSiteWebStream iframe").addEventListener("load", () => {
  254. handleConfig();
  255. });
  256. }
  257. break;
  258. }
  259. case "end": {
  260. console.log("S: ended");
  261. nextEpisode();
  262. break;
  263. }
  264. case "fullscreen": { // fullscreen workaround
  265. if (document.fullscreenElement === document.querySelector(".inSiteWebStream iframe")) {
  266. document.exitFullscreen()
  267. } else {
  268. document.querySelector(".inSiteWebStream iframe").requestFullscreen();
  269. }
  270. }
  271. }
  272. }
  273. }, false);
  274.  
  275. window.addEventListener("load", () => {
  276. autoPlaySettings();
  277. document.querySelector(".inSiteWebStream iframe").allow = document.querySelector(".inSiteWebStream iframe").allow + "; autoplay"
  278. });
  279. }
  280.  
  281. ////////// HOSTER ////////////////////////////////////////////////////////////////
  282. let checkIfVoe = document.querySelector("head > meta[name='og:url']");
  283. if (checkIfVoe && checkIfVoe.content.includes("voe")) {
  284. window.addEventListener("load", () => {
  285. console.log("VOE: loaded");
  286.  
  287. window.parent.postMessage("autoplay$url$" + window.location.href, '*');
  288. document.querySelector("video").play();
  289.  
  290. let ended = false;
  291. document.querySelector("video").addEventListener("ended", () => {
  292. if (!ended) {
  293. console.log("VEO: ended");
  294. ended = true;
  295. window.parent.postMessage("autoplay$end", "*");
  296. }
  297. });
  298.  
  299. // sometimes the "ended" event does not fire.. idk why. needs a workaround:
  300. window.setInterval(() => {
  301. let video = document.querySelector("video");
  302. if (video.currentTime + 0.5 >= video.duration && !ended) {
  303. console.log("VEO: ended workaround");
  304. ended = true;
  305. window.parent.postMessage("autoplay$end", "*");
  306. }
  307. }, 500);
  308.  
  309.  
  310. function handleSkipIntro(introTime) {
  311. let video = document.querySelector("video");
  312.  
  313. if (video.currentTime >= introTime) {
  314. console.log("Video is already past the intro or user interacted. No skipping necessary.");
  315. return;
  316. }
  317. video.currentTime = parseInt(introTime);
  318. }
  319.  
  320. function handleSkipOutro(outroTime) {
  321. let video = document.querySelector("video");
  322.  
  323. const checkOutro = () => {
  324. if (video.duration - video.currentTime <= outroTime && !ended) {
  325. console.log("Skipping outro", video.duration - video.currentTime, outroTime, video.duration - video.currentTime <= outroTime);
  326. ended = true;
  327. window.parent.postMessage("autoplay$end", "*");
  328. video.removeEventListener("timeupdate", checkOutro);
  329. }
  330. };
  331.  
  332. video.addEventListener("timeupdate", checkOutro);
  333. }
  334.  
  335. window.addEventListener("message", (e) => {
  336. let video = document.querySelector("video");
  337. if(typeof e.data === "string" && e.data.startsWith("autoplay")) {
  338. const parsed = e.data.split("$");
  339. console.log("event", parsed);
  340. switch (parsed[1]) {
  341. case "skip-intro": {
  342. handleSkipIntro(parseInt(parsed[2]));
  343. break;
  344. }
  345. case "skip-outro": {
  346. handleSkipOutro(parseInt(parsed[2]));
  347. break;
  348. }
  349. case "pause": {
  350. video.pause();
  351. break;
  352. }
  353. }
  354. }
  355. });
  356.  
  357. // fullscreen workaround
  358. if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
  359. let oldButton = document.querySelector("[data-plyr='fullscreen']");
  360. let target = oldButton.parentElement;
  361. let fsButton = oldButton.cloneNode(true);
  362. oldButton.remove();
  363. target.appendChild(fsButton);
  364.  
  365. fsButton.addEventListener("click", () => {
  366. window.parent.postMessage("autoplay$fullscreen", "*");
  367. });
  368. }
  369. });
  370. }