Scrolller.com Autoplay Feed

Autoplay Videos in Feed on Scrolller.com

当前为 2025-06-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Scrolller.com Autoplay Feed
  3. // @name:de Scrolller.com Automatische Wiedergabe im Feed
  4. // @version 1.0.4
  5. // @description Autoplay Videos in Feed on Scrolller.com
  6. // @description:de Spiele Videos im Feed automatisch ab auf Scrolller.com
  7. // @icon https://scrolller.com/assets/favicon-16x16.png
  8. // @author TalkLounge (https://github.com/TalkLounge)
  9. // @namespace https://github.com/TalkLounge/scrolller.com-autoplay-feed
  10. // @license MIT
  11. // @match https://scrolller.com/*
  12. // @grant none
  13. // @run-at document-start
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. let interval, muted = true;
  20. let cooldown = Date.now();
  21.  
  22. function video2SVGParent(video) {
  23. const parent = video.parentNode.parentNode.parentNode;
  24. //console.log(`video2SVGParent():`, video, "Return:", parent);
  25. return parent;
  26. }
  27.  
  28. function insertSound(parent, mute) {
  29. if (!parent || [...parent.classList].includes("noaudio")) {
  30. return;
  31. }
  32. //console.log(`insertSound():`, parent, mute);
  33.  
  34. parent.querySelector(".sound")?.remove();
  35.  
  36. let html;
  37. if (mute) {
  38. html = `
  39. <svg class="sound muted" viewBox="0 0 512 512" style="fill: grey; position: absolute; z-index: 1; width: 1.5em; cursor: pointer; margin-left: 0.2em">
  40. <path d="M232 416a23.88 23.88 0 01-14.2-4.68 8.27 8.27 0 01-.66-.51L125.76 336H56a24 24 0 01-24-24V200a24 24 0 0124-24h69.75l91.37-74.81a8.27 8.27 0 01.66-.51A24 24 0 01256 120v272a24 24 0 01-24 24zm-106.18-80zm-.27-159.86zM320 336a16 16 0 01-14.29-23.19c9.49-18.87 14.3-38 14.3-56.81 0-19.38-4.66-37.94-14.25-56.73a16 16 0 0128.5-14.54C346.19 208.12 352 231.44 352 256c0 23.86-6 47.81-17.7 71.19A16 16 0 01320 336z"></path>
  41. <path d="M368 384a16 16 0 01-13.86-24C373.05 327.09 384 299.51 384 256c0-44.17-10.93-71.56-29.82-103.94a16 16 0 0127.64-16.12C402.92 172.11 416 204.81 416 256c0 50.43-13.06 83.29-34.13 120a16 16 0 01-13.87 8z"></path>
  42. <path d="M416 432a16 16 0 01-13.39-24.74C429.85 365.47 448 323.76 448 256c0-66.5-18.18-108.62-45.49-151.39a16 16 0 1127-17.22C459.81 134.89 480 181.74 480 256c0 64.75-14.66 113.63-50.6 168.74A16 16 0 01416 432z"></path>
  43. </svg>`;
  44. } else {
  45. html = `
  46. <svg class="sound volume" viewBox="0 0 512 512" style="fill: white; position: absolute; z-index: 1; width: 1.5em; cursor: pointer; margin-left: 0.2em">
  47. <path d="M232 416a23.88 23.88 0 01-14.2-4.68 8.27 8.27 0 01-.66-.51L125.76 336H56a24 24 0 01-24-24V200a24 24 0 0124-24h69.75l91.37-74.81a8.27 8.27 0 01.66-.51A24 24 0 01256 120v272a24 24 0 01-24 24zm-106.18-80zm-.27-159.86zM320 336a16 16 0 01-14.29-23.19c9.49-18.87 14.3-38 14.3-56.81 0-19.38-4.66-37.94-14.25-56.73a16 16 0 0128.5-14.54C346.19 208.12 352 231.44 352 256c0 23.86-6 47.81-17.7 71.19A16 16 0 01320 336z"></path>
  48. <path d="M368 384a16 16 0 01-13.86-24C373.05 327.09 384 299.51 384 256c0-44.17-10.93-71.56-29.82-103.94a16 16 0 0127.64-16.12C402.92 172.11 416 204.81 416 256c0 50.43-13.06 83.29-34.13 120a16 16 0 01-13.87 8z"></path>
  49. <path d="M416 432a16 16 0 01-13.39-24.74C429.85 365.47 448 323.76 448 256c0-66.5-18.18-108.62-45.49-151.39a16 16 0 1127-17.22C459.81 134.89 480 181.74 480 256c0 64.75-14.66 113.63-50.6 168.74A16 16 0 01416 432z"></path>
  50. </svg>`;
  51. }
  52. html = html.replace(/>\s+</g, '><').trim(); // Clean up formatted html, Thanks to https://stackoverflow.com/a/27841683
  53. const child = new DOMParser().parseFromString(html, "text/html");
  54.  
  55. child.body.firstChild.addEventListener("click", (e) => {
  56. e.stopPropagation();
  57.  
  58. const svg = e.target.closest(".sound");
  59. //console.log("Clicked Sound Button", svg);
  60.  
  61. document.querySelectorAll("video").forEach(video => {
  62. if (!video.muted) {
  63. insertSound(video2SVGParent(video), true);
  64. }
  65.  
  66. video.muted = true;
  67. });
  68.  
  69. if ([...svg.classList].includes("muted")) {
  70. //console.log("Muted");
  71. muted = false;
  72.  
  73. parent.querySelector("video").muted = false;
  74.  
  75. insertSound(parent);
  76. } else {
  77. //console.log("Unmuted");
  78. muted = true;
  79.  
  80. insertSound(parent, true);
  81. }
  82. });
  83.  
  84. parent.insertBefore(child.body.firstChild, parent.firstChild);
  85. }
  86.  
  87. function parseJSON(data) {
  88. try {
  89. return JSON.parse(data);
  90. } catch (e) {
  91. const possibleEnds = [...data.matchAll(/\]\]/g)].map(match => match.index);
  92. for (let i = 0; i < possibleEnds.length; i++) { // Try possible ends of the Array
  93. try {
  94. return JSON.parse(data.slice(0, possibleEnds[i] + 2));
  95. } catch (e) { }
  96. }
  97.  
  98. throw e;
  99. }
  100. }
  101.  
  102. async function loadVideo(parent) {
  103. if ([...parent.classList].includes("loaded")) {
  104. return;
  105. }
  106. //console.log(`loadVideo():`, parent);
  107.  
  108. parent.classList.add("loaded");
  109. parent.querySelector("div>svg").parentNode.remove();
  110.  
  111. let response = await fetch(parent.querySelector("a").href);
  112. response = await response.text();
  113. response = new DOMParser().parseFromString(response, "text/html");
  114.  
  115. let data;
  116. try { // Try old first
  117. data = [...response.querySelectorAll("head script")];
  118. data = data.find(item => item.innerText.includes("window.scrolllerConfig"));
  119. data = data.textContent;
  120. data = data.replace("window.scrolllerConfig=", "");
  121. data = data.replace(/\\'/g, "'");
  122. data = JSON.parse(JSON.parse(data));
  123. data = data.item.mediaSources;
  124. } catch (e) { // Then try new
  125. data = [...response.querySelectorAll("script")];
  126. data = data.find(item => item.innerText.includes("mediaSources") && item.innerText.includes("blurredMediaSources"));
  127.  
  128. if (!data) {
  129. console.log("----------");
  130. console.error("Could not load url scripts");
  131. console.log(parent.querySelector("a").href);
  132. return;
  133. }
  134.  
  135. data = data.innerText;
  136. const dataIntial = data;
  137.  
  138.  
  139. // Base
  140. data = data.replace('self.__next_f.push([1,"', "");
  141.  
  142.  
  143. if (data.indexOf('],\"default\"]') != -1) { // Delete static/chunks Array
  144. data = data.slice(data.indexOf('],\"default\"]'));
  145.  
  146. console.log(data.indexOf(':[\"$undefined\"'));
  147. if (data.indexOf(':[\"$undefined\"') != -1) {
  148. data = data.slice(data.indexOf(':[\"$undefined\"') + 15);
  149. console.log(data);
  150. }
  151. }
  152.  
  153. if (data.indexOf('dangerouslySetInnerHTML\":{\"__html\":\"$') != -1) { // Delete empty dangerouslySetInnerHTML
  154. data = data.slice(data.indexOf('dangerouslySetInnerHTML\":{\"__html\":\"$') + 36);
  155. }
  156.  
  157. // Delete parts of another JSON / Array
  158. while (data.indexOf("}") < data.indexOf("{")) {
  159. data = data.slice(data.indexOf("}") + 1);
  160. }
  161.  
  162. while (data.indexOf("]") < data.indexOf("[")) {
  163. data = data.slice(data.indexOf("]") + 1);
  164. }
  165.  
  166. data = data.replace(/^,/, "");
  167. data = data.trim();
  168.  
  169.  
  170. // Detect Type
  171. let type;
  172. if (/^\d+:\[\[/.test(data)) { // 8:[[
  173. console.log('Test Case is 8:[[');
  174. type = 0;
  175. } else if (/^\[\"\$\",/.test(data)) { // ["$",
  176. console.log('Test Case is ["$",');
  177. type = 1;
  178. } else if (/^\[\\"\$\\",/.test(data)) { // ["$",
  179. console.log('Test Case is ["$",');
  180. type = 2;
  181. } else if (/^[A-Za-z]+,/.test(data)) { // ,null
  182. console.log('Test Case is ,null');
  183. type = 3;
  184. }
  185.  
  186.  
  187. // Start
  188. if (type == 0) {
  189. data = data.replace(/^\d+:\[\[/, "[[");
  190. } else if (type == 1 || type == 2) {
  191. data = data.replace(/^\[/, "[[], [");
  192. } else if (type == 3) {
  193. data = data.replace(/^[A-Za-z]+,/, '[[], ["", "", null,');
  194. }
  195.  
  196.  
  197. // De-Escape
  198. if (type == 0) {
  199. data = data.replace(/:"{/g, ":{");
  200. data = data.replace(/}"}/g, "}}");
  201.  
  202. data = data.replace(/:\\"\{/g, ":{");
  203. data = data.replace(/}\\"\}/g, "}}");
  204. }
  205.  
  206. data = data.replace(/\\"/g, '"');
  207. data = data.replace(/\\"/g, '"');
  208. data = data.replace(/\\"/g, '"');
  209.  
  210. if (type == 0) {
  211. data = data.replace(/\"\"/g, '"');
  212. data = data.replaceAll('":",', '":"",');
  213. }
  214.  
  215.  
  216. // End
  217. data = data.replace(/\"\]\)$/, "");
  218. data = data.replace(/\n/g, "");
  219.  
  220.  
  221. // Parse JSON
  222. try {
  223. const dataBefore = data;
  224. data = parseJSON(data);
  225.  
  226. if (typeof (data) != "object") {
  227. console.log("----------");
  228. console.error("JSON is not a JSON");
  229. console.log("Data Initial\n", dataIntial);
  230. console.log("Data Before\n", dataBefore);
  231. console.log("Data After\n", data);
  232. return;
  233. }
  234. } catch (e) {
  235. console.log("----------");
  236. console.error("Unable to parse JSON");
  237. console.error(e);
  238. console.log("Data Initial\n", dataIntial);
  239. console.log("Data Before\n", data);
  240. return;
  241. }
  242.  
  243. data = data[1][3].post.mediaSources;
  244. }
  245.  
  246. data = data.filter(item => (item.url.endsWith(".webm") || item.url.endsWith(".mp4")) && !item.url.endsWith("_thumb."));
  247. data = data.map(item => item.url);
  248.  
  249. const video = document.createElement("video");
  250. video.autoplay = true;
  251. video.muted = true;
  252. video.loop = true;
  253. video.style.height = "100%";
  254. video.style.position = "absolute";
  255. video.addEventListener("loadeddata", () => {
  256. parent.querySelector("picture").remove();
  257.  
  258. const hasAudio = video.mozHasAudio || Boolean(video.webkitAudioDecodedByteCount) || Boolean(video.audioTracks && video.audioTracks.length);
  259.  
  260. if (!hasAudio) {
  261. parent.classList.add("noaudio");
  262. }
  263.  
  264. insertSound(parent, true);
  265. });
  266.  
  267. for (const src of data) {
  268. const source = document.createElement("source");
  269. source.src = src;
  270. video.append(source);
  271. }
  272.  
  273. parent.querySelector("a>div").insertBefore(video, parent.querySelector("a>div").firstChild);
  274. }
  275.  
  276. function loadVideos() {
  277. const items = document.querySelectorAll("main>div>div>div a:has(div>svg), [class^=verticalView_container]>div>div a:has(div>svg)");
  278.  
  279. for (const item of items) {
  280. loadVideo(item.parentNode);
  281. }
  282. }
  283.  
  284. async function init() {
  285. if (!document.querySelector("main>div>div>div picture, [class^=verticalView_container]>div>div picture")) {
  286. return;
  287. }
  288.  
  289. window.clearInterval(interval);
  290.  
  291. window.setInterval(loadVideos, 500);
  292.  
  293. document.body.onscroll = function () {
  294. if (muted) {
  295. return;
  296. }
  297.  
  298. if (Date.now() - cooldown < 250) {
  299. return;
  300. }
  301. cooldown = Date.now();
  302.  
  303. //console.log("Scroll");
  304. let diffMin = 1000000;
  305. let nearest;
  306. let middle = window.innerHeight / 2;
  307.  
  308. document.querySelectorAll("video").forEach(video => {
  309. const loud = video2SVGParent(video).querySelector(".sound:not(.muted)");
  310. if (loud) {
  311. insertSound(video2SVGParent(video), true);
  312. }
  313. video.muted = true;
  314. const rect = video.getBoundingClientRect();
  315. const elemMiddle = rect.y + (rect.height / 2);
  316. const diff = Math.abs(middle - elemMiddle);
  317. if (diff < diffMin) {
  318. diffMin = diff;
  319. nearest = video;
  320. }
  321. });
  322.  
  323. if (!nearest || diffMin > middle || video2SVGParent(nearest).querySelector(".sound:not(.muted)")) {
  324. return;
  325. }
  326.  
  327. nearest.muted = false;
  328.  
  329. insertSound(video2SVGParent(nearest));
  330. }
  331. }
  332.  
  333. interval = window.setInterval(init, 500);
  334. })();