DAP-74: Technische Dokumentation der xStream Kodi-Addons erstellt

Analysiert und dokumentiert:
- plugin.video.xstream: Startup-Sequenz, URL-Routing, Site-Plugin-Konvention,
  Browsing- und Hoster-Auflösungsflow, globale Suche
- script.module.xstreamscraper: automatische TMDB-basierte Stream-Suche
- HTTP-Request-Handler: Caching, Cookie-Verwaltung, Anti-Bot-Mechanismen
- Plugin-Handler, GUI-Schicht, Einstellungsreferenz, Abhängigkeiten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Task-Orchestrator
2026-08-17 21:30:12 +02:00
parent 7b8f40d9da
commit 28b9afb234

431
DOKUMENTATION.md Normal file
View File

@@ -0,0 +1,431 @@
# 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 403503: 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-name>'
└── plugin = __import__('<plugin-name>')
└── 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 (050 %)
4. collectMode = False
5. Ergebnisse nach Site-Name sortiert als Listeneinträge hinzufügen (50100 %)
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 403503: 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.