// ==UserScript==
// @name Host Selector
// @version 3
// @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
// @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!
// ==/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 =>
`<option value="${hoster}">${hoster}</option>`
).join('');
} else {
optionsHTML = '<option value="">Keine Hoster gefunden</option>';
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 = `
<h2 style="font-size: 20px; margin-top:0; margin-bottom: 20px; color: #333; text-align: center;">Bevorzugter Hoster</h2>
<label for="preferredHosterSelect" style="display: block; font-size: 16px; color: #555; margin-bottom: 8px;">Verfügbarer Hoster:</label>
<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;">
${optionsHTML}
</select>
<div style="display: flex; justify-content: space-between; gap: 10px;">
<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>
<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>
</div>
`;
document.body.appendChild(popupDiv);
// Gespeicherten Hoster im Dropdown vorauswählen
const storedPreferredHoster = GM_getValue(localStorageKey, null);
const preferredHosterSelect = document.getElementById('preferredHosterSelect');
console.log(`[Host Selector] GUI: Lade gespeicherten Wert "${storedPreferredHoster}" in Select-Box.`);
if (preferredHosterSelect && storedPreferredHoster) {
if (Array.from(preferredHosterSelect.options).some(option => option.value === storedPreferredHoster)) {
preferredHosterSelect.value = storedPreferredHoster;
console.log(`[Host Selector] GUI: Gespeicherter Hoster "${storedPreferredHoster}" ist verfügbar und wurde ausgewählt.`);
} else {
console.log(`[Host Selector] GUI: Gespeicherter Hoster "${storedPreferredHoster}" ist aktuell nicht verfügbar.`);
}
} else if (preferredHosterSelect && preferredHosterSelect.options.length > 0 && preferredHosterSelect.options[0].value !== "") {
console.log('[Host Selector] GUI: Kein Hoster gespeichert. Erster verfügbarer Hoster:', preferredHosterSelect.options[0]?.value);
}
// Event Listener für Buttons
const submitButton = document.getElementById('submitButton');
submitButton.addEventListener('click', () => {
if (preferredHosterSelect.value && preferredHosterSelect.value !== "") {
const selectedHoster = preferredHosterSelect.value;
console.log(`[Host Selector] GUI: "Auswählen" geklickt. Speichere "${selectedHoster}"...`);
GM_setValue(localStorageKey, selectedHoster);
console.log(`[Host Selector] GUI: Wert für Schlüssel "${localStorageKey}" gespeichert.`);
console.log('[Host Selector] GUI: Versuche sofortigen Klick nach Auswahl...');
clickHosterLink(selectedHoster); // Versuche sofort zu klicken
document.body.removeChild(popupDiv);
console.log('[Host Selector] GUI: Popup geschlossen.');
} else {
console.warn('[Host Selector] GUI: Kein gültiger Hoster zum Speichern ausgewählt.');
alert("Bitte wähle einen verfügbaren Hoster aus.");
}
});
const closeButton = document.getElementById('closeButton');
closeButton.addEventListener('click', () => {
console.log('[Host Selector] GUI: "Schließen" geklickt.');
document.body.removeChild(popupDiv);
});
}
// --- Funktion zum Hinzufügen des GUI-Buttons zur Seite ---
function addGUIButton() {
console.log('[Host Selector] Füge GUI-Button hinzu...');
const existingButton = document.getElementById('hostSelectorButton');
if (existingButton) {
// Optional: Entfernen, falls alter Button Probleme macht
// document.body.removeChild(existingButton);
console.log('[Host Selector] GUI-Button existiert bereits.');
return; // Nicht erneut hinzufügen
}
const button = document.createElement('button');
button.id = 'hostSelectorButton';
button.innerText = '⚙️ Hoster';
button.style.position = 'fixed';
button.style.bottom = '15px';
button.style.right = '15px';
button.style.backgroundColor = '#673AB7';
button.style.color = 'white';
button.style.padding = '8px 12px';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.zIndex = '9999';
button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
button.title = 'Bevorzugten Hoster auswählen';
button.addEventListener('click', createGUI);
document.body.appendChild(button);
console.log('[Host Selector] GUI-Button hinzugefügt.');
}
// --- Hauptausführung ---
console.log('[Host Selector] Starte Hauptausführung...');
// Füge den Button hinzu (oder stelle sicher, dass er da ist)
// Die Prüfung auf Existenz erfolgt jetzt in addGUIButton selbst.
addGUIButton();
// Starte den Versuch, automatisch zu klicken
autoClickPreferredHoster();
console.log('[Host Selector] Hauptausführung initial abgeschlossen (Observer läuft ggf. weiter).');
})(); // Ende der IIFE