# xStream – Technische Dokumentation Version: `plugin.video.xstream 2026.02.11` / `script.module.xstreamscraper 2025.08.14` Erstellt: 2026-08-17 --- ## 1. Übersicht Das Projekt besteht aus zwei voneinander abhängigen Kodi-Addons: | Addon | Zweck | |---|---| | `plugin.video.xstream` | Vollständiges Video-Plugin: Browser-GUI, Site-Plugins, Hoster-Auflösung, Download | | `script.module.xstreamscraper` | Companion-Modul: automatische Suche und Stream-Auflösung via TMDB-ID, für externe Aufrufer | **Zusammenfassung der Funktion:** xStream ist eine Kodi-Suchmaschine für Filme und Serien. Es lädt Inhalte von verschiedenen Streaming-Webseiten (sogenannte „Site-Plugins"), löst die gefundenen Hoster-Links über das `resolveurl`-Addon auf und spielt den Stream in Kodi ab. Das Scraper-Modul ergänzt dies um eine vollautomatische Suche, die auf TMDB-IDs basiert. --- ## 2. Komponentenstruktur ``` Xstream-Standalone/ ├── plugin.video.xstream/ │ ├── addon.xml # Addon-Metadaten und Abhängigkeiten │ ├── default.py # Einstiegspunkt (Kodi ruft dies auf) │ ├── service.py # Kodi-Service (läuft beim Kodi-Start) │ ├── xstream.py # URL-Router und Hauptlogik │ ├── sites/ # Site-Plugins (eine Datei pro Webseite) │ │ ├── kinox.py │ │ ├── aniworld.py │ │ ├── burningseries.py │ │ └── ... │ └── resources/ │ └── lib/ │ ├── config.py # Addon-Einstellungen (Wrapper um xbmcaddon) │ ├── tools.py # Logger, Parser, Cache-Hilfsfunktionen │ ├── gui/ │ │ ├── gui.py # Kodi-GUI-Abstraktion (Listeneinträge, Ansichten) │ │ ├── guiElement.py # Einzelner Listeneintrag (Titel, Thumbnail, Metadaten) │ │ ├── hoster.py # Hoster-GUI: Abspielen, Download, Weiterleiten │ │ └── contextElement.py# Kontextmenü-Einträge │ └── handler/ │ ├── pluginHandler.py # Erkennung und Verwaltung der Site-Plugins │ ├── requestHandler.py# HTTP-Client mit Cache, Cookies, Anti-Bot │ ├── ParameterHandler.py # URL-Parameter lesen/schreiben │ ├── jdownloaderHandler.py │ ├── jdownloader2Handler.py │ ├── myjdownloaderHandler.py │ └── pyLoadHandler.py └── script.module.xstreamscraper/ ├── addon.xml ├── main.py # Einstiegspunkt des Scraper-Moduls ├── service.py # Service des Scraper-Moduls └── resources/lib/ ├── scraper.py # Kernlogik: Suche, Hoster-Auflösung, Wiedergabe ├── settings.py # Globaler Zustand (collectMode, aDirectory) ├── tools.py ├── gui/ # GUI-Klassen (Subset von plugin.video.xstream) └── handler/ # Request- und Parameter-Handler (Subset) ``` --- ## 3. Ablauf: plugin.video.xstream ### 3.1 Startup-Sequenz (Service) `service.py` wird von Kodi beim Start als separater Service-Prozess ausgeführt. ``` Kodi-Start └─► service.py: main() 1. cCache: setze '{addon_id}_main' = 'running' 2. Resolver-Update von GitHub prüfen/ausführen (updateManager.resolverUpdate) 3. Abhängigkeiten prüfen (checkDependence) – fehlende Addons installieren 4. Domain-Prüfung aller Site-Plugins parallel (cPluginHandler.checkDomain) → HTTP-Request an domain des jeweiligen Site-Plugins → Status 200/3xx: Domain aktiv, globale Suche aktiviert → Status 403–503: globale Suche deaktiviert, Link gesperrt 5. PluginDB aktualisieren (falls neue Einstellungen vorhanden) 6. cCache: setze '{addon_id}_main' = 'finished' 7. Changelog-Popup anzeigen (falls konfiguriert) 8. Alten HTML-Cache löschen (alle N Tage, konfigurierbar) ``` `showMainMenu()` in `xstream.py` wartet aktiv (max. 60 Sekunden / 0,5-Sekunden-Schleifen) auf das Signal `'finished'`, bevor das Hauptmenü aufgebaut wird. --- ### 3.2 Browsing-Flow (Plugin-Aufruf) Kodi ruft `default.py` mit einer Plugin-URL der Form `plugin://plugin.video.xstream/?site=kinox&function=showMovieMenu&...` auf. ``` default.py: main() └─► Systempfade einrichten (sites/, lib/, art/sites/) └─► xstream.py: parseUrl() ├── ParameterHandler: URL-Parameter lesen ├── Sonderfunktionen: clearCache, viewInfo, searchAlter, searchTMDB, ... ├── Kein 'site'-Parameter → showMainMenu() │ └── alle aktivierten Site-Plugins als Listeneinträge hinzufügen ├── site = 'cHosterGui' → showHosterGui() ├── site = 'globalSearch' → searchGlobal() ├── site = 'xStream' → xStream-Einstellungen öffnen ├── site = 'resolver' → Resolver-Einstellungen öffnen └── site = '' └── plugin = __import__('') └── function = getattr(plugin, sFunction) └── function() ← Site-Plugin übernimmt ``` --- ### 3.3 Site-Plugin-Konvention Jede Datei in `sites/` ist ein eigenständiges Python-Modul. Es muss folgende Modul-Variablen und Funktionen bereitstellen: | Name | Pflicht | Beschreibung | |---|---|---| | `SITE_IDENTIFIER` | Ja | Eindeutiger Bezeichner (= Dateiname ohne `.py`) | | `SITE_NAME` | Ja | Anzeigename im Menü | | `SITE_ICON` | Nein | Dateiname des Icons in `resources/art/sites/` | | `DOMAIN` | Nein | Standarddomain; kann per Einstellung überschrieben werden | | `SITE_GLOBAL_SEARCH` | Nein | `True`/`False`; steuert globale Suche | | `load()` | Ja | Einstiegspunkt – baut Hauptmenü des Sites auf | | `_search(oGui, sSearchText)` | Nein | Globale Suche; befüllt `oGui` mit Ergebnissen | | `showHosters()` | Typisch | Gibt Hoster-Liste zurück (list of dicts + Funktionsname am Ende) | | `getHosterUrl(sUrl)` | Typisch | Löst einen Hoster-Link zur Stream-URL auf | **Beispiel (kinox.py, vereinfacht):** ```python SITE_IDENTIFIER = 'kinox' SITE_NAME = 'KinoX' DOMAIN = 'ww22.kinoz.to' def load(): # Hauptmenü: News, Filme, Serien, Dokumentationen, Suche def showMovieMenu(): # Untermenü: Kino, A-Z, Genre, Beliebt, Neu def showHosters(): # Gibt Liste der Hoster-Dicts + 'getHosterUrl' zurück def getHosterUrl(): # Löst Hoster-URL auf, gibt {'streamUrl':..., 'resolved':...} zurück def _search(oGui, sSearchText): # Globale Suche, befüllt oGui ``` --- ### 3.4 Hoster-Auflösungsflow ``` User klickt auf Film/Episode └─► xstream.py: parseUrl() erkennt 'playMode' ├── hosterSelect = 'Auto' → cHosterGui.streamAuto() └── sonst → cHosterGui.stream() cHosterGui.stream(playMode, siteName, function): 1. Site-Plugin laden, Hoster-Funktion aufrufen → Hoster-Liste 2. Hoster nach Priorität sortieren (__getPriorities) → resolveurl.HostedMediaFile(url=...).get_resolvers() → Priorität nach Resolver-Ranking + Qualität + Sprache 3. Auswahl: ├── hosterSelect = 'List' → Hoster als Ordner anzeigen (showHosterFolder) ├── Mehrere Hoster → Dialog zur Auswahl (_chooseHoster) └── Genau ein Hoster → direkt weiter 4. getHosterUrl(hoster['link']) aufrufen → {'streamUrl': ..., 'resolved': bool} 5. cHosterGui.play / download / addToPlaylist / sendToJD / ... cHosterGui.play(siteResult): 1. resolveurl.resolve(streamUrl) → direkter Stream-Link 2. xbmcgui.ListItem mit Link erstellen 3. m3u8/mpd → inputstream.adaptive aktivieren 4. xbmcplugin.setResolvedUrl(handle, True, listItem) ``` --- ### 3.5 Globale Suche ``` searchGlobal(sSearchText): 1. cGui im collectMode = True (Ergebnisse werden im Speicher gesammelt) 2. ThreadPoolExecutor (max. 10 Workers): → alle aktivierten Plugins mit globalsearch != 'false' → plugin._search(oGui, sSearchText) parallel aufrufen 3. Fortschrittsdialog aktualisieren (0–50 %) 4. collectMode = False 5. Ergebnisse nach Site-Name sortiert als Listeneinträge hinzufügen (50–100 %) searchAlter (Kontextmenü „Weitere Quellen"): - Wie searchGlobal, aber mit Titel-Normalisierung (Jahr, Staffelkennung entfernen) - Ergebnisse werden nach Titel, Jahr und IMDB-ID gefiltert searchTMDB (aus TMDB-Kontext aufgerufen): - Wie searchGlobal, ohne zusätzliche Filterung ``` --- ## 4. HTTP-Request-Handler (`requestHandler.py`) `cRequestHandler` ist der zentrale HTTP-Client des Plugins. ### Initialisierung (wichtige Parameter) | Parameter | Standard | Bedeutung | |---|---|---| | `caching` | `True` | HTML-Cache aktivieren | | `ignoreErrors` | `False` | HTTP-Fehler still ignorieren | | `method` | `'GET'` | HTTP-Methode | | `compression` | `True` | gzip/deflate anfordern | | `bypass_dns` | `False` | DNS-over-HTTPS verwenden | ### Caching Zwei Cache-Ebenen, konfigurierbar per Einstellung: | Ebene | Speicherort | Aktivierung | |---|---|---| | Persistent | `{profile}/htmlcache/{md5(url)}` | Standard | | Volatil (In-Memory) | `cCache` (Wörterbuch) | Einstellung `volatileHtmlCache = true` | Cache-Lebensdauer: konfigurierbar, Standard 6 Stunden (360 min × 60 s). ### Anti-Bot-Mechanismen | Schutzmechanismus | Erkennung | Behandlung | |---|---|---| | Cloudflare | `cloudflare` im Response-Header | Fehlermeldung an Nutzer, kein Retry | | DDOS-Guard | `DDOS-GUARD` im Response-Body | Automatischer Cookie-Challenge via `check.ddos-guard.net` | | Blazingfast | `lazingfast` im Response-Body | AES-Entschlüsselung via pyaes, Cookie-Setzung | | DNS-Sperre (CUII) | Redirect zu `notice.cuii.info` | Fehlerdialog; Bypass via DoH (Cloudflare) wenn aktiviert | ### DNS-over-HTTPS (DoH) Wenn `bypass_dns = True` und Einstellung `bypassDNSlock = true`: → DNS-Auflösung via `https://cloudflare-dns.com/dns-query` statt System-DNS → IP-Adresse wird direkt für TCP-Verbindung genutzt; SNI-Header enthält weiterhin den Hostnamen → IP-Ergebnis wird im volatilen Cache gespeichert ### Cookie-Verwaltung Pro Domain wird eine eigene Cookie-Datei im LWP-Format gespeichert: `{profile}/cookies/{domain_mit_unterstrichen}.txt` --- ## 5. Plugin-Handler (`pluginHandler.py`) Verwaltet die Erkennung und den Zustand der Site-Plugins. ### PluginDB Eine JSON-Datei unter `{profile}/pluginDB`. Sie enthält für jedes Site-Plugin: ```json { "kinox": { "name": "KinoX", "identifier": "kinox", "icon": "kinox.png", "domain": "ww22.kinoz.to", "globalsearch": "true", "modified": 1700000000.0 } } ``` Die DB wird aktualisiert, wenn: - Eine neue `.py`-Datei im `sites/`-Ordner gefunden wird - Eine vorhandene Datei seit dem letzten Einlesen geändert wurde - Die globale Sucheinstellung für ein Plugin geändert wurde ### Domain-Prüfung (`checkDomain`) Wird beim Kodi-Start vom Service aufgerufen. Für jedes aktivierte Plugin: 1. HTTP-Request an `http://{domain}/` 2. Status-Code wird in der Einstellung `plugin_{id}_status` gespeichert 3. Bei Redirect (3xx): neue Domain aus dem `Location`-Header übernehmen 4. Bei 403–503: globale Suche für das Plugin deaktivieren --- ## 6. GUI-Schicht (`gui.py`, `guiElement.py`) ### `cGui` Abstraktion über Kodis `xbmcplugin`-API. Wesentliche Konzepte: **collectMode**: Wenn `True`, werden Listeneinträge nicht an Kodi gesendet, sondern in `self.searchResults` gesammelt. Wird für die globale Suche verwendet, damit alle Plugins ihre Ergebnisse in eine gemeinsame Liste schreiben können. **TMDB-Metadaten**: Wenn die Einstellung `TMDBMETA = true`, wird für jeden Eintrag automatisch `guiElement.getMeta()` aufgerufen (via TMDB-API), bevor er angezeigt wird. **Kontext-Menü-Einträge** (automatisch für alle Einträge): - Trailer (wenn Addon `script.module.xstream.trailer` installiert) - Erweiterte Info (TMDB-Infobox) - Weitere Quellen (searchAlter) - Playlist hinzufügen - Download - An JDownloader / JDownloader2 / My.JDownloader / pyLoad senden - Manueller Hoster-Auswahldialog (wenn Auto-Modus aktiv) ### `cGuiElement` Repräsentiert einen einzelnen Listeneintrag. Wesentliche Felder: | Feld | Methode | Beschreibung | |---|---|---| | Titel | `setTitle` | Anzeigename | | Site-Name | `setSiteName` | Zugehöriges Site-Plugin | | Funktion | `setFunction` | Aufzurufende Funktion beim Klick | | Thumbnail | `setThumbnail` | Vorschaubild-URL | | Fanart | `setFanart` | Hintergrundbild-URL | | Medientyp | `setMediaType` | `movie`, `tvshow`, `season`, `episode` | | Sprache | `setLanguage` | Sprach-Code (z. B. `DE`, `EN`) | | Qualität | `setQuality` | z. B. `1080p`, `720p` | | Jahr | `setYear` | Erscheinungsjahr | | Staffel/Episode | `setSeason`/`setEpisode` | Serieninformationen | --- ## 7. script.module.xstreamscraper Das Scraper-Modul ist für den Fall gedacht, dass externe Tools (z. B. über Kodi-Addons, die TMDB-IDs kennen) einen Film oder eine Serie automatisch abspielen wollen, ohne durch das xStream-Menü navigieren zu müssen. ### Haupt-API: `scraper.play(_type, _id, season, episode)` ``` play("movie", "123456", None, None) play("tv", "78910", "2", "5") # Staffel 2, Episode 5 ``` **Ablauf:** ``` play(_type, _id, season, episode): 1. TMDB-API: Film-/Seriendaten laden (Name, Erscheinungsdatum, alternative Titel) 2. Optional: vavoo-Integration prüfen (direkter Stream ohne Scraping) 3. searchGlobal(name, searchtitles, isSerie, ...) aufrufen → alle aktivierten Site-Plugins parallel durchsuchen (ThreadPoolExecutor) → Ergebnisse filtern: - Sprache muss Deutsch sein - Medientyp muss passen (Film ≠ Serie) - Bei Serien: Staffelnummer prüfen - Jahresabgleich (±0 bei Filmen) - Titelabgleich (normalisiert: Sonderzeichen entfernt) 4. get_hosters(sources, isSerie, season, episode) aufrufen → Bei Serien: get_episodes() navigiert zu korrekter Episode → site-plugin.getHosterUrl() für jeden Treffer 5. Jeden Hoster der Reihe nach testen: → resolveurl.resolve(streamUrl) aufrufen → HTTP-HEAD-Request prüfen (Content-Type muss Video sein) → Erster gültiger Link → _play(url) 6. _play(url): → xbmcgui.ListItem mit URL → m3u8 → inputstream.adaptive → xbmcplugin.setResolvedUrl() ``` ### Unterschied zur manuellen Nutzung via plugin.video.xstream | Aspekt | plugin.video.xstream | script.module.xstreamscraper | |---|---|---| | Einstieg | Nutzer-Navigation | TMDB-ID als Parameter | | Hoster-Auswahl | Manuell oder nach Priorität | Vollautomatisch, erster funktionierender | | Stream-Validierung | Keiner | HTTP-HEAD-Request vor Wiedergabe | | Sprach-/Jahresfilter | Keine automatische Filterung | Automatisch (nur Deutsch, korrektes Jahr) | | Zielgruppe | Endnutzer in Kodi-GUI | Andere Addons / Automatisierung | --- ## 8. Einstellungen (Auszug) Alle Einstellungen befinden sich in `resources/settings.xml` und werden über `cConfig` gelesen. | Einstellung | Bedeutung | |---|---| | `plugin_{id}` | Site-Plugin aktiviert (`true`/`false`) | | `plugin_{id}.domain` | Überschreibt Standard-Domain des Plugins | | `plugin_{id}_status` | Letzter HTTP-Status-Code der Domain | | `plugin_{id}_checkdomain` | Domain-Prüfung für dieses Plugin aktiviert | | `global_search_{id}` | Globale Suche für dieses Plugin aktiviert | | `hosterSelect` | `Auto` (automatisch) oder `List` (manuelle Auswahl) | | `cacheTime` | HTML-Cache-Lebensdauer in Minuten (Standard: 360) | | `requestTimeout` | HTTP-Timeout in Sekunden (Standard: 10) | | `volatileHtmlCache` | In-Memory-Cache aktivieren | | `bypassDNSlock` | DNS-over-HTTPS für gesperrte Domains | | `TMDBMETA` | TMDB-Metadaten für Listeneinträge laden | | `metaOverwrite` | TMDB-Daten ersetzen statt ergänzen | | `prefLanguage` | Bevorzugte Sprache (`1` = Deutsch, `2` = Englisch) | | `preferedQuality` | Bevorzugte Videoqualität für Hoster-Sortierung | | `presortHoster` | Hoster nach Resolver-Priorität vorsortieren | | `maxHoster` | Maximale Anzahl Hoster in der Liste | | `jd_enabled` / `jd2_enabled` / `myjd_enabled` / `pyload_enabled` | Download-Manager aktivieren | | `githubUpdateResolver` | Resolver bei Kodi-Start aktualisieren | | `cacheDeltaDay` | HTML-Cache alle N Tage beim Start löschen | | `popup.update.notification` | Changelog-Popup beim Start anzeigen | --- ## 9. Abhängigkeiten ### plugin.video.xstream | Addon | Pflicht | Zweck | |---|---|---| | `script.module.requests` | Ja | HTTP (via scraper-Modul) | | `script.module.resolveurl` ≥ 5.1.173 | Ja | Hoster-Auflösung | | `script.module.six` ≥ 1.11.0 | Ja | Python-2/3-Kompatibilität | | `script.module.pyaes` | Ja | AES-Entschlüsselung (Blazingfast-Bypass) | | `script.module.inputstreamhelper` ≥ 0.3.3 | Optional | HLS/DASH über inputstream.adaptive | | `repository.resolveurl` | Optional | Resolver-Repository | | `script.module.pydevd` / `script.module.web-pdb` | Optional | Debugging | ### script.module.xstreamscraper Zusätzlich zu den obigen: | Addon | Pflicht | Zweck | |---|---|---| | `plugin.video.xstream` | Ja | Nutzt dessen Site-Plugins direkt | | `plugin.video.vavooto` | Ja | Vavoo-Stream-Integration | --- ## 10. Bekannte Einschränkungen - **Cloudflare**: Seiten mit aktivem Cloudflare-Schutz werden nicht automatisch umgangen. Der Nutzer erhält eine Fehlermeldung. - **Domain-Wechsel**: Wenn eine Streaming-Seite ihre Domain ändert, muss entweder der Kodi-Start abgewartet werden (Domain-Prüfung aktualisiert die Einstellung automatisch via Redirect-Erkennung) oder die Domain muss manuell in den xStream-Einstellungen angepasst werden. - **Thread-Safety in Global Search**: Die Site-Plugins werden parallel in Threads ausgeführt. Plugins, die globalen Zustand modifizieren, können Race-Conditions verursachen. - **scraper.py `plugin_` Funktion**: Der Scraper importiert Site-Plugins dynamisch und setzt `settings.urlparams` als globalen Zustand – dies ist nicht thread-sicher und kann bei paralleler Nutzung zu Problemen führen. - **Kodi-Version**: Der Code enthält explizite Versionsprüfungen (`kodi_version[:2] < '20'` usw.). Unter Kodi 19 wird `setInfo` statt `getVideoInfoTag()` verwendet.