Host Selector

Enhanced GUI for aniworld.to and s.to websites, allowing you to effortlessly choose your preferred video host and have it automatically opened. A convenient green button positioned at the bottom right corner of the page will appear, prompting you to click and select your desired host from a user-friendly drop-down menu. Enjoy seamless video streaming with the host of your choice!

  1. // ==UserScript==
  2. // @name Host Selector
  3. // @version 3.1
  4. // @description:de Ermöglicht Auswahl von Video-Host und speichert die Auswahl. Funktioniert bei https://aniworld.to/ und https://s.to
  5. // @description:en Enables selection of video host and saves the choice. Works on https://aniworld.to/ and https://s.to
  6. // @description:ja ビデオホストの選択と選択内容の保存を可能にします。https://aniworld.to/ および https://s.to で動作します。
  7. // @author 𝕭𝖚𝖉𝖚𝖒𝖟
  8. // @icon https://w0.peakpx.com/wallpaper/40/813/HD-wallpaper-walpaper-zedge.jpg
  9. // @match https://aniworld.to/*
  10. // @match https://s.to/serie/stream/*
  11. // @grant GM_addStyle
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @namespace http://tampermonkey.net/
  15. // @description Enhanced GUI for aniworld.to and s.to websites, allowing you to effortlessly choose your preferred video host and have it automatically opened. A convenient green button positioned at the bottom right corner of the page will appear, prompting you to click and select your desired host from a user-friendly drop-down menu. Enjoy seamless video streaming with the host of your choice!
  16. // ==/UserScript==
  17.  
  18.  
  19. (function () {
  20. 'use strict';
  21. console.log('[Host Selector] Skript startet...');
  22.  
  23. // --- Prüfen, ob GM_* Funktionen verfügbar sind ---
  24. if (typeof GM_getValue === 'undefined' || typeof GM_setValue === 'undefined' || typeof GM_addStyle === 'undefined') {
  25. console.error('[Host Selector] Fehler: GM_getValue, GM_setValue oder GM_addStyle nicht verfügbar! Stellen Sie sicher, dass Tampermonkey/Greasemonkey korrekt läuft und die @grant-Anweisungen vorhanden sind.');
  26. alert('[Host Selector] Fehler: Benötigte GM_* Funktionen nicht gefunden. Skript kann nicht korrekt ausgeführt werden.');
  27. return; // Skript beenden, wenn wichtige Funktionen fehlen
  28. } else {
  29. console.log('[Host Selector] GM_* Funktionen verfügbar.');
  30. }
  31.  
  32. // --- Konfiguration ---
  33. const hosterListSelector = 'ul.row > li'; // Selektor für die Listenelemente der Hoster
  34. const hosterLinkSelector = 'a.watchEpisode'; // Selektor für die Links zu den einzelnen Hostern (innerhalb li)
  35. const hosterNameSelector = 'h4'; // Selektor für den Namen des Hosters innerhalb des Links
  36. const hosterButtonSelector = '.hosterSiteVideoButton'; // Selektor für den eigentlichen Klick-Button im Link
  37. const clickDelay = 250; // Etwas längere Verzögerung vor dem Klick (in Millisekunden)
  38. const localStorageKey = 'preferredHoster'; // Schlüssel für GM Speicher
  39. const OBSERVER_TIMEOUT_MS = 15000; // Max. Wartezeit für Auto-Klick (15 Sekunden)
  40.  
  41. // --- Funktion zum Klicken des bevorzugten Hoster-Links ---
  42. // Gibt true zurück, wenn der Klick-Versuch gestartet wurde, sonst false.
  43. function clickHosterLink(preferredHoster) {
  44. const hosterListItems = document.querySelectorAll(hosterListSelector);
  45. console.log(`[Host Selector] clickHosterLink: Aufgerufen mit preferredHoster="${preferredHoster}". Fand ${hosterListItems.length} Listenelemente.`);
  46. let foundAndClicked = false; // Wird true, wenn der Klick-Versuch startet
  47.  
  48. for (const item of hosterListItems) {
  49. // Prüfen, ob das Element sichtbar ist
  50. if (window.getComputedStyle(item).display === 'none') {
  51. // console.log('[Host Selector] clickHosterLink: Überspringe unsichtbares Listenelement.'); // Optional: Weniger Logs
  52. continue;
  53. }
  54.  
  55. const link = item.querySelector(hosterLinkSelector);
  56. if (!link) continue;
  57.  
  58. const hosterNameElement = link.querySelector(hosterNameSelector);
  59. const button = link.querySelector(hosterButtonSelector);
  60.  
  61. if (hosterNameElement) {
  62. const currentHosterName = hosterNameElement.innerText.trim();
  63. // console.log(`[Host Selector] Prüfe sichtbaren Hoster: "${currentHosterName}"`); // Optional: Weniger Logs
  64. if (currentHosterName === preferredHoster) {
  65. console.log(`[Host Selector] Match gefunden für "${preferredHoster}"!`);
  66. if (button) {
  67. console.log(`[Host Selector] Button gefunden. Versuche Klick nach ${clickDelay}ms...`);
  68. // Klick wird verzögert ausgeführt
  69. setTimeout(() => {
  70. try {
  71. button.click();
  72. console.log(`[Host Selector] Klick erfolgreich ausgelöst für "${preferredHoster}".`);
  73. } catch (e) {
  74. console.error(`[Host Selector] Fehler während button.click():`, e);
  75. }
  76. }, clickDelay);
  77. foundAndClicked = true; // WICHTIG: Setze auf true, da der Klick *versucht* wird
  78. break; // Schleife verlassen, Hoster gefunden und Klick initiiert
  79. } else {
  80. console.warn(`[Host Selector] Hoster "${preferredHoster}" gematcht, aber Button mit Selektor "${hosterButtonSelector}" nicht im Link gefunden.`);
  81. }
  82. }
  83. } else {
  84. // console.warn(`[Host Selector] Sichtbarer Link gefunden, aber Hoster-Namenselement mit Selektor "${hosterNameSelector}" nicht darin gefunden.`); // Optional: Weniger Logs
  85. }
  86. }
  87. // Gib zurück, ob der Klick-Versuch für den bevorzugten Hoster gestartet wurde
  88. if (!foundAndClicked && hosterListItems.length > 0) {
  89. console.log(`[Host Selector] clickHosterLink: Bevorzugter Hoster "${preferredHoster}" wurde unter den ${hosterListItems.length} sichtbaren Hostern nicht gefunden.`);
  90. } else if (hosterListItems.length === 0) {
  91. console.log(`[Host Selector] clickHosterLink: Keine Hoster-Listenelemente gefunden.`);
  92. }
  93. return foundAndClicked; // Gib den Status zurück
  94. }
  95.  
  96.  
  97. // --- Funktion zur Behandlung des automatischen Klickens beim Laden der Seite ---
  98. function autoClickPreferredHoster() {
  99. console.log('[Host Selector] Lese Hoster aus Speicher...');
  100. const storedPreferredHoster = GM_getValue(localStorageKey, null);
  101. console.log(`[Host Selector] Wert gelesen für Schlüssel "${localStorageKey}":`, storedPreferredHoster);
  102.  
  103. if (!storedPreferredHoster) {
  104. console.log('[Host Selector] Kein bevorzugter Hoster im Speicher gefunden. Automatisches Klicken übersprungen.');
  105. return;
  106. }
  107.  
  108. console.log(`[Host Selector] Bevorzugter Hoster gefunden: "${storedPreferredHoster}". Warte auf Hoster-Links...`);
  109.  
  110. // --- Variablen für den Observer und Timeout ---
  111. let observer = null;
  112. let observerTimeout = null;
  113.  
  114. // Funktion, die prüft und klickt. Gibt true zurück, wenn Klick ausgelöst wurde.
  115. const checkForLinksAndClick = () => {
  116. console.log(`[Host Selector] checkForLinksAndClick: Suche nach "${storedPreferredHoster}".`);
  117. const hosterItemsExist = document.querySelector(hosterListSelector);
  118. if (hosterItemsExist) {
  119. console.log(`[Host Selector] Mindestens ein Listenelement gefunden. Rufe clickHosterLink auf.`);
  120. // clickHosterLink gibt true zurück, wenn der Klick-Versuch gestartet wurde
  121. return clickHosterLink(storedPreferredHoster);
  122. }
  123. console.log(`[Host Selector] Noch keine Listenelemente gefunden.`);
  124. return false; // Hoster-Liste noch nicht gefunden
  125. };
  126.  
  127. // Funktion zum Stoppen des Observers und des Timeouts
  128. const stopObserver = (reason) => {
  129. if (observer) {
  130. observer.disconnect();
  131. observer = null;
  132. console.log(`[Host Selector] MutationObserver gestoppt (${reason}).`);
  133. }
  134. if (observerTimeout) {
  135. clearTimeout(observerTimeout);
  136. observerTimeout = null;
  137. console.log(`[Host Selector] Observer-Timeout gelöscht (${reason}).`);
  138. }
  139. };
  140.  
  141. // 1. Zuerst prüfen, ob die Links vielleicht schon da sind
  142. if (checkForLinksAndClick()) {
  143. console.log('[Host Selector] Klick bei initialer Prüfung erfolgreich.');
  144. return; // Kein Observer nötig
  145. }
  146.  
  147. // 2. Wenn nicht, MutationObserver verwenden
  148. console.log('[Host Selector] Starte MutationObserver, um auf Listenelemente zu warten...');
  149. observer = new MutationObserver((mutationsList, obs) => {
  150. console.log('[Host Selector] MutationObserver ausgelöst.');
  151.  
  152. // Prüfen, ob der bevorzugte Hoster jetzt geklickt werden kann
  153. if (checkForLinksAndClick()) {
  154. console.log('[Host Selector] Klick nach DOM-Änderung erfolgreich.');
  155. stopObserver("Hoster gefunden und geklickt");
  156. } else {
  157. // Noch nicht erfolgreich, warte auf weitere Mutationen oder Timeout
  158. console.log(`[Host Selector] Bevorzugter Hoster "${storedPreferredHoster}" nach Mutation noch nicht klickbar/gefunden.`);
  159. }
  160. });
  161.  
  162. // Beobachte Änderungen im Body
  163. observer.observe(document.body, {
  164. childList: true, // Achte auf hinzugefügte/entfernte Kind-Elemente
  165. subtree: true // Beobachte auch alle Unterelemente
  166. });
  167.  
  168. // 3. Setze ein Timeout, um den Observer zu stoppen, falls der Hoster nie erscheint
  169. console.log(`[Host Selector] Setze Observer-Timeout auf ${OBSERVER_TIMEOUT_MS / 1000} Sekunden.`);
  170. observerTimeout = setTimeout(() => {
  171. console.warn(`[Host Selector] Observer-Timeout erreicht. Bevorzugter Hoster "${storedPreferredHoster}" wurde nicht gefunden oder konnte nicht geklickt werden.`);
  172. stopObserver("Timeout");
  173. }, OBSERVER_TIMEOUT_MS);
  174. }
  175.  
  176.  
  177. // --- Funktion zum Erstellen und Anzeigen des GUI-Popups ---
  178. function createGUI() {
  179. console.log('[Host Selector] createGUI aufgerufen.');
  180. const existingPopup = document.getElementById('hostSelectorPopup');
  181. if (existingPopup) {
  182. document.body.removeChild(existingPopup);
  183. }
  184.  
  185. // --- Verfügbare Hoster dynamisch ermitteln ---
  186. const hosterListItems = document.querySelectorAll(hosterListSelector);
  187. const availableHostersSet = new Set();
  188. console.log(`[Host Selector] GUI: Fand ${hosterListItems.length} potenzielle Hoster-Listenelemente.`);
  189.  
  190. hosterListItems.forEach(item => {
  191. if (window.getComputedStyle(item).display !== 'none') {
  192. const hosterNameElement = item.querySelector(hosterNameSelector);
  193. if (hosterNameElement) {
  194. const hosterName = hosterNameElement.innerText.trim();
  195. if (hosterName) {
  196. availableHostersSet.add(hosterName);
  197. console.log(`[Host Selector] GUI: Füge sichtbaren Hoster "${hosterName}" zur Liste hinzu.`);
  198. }
  199. }
  200. } else {
  201. // console.log('[Host Selector] GUI: Überspringe unsichtbares Hoster-Listenelement.');
  202. }
  203. });
  204.  
  205. const availableHosters = Array.from(availableHostersSet).sort();
  206. console.log('[Host Selector] GUI: Verfügbare Hoster ermittelt:', availableHosters);
  207.  
  208. let optionsHTML = '';
  209. if (availableHosters.length > 0) {
  210. optionsHTML = availableHosters.map(hoster =>
  211. `<option value="${hoster}">${hoster}</option>`
  212. ).join('');
  213. } else {
  214. optionsHTML = '<option value="">Keine Hoster gefunden</option>';
  215. console.warn('[Host Selector] GUI: Keine sichtbaren Hoster gefunden, um die Dropdown-Liste zu füllen.');
  216. }
  217. // --- Ende Hoster ermitteln ---
  218.  
  219. const popupDiv = document.createElement('div');
  220. popupDiv.id = 'hostSelectorPopup';
  221. // Style für das Popup
  222. popupDiv.style.position = 'fixed';
  223. popupDiv.style.top = '50%';
  224. popupDiv.style.left = '50%';
  225. popupDiv.style.transform = 'translate(-50%, -50%)';
  226. popupDiv.style.background = '#ffffff';
  227. popupDiv.style.border = '1px solid #ddd';
  228. popupDiv.style.boxShadow = '0px 0px 15px rgba(0, 0, 0, 0.2)';
  229. popupDiv.style.maxWidth = '400px';
  230. popupDiv.style.width = '90%';
  231. popupDiv.style.padding = '25px';
  232. popupDiv.style.borderRadius = '8px';
  233. popupDiv.style.fontFamily = 'Arial, sans-serif';
  234. popupDiv.style.zIndex = '10000';
  235. popupDiv.style.boxSizing = 'border-box';
  236.  
  237. // HTML-Inhalt des Popups
  238. popupDiv.innerHTML = `
  239. <h2 style="font-size: 20px; margin-top:0; margin-bottom: 20px; color: #333; text-align: center;">Bevorzugter Hoster</h2>
  240. <label for="preferredHosterSelect" style="display: block; font-size: 16px; color: #555; margin-bottom: 8px;">Verfügbarer Hoster:</label>
  241. <select id="preferredHosterSelect" style="width: 100%; padding: 12px; margin-bottom: 25px; font-size: 16px; border: 1px solid #ccc; border-radius: 4px; color: #555; box-sizing: border-box;">
  242. ${optionsHTML}
  243. </select>
  244. <div style="display: flex; justify-content: space-between; gap: 10px;">
  245. <button id="submitButton" style="flex-grow: 1; background-color: #4CAF50; color: white; padding: 12px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;">Auswählen & Speichern</button>
  246. <button id="closeButton" style="background-color: #f44336; color: white; padding: 12px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;">Schließen</button>
  247. </div>
  248. `;
  249. document.body.appendChild(popupDiv);
  250.  
  251. // Gespeicherten Hoster im Dropdown vorauswählen
  252. const storedPreferredHoster = GM_getValue(localStorageKey, null);
  253. const preferredHosterSelect = document.getElementById('preferredHosterSelect');
  254. console.log(`[Host Selector] GUI: Lade gespeicherten Wert "${storedPreferredHoster}" in Select-Box.`);
  255.  
  256. if (preferredHosterSelect && storedPreferredHoster) {
  257. if (Array.from(preferredHosterSelect.options).some(option => option.value === storedPreferredHoster)) {
  258. preferredHosterSelect.value = storedPreferredHoster;
  259. console.log(`[Host Selector] GUI: Gespeicherter Hoster "${storedPreferredHoster}" ist verfügbar und wurde ausgewählt.`);
  260. } else {
  261. console.log(`[Host Selector] GUI: Gespeicherter Hoster "${storedPreferredHoster}" ist aktuell nicht verfügbar.`);
  262. }
  263. } else if (preferredHosterSelect && preferredHosterSelect.options.length > 0 && preferredHosterSelect.options[0].value !== "") {
  264. console.log('[Host Selector] GUI: Kein Hoster gespeichert. Erster verfügbarer Hoster:', preferredHosterSelect.options[0]?.value);
  265. }
  266.  
  267. // Event Listener für Buttons
  268. const submitButton = document.getElementById('submitButton');
  269. submitButton.addEventListener('click', () => {
  270. if (preferredHosterSelect.value && preferredHosterSelect.value !== "") {
  271. const selectedHoster = preferredHosterSelect.value;
  272. console.log(`[Host Selector] GUI: "Auswählen" geklickt. Speichere "${selectedHoster}"...`);
  273. GM_setValue(localStorageKey, selectedHoster);
  274. console.log(`[Host Selector] GUI: Wert für Schlüssel "${localStorageKey}" gespeichert.`);
  275.  
  276. console.log('[Host Selector] GUI: Versuche sofortigen Klick nach Auswahl...');
  277. clickHosterLink(selectedHoster); // Versuche sofort zu klicken
  278.  
  279. document.body.removeChild(popupDiv);
  280. console.log('[Host Selector] GUI: Popup geschlossen.');
  281. } else {
  282. console.warn('[Host Selector] GUI: Kein gültiger Hoster zum Speichern ausgewählt.');
  283. alert("Bitte wähle einen verfügbaren Hoster aus.");
  284. }
  285. });
  286.  
  287. const closeButton = document.getElementById('closeButton');
  288. closeButton.addEventListener('click', () => {
  289. console.log('[Host Selector] GUI: "Schließen" geklickt.');
  290. document.body.removeChild(popupDiv);
  291. });
  292. }
  293.  
  294.  
  295. // --- Funktion zum Hinzufügen des GUI-Buttons zur Seite ---
  296. function addGUIButton() {
  297. console.log('[Host Selector] Füge GUI-Button hinzu...');
  298. const existingButton = document.getElementById('hostSelectorButton');
  299. if (existingButton) {
  300. // Optional: Entfernen, falls alter Button Probleme macht
  301. // document.body.removeChild(existingButton);
  302. console.log('[Host Selector] GUI-Button existiert bereits.');
  303. return; // Nicht erneut hinzufügen
  304. }
  305.  
  306. const button = document.createElement('button');
  307. button.id = 'hostSelectorButton';
  308. button.innerText = '⚙️ Hoster';
  309. button.style.position = 'fixed';
  310. button.style.bottom = '15px';
  311. button.style.right = '15px';
  312. button.style.backgroundColor = '#673AB7';
  313. button.style.color = 'white';
  314. button.style.padding = '8px 12px';
  315. button.style.border = 'none';
  316. button.style.borderRadius = '5px';
  317. button.style.cursor = 'pointer';
  318. button.style.fontSize = '14px';
  319. button.style.zIndex = '9999';
  320. button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
  321. button.title = 'Bevorzugten Hoster auswählen';
  322. button.addEventListener('click', createGUI);
  323. document.body.appendChild(button);
  324. console.log('[Host Selector] GUI-Button hinzugefügt.');
  325. }
  326.  
  327.  
  328. // --- Hauptausführung ---
  329. console.log('[Host Selector] Starte Hauptausführung...');
  330.  
  331. // Füge den Button hinzu (oder stelle sicher, dass er da ist)
  332. // Die Prüfung auf Existenz erfolgt jetzt in addGUIButton selbst.
  333. addGUIButton();
  334.  
  335. // Starte den Versuch, automatisch zu klicken
  336. autoClickPreferredHoster();
  337.  
  338. console.log('[Host Selector] Hauptausführung initial abgeschlossen (Observer läuft ggf. weiter).');
  339.  
  340. })(); // Ende der IIFE