// ==UserScript== // @name Host Selector // @version 3.1 // @description:de Ermöglicht Auswahl von Video-Host und speichert die Auswahl. Funktioniert bei https://aniworld.to/ und https://s.to // @description:en Enables selection of video host and saves the choice. Works on https://aniworld.to/ and https://s.to // @description:ja ビデオホストの選択と選択内容の保存を可能にします。https://aniworld.to/ および https://s.to で動作します。 // @author 𝕭𝖚𝖉𝖚𝖒𝖟 // @icon https://w0.peakpx.com/wallpaper/40/813/HD-wallpaper-walpaper-zedge.jpg // @match https://aniworld.to/* // @match https://s.to/serie/stream/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @namespace http://tampermonkey.net/ // @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! // @downloadURL none // ==/UserScript== (function () { 'use strict'; console.log('[Host Selector] Skript startet...'); // --- Prüfen, ob GM_* Funktionen verfügbar sind --- if (typeof GM_getValue === 'undefined' || typeof GM_setValue === 'undefined' || typeof GM_addStyle === 'undefined') { 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.'); alert('[Host Selector] Fehler: Benötigte GM_* Funktionen nicht gefunden. Skript kann nicht korrekt ausgeführt werden.'); return; // Skript beenden, wenn wichtige Funktionen fehlen } else { console.log('[Host Selector] GM_* Funktionen verfügbar.'); } // --- Konfiguration --- const hosterListSelector = 'ul.row > li'; // Selektor für die Listenelemente der Hoster const hosterLinkSelector = 'a.watchEpisode'; // Selektor für die Links zu den einzelnen Hostern (innerhalb li) const hosterNameSelector = 'h4'; // Selektor für den Namen des Hosters innerhalb des Links const hosterButtonSelector = '.hosterSiteVideoButton'; // Selektor für den eigentlichen Klick-Button im Link const clickDelay = 250; // Etwas längere Verzögerung vor dem Klick (in Millisekunden) const localStorageKey = 'preferredHoster'; // Schlüssel für GM Speicher const OBSERVER_TIMEOUT_MS = 15000; // Max. Wartezeit für Auto-Klick (15 Sekunden) // --- Funktion zum Klicken des bevorzugten Hoster-Links --- // Gibt true zurück, wenn der Klick-Versuch gestartet wurde, sonst false. function clickHosterLink(preferredHoster) { const hosterListItems = document.querySelectorAll(hosterListSelector); console.log(`[Host Selector] clickHosterLink: Aufgerufen mit preferredHoster="${preferredHoster}". Fand ${hosterListItems.length} Listenelemente.`); let foundAndClicked = false; // Wird true, wenn der Klick-Versuch startet for (const item of hosterListItems) { // Prüfen, ob das Element sichtbar ist if (window.getComputedStyle(item).display === 'none') { // console.log('[Host Selector] clickHosterLink: Überspringe unsichtbares Listenelement.'); // Optional: Weniger Logs continue; } const link = item.querySelector(hosterLinkSelector); if (!link) continue; const hosterNameElement = link.querySelector(hosterNameSelector); const button = link.querySelector(hosterButtonSelector); if (hosterNameElement) { const currentHosterName = hosterNameElement.innerText.trim(); // console.log(`[Host Selector] Prüfe sichtbaren Hoster: "${currentHosterName}"`); // Optional: Weniger Logs if (currentHosterName === preferredHoster) { console.log(`[Host Selector] Match gefunden für "${preferredHoster}"!`); if (button) { console.log(`[Host Selector] Button gefunden. Versuche Klick nach ${clickDelay}ms...`); // Klick wird verzögert ausgeführt setTimeout(() => { try { button.click(); console.log(`[Host Selector] Klick erfolgreich ausgelöst für "${preferredHoster}".`); } catch (e) { console.error(`[Host Selector] Fehler während button.click():`, e); } }, clickDelay); foundAndClicked = true; // WICHTIG: Setze auf true, da der Klick *versucht* wird break; // Schleife verlassen, Hoster gefunden und Klick initiiert } else { console.warn(`[Host Selector] Hoster "${preferredHoster}" gematcht, aber Button mit Selektor "${hosterButtonSelector}" nicht im Link gefunden.`); } } } else { // console.warn(`[Host Selector] Sichtbarer Link gefunden, aber Hoster-Namenselement mit Selektor "${hosterNameSelector}" nicht darin gefunden.`); // Optional: Weniger Logs } } // Gib zurück, ob der Klick-Versuch für den bevorzugten Hoster gestartet wurde if (!foundAndClicked && hosterListItems.length > 0) { console.log(`[Host Selector] clickHosterLink: Bevorzugter Hoster "${preferredHoster}" wurde unter den ${hosterListItems.length} sichtbaren Hostern nicht gefunden.`); } else if (hosterListItems.length === 0) { console.log(`[Host Selector] clickHosterLink: Keine Hoster-Listenelemente gefunden.`); } return foundAndClicked; // Gib den Status zurück } // --- Funktion zur Behandlung des automatischen Klickens beim Laden der Seite --- function autoClickPreferredHoster() { console.log('[Host Selector] Lese Hoster aus Speicher...'); const storedPreferredHoster = GM_getValue(localStorageKey, null); console.log(`[Host Selector] Wert gelesen für Schlüssel "${localStorageKey}":`, storedPreferredHoster); if (!storedPreferredHoster) { console.log('[Host Selector] Kein bevorzugter Hoster im Speicher gefunden. Automatisches Klicken übersprungen.'); return; } console.log(`[Host Selector] Bevorzugter Hoster gefunden: "${storedPreferredHoster}". Warte auf Hoster-Links...`); // --- Variablen für den Observer und Timeout --- let observer = null; let observerTimeout = null; // Funktion, die prüft und klickt. Gibt true zurück, wenn Klick ausgelöst wurde. const checkForLinksAndClick = () => { console.log(`[Host Selector] checkForLinksAndClick: Suche nach "${storedPreferredHoster}".`); const hosterItemsExist = document.querySelector(hosterListSelector); if (hosterItemsExist) { console.log(`[Host Selector] Mindestens ein Listenelement gefunden. Rufe clickHosterLink auf.`); // clickHosterLink gibt true zurück, wenn der Klick-Versuch gestartet wurde return clickHosterLink(storedPreferredHoster); } console.log(`[Host Selector] Noch keine Listenelemente gefunden.`); return false; // Hoster-Liste noch nicht gefunden }; // Funktion zum Stoppen des Observers und des Timeouts const stopObserver = (reason) => { if (observer) { observer.disconnect(); observer = null; console.log(`[Host Selector] MutationObserver gestoppt (${reason}).`); } if (observerTimeout) { clearTimeout(observerTimeout); observerTimeout = null; console.log(`[Host Selector] Observer-Timeout gelöscht (${reason}).`); } }; // 1. Zuerst prüfen, ob die Links vielleicht schon da sind if (checkForLinksAndClick()) { console.log('[Host Selector] Klick bei initialer Prüfung erfolgreich.'); return; // Kein Observer nötig } // 2. Wenn nicht, MutationObserver verwenden console.log('[Host Selector] Starte MutationObserver, um auf Listenelemente zu warten...'); observer = new MutationObserver((mutationsList, obs) => { console.log('[Host Selector] MutationObserver ausgelöst.'); // Prüfen, ob der bevorzugte Hoster jetzt geklickt werden kann if (checkForLinksAndClick()) { console.log('[Host Selector] Klick nach DOM-Änderung erfolgreich.'); stopObserver("Hoster gefunden und geklickt"); } else { // Noch nicht erfolgreich, warte auf weitere Mutationen oder Timeout console.log(`[Host Selector] Bevorzugter Hoster "${storedPreferredHoster}" nach Mutation noch nicht klickbar/gefunden.`); } }); // Beobachte Änderungen im Body observer.observe(document.body, { childList: true, // Achte auf hinzugefügte/entfernte Kind-Elemente subtree: true // Beobachte auch alle Unterelemente }); // 3. Setze ein Timeout, um den Observer zu stoppen, falls der Hoster nie erscheint console.log(`[Host Selector] Setze Observer-Timeout auf ${OBSERVER_TIMEOUT_MS / 1000} Sekunden.`); observerTimeout = setTimeout(() => { console.warn(`[Host Selector] Observer-Timeout erreicht. Bevorzugter Hoster "${storedPreferredHoster}" wurde nicht gefunden oder konnte nicht geklickt werden.`); stopObserver("Timeout"); }, OBSERVER_TIMEOUT_MS); } // --- Funktion zum Erstellen und Anzeigen des GUI-Popups --- function createGUI() { console.log('[Host Selector] createGUI aufgerufen.'); const existingPopup = document.getElementById('hostSelectorPopup'); if (existingPopup) { document.body.removeChild(existingPopup); } // --- Verfügbare Hoster dynamisch ermitteln --- const hosterListItems = document.querySelectorAll(hosterListSelector); const availableHostersSet = new Set(); console.log(`[Host Selector] GUI: Fand ${hosterListItems.length} potenzielle Hoster-Listenelemente.`); hosterListItems.forEach(item => { if (window.getComputedStyle(item).display !== 'none') { const hosterNameElement = item.querySelector(hosterNameSelector); if (hosterNameElement) { const hosterName = hosterNameElement.innerText.trim(); if (hosterName) { availableHostersSet.add(hosterName); console.log(`[Host Selector] GUI: Füge sichtbaren Hoster "${hosterName}" zur Liste hinzu.`); } } } else { // console.log('[Host Selector] GUI: Überspringe unsichtbares Hoster-Listenelement.'); } }); const availableHosters = Array.from(availableHostersSet).sort(); console.log('[Host Selector] GUI: Verfügbare Hoster ermittelt:', availableHosters); let optionsHTML = ''; if (availableHosters.length > 0) { optionsHTML = availableHosters.map(hoster => `` ).join(''); } else { optionsHTML = ''; console.warn('[Host Selector] GUI: Keine sichtbaren Hoster gefunden, um die Dropdown-Liste zu füllen.'); } // --- Ende Hoster ermitteln --- const popupDiv = document.createElement('div'); popupDiv.id = 'hostSelectorPopup'; // Style für das Popup popupDiv.style.position = 'fixed'; popupDiv.style.top = '50%'; popupDiv.style.left = '50%'; popupDiv.style.transform = 'translate(-50%, -50%)'; popupDiv.style.background = '#ffffff'; popupDiv.style.border = '1px solid #ddd'; popupDiv.style.boxShadow = '0px 0px 15px rgba(0, 0, 0, 0.2)'; popupDiv.style.maxWidth = '400px'; popupDiv.style.width = '90%'; popupDiv.style.padding = '25px'; popupDiv.style.borderRadius = '8px'; popupDiv.style.fontFamily = 'Arial, sans-serif'; popupDiv.style.zIndex = '10000'; popupDiv.style.boxSizing = 'border-box'; // HTML-Inhalt des Popups popupDiv.innerHTML = `