Migrate Xstream Kodi plugin sources from Nextcloud Kino-Projekt
- plugin.video.xstream: Kodi video addon - script.module.xstreamscraper: scraper module Source: Nextcloud Botomir/Projekte/Angefangen/Kino-Projekt/
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.paperclip/
|
||||
@@ -1,5 +1,10 @@
|
||||
# Xstream-Standalone
|
||||
|
||||
Standalone-Migration des Xstream-Kodi-Plugins fuer die Kino-/Jellyfin-Medienbaustein-Initiative.
|
||||
Standalone-Migration des Xstream-Kodi-Plugins aus dem Kino-Projekt (Nextcloud).
|
||||
|
||||
Status: Backlog / initiale Repo-Struktur.
|
||||
## Inhalt
|
||||
|
||||
- plugin.video.xstream — Kodi Video-Addon
|
||||
- script.module.xstreamscraper — zugehoeriges Scraper-Modul
|
||||
|
||||
Quelle: Nextcloud Botomir/Projekte/Angefangen/Kino-Projekt/. Migration am 2026-08-17 durchgefuehrt.
|
||||
|
||||
76
plugin.video.xstream/ScraperInfo.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
Scaper Aufbau
|
||||
-------------
|
||||
|
||||
# Imports bitte alle oben für den Überblick !
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser, cUtil
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
# Namen für Quellen
|
||||
|
||||
SITE_IDENTIFIER = ''
|
||||
SITE_NAME = ''
|
||||
SITE_ICON = ''
|
||||
URL_MAIN = ''
|
||||
URL_MOVIES = ''
|
||||
URL_POPULAR_MOVIES = URL_MAIN + ''
|
||||
URL_KINO = URL_MAIN + ''
|
||||
URL_SERIES = URL_MAIN + ''
|
||||
URL_POPULAR_SERIES = URL_MAIN + ''
|
||||
URL_POPULAR = URL_MAIN + ''
|
||||
URL_GENRE = URL_MAIN + ''
|
||||
URL_SEARCH = URL_MAIN + ''
|
||||
URL_LOGIN = URL_MAIN + ''
|
||||
URL_ENGLISH = URL_MAIN + ''
|
||||
URL_NEW = URL_MAIN + ''
|
||||
URL_ANIMATION = URL_MAIN + ''
|
||||
URL_DOKU = URL_MAIN + ''
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
# Einbau der Sprachfunktion in Indexseiten
|
||||
|
||||
- sLanguage = cConfig().getSetting('prefLanguage') # Sprachauswahl aus settings.xml daraus eine Standard def schreiben
|
||||
params.setParam('sLanguage', sLanguage)
|
||||
0 = Alle Sprachen
|
||||
1 = Deutsch
|
||||
2 = English
|
||||
3 = Japanisch
|
||||
|
||||
# z.B. beim Menü
|
||||
def load():
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
# Spracheinstellung aus der settings.xml
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if sLanguage == '1': # 1 = Deutsch
|
||||
sLanguage = '2'
|
||||
elif sLanguage == '2': # 2 = Englisch
|
||||
sLanguage = '3'
|
||||
else:
|
||||
sLanguage = 'all' # 0 = Alle Sprachen
|
||||
params.setParam('sLanguage', sLanguage)
|
||||
params.setParam('sUrl', URL_KINO)
|
||||
cGui().addFolder(cGuiElement('Aktuelle Kinofilme', SITE_IDENTIFIER, 'showEntries'), params) # cConfig().getLocalizedString(30500) setzen für den jeweiligen Eintrag
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30500), SITE_IDENTIFIER, 'showEntries'), params)
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
# Einbau der Qualitätfunktion in Indexseiten
|
||||
|
||||
|
||||
- sQuality = cConfig().getSetting('preferedQuality') # Qualitätsauswahl aus settings.xml daraus eine Standard def schreiben
|
||||
params.setParam('sQuality', sQuality)
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
# Einbau der P18 funktion in Indexseiten
|
||||
|
||||
- sAdult = cConfig().getSetting('showAdult') # Adult An / Aus aus settings.xml daraus eine Standard def schreiben wie bei SITE_GLOBAL_SEARCH z.B.
|
||||
params.setParam('sAdult', sAdult)
|
||||
35
plugin.video.xstream/addon.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.xstream" name="xStream" version="2026.02.11" provider-name="Michaz">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="3.0.0"/>
|
||||
<import addon="script.module.requests"/>
|
||||
<import addon="script.module.resolveurl" version="5.1.173"/>
|
||||
<import addon="script.module.six" version="1.11.0"/>
|
||||
<import addon="script.module.pyaes"/>
|
||||
<import addon="repository.resolveurl" optional="true"/>
|
||||
<import addon="script.module.inputstreamhelper" version="0.3.3" optional="true"/>
|
||||
<import addon="script.module.pydevd" optional="true" />
|
||||
<import addon="script.module.web-pdb" optional="true" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||
<provides>video</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="service.py" start="startup" />
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="de">xStream</summary>
|
||||
<summary lang="en_GB">xStream</summary>
|
||||
<description lang="de">xStream ist ein Video-Addon für Kodi. xStream dient als Suchmaschine für Filme und Serien auf verschiedenen Webseiten.</description>
|
||||
<description lang="en_GB">xStream is a video add-on for Kodi. xStream serves as a search engine for films and series on various websites.</description>
|
||||
<disclaimer lang="de">Sie können dieses Video Addon gerne verwenden. Der bereitgestellte Inhalt der Webseiten steht in keinem Bezug zu xStream oder den Entwicklern! Weitere Informationen finden Sie in unserer Wiki und FaQ.</disclaimer>
|
||||
<disclaimer lang="en_GB">You are welcome to use this video addon. The content provided on the websites has no connection to xStream or the developers! For more information, see our Wiki and FaQ.</disclaimer>
|
||||
<language>de en</language>
|
||||
<platform>all</platform>
|
||||
<license>GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007</license>
|
||||
<assets>
|
||||
<icon>resources/icon.png</icon>
|
||||
<fanart>resources/fanart.jpg</fanart>
|
||||
<banner>resources/banner.png</banner>
|
||||
<clearlogo>resources/clearlogo.png</clearlogo>
|
||||
</assets>
|
||||
</extension>
|
||||
</addon>
|
||||
40
plugin.video.xstream/default.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
def main():
|
||||
from xstream import parseUrl
|
||||
from os.path import join
|
||||
from sys import path
|
||||
import platform
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
from xbmcvfs import translatePath
|
||||
|
||||
_addonPath_ = translatePath(cConfig().getAddonInfo('path'))
|
||||
path.append(join(_addonPath_, 'resources', 'lib'))
|
||||
path.append(join(_addonPath_, 'resources', 'lib', 'gui'))
|
||||
path.append(join(_addonPath_, 'resources', 'lib', 'handler'))
|
||||
path.append(join(_addonPath_, 'resources', 'art', 'sites'))
|
||||
path.append(join(_addonPath_, 'resources', 'art'))
|
||||
path.append(join(_addonPath_, 'sites'))
|
||||
|
||||
LOGMESSAGE = cConfig().getLocalizedString(30166)
|
||||
log('-----------------------------------------------------------------------', LOGNOTICE)
|
||||
log(LOGMESSAGE + ' -> [default]: Start xStream Log, Version %s ' % cConfig().getAddonInfo('version'), LOGNOTICE)
|
||||
log(LOGMESSAGE + ' -> [default]: Python-Version: %s' % platform.python_version(), LOGNOTICE)
|
||||
|
||||
try:
|
||||
parseUrl()
|
||||
except Exception as e:
|
||||
if str(e) == 'UserAborted':
|
||||
log(LOGMESSAGE + ' -> [default]: User aborted list creation', LOGNOTICE)
|
||||
else:
|
||||
import traceback
|
||||
import xbmcgui
|
||||
log(traceback.format_exc(), LOGNOTICE)
|
||||
value = (str(e.__class__.__name__) + ' : ' + str(e), str(traceback.format_exc().splitlines()[-3].split('addons')[-1]))
|
||||
dialog = xbmcgui.Dialog().ok(cConfig().getLocalizedString(257), str(value)) # Error
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
plugin.video.xstream/resources/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
plugin.video.xstream/resources/art/manuel_update.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
plugin.video.xstream/resources/art/no_cover.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
plugin.video.xstream/resources/art/plugin_info.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
plugin.video.xstream/resources/art/resolveurl_settings.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
plugin.video.xstream/resources/art/search.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
plugin.video.xstream/resources/art/settings.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
plugin.video.xstream/resources/art/sites/Vorlage.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
plugin.video.xstream/resources/art/sites/aniworld.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
plugin.video.xstream/resources/art/sites/api.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
plugin.video.xstream/resources/art/sites/burningseries.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
plugin.video.xstream/resources/art/sites/dokus.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
plugin.video.xstream/resources/art/sites/einschalten.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
plugin.video.xstream/resources/art/sites/filmpalast.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
plugin.video.xstream/resources/art/sites/hdfilme.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
plugin.video.xstream/resources/art/sites/hdfilme_1.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
plugin.video.xstream/resources/art/sites/internetarchive.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
plugin.video.xstream/resources/art/sites/kids_tube.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
plugin.video.xstream/resources/art/sites/kinoger.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
plugin.video.xstream/resources/art/sites/kinokistetech.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
plugin.video.xstream/resources/art/sites/kinox.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
plugin.video.xstream/resources/art/sites/kkiste.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
plugin.video.xstream/resources/art/sites/megakino.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
plugin.video.xstream/resources/art/sites/moflix-stream.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
plugin.video.xstream/resources/art/sites/movie2k.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
plugin.video.xstream/resources/art/sites/movie4k.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
plugin.video.xstream/resources/art/sites/netzkino.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
plugin.video.xstream/resources/art/sites/serienstream.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
plugin.video.xstream/resources/art/sites/streamcloud.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
plugin.video.xstream/resources/art/sites/topstreamfilm.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
plugin.video.xstream/resources/art/sites/vod_huhu.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
plugin.video.xstream/resources/art/sites/vod_kool.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
plugin.video.xstream/resources/art/sites/vod_oha.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
plugin.video.xstream/resources/art/sites/vod_vavoo.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
plugin.video.xstream/resources/art/sites/xcinetop.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
plugin.video.xstream/resources/art/vod.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
plugin.video.xstream/resources/art/xstream_settings.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
plugin.video.xstream/resources/banner.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
plugin.video.xstream/resources/clearlogo.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
plugin.video.xstream/resources/fanart.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
plugin.video.xstream/resources/icon.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
1
plugin.video.xstream/resources/lib/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
81
plugin.video.xstream/resources/lib/captcha/captcha_helper.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
#
|
||||
#05.07.2025 Heptamer
|
||||
#
|
||||
# Captcha Helper
|
||||
|
||||
from resources.lib.tools import logger
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.captcha.captcha_solver import CaptchaSolver
|
||||
|
||||
|
||||
def solve_recaptcha(site_key, page_url, provider=None):
|
||||
"""
|
||||
Hilfsfunktion zum Lösen von reCAPTCHAs
|
||||
|
||||
Args:
|
||||
site_key (str): Der Google reCAPTCHA Site-Key
|
||||
page_url (str): Die URL der Seite mit dem Captcha
|
||||
provider (str): Optional, überschreibt den konfigurierten Provider
|
||||
|
||||
|
||||
Returns:
|
||||
str: Der Captcha-Lösungstoken
|
||||
"""
|
||||
# Bestimme den Provider aus den Einstellungen oder dem Parameter
|
||||
if provider is None:
|
||||
provider = cConfig().getSetting('captcha.provider', '2captcha')
|
||||
|
||||
# API-Key abhängig vom Provider holen
|
||||
if provider == '2captcha':
|
||||
api_key = cConfig().getSetting('2captcha.pass')
|
||||
if not api_key:
|
||||
logger.error("2Captcha API-Schlüssel ist nicht gesetzt")
|
||||
return None
|
||||
elif provider == '9kw':
|
||||
api_key = cConfig().getSetting('9kw.pass')
|
||||
if not api_key:
|
||||
logger.error("9kw.eu API-Schlüssel ist nicht gesetzt")
|
||||
return None
|
||||
###############################################################
|
||||
else:
|
||||
logger.error(f"Unbekannter Captcha-Provider '{provider}'")
|
||||
return None
|
||||
|
||||
# Timeout aus Einstellungen holen
|
||||
timeout = int(cConfig().getSetting('captcha.timeout', '120'))
|
||||
|
||||
# Solver erstellen und Captcha lösen
|
||||
solver = CaptchaSolver(api_key=api_key, provider=provider, timeout=timeout)
|
||||
try:
|
||||
return solver.solve_recaptcha_v2(site_key, page_url)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Lösen des reCAPTCHAs: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def extract_recaptcha_sitekey(html_content):
|
||||
"""
|
||||
Extrahiert den reCAPTCHA Site-Key aus dem HTML-Inhalt.
|
||||
|
||||
Args:
|
||||
html_content (str): Der HTML-Inhalt der Webseite
|
||||
|
||||
Returns:
|
||||
str: Der extrahierte Site-Key oder None
|
||||
"""
|
||||
from resources.lib.tools import cParser
|
||||
|
||||
patterns = [
|
||||
r"series\.init\s*\(\s*\d+\s*,\s*\d+\s*,\s*'([^']+)'\s*\)\s*;", # BurningSeries-Muster
|
||||
r'data-sitekey="([^"]+)"', # Standard reCAPTCHA-Muster
|
||||
r"grecaptcha.execute\s*\(\s*'([^']+)'" # Unsichtbares reCAPTCHA
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
isMatch, site_key = cParser.parseSingleResult(html_content, pattern)
|
||||
if isMatch:
|
||||
return site_key
|
||||
|
||||
return None
|
||||
194
plugin.video.xstream/resources/lib/captcha/captcha_solver.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
#
|
||||
#05.07.2025 Heptamer
|
||||
#
|
||||
# Captcha Bibliothek
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger
|
||||
from resources.lib.config import cConfig
|
||||
|
||||
|
||||
class CaptchaSolver:
|
||||
"""
|
||||
Eine wiederverwendbare Klasse zum Lösen verschiedener Captcha-Typen.
|
||||
Unterstützt aktuell 2Captcha für reCAPTCHA v2.
|
||||
"""
|
||||
|
||||
def __init__(self, api_key=None, provider=None, timeout=120):
|
||||
"""
|
||||
Initialisiert den Captcha-Solver.
|
||||
|
||||
Args:
|
||||
api_key (str): API-Schlüssel für den Captcha-Dienst
|
||||
provider (str): Captcha-Dienst ('2captcha', '9kw', etc.)
|
||||
timeout (int): Maximale Wartezeit in Sekunden
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.provider = provider
|
||||
self.timeout = timeout
|
||||
self.is_alive = True # Für 9kw-Abbruch
|
||||
|
||||
def set_api_key(self, api_key):
|
||||
"""Setzt den API-Schlüssel"""
|
||||
self.api_key = api_key
|
||||
|
||||
def set_provider(self, provider):
|
||||
"""Setzt den Provider"""
|
||||
self.provider = provider
|
||||
|
||||
def set_kill(self):
|
||||
"""Abbruch-Signal für 9kw"""
|
||||
self.is_alive = False
|
||||
|
||||
def solve_recaptcha_v2(self, site_key, page_url):
|
||||
"""
|
||||
Löst ein reCAPTCHA v2.
|
||||
|
||||
Args:
|
||||
site_key (str): Der Google reCAPTCHA Site-Key
|
||||
page_url (str): Die URL der Seite mit dem Captcha
|
||||
|
||||
Returns:
|
||||
str: Der Captcha-Lösungstoken
|
||||
"""
|
||||
if self.provider == '2captcha':
|
||||
return self._solve_with_2captcha(site_key, page_url)
|
||||
# Hier können später weitere Provider hinzugefügt werden
|
||||
elif self.provider == '9kw':
|
||||
return self._solve_with_9kw(site_key, page_url)
|
||||
else:
|
||||
raise ValueError(f"Captcha-Provider '{self.provider}' wird nicht unterstützt")
|
||||
|
||||
def _solve_with_2captcha(self, site_key, page_url):
|
||||
"""2Captcha-Implementation für reCAPTCHA v2"""
|
||||
if not self.api_key:
|
||||
xbmcgui.Dialog().ok(cConfig().getLocalizedString(30241), cConfig().getLocalizedString(30291))
|
||||
return None
|
||||
|
||||
params = {
|
||||
'key': self.api_key,
|
||||
'method': 'userrecaptcha',
|
||||
'googlekey': site_key,
|
||||
'pageurl': page_url,
|
||||
'json': 1,
|
||||
}
|
||||
|
||||
# Captcha-Anfrage senden
|
||||
captcha_request = cRequestHandler('https://2captcha.com/in.php', caching=False, method='POST',
|
||||
data=json.dumps(params))
|
||||
captcha_request.addHeaderEntry('Content-Type', 'application/json')
|
||||
response_text = captcha_request.request()
|
||||
|
||||
try:
|
||||
json_response = json.loads(response_text)
|
||||
except json.JSONDecodeError:
|
||||
raise Exception(f"Ungültige Antwort vom Captcha-Dienst: {response_text}")
|
||||
|
||||
if 'request' not in json_response:
|
||||
raise Exception(f"Ungültige Antwort vom Captcha-Dienst: {json_response}")
|
||||
|
||||
captcha_id = json_response['request']
|
||||
logger.info(f'Captcha-Anfrage gesendet mit ID: {captcha_id}')
|
||||
|
||||
# Auf die Lösung warten
|
||||
return self._get_2captcha_result(captcha_id)
|
||||
|
||||
def _get_2captcha_result(self, captcha_id):
|
||||
"""Wartet auf und holt das Ergebnis von 2Captcha"""
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
request = cRequestHandler(
|
||||
f"https://2captcha.com/res.php?key={self.api_key}&json=1&action=get&id={captcha_id}",
|
||||
caching=False
|
||||
)
|
||||
request.addHeaderEntry('Content-Type', 'application/json')
|
||||
response_text = request.request()
|
||||
|
||||
try:
|
||||
json_res = json.loads(response_text)
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"Fehler beim Parsen der 2Captcha-Antwort: {response_text}")
|
||||
raise Exception(f"Ungültige JSON-Antwort: {response_text}")
|
||||
|
||||
if json_res.get('status') == 1:
|
||||
return json_res.get('request')
|
||||
elif json_res.get('request') != 'CAPCHA_NOT_READY':
|
||||
error_msg = f"Fehler beim Lösen des Captchas: {json.dumps(json_res, indent=2)} \nID: {captcha_id}"
|
||||
logger.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
# Timeout-Überprüfung
|
||||
if time.time() - start_time >= self.timeout:
|
||||
error_msg = f"Timeout beim Warten auf Captcha-Lösung (ID: {captcha_id})"
|
||||
logger.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
# Kurze Pause zwischen den Anfragen
|
||||
time.sleep(2)
|
||||
|
||||
def _solve_with_9kw(self, site_key, page_url):
|
||||
"""9kw.eu-Implementation für reCAPTCHA v2"""
|
||||
if not self.api_key:
|
||||
xbmcgui.Dialog().ok(cConfig().getLocalizedString(30241), cConfig().getLocalizedString(30291))
|
||||
return None
|
||||
|
||||
# Prüfen, ob Selfsolve aktiviert ist (über Addon-Einstellungen)
|
||||
selfsolve = '1' if cConfig().getSetting('9kw.SelfSolve', 'false') == 'true' else '0'
|
||||
|
||||
post = {
|
||||
'apikey': self.api_key,
|
||||
'action': 'usercaptchaupload',
|
||||
'interactive': '1',
|
||||
'json': '1',
|
||||
'file-upload-01': site_key,
|
||||
'oldsource': 'recaptchav2',
|
||||
'pageurl': page_url,
|
||||
'maxtimeout': str(self.timeout)
|
||||
}
|
||||
|
||||
if selfsolve == '1':
|
||||
post['selfsolve'] = '1'
|
||||
|
||||
token = ''
|
||||
|
||||
try:
|
||||
URL = 'https://www.9kw.eu/index.cgi'
|
||||
request = cRequestHandler(URL, caching=False, method='POST', data=post)
|
||||
response_text = request.request()
|
||||
|
||||
if response_text:
|
||||
data = json.loads(response_text)
|
||||
if 'captchaid' in data:
|
||||
captcha_id = data['captchaid']
|
||||
tries = 0
|
||||
|
||||
# Warte auf Captcha-Lösung
|
||||
while tries < self.timeout and self.is_alive:
|
||||
tries += 1
|
||||
xbmc.sleep(1000) # 1 Sekunde warten
|
||||
|
||||
check_url = f"https://www.9kw.eu/index.cgi?action=usercaptchacorrectdata&id={captcha_id}&apikey={self.api_key}&json=1"
|
||||
result_request = cRequestHandler(check_url, caching=False)
|
||||
result_text = result_request.request()
|
||||
|
||||
if result_text:
|
||||
try:
|
||||
result_data = json.loads(result_text)
|
||||
token = result_data.get('answer', '')
|
||||
if token:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Parsen der 9kw-Antwort: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"9kw Error: {str(e)}")
|
||||
|
||||
return token
|
||||
73
plugin.video.xstream/resources/lib/config.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import xbmcaddon
|
||||
import resolveurl as resolver
|
||||
import threading
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from xbmc import LOGWARNING, log
|
||||
|
||||
class cConfig:
|
||||
_instances = {} # Cache for addon_id -> cConfig instance
|
||||
_addon_cache = {} # Cache for addon_id -> xbmcaddon.Addon instance
|
||||
_settings_lock = threading.Lock()
|
||||
|
||||
# singleton implementation
|
||||
def __new__(cls, *args, **kwargs):
|
||||
addon_id = kwargs.get('addon_id') or (args[0] if args else xbmcaddon.Addon().getAddonInfo('id'))
|
||||
if addon_id not in cls._instances:
|
||||
instance = super(cConfig, cls).__new__(cls)
|
||||
instance._addon_id = addon_id
|
||||
if addon_id not in cls._addon_cache:
|
||||
cls._addon_cache[addon_id] = xbmcaddon.Addon(addon_id)
|
||||
instance.__addon = cls._addon_cache[addon_id]
|
||||
instance.__aLanguage = instance.__addon.getLocalizedString
|
||||
cls._instances[addon_id] = instance
|
||||
return cls._instances[addon_id]
|
||||
|
||||
def showSettingsWindow(self):
|
||||
self.__addon.openSettings()
|
||||
|
||||
def getSetting(self, sName, default=''):
|
||||
result = self.__addon.getSetting(sName)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
return default
|
||||
|
||||
def getSettingString(self, sName, default=''):
|
||||
result = self.__addon.getSetting(sName)
|
||||
if result:
|
||||
return str(result)
|
||||
else:
|
||||
return default
|
||||
|
||||
def setSetting(self, id, value):
|
||||
if id and value:
|
||||
with cConfig._settings_lock:
|
||||
self.__addon.setSetting(id, value)
|
||||
|
||||
def getAddonInfo(self, sName):
|
||||
result = self.__addon.getAddonInfo(sName)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
return ''
|
||||
|
||||
def getLocalizedString(self, sCode):
|
||||
return self.__aLanguage(sCode)
|
||||
|
||||
def isBlockedHoster(self, domain, checkResolver=True ):
|
||||
domain = urlparse(domain).path if urlparse(domain).hostname == None else urlparse(domain).hostname
|
||||
hostblockDict = ['flashx','streamlare','evoload', 'hd-stream', 'vivo'] # permanenter Block
|
||||
blockedHoster = cConfig().getSetting('blockedHoster').split(',') # aus setting.xml blockieren
|
||||
if len(blockedHoster) <= 1: blockedHoster = cConfig().getSetting('blockedHoster').split()
|
||||
for i in blockedHoster: hostblockDict.append(i.lower())
|
||||
for i in hostblockDict:
|
||||
if i in domain.lower() or i.split('.')[0] in domain.lower(): return True, domain
|
||||
if checkResolver: # Überprüfung in resolveUrl
|
||||
if resolver.relevant_resolvers(domain=domain) == []:
|
||||
log('[xStream] -> [isblockedHoster]: In resolveUrl no domain for url: %s' % domain, LOGWARNING)
|
||||
return True, domain # Domain nicht in resolveUrl gefunden
|
||||
return False, domain
|
||||
127
plugin.video.xstream/resources/lib/download.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import os
|
||||
import time
|
||||
import xbmcgui
|
||||
|
||||
from resources.lib import utils
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
from xbmcvfs import translatePath
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
class cDownload:
|
||||
def __createProcessDialog(self, downloadDialogTitle):
|
||||
if cConfig().getSetting('backgrounddownload') == 'true':
|
||||
oDialog = xbmcgui.DialogProgressBG()
|
||||
else:
|
||||
oDialog = xbmcgui.DialogProgress()
|
||||
oDialog.create(downloadDialogTitle)
|
||||
self.__oDialog = oDialog
|
||||
|
||||
|
||||
def __createDownloadFilename(self, filename):
|
||||
filename = filename.replace(' ', '_')
|
||||
return filename
|
||||
|
||||
|
||||
def download(self, url, sTitle, showDialog=True, downloadDialogTitle=cConfig().getLocalizedString(30245)):
|
||||
sTitle = '%s' % sTitle
|
||||
self.__processIsCanceled = False
|
||||
try:
|
||||
header = dict([item.split('=') for item in (url.split('|')[1]).split('&')])
|
||||
except Exception:
|
||||
header = {}
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [download]: Header for download: %s' % header, LOGNOTICE)
|
||||
url = url.split('|')[0]
|
||||
sTitle = self.__createTitle(url, sTitle)
|
||||
self.__sTitle = self.__createDownloadFilename(sTitle)
|
||||
if showDialog:
|
||||
self.__sTitle = cGui().showKeyBoard(self.__sTitle, sHeading=cConfig().getLocalizedString(30290))
|
||||
if self.__sTitle != False and len(self.__sTitle) > 0:
|
||||
sPath = cConfig().getSetting('download-folder')
|
||||
if sPath == '':
|
||||
dialog = xbmcgui.Dialog()
|
||||
sPath = dialog.browse(3, 'Downloadfolder', 'files', '')
|
||||
if sPath != '':
|
||||
sDownloadPath = translatePath(sPath + '%s' % (self.__sTitle,))
|
||||
self.__prepareDownload(url, header, sDownloadPath, downloadDialogTitle)
|
||||
elif self.__sTitle != False:
|
||||
temp_dir = os.path.join(translatePath(cConfig().getAddonInfo('profile')))
|
||||
if not os.path.isdir(temp_dir):
|
||||
os.makedirs(os.path.join(temp_dir))
|
||||
self.__prepareDownload(url, header, os.path.join(temp_dir, sTitle), downloadDialogTitle)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [download]: download completed', LOGNOTICE)
|
||||
|
||||
|
||||
def __prepareDownload(self, url, header, sDownloadPath, downloadDialogTitle):
|
||||
try:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [download]: download file: ' + str(url) + ' to ' + str(sDownloadPath), LOGNOTICE)
|
||||
self.__createProcessDialog(downloadDialogTitle)
|
||||
request = Request(url, headers=header)
|
||||
self.__download(urlopen(request, timeout=240), sDownloadPath)
|
||||
except Exception as e:
|
||||
log(e)
|
||||
self.__oDialog.close()
|
||||
|
||||
|
||||
def __download(self, oUrlHandler, fpath):
|
||||
headers = oUrlHandler.info()
|
||||
iTotalSize = -1
|
||||
if 'content-length' in headers:
|
||||
iTotalSize = (headers['Content-Length'])
|
||||
chunk = 4096
|
||||
#f = open(r'%s' % fpath, 'wb')
|
||||
import xbmcvfs
|
||||
f = xbmcvfs.File(fpath, 'w')
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [download]: start download', LOGNOTICE)
|
||||
try:
|
||||
iCount = 0
|
||||
self._startTime = time.time()
|
||||
while 1:
|
||||
iCount = iCount + 1
|
||||
data = oUrlHandler.read(chunk)
|
||||
if not data or self.__processIsCanceled == True:
|
||||
break
|
||||
f.write(data)
|
||||
self.__stateCallBackFunction(iCount, chunk, iTotalSize)
|
||||
f.close()
|
||||
|
||||
except:
|
||||
log(cConfig().getLocalizedString(30166) + '-> [download]: download failed', LOGNOTICE)
|
||||
f.close()
|
||||
|
||||
|
||||
def __createTitle(self, sUrl, sTitle):
|
||||
aTitle = sTitle.rsplit('.')
|
||||
if len(aTitle) > 1:
|
||||
return sTitle
|
||||
aUrl = sUrl.rsplit('.')
|
||||
if len(aUrl) > 1:
|
||||
sSuffix = aUrl[-1]
|
||||
sTitle = sTitle + '.' + sSuffix
|
||||
return sTitle
|
||||
|
||||
|
||||
def __stateCallBackFunction(self, iCount, iBlocksize, iTotalSize):
|
||||
timedif = time.time() - self._startTime
|
||||
currentLoaded = int(iCount) * iBlocksize
|
||||
iPercent = (currentLoaded * 100 // int(iTotalSize))
|
||||
if timedif > 0.0:
|
||||
avgSpd = (currentLoaded // timedif // 1024.0)
|
||||
else:
|
||||
avgSpd = 5
|
||||
value = self.__sTitle, str('%s/%s@%dKB/s' % (self.__formatFileSize(currentLoaded), self.__formatFileSize(iTotalSize), avgSpd))
|
||||
self.__oDialog.update(iPercent, str(value))
|
||||
if cConfig().getSetting('backgrounddownload') == 'false' and self.__oDialog.iscanceled():
|
||||
self.__processIsCanceled = True
|
||||
self.__oDialog.close()
|
||||
|
||||
|
||||
def __formatFileSize(self, iBytes):
|
||||
iBytes = int(iBytes)
|
||||
if iBytes == 0:
|
||||
return '%.*f %s' % (2, 0, 'MB')
|
||||
return '%.*f %s' % (2, int(iBytes) // (1024 * 1024.0), 'MB')
|
||||
1
plugin.video.xstream/resources/lib/gui/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
40
plugin.video.xstream/resources/lib/gui/contextElement.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
|
||||
|
||||
class cContextElement:
|
||||
def __init__(self):
|
||||
self.__sTitle = ''
|
||||
self.__oOutputParameterHandler = ParameterHandler()
|
||||
|
||||
def setFunction(self, sFunctionName):
|
||||
self.__sFunctionName = sFunctionName
|
||||
|
||||
def getFunction(self):
|
||||
return self.__sFunctionName
|
||||
|
||||
def setFile(self, sFile):
|
||||
self.__sFile = sFile
|
||||
|
||||
def getFile(self):
|
||||
return self.__sFile
|
||||
|
||||
def setTitle(self, sTitle):
|
||||
self.__sTitle = sTitle
|
||||
|
||||
def getTitle(self):
|
||||
return self.__sTitle
|
||||
|
||||
def setSiteName(self, sSiteName):
|
||||
self.__sSiteName = sSiteName
|
||||
|
||||
def getSiteName(self):
|
||||
return self.__sSiteName
|
||||
|
||||
def setOutputParameterHandler(self, oOutputParameterHandler):
|
||||
self.__oOutputParameterHandler = oOutputParameterHandler
|
||||
|
||||
def getOutputParameterHandler(self):
|
||||
return self.__oOutputParameterHandler
|
||||
418
plugin.video.xstream/resources/lib/gui/gui.py
Normal file
@@ -0,0 +1,418 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import sys
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
from resources.lib import utils
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.contextElement import cContextElement
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
|
||||
|
||||
class cGui:
|
||||
# This class "abstracts" a list of xbmc listitems.
|
||||
def __init__(self):
|
||||
try:
|
||||
self.pluginHandle = int(sys.argv[1])
|
||||
except:
|
||||
self.pluginHandle = 0
|
||||
try:
|
||||
self.pluginPath = sys.argv[0]
|
||||
except:
|
||||
self.pluginPath = ''
|
||||
self.isMetaOn = cConfig().getSetting('TMDBMETA') == 'true'
|
||||
if cConfig().getSetting('metaOverwrite') == 'true':
|
||||
self.metaMode = 'replace'
|
||||
else:
|
||||
self.metaMode = 'add'
|
||||
# for globalSearch or alterSearch
|
||||
self.globalSearch = False
|
||||
self._collectMode = False
|
||||
self._isViewSet = False
|
||||
self.searchResults = []
|
||||
|
||||
def addFolder(self, oGuiElement, params='', bIsFolder=True, iTotal=0, isHoster=False):
|
||||
# add GuiElement to Gui, adds listitem to a list
|
||||
# abort xbmc list creation if user requests abort
|
||||
if xbmc.Monitor().abortRequested():
|
||||
self.setEndOfDirectory(False)
|
||||
raise RuntimeError('UserAborted')
|
||||
# store result in list if we searched global for other sources
|
||||
if self._collectMode:
|
||||
import copy
|
||||
self.searchResults.append({'guiElement': oGuiElement, 'params': copy.deepcopy(params), 'isFolder': bIsFolder})
|
||||
return
|
||||
if not oGuiElement._isMetaSet and self.isMetaOn and oGuiElement._mediaType and iTotal < 100:
|
||||
tmdbID = params.getValue('tmdbID')
|
||||
if tmdbID:
|
||||
oGuiElement.getMeta(oGuiElement._mediaType, tmdbID, mode=self.metaMode)
|
||||
else:
|
||||
oGuiElement.getMeta(oGuiElement._mediaType, mode=self.metaMode)
|
||||
sUrl = self.__createItemUrl(oGuiElement, bIsFolder, params)
|
||||
#kasi
|
||||
try:
|
||||
if params.exist('trumb'): oGuiElement.setIcon(params.getValue('trumb'))
|
||||
except:
|
||||
pass
|
||||
|
||||
listitem = self.createListItem(oGuiElement)
|
||||
if not bIsFolder and cConfig().getSetting('hosterSelect') == 'List':
|
||||
bIsFolder = True
|
||||
if isHoster:
|
||||
bIsFolder = False
|
||||
listitem = self.__createContextMenu(oGuiElement, listitem, bIsFolder, sUrl)
|
||||
if not bIsFolder:
|
||||
listitem.setProperty('IsPlayable', 'true')
|
||||
xbmcplugin.addDirectoryItem(self.pluginHandle, sUrl, listitem, bIsFolder, iTotal)
|
||||
|
||||
def addNextPage(self, site, function, params=''):
|
||||
guiElement = cGuiElement(cConfig().getLocalizedString(30279), site, function)
|
||||
self.addFolder(guiElement, params)
|
||||
|
||||
def searchNextPage(self, sTitle, site, function, params=''):
|
||||
guiElement = cGuiElement(sTitle, site, function)
|
||||
self.addFolder(guiElement, params)
|
||||
|
||||
def createListItem(self, oGuiElement):
|
||||
itemValues = oGuiElement.getItemValues()
|
||||
itemTitle = oGuiElement.getTitle()
|
||||
infoString = ''
|
||||
if self.globalSearch: # Reihenfolge der zu anzeigenden GUI Elemente
|
||||
infoString += ' %s' % oGuiElement.getSiteName()
|
||||
if oGuiElement._sLanguage != '':
|
||||
infoString += ' (%s)' % oGuiElement._sLanguage
|
||||
if oGuiElement._sSubLanguage != '':
|
||||
infoString += ' *Sub: %s*' % oGuiElement._sSubLanguage
|
||||
if oGuiElement._sQuality != '':
|
||||
infoString += ' [%s]' % oGuiElement._sQuality
|
||||
if oGuiElement._sInfo != '':
|
||||
infoString += ' [%s]' % oGuiElement._sInfo
|
||||
# if self.globalSearch:
|
||||
# infoString += ' %s' % oGuiElement.getSiteName()
|
||||
if infoString:
|
||||
infoString = '[I]%s[/I]' % infoString
|
||||
itemValues['title'] = itemTitle + infoString
|
||||
try:
|
||||
if not 'plot' in str(itemValues) or itemValues['plot'] == '':
|
||||
itemValues['plot'] = ' ' #kasi Alt 255
|
||||
except:
|
||||
pass
|
||||
#listitem = xbmcgui.ListItem(itemTitle + infoString, oGuiElement.getIcon(), oGuiElement.getThumbnail())
|
||||
listitem = xbmcgui.ListItem(itemTitle + infoString)
|
||||
# Function: setInfo(type, infoLabels)
|
||||
# listitem.setInfo('video', { 'genre': 'Comedy' })
|
||||
listitem.setInfo(oGuiElement.getType(), itemValues)
|
||||
#Wenn Kodi 19, dann ignoriere setinfotagvideo
|
||||
kodi_version = xbmc.getInfoLabel('System.BuildVersion')
|
||||
if kodi_version[:2] > '19':
|
||||
self.setInfoTagVideo(oGuiElement, listitem)
|
||||
|
||||
listitem.setProperty('fanart_image', oGuiElement.getFanart())
|
||||
listitem.setArt({'icon': oGuiElement.getIcon(), 'thumb': oGuiElement.getThumbnail(), 'poster': oGuiElement.getThumbnail(), 'fanart': oGuiElement.getFanart()})
|
||||
aProperties = oGuiElement.getItemProperties()
|
||||
if len(aProperties) > 0:
|
||||
for sPropertyKey in aProperties.keys():
|
||||
listitem.setProperty(sPropertyKey, aProperties[sPropertyKey])
|
||||
return listitem
|
||||
|
||||
### ÄNDERUNG ANFANG ###
|
||||
def setInfoTagVideo(self, oGuiElement, listitem):
|
||||
itemValues = oGuiElement.getItemValues()
|
||||
vtag = listitem.getVideoInfoTag()
|
||||
|
||||
vtag.setMediaType(oGuiElement.getType())
|
||||
|
||||
# Titel ist bereits gesetzt und der Infostring geht hier verloren, wenn man den Titel erneut setzt
|
||||
#if 'title' in itemValues:
|
||||
# try:
|
||||
# vtag.setTitle(itemValues['title'])
|
||||
# except: pass
|
||||
if 'plot' in itemValues:
|
||||
try:
|
||||
vtag.setPlot(itemValues['plot'])
|
||||
except: pass
|
||||
if 'year' in itemValues:
|
||||
try:
|
||||
vtag.setYear(int(itemValues['year']))
|
||||
except: pass
|
||||
if 'season' in itemValues:
|
||||
try:
|
||||
vtag.setSeason(int(itemValues['season']))
|
||||
except: pass
|
||||
if 'episode' in itemValues:
|
||||
try:
|
||||
vtag.setEpisode(int(itemValues['episode']))
|
||||
except: pass
|
||||
if 'TVShowTitle' in itemValues:
|
||||
try:
|
||||
vtag.setTvShowTitle(itemValues['TVShowTitle'])
|
||||
except: pass
|
||||
if 'cast' in itemValues:
|
||||
try:
|
||||
vtag.setCast([xbmc.Actor(cast[0], cast[1], thumbnail=cast[2]) for cast in itemValues['cast']])
|
||||
except: pass
|
||||
if 'countries' in itemValues:
|
||||
try:
|
||||
vtag.setCountries(itemValues['countries'])
|
||||
except: pass
|
||||
if 'country' in itemValues:
|
||||
try:
|
||||
vtag.setCountries([itemValues['country']])
|
||||
except: pass
|
||||
if 'dateadded' in itemValues:
|
||||
try:
|
||||
vtag.setDateAdded(itemValues['dateadded'])
|
||||
except: pass
|
||||
if 'directors' in itemValues:
|
||||
try:
|
||||
vtag.setDirectors(itemValues['directors'])
|
||||
except: pass
|
||||
if 'duration' in itemValues:
|
||||
# minuten in sekunden umrechnen
|
||||
try:
|
||||
vtag.setDuration(int(itemValues['duration']) * 60)
|
||||
except: pass
|
||||
if 'rating' in itemValues:
|
||||
try:
|
||||
vtag.setRating(float(itemValues['rating']))
|
||||
except: pass
|
||||
if 'genre' in itemValues:
|
||||
try:
|
||||
vtag.setGenres(itemValues['genres'].split(' / '))
|
||||
except: pass
|
||||
if 'imdb_id' in itemValues:
|
||||
try:
|
||||
vtag.setUniqueID(str(itemValues['imdb_id']), 'imdb')
|
||||
except: pass
|
||||
if 'tmdb_id' in itemValues:
|
||||
try:
|
||||
vtag.setUniqueID(str(itemValues['tmdb_id']), 'tmdb')
|
||||
except: pass
|
||||
if 'originaltitle' in itemValues:
|
||||
try:
|
||||
vtag.setOriginalTitle(itemValues['originaltitle'])
|
||||
except: pass
|
||||
if 'trailer' in itemValues:
|
||||
try:
|
||||
vtag.setTrailer(itemValues['trailer'])
|
||||
except: pass
|
||||
if 'tagline' in itemValues:
|
||||
try:
|
||||
vtag.setTagLine(itemValues['tagline'])
|
||||
except: pass
|
||||
# Kodi versucht Bilder er Studios zu finden, was zu Fehlern im Logfile führt
|
||||
#if 'studio' in itemValues:
|
||||
# try:
|
||||
# vtag.setStudios(itemValues['studio'].split(' / '))
|
||||
# except: pass
|
||||
if 'premiered' in itemValues:
|
||||
try:
|
||||
vtag.setPremiered(itemValues['premiered'])
|
||||
except: pass
|
||||
### ÄNDERUNG ENDE ###
|
||||
|
||||
|
||||
def __createContextMenu(self, oGuiElement, listitem, bIsFolder, sUrl):
|
||||
contextmenus = []
|
||||
if len(oGuiElement.getContextItems()) > 0:
|
||||
for contextitem in oGuiElement.getContextItems():
|
||||
params = contextitem.getOutputParameterHandler()
|
||||
sParams = params.getParameterAsUri()
|
||||
sTest = "%s?site=%s&function=%s&%s" % (self.pluginPath, contextitem.getFile(), contextitem.getFunction(), sParams)
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s)" % (sTest,),)]
|
||||
itemValues = oGuiElement.getItemValues()
|
||||
contextitem = cContextElement()
|
||||
if oGuiElement._mediaType == 'movie' or oGuiElement._mediaType == 'tvshow':
|
||||
if cConfig().getSetting('xstream.trailer') == 'true':
|
||||
if not xbmc.getCondVisibility('System.HasAddon(%s)' % 'script.module.xstream.trailer'): # Schauen ob Addon installiert
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % 'script.module.xstream.trailer') # Addon installieren
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30027)) # Trailer Funktion
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(plugin://script.module.xstream.trailer/?action=play&name=%s&url=&language=)" % (itemValues['title'],),)]
|
||||
if oGuiElement._mediaType == 'movie' or oGuiElement._mediaType == 'tvshow':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30239)) # Erweiterte Info
|
||||
searchParams = {'searchTitle': oGuiElement.getTitle(), 'sMeta': oGuiElement._mediaType, 'sYear': oGuiElement._sYear}
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s?function=viewInfo&%s)" % (self.pluginPath, urlencode(searchParams),),)]
|
||||
if oGuiElement._mediaType == 'season' or oGuiElement._mediaType == 'episode':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30241)) # Info
|
||||
contextmenus += [(contextitem.getTitle(), cConfig().getLocalizedString(30242),)] # Action(Info)
|
||||
# search for alternative source
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30243)) # Weitere Quellen
|
||||
searchParams = {'searchTitle': oGuiElement.getTitle()}
|
||||
if 'imdb_id' in itemValues:
|
||||
searchParams['searchImdbID'] = itemValues['imdb_id']
|
||||
contextmenus += [(contextitem.getTitle(), "Container.Update(%s?function=searchAlter&%s)" % (self.pluginPath, urlencode(searchParams),),)]
|
||||
if 'imdb_id' in itemValues and 'title' in itemValues:
|
||||
metaParams = {}
|
||||
if itemValues['title']:
|
||||
metaParams['title'] = oGuiElement.getTitle()
|
||||
if 'mediaType' in itemValues and itemValues['mediaType']:
|
||||
metaParams['mediaType'] = itemValues['mediaType']
|
||||
elif 'TVShowTitle' in itemValues and itemValues['TVShowTitle']:
|
||||
metaParams['mediaType'] = 'tvshow'
|
||||
else:
|
||||
metaParams['mediaType'] = 'movie'
|
||||
if 'season' in itemValues and itemValues['season'] and int(itemValues['season']) > 0:
|
||||
metaParams['season'] = itemValues['season']
|
||||
metaParams['mediaType'] = 'season'
|
||||
if 'episode' in itemValues and itemValues['episode'] and int(itemValues['episode']) > 0 and 'season' in itemValues and itemValues['season'] and int(itemValues['season']):
|
||||
metaParams['episode'] = itemValues['episode']
|
||||
metaParams['mediaType'] = 'episode'
|
||||
|
||||
# context options for movies or episodes
|
||||
if not bIsFolder:
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30244)) # Playlist hinzufügen
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=enqueue)" % (sUrl,),)]
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30245)) # Download
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=download)" % (sUrl,),)]
|
||||
if cConfig().getSetting('jd_enabled') == 'true':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30246)) # send JD
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=jd)" % (sUrl,),)]
|
||||
if cConfig().getSetting('jd2_enabled') == 'true':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30247)) # Send JD2
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=jd2)" % (sUrl,),)]
|
||||
if cConfig().getSetting('myjd_enabled') == 'true':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30248)) # Send myjd
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=myjd)" % (sUrl,),)]
|
||||
if cConfig().getSetting('pyload_enabled') == 'true':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30249)) # Send Pyload
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=pyload)" % (sUrl,),)]
|
||||
if cConfig().getSetting('hosterSelect') == 'Auto':
|
||||
contextitem.setTitle(cConfig().getLocalizedString(30149)) # select Hoster
|
||||
contextmenus += [(contextitem.getTitle(), "RunPlugin(%s&playMode=play&manual=1)" % (sUrl,),)]
|
||||
listitem.addContextMenuItems(contextmenus)
|
||||
# listitem.addContextMenuItems(contextmenus, True)
|
||||
return listitem
|
||||
|
||||
def setEndOfDirectory(self, success=True):
|
||||
# mark the listing as completed, this is mandatory
|
||||
if not self._isViewSet:
|
||||
self.setView('files')
|
||||
xbmcplugin.setPluginCategory(self.pluginHandle, "")
|
||||
# add some sort methods, these will be available in all views
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_UNSORTED)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_VIDEO_RATING)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_LABEL)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_DATE)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_PROGRAM_COUNT)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
|
||||
xbmcplugin.addSortMethod(self.pluginHandle, xbmcplugin.SORT_METHOD_GENRE)
|
||||
xbmcplugin.endOfDirectory(self.pluginHandle, success)
|
||||
|
||||
def setView(self, content='movies'):
|
||||
# set the listing to a certain content, makes special views available
|
||||
# sets view to the viewID which is selected in xStream settings
|
||||
# see http://mirrors.xbmc.org/docs/python-docs/stable/xbmcplugin.html#-setContent
|
||||
# (seasons is also supported but not listed)
|
||||
content = content.lower()
|
||||
supportedViews = ['files', 'songs', 'artists', 'albums', 'movies', 'tvshows', 'seasons', 'episodes', 'musicvideos']
|
||||
if content in supportedViews:
|
||||
self._isViewSet = True
|
||||
xbmcplugin.setContent(self.pluginHandle, content)
|
||||
if cConfig().getSetting('auto-view') == 'true' and content:
|
||||
viewId = cConfig().getSetting(content + '-view')
|
||||
if viewId:
|
||||
xbmc.executebuiltin("Container.SetViewMode(%s)" % viewId)
|
||||
|
||||
def updateDirectory(self):
|
||||
# update the current listing
|
||||
xbmc.executebuiltin("Container.Refresh")
|
||||
|
||||
def __createItemUrl(self, oGuiElement, bIsFolder, params=''):
|
||||
if params == '':
|
||||
params = ParameterHandler()
|
||||
itemValues = oGuiElement.getItemValues()
|
||||
if 'tmdb_id' in itemValues and itemValues['tmdb_id']:
|
||||
params.setParam('tmdbID', itemValues['tmdb_id'])
|
||||
if 'TVShowTitle' in itemValues and itemValues['TVShowTitle']:
|
||||
params.setParam('TVShowTitle', itemValues['TVShowTitle'])
|
||||
if 'season' in itemValues and itemValues['season'] and int(itemValues['season']) > 0:
|
||||
params.setParam('season', itemValues['season'])
|
||||
if 'episode' in itemValues and itemValues['episode'] and float(itemValues['episode']) > 0:
|
||||
params.setParam('episode', itemValues['episode'])
|
||||
# TODO change this, it can cause bugs it influencec the params for the following listitems
|
||||
if not bIsFolder:
|
||||
params.setParam('MovieTitle', oGuiElement.getTitle())
|
||||
thumbnail = oGuiElement.getThumbnail()
|
||||
if thumbnail:
|
||||
params.setParam('thumb', thumbnail)
|
||||
if oGuiElement._mediaType:
|
||||
params.setParam('mediaType', oGuiElement._mediaType)
|
||||
elif 'TVShowTitle' in itemValues and itemValues['TVShowTitle']:
|
||||
params.setParam('mediaType', 'tvshow')
|
||||
if 'season' in itemValues and itemValues['season'] and int(itemValues['season']) > 0:
|
||||
params.setParam('mediaType', 'season')
|
||||
if 'episode' in itemValues and itemValues['episode'] and float(itemValues['episode']) > 0:
|
||||
params.setParam('mediaType', 'episode')
|
||||
sParams = params.getParameterAsUri()
|
||||
try:
|
||||
if params.getValue('sUrl').startswith("plugin://"):
|
||||
return params.getValue('sUrl')
|
||||
except: pass
|
||||
if len(oGuiElement.getFunction()) == 0:
|
||||
sUrl = "%s?site=%s&title=%s&%s" % (self.pluginPath, oGuiElement.getSiteName(), quote_plus(oGuiElement.getTitle()), sParams)
|
||||
else:
|
||||
#kasi
|
||||
sUrl = "%s?site=%s&function=%s&title=%s&trumb=%s&%s" % (self.pluginPath, oGuiElement.getSiteName(), oGuiElement.getFunction(), quote_plus(oGuiElement.getTitle()), oGuiElement.getThumbnail(), sParams)
|
||||
if not bIsFolder:
|
||||
sUrl += '&playMode=play'
|
||||
return sUrl
|
||||
|
||||
@staticmethod
|
||||
def showKeyBoard(sDefaultText="", sHeading=""):
|
||||
# Create the keyboard object and display it modal
|
||||
oKeyboard = xbmc.Keyboard(sDefaultText, sHeading)
|
||||
oKeyboard.doModal()
|
||||
# If key board is confirmed and there was text entered return the text
|
||||
if oKeyboard.isConfirmed():
|
||||
sSearchText = oKeyboard.getText()
|
||||
if len(sSearchText) > 0:
|
||||
return sSearchText
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def showNumpad(defaultNum="", numPadTitle=cConfig().getLocalizedString(30251)):
|
||||
defaultNum = str(defaultNum)
|
||||
dialog = xbmcgui.Dialog()
|
||||
num = dialog.numeric(0, numPadTitle, defaultNum)
|
||||
return num
|
||||
|
||||
@staticmethod
|
||||
def openSettings():
|
||||
cConfig().showSettingsWindow()
|
||||
|
||||
@staticmethod
|
||||
def showNofication(sTitle, iSeconds=0):
|
||||
if iSeconds == 0:
|
||||
iSeconds = 1000
|
||||
else:
|
||||
iSeconds = iSeconds * 1000
|
||||
xbmc.executebuiltin("Notification(%s,%s,%s,%s)" % (cConfig().getLocalizedString(30308), (cConfig().getLocalizedString(30309) % str(sTitle)), iSeconds, cConfig().getAddonInfo('icon')))
|
||||
|
||||
@staticmethod
|
||||
def showError(sTitle, sDescription, iSeconds=0):
|
||||
if iSeconds == 0:
|
||||
iSeconds = 1000
|
||||
else:
|
||||
iSeconds = iSeconds * 1000
|
||||
xbmc.executebuiltin("Notification(%s,%s,%s,%s)" % (str(sTitle), (str(sDescription)), iSeconds, cConfig().getAddonInfo('icon')))
|
||||
|
||||
@staticmethod
|
||||
def showInfo(sTitle='xStream', sDescription=cConfig().getLocalizedString(30253), iSeconds=0):
|
||||
if iSeconds == 0:
|
||||
iSeconds = 1000
|
||||
else:
|
||||
iSeconds = iSeconds * 1000
|
||||
xbmc.executebuiltin("Notification(%s,%s,%s,%s)" % (str(sTitle), (str(sDescription)), iSeconds, cConfig().getAddonInfo('icon')))
|
||||
|
||||
@staticmethod
|
||||
def showLanguage(sTitle='xStream', sDescription=cConfig().getLocalizedString(30403), iSeconds=0):
|
||||
if iSeconds == 0:
|
||||
iSeconds = 1000
|
||||
else:
|
||||
iSeconds = iSeconds * 1000
|
||||
xbmc.executebuiltin("Notification(%s,%s,%s,%s)" % (str(sTitle), (str(sDescription)), iSeconds, cConfig().getAddonInfo('icon')))
|
||||
344
plugin.video.xstream/resources/lib/gui/guiElement.py
Normal file
@@ -0,0 +1,344 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
from resources.lib.tools import cParser, cUtil
|
||||
from resources.lib.config import cConfig
|
||||
from xbmc import LOGERROR, log
|
||||
from os import path
|
||||
|
||||
class cGuiElement:
|
||||
'''
|
||||
This class "abstracts" a xbmc listitem.
|
||||
Kwargs:
|
||||
sTitle (str): title/label oft the GuiElement/listitem
|
||||
sSite (str): siteidentifier of the siteplugin, which is called if the GuiElement is selected
|
||||
sFunction (str): name of the function, which is called if the GuiElement is selected
|
||||
These arguments are mandatory. If not given on init, they have to be set by their setter-methods, before the GuiElement is added to the Gui.
|
||||
'''
|
||||
DEFAULT_FOLDER_ICON = 'DefaultFolder.png'
|
||||
DEFAULT_FANART = path.join(cConfig().getAddonInfo('path'), 'fanart.jpg')
|
||||
MEDIA_TYPES = ['movie', 'tvshow', 'season', 'episode']
|
||||
|
||||
def __init__(self, sTitle: object = '', sSite: object = None, sFunction: object = None) -> None:
|
||||
self.__sType = 'video'
|
||||
self.__sMediaUrl = ''
|
||||
self.__sTitle = cUtil.cleanse_text(sTitle)
|
||||
self.__sTitleSecond = ''
|
||||
self.__sDescription = ''
|
||||
self.__sThumbnail = ''
|
||||
self.__sIcon = self.DEFAULT_FOLDER_ICON
|
||||
self.__aItemValues = {}
|
||||
self.__aProperties = {}
|
||||
self.__aContextElements = []
|
||||
self.__sFanart = self.DEFAULT_FANART
|
||||
self.__sSiteName = sSite
|
||||
self.__sFunctionName = sFunction
|
||||
self._sLanguage = ''
|
||||
self._sSubLanguage = ''
|
||||
self._sYear = ''
|
||||
self._sQuality = ''
|
||||
self._sInfo = ''
|
||||
self._mediaType = ''
|
||||
self._season = ''
|
||||
self._episode = ''
|
||||
self._tmdbID = ''
|
||||
self._rating = ''
|
||||
self._isMetaSet = False
|
||||
|
||||
def setType(self, sType):
|
||||
self.__sType = sType
|
||||
|
||||
def getType(self):
|
||||
return self.__sType
|
||||
|
||||
def setMediaUrl(self, sMediaUrl):
|
||||
self.__sMediaUrl = sMediaUrl
|
||||
|
||||
def getMediaUrl(self):
|
||||
return self.__sMediaUrl
|
||||
|
||||
def setSiteName(self, sSiteName):
|
||||
self.__sSiteName = sSiteName
|
||||
|
||||
def getSiteName(self):
|
||||
return self.__sSiteName
|
||||
|
||||
def setFunction(self, sFunctionName):
|
||||
self.__sFunctionName = sFunctionName
|
||||
|
||||
def getFunction(self):
|
||||
return self.__sFunctionName
|
||||
|
||||
def setTitle(self, sTitle):
|
||||
self.__sTitle = cUtil.cleanse_text(sTitle)
|
||||
|
||||
# Sprachen im sName ins GUI Element übernehmen
|
||||
def getTitle(self):
|
||||
if ' (19' in self.__sTitle or ' (20' in self.__sTitle:
|
||||
isMatch, aYear = cParser.parse(self.__sTitle, r'(.*?)\((\d{4})\)')
|
||||
if isMatch:
|
||||
self.__sTitle = aYear[0][0]
|
||||
self.setYear(aYear[0][1])
|
||||
if '*19' in self.__sTitle or '*20' in self.__sTitle:
|
||||
isMatch, aYear = cParser.parse(self.__sTitle, r'(.*?)\*(\d{4})\*')
|
||||
if isMatch:
|
||||
self.__sTitle = aYear[0][0]
|
||||
self.setYear(aYear[0][1])
|
||||
if '*english*' in self.__sTitle.lower():
|
||||
isMatch, aLang = cParser.parse(self.__sTitle, r'(.*?)\*(.*?)\*')
|
||||
if isMatch:
|
||||
self.__sTitle = aLang[0][0]
|
||||
self.setLanguage('EN')
|
||||
if '*deutsch*' in self.__sTitle.lower():
|
||||
isMatch, aLang = cParser.parse(self.__sTitle, r'(.*?)\*(.*?)\*')
|
||||
if isMatch:
|
||||
self.__sTitle = aLang[0][0]
|
||||
self.setLanguage('DE')
|
||||
if 'English:' in self.__sTitle:
|
||||
self.__sTitle = self.__sTitle.replace('English:', '')
|
||||
self.setLanguage('EN')
|
||||
if 'Deutsch:' in self.__sTitle:
|
||||
self.__sTitle = self.__sTitle.replace('Deutsch:', '')
|
||||
self.setLanguage('DE')
|
||||
if '(omu)' in self.__sTitle.lower() or '*OmU*' in self.__sTitle:
|
||||
self.__sTitle = self.__sTitle.replace('(OmU) ', '')
|
||||
self.__sTitle = self.__sTitle.replace('(Omu) ', '')
|
||||
self.setLanguage('OmU')
|
||||
|
||||
if self._sYear: self.__sTitle = self.__sTitle.strip() + ' (' + self._sYear + ')'
|
||||
return self.__sTitle.strip()
|
||||
|
||||
def setMediaType(self, mediaType):
|
||||
'''
|
||||
Set mediatype for GuiElement
|
||||
Args:
|
||||
mediaType(str): 'movie'/'tvshow'/'season'/'episode'
|
||||
'''
|
||||
mediaType = mediaType.lower()
|
||||
if mediaType in self.MEDIA_TYPES:
|
||||
self._mediaType = mediaType
|
||||
else:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Unknown MediaType given for %s' % self.getTitle(), LOGERROR)
|
||||
|
||||
def setSeason(self, season):
|
||||
self._season = season
|
||||
self.__aItemValues['season'] = str(season)
|
||||
|
||||
def setEpisode(self, episode):
|
||||
self._episode = episode
|
||||
self.__aItemValues['episode'] = str(episode)
|
||||
|
||||
def setTVShowTitle(self, tvShowTitle):
|
||||
self.__aItemValues['TVShowTitle'] = str(tvShowTitle)
|
||||
|
||||
def setYear(self, year):
|
||||
try:
|
||||
year = int(year)
|
||||
except:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Year given for %s seems not to be a valid number' % self.getTitle(), LOGERROR)
|
||||
return False
|
||||
if len(str(year)) != 4:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Year given for %s has %s digits, required 4 digits' % (self.getTitle(), len(str(year))), LOGERROR)
|
||||
return False
|
||||
if year > 0:
|
||||
self._sYear = str(year)
|
||||
self.__aItemValues['year'] = year
|
||||
return True
|
||||
else:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Year given for %s must be greater than 0' % self.getTitle(), LOGERROR)
|
||||
return False
|
||||
|
||||
def setQuality(self, quality):
|
||||
try:
|
||||
if '2160' in quality:
|
||||
self._sQuality = '4K 2160p'
|
||||
elif '1440' in quality:
|
||||
self._sQuality = '2K 1440p'
|
||||
elif '1080' in quality:
|
||||
self._sQuality = 'HD 1080p'
|
||||
elif '720' in quality:
|
||||
self._sQuality = 'HD 720p'
|
||||
elif '480' in quality:
|
||||
self._sQuality = 'SD 480p'
|
||||
elif '360' in quality:
|
||||
self._sQuality = 'SD 360p'
|
||||
elif 'HD' in quality:
|
||||
self._sQuality = 'HD'
|
||||
elif 'BDRip' in quality:
|
||||
self._sQuality = 'BD Rip'
|
||||
elif 'WEBRip' in quality:
|
||||
self._sQuality = 'WEB Rip'
|
||||
elif 'TS.MD' in quality:
|
||||
self._sQuality = 'TS Mic'
|
||||
elif 'MD.TS' in quality:
|
||||
self._sQuality = 'TS Mic'
|
||||
elif 'TS.LD' in quality:
|
||||
self._sQuality = 'TS Line'
|
||||
elif 'LD.TS' in quality:
|
||||
self._sQuality = 'TS Line'
|
||||
elif 'TS' in quality:
|
||||
self._sQuality = 'TS'
|
||||
#self._sQuality = quality
|
||||
except:
|
||||
pass
|
||||
|
||||
def getQuality(self):
|
||||
return self._sQuality
|
||||
|
||||
def setInfo(self, info):
|
||||
self._sInfo = info
|
||||
|
||||
def getInfo(self):
|
||||
return self._sInfo
|
||||
|
||||
def setTitleSecond(self, sTitleSecond):
|
||||
self.__sTitleSecond = cUtil.cleanse_text(str(sTitleSecond))
|
||||
|
||||
def getTitleSecond(self):
|
||||
return self.__sTitleSecond
|
||||
|
||||
def setDescription(self, sDescription):
|
||||
sDescription = cUtil.cleanse_text(sDescription)
|
||||
self.__sDescription = sDescription
|
||||
self.__aItemValues['plot'] = sDescription
|
||||
|
||||
def getDescription(self):
|
||||
if 'plot' not in self.__aItemValues:
|
||||
return self.__sDescription
|
||||
else:
|
||||
return self.__aItemValues['plot']
|
||||
|
||||
def setThumbnail(self, sThumbnail):
|
||||
self.__sThumbnail = sThumbnail
|
||||
try:
|
||||
if cConfig().getSetting('replacefanart') == 'true' and sThumbnail.startswith('http'):
|
||||
self.__sFanart = sThumbnail
|
||||
except:
|
||||
pass
|
||||
def getThumbnail(self):
|
||||
return self.__sThumbnail
|
||||
|
||||
def setIcon(self, sIcon):
|
||||
self.__sIcon = sIcon
|
||||
|
||||
def getIcon(self):
|
||||
return self.__sIcon
|
||||
|
||||
def setFanart(self, sFanart):
|
||||
self.__sFanart = sFanart
|
||||
|
||||
def getFanart(self):
|
||||
return self.__sFanart
|
||||
|
||||
def addItemValue(self, sItemKey, sItemValue):
|
||||
self.__aItemValues[sItemKey] = sItemValue
|
||||
|
||||
def setItemValues(self, aValueList):
|
||||
self.__aItemValues = aValueList
|
||||
|
||||
def getItemValues(self):
|
||||
self.__aItemValues['title'] = self.getTitle()
|
||||
if self.getDescription():
|
||||
self.__aItemValues['plot'] = self.getDescription()
|
||||
for sPropertyKey in self.__aProperties.keys():
|
||||
self.__aItemValues[sPropertyKey] = self.__aProperties[sPropertyKey]
|
||||
return self.__aItemValues
|
||||
|
||||
# siehe gui.setInfoTagVideo()
|
||||
def addItemProperties(self, sPropertyKey, sPropertyValue):
|
||||
self.__aProperties[sPropertyKey] = sPropertyValue
|
||||
|
||||
def getItemProperties(self):
|
||||
for sItemValueKey in self.__aItemValues.keys():
|
||||
if not self.__aItemValues[sItemValueKey] == '':
|
||||
try:
|
||||
self.__aProperties[sItemValueKey] = str(self.__aItemValues[sItemValueKey])
|
||||
except:
|
||||
pass
|
||||
return self.__aProperties
|
||||
|
||||
def addContextItem(self, oContextElement):
|
||||
self.__aContextElements.append(oContextElement)
|
||||
|
||||
def getContextItems(self):
|
||||
return self.__aContextElements
|
||||
|
||||
def setLanguage(self, sLang):
|
||||
self._sLanguage = str(sLang)
|
||||
|
||||
def setSubLanguage(self, sLang):
|
||||
self._sSubLanguage = str(sLang)
|
||||
|
||||
def getMeta(self, mediaType, tmdbID='', TVShowTitle='', season='', episode='', mode='add'):
|
||||
'''
|
||||
Fetch metainformations for GuiElement.
|
||||
Args:
|
||||
mediaType(str): 'movie'/'tvshow'/'season'/'episode'
|
||||
Kwargs:
|
||||
tmdbID (str) :
|
||||
TVShowTitle (str) :
|
||||
mode (str) : 'add'/'replace' defines if fetched metainformtions should be added to existing informations, or if they should replace them.
|
||||
'''
|
||||
if cConfig().getSetting('TMDBMETA') == 'false':
|
||||
return False
|
||||
if not self._mediaType:
|
||||
self.setMediaType(mediaType)
|
||||
if mode not in ['add', 'replace']:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Wrong meta set mode', LOGERROR)
|
||||
if not season and self._season:
|
||||
season = self._season
|
||||
if not episode and self._episode:
|
||||
episode = self._episode
|
||||
if not self._mediaType:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [guiElement]: Could not get MetaInformations for %s, mediaType not defined' % self.getTitle(), LOGERROR)
|
||||
return False
|
||||
from resources.lib.tmdb import cTMDB
|
||||
oMetaget = cTMDB()
|
||||
if not oMetaget:
|
||||
return False
|
||||
|
||||
if self._mediaType == 'movie':
|
||||
if self._sYear:
|
||||
meta = oMetaget.get_meta(self._mediaType, self.getTitle(), year=self._sYear, advanced=cConfig().getSetting('advanced'))
|
||||
else:
|
||||
meta = oMetaget.get_meta(self._mediaType, self.getTitle(), advanced=cConfig().getSetting('advanced'))
|
||||
elif self._mediaType == 'tvshow':
|
||||
if self._sYear:
|
||||
meta = oMetaget.get_meta(self._mediaType, self.getTitle(), year=self._sYear, advanced=cConfig().getSetting('advanced'))
|
||||
else:
|
||||
meta = oMetaget.get_meta(self._mediaType, self.getTitle(), advanced=cConfig().getSetting('advanced'))
|
||||
elif self._mediaType == 'season':
|
||||
meta = {}
|
||||
elif self._mediaType == 'episode':
|
||||
meta = oMetaget.get_meta_episodes(self._mediaType, TVShowTitle, tmdbID, str(season), str(episode))
|
||||
else:
|
||||
return False
|
||||
|
||||
if not meta:
|
||||
return False
|
||||
|
||||
if self._mediaType == 'season':
|
||||
meta = meta[0]
|
||||
|
||||
if mode == 'replace':
|
||||
self.setItemValues(meta)
|
||||
if 'cover_url' in meta:
|
||||
self.setThumbnail(meta['cover_url'])
|
||||
if 'backdrop_url' in meta:
|
||||
self.setFanart(meta['backdrop_url'])
|
||||
if 'title' in meta and episode:
|
||||
self.setTitle(str(episode) + '. ' + meta['title'])
|
||||
|
||||
else:
|
||||
meta.update(self.__aItemValues)
|
||||
meta.update(self.__aProperties)
|
||||
if 'cover_url' in meta != '' and self.__sThumbnail == '':
|
||||
self.setThumbnail(meta['cover_url'])
|
||||
|
||||
if 'backdrop_url' in meta and self.__sFanart == self.DEFAULT_FANART:
|
||||
self.setFanart(meta['backdrop_url'])
|
||||
self.setItemValues(meta)
|
||||
if 'tmdb_id' in meta:
|
||||
self._tmdbID = meta['tmdb_id']
|
||||
self._isMetaSet = True
|
||||
return meta
|
||||
508
plugin.video.xstream/resources/lib/gui/hoster.py
Normal file
@@ -0,0 +1,508 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
#
|
||||
# 24.01.23 - Heptamer: Korrektur getpriorities (nun werden alle Hoster gelesen und sortiert)
|
||||
# 22.12.24 - Heptamer: m3u8 und mpd Files über inputstream adaptive abspielen lassen
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.player import cPlayer
|
||||
from resources.lib.tools import logger
|
||||
|
||||
|
||||
class cHosterGui:
|
||||
SITE_NAME = 'cHosterGui'
|
||||
|
||||
def __init__(self):
|
||||
self.maxHoster = int(cConfig().getSetting('maxHoster', 100))
|
||||
self.dialog = False
|
||||
|
||||
# TODO: unify parts of play, download etc.
|
||||
def _getInfoAndResolve(self, siteResult):
|
||||
oGui = cGui()
|
||||
params = ParameterHandler()
|
||||
# get data
|
||||
mediaUrl = params.getValue('sMediaUrl')
|
||||
fileName = params.getValue('MovieTitle')
|
||||
try:
|
||||
try:
|
||||
import resolveurl as resolver
|
||||
except:
|
||||
import urlresolver as resolver
|
||||
# resolve
|
||||
if siteResult:
|
||||
mediaUrl = siteResult.get('streamUrl', False)
|
||||
mediaId = siteResult.get('streamID', False)
|
||||
if mediaUrl:
|
||||
logger.info('-> [hoster]: resolve: ' + mediaUrl)
|
||||
link = mediaUrl if siteResult['resolved'] else resolver.resolve(mediaUrl)
|
||||
elif mediaId:
|
||||
logger.info('-> [hoster]: resolve: hoster: %s - mediaID: %s' % (siteResult['host'], mediaId))
|
||||
link = resolver.HostedMediaFile(host=siteResult['host'].lower(), media_id=mediaId).resolve()
|
||||
else:
|
||||
oGui.showError('xStream', cConfig().getLocalizedString(30134), 5)
|
||||
return False
|
||||
elif mediaUrl:
|
||||
logger.info('-> [hoster]: resolve: ' + mediaUrl)
|
||||
link = resolver.resolve(mediaUrl)
|
||||
else:
|
||||
oGui.showError('xStream', cConfig().getLocalizedString(30134), 5)
|
||||
return False
|
||||
except resolver.resolver.ResolverError as e:
|
||||
logger.error('-> [hoster]: ResolverError: %s' % e)
|
||||
oGui.showError('xStream', cConfig().getLocalizedString(30135), 7)
|
||||
return False
|
||||
# resolver response
|
||||
if link is not False:
|
||||
data = {'title': fileName, 'season': params.getValue('season'), 'episode': params.getValue('episode'), 'showTitle': params.getValue('TVShowTitle'), 'thumb': params.getValue('thumb'), 'link': link}
|
||||
return data
|
||||
return False
|
||||
|
||||
def play(self, siteResult=False):
|
||||
logger.info('-> [hoster]: attempt to play file')
|
||||
data = self._getInfoAndResolve(siteResult)
|
||||
if not data:
|
||||
return False
|
||||
if self.dialog:
|
||||
try:
|
||||
self.dialog.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
vers = int(xbmc.getInfoLabel("System.BuildVersion").split(".")[0])
|
||||
|
||||
logger.info('-> [hoster]: play file link: ' + str(data['link']))
|
||||
list_item = xbmcgui.ListItem(path=data['link'])
|
||||
#m3u8 und mpd via inputstream, exklusive Filemoon, da IA mit dem Hoster nicht unter Android läuft
|
||||
if not 'filemoon' in siteResult['streamUrl']:
|
||||
if '.m3u8' in data['link'] or '.mpd' in data['link']:
|
||||
list_item.setProperty("inputstream", "inputstream.adaptive")
|
||||
if '.mpd' in data['link']:
|
||||
if vers < 21: list_item.setProperty('inputstream.adaptive.manifest_type', 'mpd')
|
||||
list_item.setMimeType('application/dash+xml')
|
||||
else:
|
||||
if vers < 21: list_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
|
||||
list_item.setMimeType("application/vnd.apple.mpegurl")
|
||||
if '|' in data['link']:
|
||||
data['link'], header = data['link'].split('|')
|
||||
list_item.setProperty('inputstream.adaptive.stream_headers', header)
|
||||
if vers > 19: list_item.setProperty('inputstream.adaptive.manifest_headers', header)
|
||||
|
||||
if 'youtube' in data['link']:
|
||||
import time
|
||||
time.sleep(1)
|
||||
info = {'Title': data['title']}
|
||||
if data['thumb']:
|
||||
list_item.setArt(data['thumb'])
|
||||
if data['showTitle']:
|
||||
info['Episode'] = data['episode']
|
||||
info['Season'] = data['season']
|
||||
info['TVShowTitle'] = data['showTitle']
|
||||
|
||||
#Neuer Video-Tag, mit Kodi 19 nicht kompatibel, daher folgende Abfrage
|
||||
kodi_version = xbmc.getInfoLabel('System.BuildVersion')
|
||||
if kodi_version[:2] < '20':
|
||||
list_item.setInfo(type="Video", infoLabels=info)
|
||||
else:
|
||||
vtag = list_item.getVideoInfoTag()
|
||||
vtag.setMediaType('video')
|
||||
if 'Title' in info:
|
||||
try:
|
||||
vtag.setTitle(str(info['Title']))
|
||||
except: pass
|
||||
if 'Season' in info:
|
||||
try:
|
||||
vtag.setSeason(int(info['Season']))
|
||||
except: pass
|
||||
if 'Episode' in info:
|
||||
try:
|
||||
vtag.setEpisode(int(info['Episode']))
|
||||
except: pass
|
||||
if 'TVShowTitle' in info:
|
||||
try:
|
||||
vtag.setTvShowTitle(info['TVShowTitle'])
|
||||
except: pass
|
||||
|
||||
list_item.setProperty('IsPlayable', 'true')
|
||||
if cGui().pluginHandle > 0:
|
||||
xbmcplugin.setResolvedUrl(cGui().pluginHandle, True, list_item)
|
||||
else:
|
||||
xbmc.Player().play(data['link'], list_item)
|
||||
return cPlayer().startPlayer()
|
||||
|
||||
def addToPlaylist(self, siteResult=False):
|
||||
oGui = cGui()
|
||||
logger.info('-> [hoster]: attempt addToPlaylist')
|
||||
data = self._getInfoAndResolve(siteResult)
|
||||
if not data: return False
|
||||
logger.info('-> [hoster]: addToPlaylist file link: ' + str(data['link']))
|
||||
oGuiElement = cGuiElement()
|
||||
oGuiElement.setSiteName(self.SITE_NAME)
|
||||
oGuiElement.setMediaUrl(data['link'])
|
||||
oGuiElement.setTitle(data['title'])
|
||||
if data['thumb']:
|
||||
oGuiElement.setThumbnail(data['thumb'])
|
||||
if data['showTitle']:
|
||||
oGuiElement.setEpisode(data['episode'])
|
||||
oGuiElement.setSeason(data['season'])
|
||||
oGuiElement.setTVShowTitle(data['showTitle'])
|
||||
if self.dialog:
|
||||
self.dialog.close()
|
||||
oPlayer = cPlayer()
|
||||
oPlayer.addItemToPlaylist(oGuiElement)
|
||||
oGui.showInfo(cConfig().getLocalizedString(30136), cConfig().getLocalizedString(30137), 5)
|
||||
return True
|
||||
|
||||
def download(self, siteResult=False):
|
||||
from resources.lib.download import cDownload
|
||||
logger.info('-> [hoster]: attempt download')
|
||||
data = self._getInfoAndResolve(siteResult)
|
||||
if not data: return False
|
||||
logger.info('-> [hoster]: download file link: ' + data['link'])
|
||||
if self.dialog:
|
||||
self.dialog.close()
|
||||
oDownload = cDownload()
|
||||
oDownload.download(data['link'], data['title'])
|
||||
return True
|
||||
|
||||
def sendToPyLoad(self, siteResult=False):
|
||||
from resources.lib.handler.pyLoadHandler import cPyLoadHandler
|
||||
logger.info('-> [hoster]: attempt download with pyLoad')
|
||||
data = self._getInfoAndResolve(siteResult)
|
||||
if not data: return False
|
||||
cPyLoadHandler().sendToPyLoad(data['title'], data['link'])
|
||||
return True
|
||||
|
||||
def sendToJDownloader(self, sMediaUrl=False):
|
||||
from resources.lib.handler.jdownloaderHandler import cJDownloaderHandler
|
||||
params = ParameterHandler()
|
||||
if not sMediaUrl:
|
||||
sMediaUrl = params.getValue('sMediaUrl')
|
||||
if self.dialog:
|
||||
self.dialog.close()
|
||||
logger.info('-> [hoster]: call send to JDownloader: ' + sMediaUrl)
|
||||
cJDownloaderHandler().sendToJDownloader(sMediaUrl)
|
||||
|
||||
def sendToJDownloader2(self, sMediaUrl=False):
|
||||
from resources.lib.handler.jdownloader2Handler import cJDownloader2Handler
|
||||
params = ParameterHandler()
|
||||
if not sMediaUrl:
|
||||
sMediaUrl = params.getValue('sMediaUrl')
|
||||
if self.dialog:
|
||||
self.dialog.close()
|
||||
logger.info('-> [hoster]: call send to JDownloader2: ' + sMediaUrl)
|
||||
cJDownloader2Handler().sendToJDownloader2(sMediaUrl)
|
||||
|
||||
def sendToMyJDownloader(self, sMediaUrl=False, sMovieTitle='xStream'):
|
||||
from resources.lib.handler.myjdownloaderHandler import cMyJDownloaderHandler
|
||||
params = ParameterHandler()
|
||||
if not sMediaUrl:
|
||||
sMediaUrl = params.getValue('sMediaUrl')
|
||||
sMovieTitle = params.getValue('MovieTitle')
|
||||
if not sMovieTitle:
|
||||
sMovieTitle = params.getValue('Title')
|
||||
if not sMovieTitle: # only temporary
|
||||
sMovieTitle = params.getValue('sMovieTitle')
|
||||
if not sMovieTitle:
|
||||
sMovieTitle = params.getValue('title')
|
||||
if self.dialog:
|
||||
self.dialog.close()
|
||||
logger.info('-> [hoster]: call send to My.JDownloader: ' + sMediaUrl)
|
||||
cMyJDownloaderHandler().sendToMyJDownloader(sMediaUrl, sMovieTitle)
|
||||
|
||||
def __getPriorities(self, hosterList, filter=True):
|
||||
# Sort hosters based on their resolvers priority.
|
||||
ranking = []
|
||||
# handles multihosters but is about 10 times slower
|
||||
for hoster in hosterList:
|
||||
|
||||
# we try to load resolveurl within the loop, making sure that the resolver loads new with every cycle
|
||||
try:
|
||||
import resolveurl as resolver
|
||||
except:
|
||||
import urlresolver as resolver
|
||||
|
||||
# accept hoster which is marked as resolveable by sitePlugin
|
||||
if hoster.get('resolveable', False):
|
||||
ranking.append([0, hoster])
|
||||
continue
|
||||
|
||||
try:
|
||||
# serienstream VOE hoster = {'link': [sUrl, sName], aus array "[0]" True bzw. False
|
||||
link = hoster['link'][0] if isinstance(hoster['link'], list) else hoster['link']
|
||||
hmf = resolver.HostedMediaFile(url=link)
|
||||
#hmf = resolver.HostedMediaFile(url=hoster['link'])
|
||||
except:
|
||||
continue
|
||||
|
||||
if not hmf.valid_url():
|
||||
hmf = resolver.HostedMediaFile(host=hoster['name'].lower(), media_id='dummy')
|
||||
|
||||
if len(hmf.get_resolvers()):
|
||||
priority = False
|
||||
for resolver in hmf.get_resolvers():
|
||||
# prefer individual priority
|
||||
if not resolver.isUniversal():
|
||||
priority = resolver._get_priority()
|
||||
break
|
||||
if not priority:
|
||||
priority = resolver._get_priority()
|
||||
if priority:
|
||||
ranking.append([priority, hoster])
|
||||
elif not filter:
|
||||
ranking.append([999, hoster])
|
||||
|
||||
# Reset resolver so we have a fresh instance when loop starts again
|
||||
del(resolver)
|
||||
|
||||
if any('quality' in hoster[1] for hoster in ranking):
|
||||
try:
|
||||
# Sortiere Hoster nach Qualität (cConfig().getSetting('preferedQuality') == '5')
|
||||
pref_quali = cConfig().getSetting('preferedQuality')
|
||||
if pref_quali != '5' and any('quality' in hoster[1] and int(hoster[1]['quality']) == int(pref_quali) for hoster in ranking):
|
||||
ranking = sorted(ranking, key=lambda hoster: int('quality' in hoster[1] and hoster[1]['quality']) == int(pref_quali), reverse=True)
|
||||
else:
|
||||
# Wenn Hosterliste prüfen an ist, sortiere Hoster nach Prio Qualität
|
||||
ranking = sorted(ranking, key=lambda hoster: 'quality' in hoster[1] and int(hoster[1]['quality']), reverse=True)
|
||||
except:
|
||||
pass
|
||||
# After sorting Quality, we sort for Hoster-Priority :) -Hep 24.01.23
|
||||
# ranking = sorted(ranking, key=lambda ranking: ranking[0])
|
||||
|
||||
# Hoster Sprache über sLang im Siteplugin Prio nach sLang Code Reihenfolge (Deutsch, Englisch, Englisch mit untertitel
|
||||
if ranking:
|
||||
if "languageCode" in ranking[0][1]:
|
||||
ranking = sorted(ranking, key=lambda ranking: (ranking[1]["languageCode"],ranking[0]))
|
||||
else:
|
||||
ranking = sorted(ranking, key=lambda ranking: ranking[0])
|
||||
|
||||
|
||||
hosterQueue = []
|
||||
|
||||
for i, hoster in ranking:
|
||||
hosterQueue.append(hoster)
|
||||
return hosterQueue
|
||||
|
||||
def stream(self, playMode, siteName, function, url):
|
||||
self.dialog = xbmcgui.DialogProgress()
|
||||
self.dialog.create('xStream', cConfig().getLocalizedString(30138))
|
||||
# load site as plugin and run the function
|
||||
self.dialog.update(5, cConfig().getLocalizedString(30139))
|
||||
plugin = __import__(siteName, globals(), locals())
|
||||
function = getattr(plugin, function)
|
||||
self.dialog.update(10, cConfig().getLocalizedString(30140))
|
||||
if url:
|
||||
siteResult = function(url)
|
||||
else:
|
||||
siteResult = function()
|
||||
self.dialog.update(40)
|
||||
if not siteResult:
|
||||
self.dialog.close()
|
||||
cGui().showInfo('xStream', cConfig().getLocalizedString(30141))
|
||||
return
|
||||
# if result is not a list, make in one
|
||||
if not type(siteResult) is list:
|
||||
temp = [siteResult]
|
||||
siteResult = temp
|
||||
# field "name" marks hosters
|
||||
if 'name' in siteResult[0]:
|
||||
functionName = siteResult[-1]
|
||||
del siteResult[-1]
|
||||
if not siteResult:
|
||||
self.dialog.close()
|
||||
cGui().showInfo('xStream', cConfig().getLocalizedString(30142))
|
||||
return
|
||||
|
||||
self.dialog.update(60, cConfig().getLocalizedString(30143))
|
||||
# Sitplugins VOD mit in automatische Abspielliste aufnehmen (Da Links bei der Überprüfung der Verfügbarkeit gekickt werden)
|
||||
if (playMode != 'jd') and (playMode != 'jd2') and (playMode != 'pyload') and (cConfig().getSetting('presortHoster') == 'true') and (playMode != 'myjd'):
|
||||
#if (not siteName.startswith('vod_')) and (playMode != 'jd') and (playMode != 'jd2') and (playMode != 'pyload') and (cConfig().getSetting('presortHoster') == 'true') and (playMode != 'myjd'):
|
||||
siteResult = self.__getPriorities(siteResult)
|
||||
if not siteResult:
|
||||
self.dialog.close()
|
||||
cGui().showInfo('xStream', cConfig().getLocalizedString(30144))
|
||||
return False
|
||||
self.dialog.update(90)
|
||||
# self.dialog.close()
|
||||
if len(siteResult) > self.maxHoster:
|
||||
siteResult = siteResult[:self.maxHoster - 1]
|
||||
if cConfig().getSetting('hosterSelect') == 'List':
|
||||
self.showHosterFolder(siteResult, siteName, functionName)
|
||||
return
|
||||
if len(siteResult) > 1:
|
||||
# choose hoster
|
||||
siteResult = self._chooseHoster(siteResult)
|
||||
if not siteResult:
|
||||
return
|
||||
else:
|
||||
siteResult = siteResult[0]
|
||||
# get stream links
|
||||
logger.info(siteResult['link'])
|
||||
function = getattr(plugin, functionName)
|
||||
siteResult = function(siteResult['link'])
|
||||
# if result is not a list, make in one
|
||||
if not type(siteResult) is list:
|
||||
temp = [siteResult]
|
||||
siteResult = temp
|
||||
# choose part
|
||||
if len(siteResult) > 1:
|
||||
siteResult = self._choosePart(siteResult)
|
||||
if not siteResult:
|
||||
logger.info('-> [hoster]: no part selected')
|
||||
return
|
||||
else:
|
||||
siteResult = siteResult[0]
|
||||
|
||||
self.dialog = xbmcgui.DialogProgress()
|
||||
self.dialog.create('xStream', cConfig().getLocalizedString(30145))
|
||||
self.dialog.update(95, cConfig().getLocalizedString(30146))
|
||||
if playMode == 'play':
|
||||
self.play(siteResult)
|
||||
elif playMode == 'download':
|
||||
self.download(siteResult)
|
||||
elif playMode == 'enqueue':
|
||||
self.addToPlaylist(siteResult)
|
||||
elif playMode == 'jd':
|
||||
self.sendToJDownloader(siteResult['streamUrl'])
|
||||
elif playMode == 'jd2':
|
||||
self.sendToJDownloader2(siteResult['streamUrl'])
|
||||
elif playMode == 'myjd':
|
||||
self.sendToMyJDownloader(siteResult['streamUrl'])
|
||||
elif playMode == 'pyload':
|
||||
self.sendToPyLoad(siteResult)
|
||||
|
||||
def streamAuto(self, playMode, siteName, function):
|
||||
logger.info('-> [hoster]: auto stream initiated')
|
||||
self.dialog = xbmcgui.DialogProgress()
|
||||
self.dialog.create('xStream', cConfig().getLocalizedString(30138))
|
||||
# load site as plugin and run the function
|
||||
self.dialog.update(5, cConfig().getLocalizedString(30139))
|
||||
plugin = __import__(siteName, globals(), locals())
|
||||
function = getattr(plugin, function)
|
||||
self.dialog.update(10, cConfig().getLocalizedString(30140))
|
||||
siteResult = function()
|
||||
if not siteResult:
|
||||
self.dialog.close()
|
||||
cGui().showInfo('xStream', cConfig().getLocalizedString(30141))
|
||||
return False
|
||||
# if result is not a list, make in one
|
||||
if not type(siteResult) is list:
|
||||
temp = [siteResult]
|
||||
siteResult = temp
|
||||
# field "name" marks hosters
|
||||
if 'name' in siteResult[0]:
|
||||
self.dialog.update(90, cConfig().getLocalizedString(30143))
|
||||
functionName = siteResult[-1]
|
||||
del siteResult[-1]
|
||||
# Sitplugins aus dem VOD Bereich bei self.__getPriorities(siteResult) ausschliessen da sonst die Hoster gekickt werden.
|
||||
if siteName.startswith('dummy'): #Falls Servernamen im VOD sich ändern, hier vod_ eintragen
|
||||
hosters = siteResult
|
||||
else:
|
||||
hosters = self.__getPriorities(siteResult)
|
||||
if not hosters:
|
||||
self.dialog.close()
|
||||
cGui().showInfo('xStream', cConfig().getLocalizedString(30144))
|
||||
return False
|
||||
if len(siteResult) > self.maxHoster:
|
||||
siteResult = siteResult[:self.maxHoster - 1]
|
||||
check = False
|
||||
self.dialog.create('xStream', cConfig().getLocalizedString(30147))
|
||||
total = len(hosters)
|
||||
for count, hoster in enumerate(hosters):
|
||||
if self.dialog.iscanceled() or xbmc.Monitor().abortRequested() or check: return
|
||||
percent = (count + 1) * 100 // total
|
||||
try:
|
||||
logger.info('-> [hoster]: try hoster %s' % hoster['name'])
|
||||
self.dialog.create('xStream', cConfig().getLocalizedString(30147))
|
||||
self.dialog.update(percent, cConfig().getLocalizedString(30147) + ' %s' % hoster['name'])
|
||||
# get stream links
|
||||
function = getattr(plugin, functionName)
|
||||
siteResult = function(hoster['link'])
|
||||
check = self.__autoEnqueue(siteResult, playMode)
|
||||
if check:
|
||||
return True
|
||||
except:
|
||||
self.dialog.update(percent, cConfig().getLocalizedString(30148) % hoster['name'])
|
||||
logger.error('-> [hoster]: playback with hoster %s failed' % hoster['name'])
|
||||
# field "resolved" marks streamlinks
|
||||
elif 'resolved' in siteResult[0]:
|
||||
for stream in siteResult:
|
||||
try:
|
||||
if self.__autoEnqueue(siteResult, playMode):
|
||||
self.dialog.close()
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
def _chooseHoster(self, siteResult):
|
||||
dialog = xbmcgui.Dialog()
|
||||
titles = []
|
||||
for result in siteResult:
|
||||
if 'displayedName' in result:
|
||||
titles.append(str(result['displayedName']))
|
||||
else:
|
||||
titles.append(str(result['name']))
|
||||
index = dialog.select(cConfig().getLocalizedString(30149), titles)
|
||||
if index > -1:
|
||||
siteResult = siteResult[index]
|
||||
return siteResult
|
||||
else:
|
||||
logger.info('-> [hoster]: no hoster selected')
|
||||
return False
|
||||
|
||||
def _choosePart(self, siteResult):
|
||||
self.dialog = xbmcgui.Dialog()
|
||||
titles = []
|
||||
for result in siteResult:
|
||||
titles.append(str(result['title']))
|
||||
index = self.dialog.select(cConfig().getLocalizedString(30150), titles)
|
||||
if index > -1:
|
||||
siteResult = siteResult[index]
|
||||
return siteResult
|
||||
else:
|
||||
return False
|
||||
|
||||
def showHosterFolder(self, siteResult, siteName, functionName):
|
||||
oGui = cGui()
|
||||
total = len(siteResult)
|
||||
params = ParameterHandler()
|
||||
for hoster in siteResult:
|
||||
if 'displayedName' in hoster:
|
||||
name = hoster['displayedName']
|
||||
else:
|
||||
name = hoster['name']
|
||||
oGuiElement = cGuiElement(name, siteName, functionName)
|
||||
oGuiElement.setThumbnail(str(params.getValue('thumb')))
|
||||
params.setParam('url', hoster['link'])
|
||||
params.setParam('isHoster', 'true')
|
||||
oGui.addFolder(oGuiElement, params, iTotal=total, isHoster=True)
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def __autoEnqueue(self, partList, playMode):
|
||||
# choose part
|
||||
if not partList:
|
||||
return False
|
||||
for i in range(len(partList) - 1, -1, -1):
|
||||
try:
|
||||
if playMode == 'play' and i == 0:
|
||||
if not self.play(partList[i]):
|
||||
return False
|
||||
elif playMode == 'download':
|
||||
self.download(partList[i])
|
||||
elif playMode == 'enqueue' or (playMode == 'play' and i > 0):
|
||||
self.addToPlaylist(partList[i])
|
||||
except:
|
||||
return False
|
||||
logger.info('-> [hoster]: autoEnqueue successful')
|
||||
return True
|
||||
|
||||
|
||||
class Hoster:
|
||||
def __init__(self, name, link):
|
||||
self.name = name
|
||||
self.link = link
|
||||
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import sys
|
||||
|
||||
from urllib.parse import parse_qsl, urlsplit, unquote_plus, urlencode
|
||||
|
||||
|
||||
class ParameterHandler:
|
||||
def __init__(self):
|
||||
params = dict()
|
||||
if len(sys.argv) >= 3 and len(sys.argv[2]) > 0:
|
||||
params = dict(parse_qsl(urlsplit(sys.argv[2]).query))
|
||||
self.__params = params
|
||||
|
||||
def getAllParameters(self):
|
||||
# returns all parameters as dictionary
|
||||
return self.__params
|
||||
|
||||
def getValue(self, paramName):
|
||||
# returns value of one parameter as string, if parameter does not exists "False" is returned
|
||||
if self.exist(paramName):
|
||||
return self.__params[paramName]
|
||||
# paramValue = self.__params[paramName]
|
||||
# return unquote_plus(paramValue)
|
||||
return False
|
||||
|
||||
def exist(self, paramName):
|
||||
# checks if paramter with the name "paramName" exists
|
||||
return paramName in self.__params
|
||||
|
||||
def setParam(self, paramName, paramValue):
|
||||
# set the value of the parameter with the name "paramName" to "paramValue"
|
||||
# if there is no such parameter, the parameter is created
|
||||
# if no value is given "paramValue" is set "None"
|
||||
paramValue = str(paramValue)
|
||||
self.__params.update({paramName: paramValue})
|
||||
|
||||
def addParams(self, paramDict):
|
||||
# adds a whole dictionary {key1:value1,...,keyN:valueN} of parameters to the ParameterHandler
|
||||
# existing parameters are updated
|
||||
for key, value in paramDict.items():
|
||||
self.__params.update({key: str(value)})
|
||||
|
||||
def getParameterAsUri(self):
|
||||
outParams = dict()
|
||||
if 'params' in self.__params:
|
||||
del self.__params['params']
|
||||
if 'function' in self.__params:
|
||||
del self.__params['function']
|
||||
if 'title' in self.__params:
|
||||
del self.__params['title']
|
||||
if 'site' in self.__params:
|
||||
del self.__params['site']
|
||||
|
||||
if len(self.__params) > 0:
|
||||
for param in self.__params:
|
||||
if len(self.__params[param]) < 1:
|
||||
continue
|
||||
outParams[param] = unquote_plus(self.__params[param])
|
||||
return urlencode(outParams)
|
||||
return 'params=0'
|
||||
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import re
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class cJDownloader2Handler:
|
||||
def sendToJDownloader2(self, sUrl):
|
||||
if self.__checkConfig() is False:
|
||||
cGui().showError(cConfig().getLocalizedString(30076), cConfig().getLocalizedString(30254), 5)
|
||||
return False
|
||||
|
||||
if self.__checkConnection() is False:
|
||||
cGui().showError(cConfig().getLocalizedString(30076), cConfig().getLocalizedString(30255), 5)
|
||||
return False
|
||||
|
||||
if self.__download(sUrl) is True:
|
||||
cGui().showInfo(cConfig().getLocalizedString(30076), cConfig().getLocalizedString(30256), 5)
|
||||
return True
|
||||
return False
|
||||
|
||||
def __client(self, path, params):
|
||||
sHost = self.__getHost()
|
||||
sPort = self.__getPort()
|
||||
ENCODING = 'utf-8'
|
||||
url = 'http://{}:{}/{}'.format(sHost, sPort, path)
|
||||
if params is not None:
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset={}'.format(ENCODING), }
|
||||
request = Request(url, urlencode(params).encode(ENCODING), headers)
|
||||
else:
|
||||
request = Request(url)
|
||||
return urlopen(request).read().decode(ENCODING).strip()
|
||||
|
||||
def __download(self, sFileUrl):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloader2Handler]: JD2 Link: ' + str(sFileUrl), LOGNOTICE)
|
||||
params = {'passwords': 'myPassword', 'source': 'http://jdownloader.org/spielwiese', 'urls': sFileUrl, 'submit': 'Add Link to JDownloader', }
|
||||
if self.__client('flash/add', params).lower() == 'success':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __checkConfig(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloader2Handler]: check JD2 Addon settings', LOGNOTICE)
|
||||
bEnabled = cConfig().getSetting('jd2_enabled')
|
||||
if bEnabled == 'true':
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getHost(self):
|
||||
return cConfig().getSetting('jd2_host')
|
||||
|
||||
def __getPort(self):
|
||||
return cConfig().getSetting('jd2_port')
|
||||
|
||||
def __checkConnection(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloader2Handler]: check JD2 Connection', LOGNOTICE)
|
||||
try:
|
||||
output = self.__client('jdcheck.js', None)
|
||||
pattern = re.compile(r'jdownloader\s*=\s*true', re.IGNORECASE)
|
||||
if pattern.search(output) != None:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
|
||||
class cJDownloaderHandler:
|
||||
def sendToJDownloader(self, sUrl):
|
||||
if self.__checkConfig() == False:
|
||||
cGui().showError(cConfig().getLocalizedString(30070), cConfig().getLocalizedString(30254), 5)
|
||||
return False
|
||||
|
||||
if self.__checkConnection() == False:
|
||||
cGui().showError(cConfig().getLocalizedString(30070), cConfig().getLocalizedString(30255), 5)
|
||||
return False
|
||||
|
||||
bDownload = self.__download(sUrl)
|
||||
if bDownload == True:
|
||||
cGui().showInfo(cConfig().getLocalizedString(30070), cConfig().getLocalizedString(30256), 5)
|
||||
|
||||
def __checkConfig(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloaderHandler]: check JD Addon settings', LOGNOTICE)
|
||||
|
||||
bEnabled = cConfig().getSetting('jd_enabled')
|
||||
if bEnabled == 'true':
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getHost(self):
|
||||
return cConfig().getSetting('jd_host')
|
||||
|
||||
def __getPort(self):
|
||||
return cConfig().getSetting('jd_port')
|
||||
|
||||
def __getAutomaticStart(self):
|
||||
bAutomaticStart = cConfig().getSetting('jd_automatic_start')
|
||||
if bAutomaticStart == 'true':
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getLinkGrabber(self):
|
||||
bAutomaticStart = cConfig().getSetting('jd_grabber')
|
||||
if bAutomaticStart == 'true':
|
||||
return True
|
||||
return False
|
||||
|
||||
def __download(self, sFileUrl):
|
||||
sHost = self.__getHost()
|
||||
sPort = self.__getPort()
|
||||
bAutomaticDownload = self.__getAutomaticStart()
|
||||
bLinkGrabber = self.__getLinkGrabber()
|
||||
sLinkForJd = self.__createJDUrl(sFileUrl, sHost, sPort, bAutomaticDownload, bLinkGrabber)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloaderHandler]: JD Link: ' + str(sLinkForJd), LOGNOTICE)
|
||||
oRequestHandler = cRequestHandler(sLinkForJd)
|
||||
oRequestHandler.request()
|
||||
return True
|
||||
|
||||
def __createJDUrl(self, sFileUrl, sHost, sPort, bAutomaticDownload, bLinkGrabber):
|
||||
sGrabber = '0'
|
||||
if bLinkGrabber == True:
|
||||
sGrabber = '1'
|
||||
sAutomaticStart = '0'
|
||||
if bAutomaticDownload == True:
|
||||
sAutomaticStart = '1'
|
||||
sUrl = 'http://' + str(sHost) + ':' + str(sPort) + '/action/add/links/grabber' + str(sGrabber) + '/start' + str(
|
||||
sAutomaticStart) + '/' + sFileUrl
|
||||
return sUrl
|
||||
|
||||
def __checkConnection(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [jdownloaderHandler]: check JD Connection', LOGNOTICE)
|
||||
sHost = self.__getHost()
|
||||
sPort = self.__getPort()
|
||||
sLinkForJd = 'http://' + str(sHost) + ':' + str(sPort)
|
||||
try:
|
||||
oRequestHandler = cRequestHandler(sLinkForJd)
|
||||
oRequestHandler.request()
|
||||
return True
|
||||
except Exception as e:
|
||||
return False
|
||||
return False
|
||||
452
plugin.video.xstream/resources/lib/handler/myjdapi.py
Normal file
@@ -0,0 +1,452 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import requests
|
||||
|
||||
try:
|
||||
from resources.lib import pyaes
|
||||
except ImportError:
|
||||
try:
|
||||
import pyaes
|
||||
except ImportError:
|
||||
from resolveurl.lib import pyaes
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
class MYJDException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
def PAD(s):
|
||||
BS = 16
|
||||
try:
|
||||
return s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
|
||||
except Exception:
|
||||
return s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
|
||||
|
||||
|
||||
def UNPAD(s):
|
||||
try:
|
||||
return s[0:-s[-1]]
|
||||
except Exception:
|
||||
return s[0:-ord(s[-1])]
|
||||
|
||||
|
||||
class System:
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
self.url = '/system'
|
||||
|
||||
def exit_jd(self):
|
||||
return self.device.action(self.url + "/exitJD")
|
||||
|
||||
def restart_jd(self):
|
||||
return self.device.action(self.url + "/restartJD")
|
||||
|
||||
def hibernate_os(self):
|
||||
return self.device.action(self.url + "/hibernateOS")
|
||||
|
||||
def shutdown_os(self, force):
|
||||
return self.device.action(self.url + "/shutdownOS", force)
|
||||
|
||||
def standby_os(self):
|
||||
return self.device.action(self.url + "/standbyOS")
|
||||
|
||||
|
||||
class Update:
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
self.url = '/update'
|
||||
|
||||
def restart_and_update(self):
|
||||
return self.device.action(self.url + "/restartAndUpdate")
|
||||
|
||||
def run_update_check(self):
|
||||
return self.device.action(self.url + "/runUpdateCheck")
|
||||
|
||||
def is_update_available(self):
|
||||
return self.device.action(self.url + "/isUpdateAvailable")
|
||||
|
||||
|
||||
class DownloadController:
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
self.url = '/downloadcontroller'
|
||||
|
||||
def start_downloads(self):
|
||||
return self.device.action(self.url + "/start")
|
||||
|
||||
def stop_downloads(self):
|
||||
return self.device.action(self.url + "/stop")
|
||||
|
||||
def pause_downloads(self, value):
|
||||
params = [value]
|
||||
return self.device.action(self.url + "/pause", params)
|
||||
|
||||
def get_speed_in_bytes(self):
|
||||
return self.device.action(self.url + "/getSpeedInBps")
|
||||
|
||||
def force_download(self, link_ids, package_ids):
|
||||
params = [link_ids, package_ids]
|
||||
return self.device.action(self.url + "/forceDownload", params)
|
||||
|
||||
def get_current_state(self):
|
||||
return self.device.action(self.url + "/getCurrentState")
|
||||
|
||||
|
||||
class Linkgrabber:
|
||||
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
self.url = '/linkgrabberv2'
|
||||
|
||||
def clear_list(self):
|
||||
return self.device.action(self.url + "/clearList", http_action="POST")
|
||||
|
||||
def move_to_downloadlist(self, links_ids, packages_ids):
|
||||
params = [links_ids, packages_ids]
|
||||
return self.device.action(self.url + "/moveToDownloadlist", params)
|
||||
|
||||
def query_links(self, params=[
|
||||
{"bytesTotal": True,
|
||||
"comment": True,
|
||||
"status": True,
|
||||
"enabled": True,
|
||||
"maxResults": -1,
|
||||
"startAt": 0,
|
||||
"hosts": True,
|
||||
"url": True,
|
||||
"availability": True,
|
||||
"variantIcon": True,
|
||||
"variantName": True,
|
||||
"variantID": True,
|
||||
"variants": True,
|
||||
"priority": True}]):
|
||||
return self.device.action(self.url + "/queryLinks", params)
|
||||
|
||||
def cleanup(self, action, mode, selection_type, links_ids=[], packages_ids=[]):
|
||||
params = [links_ids, packages_ids]
|
||||
params += [action, mode, selection_type]
|
||||
return self.device.action(self.url + "/cleanup", params)
|
||||
|
||||
def add_container(self, type_, content):
|
||||
params = [type_, content]
|
||||
return self.device.action(self.url + "/addContainer", params)
|
||||
|
||||
def get_download_urls(self, links_ids, packages_ids, url_display_type):
|
||||
params = [packages_ids, links_ids, url_display_type]
|
||||
return self.device.action(self.url + "/getDownloadUrls", params)
|
||||
|
||||
def set_priority(self, priority, links_ids, packages_ids):
|
||||
params = [priority, links_ids, packages_ids]
|
||||
return self.device.action(self.url + "/setPriority", params)
|
||||
|
||||
def set_enabled(self, params):
|
||||
return self.device.action(self.url + "/setEnabled", params)
|
||||
|
||||
def get_variants(self, params):
|
||||
return self.device.action(self.url + "/getVariants", params)
|
||||
|
||||
def add_links(self, params=[
|
||||
{"autostart": False,
|
||||
"links": None,
|
||||
"packageName": None,
|
||||
"extractPassword": None,
|
||||
"priority": "DEFAULT",
|
||||
"downloadPassword": None,
|
||||
"destinationFolder": None,
|
||||
"overwritePackagizerRules": False}]):
|
||||
return self.device.action("/linkgrabberv2/addLinks", params)
|
||||
|
||||
def get_childrenchanged(self):
|
||||
pass
|
||||
|
||||
def remove_links(self):
|
||||
pass
|
||||
|
||||
def get_downfolderhistoryselectbase(self):
|
||||
pass
|
||||
|
||||
def help(self):
|
||||
return self.device.action("/linkgrabberv2/help", http_action="GET")
|
||||
|
||||
def rename_link(self):
|
||||
pass
|
||||
|
||||
def move_links(self):
|
||||
pass
|
||||
|
||||
def set_variant(self):
|
||||
pass
|
||||
|
||||
def get_package_count(self):
|
||||
pass
|
||||
|
||||
def rename_package(self):
|
||||
pass
|
||||
|
||||
def query_packages(self):
|
||||
pass
|
||||
|
||||
def move_packages(self):
|
||||
pass
|
||||
|
||||
def add_variant_copy(self):
|
||||
pass
|
||||
|
||||
|
||||
class Downloads:
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
self.url = "/downloadsV2"
|
||||
|
||||
def query_links(self, params=[
|
||||
{"bytesTotal": True,
|
||||
"comment": True,
|
||||
"status": True,
|
||||
"enabled": True,
|
||||
"maxResults": -1,
|
||||
"startAt": 0,
|
||||
"packageUUIDs": [],
|
||||
"host": True,
|
||||
"url": True,
|
||||
"bytesloaded": True,
|
||||
"speed": True,
|
||||
"eta": True,
|
||||
"finished": True,
|
||||
"priority": True,
|
||||
"running": True,
|
||||
"skipped": True,
|
||||
"extractionStatus": True}]):
|
||||
return self.device.action(self.url + "/queryLinks", params)
|
||||
|
||||
def query_packages(self, params=[
|
||||
{"bytesLoaded": True,
|
||||
"bytesTotal": True,
|
||||
"comment": True,
|
||||
"enabled": True,
|
||||
"eta": True,
|
||||
"priority": True,
|
||||
"finished": True,
|
||||
"running": True,
|
||||
"speed": True,
|
||||
"status": True,
|
||||
"childCount": True,
|
||||
"hosts": True,
|
||||
"saveTo": True,
|
||||
"maxResults": -1,
|
||||
"startAt": 0}]):
|
||||
return self.device.action(self.url + "/queryPackages", params)
|
||||
|
||||
def cleanup(self, action, mode, selection_type, links_ids=[], packages_ids=[]):
|
||||
params = [links_ids, packages_ids]
|
||||
params += [action, mode, selection_type]
|
||||
return self.device.action(self.url + "/cleanup", params)
|
||||
|
||||
|
||||
class Jddevice:
|
||||
|
||||
def __init__(self, jd, device_dict):
|
||||
self.name = device_dict["name"]
|
||||
self.device_id = device_dict["id"]
|
||||
self.device_type = device_dict["type"]
|
||||
self.myjd = jd
|
||||
self.linkgrabber = Linkgrabber(self)
|
||||
self.downloads = Downloads(self)
|
||||
self.downloadcontroller = DownloadController(self)
|
||||
self.update = Update(self)
|
||||
self.system = System(self)
|
||||
|
||||
def action(self, path, params=(), http_action="POST"):
|
||||
action_url = self.__action_url()
|
||||
response = self.myjd.request_api(path, http_action, params, action_url)
|
||||
if response is None:
|
||||
return False
|
||||
return response['data']
|
||||
|
||||
def __action_url(self):
|
||||
return "/t_" + self.myjd.get_session_token() + "_" + self.device_id
|
||||
|
||||
|
||||
class Myjdapi:
|
||||
|
||||
def __init__(self):
|
||||
self.__request_id = int(time.time() * 1000)
|
||||
self.__api_url = "http://api.jdownloader.org"
|
||||
self.__app_key = "http://git.io/vmcsk"
|
||||
self.__api_version = 1
|
||||
self.__devices = None
|
||||
self.__login_secret = None
|
||||
self.__device_secret = None
|
||||
self.__session_token = None
|
||||
self.__regain_token = None
|
||||
self.__server_encryption_token = None
|
||||
self.__device_encryption_token = None
|
||||
self.__connected = False
|
||||
|
||||
def get_session_token(self):
|
||||
return self.__session_token
|
||||
|
||||
def is_connected(self):
|
||||
return self.__connected
|
||||
|
||||
def set_app_key(self, app_key):
|
||||
self.__app_key = app_key
|
||||
|
||||
def __secret_create(self, email, password, domain):
|
||||
secret_hash = hashlib.sha256()
|
||||
secret_hash.update(email.lower().encode('utf-8') + password.encode('utf-8') + domain.lower().encode('utf-8'))
|
||||
return secret_hash.digest()
|
||||
|
||||
def __update_encryption_tokens(self):
|
||||
if self.__server_encryption_token is None:
|
||||
old_token = self.__login_secret
|
||||
else:
|
||||
old_token = self.__server_encryption_token
|
||||
new_token = hashlib.sha256()
|
||||
new_token.update(old_token + bytearray.fromhex(self.__session_token))
|
||||
self.__server_encryption_token = new_token.digest()
|
||||
new_token = hashlib.sha256()
|
||||
new_token.update(self.__device_secret + bytearray.fromhex(self.__session_token))
|
||||
self.__device_encryption_token = new_token.digest()
|
||||
|
||||
def __signature_create(self, key, data):
|
||||
signature = hmac.new(key, data.encode('utf-8'), hashlib.sha256)
|
||||
return signature.hexdigest()
|
||||
|
||||
def __decrypt(self, secret_token, data):
|
||||
init_vector = secret_token[:len(secret_token) // 2]
|
||||
key = secret_token[len(secret_token) // 2:]
|
||||
decryptor = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, init_vector))
|
||||
decrypted_data = decryptor.feed(base64.b64decode(data))
|
||||
decrypted_data += decryptor.feed()
|
||||
return decrypted_data
|
||||
|
||||
def __encrypt(self, secret_token, data):
|
||||
init_vector = secret_token[:len(secret_token) // 2]
|
||||
key = secret_token[len(secret_token) // 2:]
|
||||
encryptor = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, init_vector))
|
||||
encrypted_data = encryptor.feed(data)
|
||||
encrypted_data += encryptor.feed()
|
||||
encrypted_data = base64.b64encode(encrypted_data)
|
||||
return encrypted_data.decode('utf-8')
|
||||
|
||||
def update_request_id(self):
|
||||
self.__request_id = int(time.time())
|
||||
|
||||
def connect(self, email, password):
|
||||
self.__login_secret = self.__secret_create(email, password, "server")
|
||||
self.__device_secret = self.__secret_create(email, password, "device")
|
||||
response = self.request_api("/my/connect", "GET", [("email", email), ("appkey", self.__app_key)])
|
||||
self.__connected = True
|
||||
self.update_request_id()
|
||||
self.__session_token = response["sessiontoken"]
|
||||
self.__regain_token = response["regaintoken"]
|
||||
self.__update_encryption_tokens()
|
||||
self.update_devices()
|
||||
|
||||
def reconnect(self):
|
||||
response = self.request_api("/my/reconnect", "GET", [("sessiontoken", self.__session_token), ("regaintoken", self.__regain_token)])
|
||||
self.update_request_id()
|
||||
self.__session_token = response["sessiontoken"]
|
||||
self.__regain_token = response["regaintoken"]
|
||||
self.__update_encryption_tokens()
|
||||
|
||||
def disconnect(self):
|
||||
self.request_api("/my/disconnect", "GET", [("sessiontoken", self.__session_token)])
|
||||
self.update_request_id()
|
||||
self.__login_secret = None
|
||||
self.__device_secret = None
|
||||
self.__session_token = None
|
||||
self.__regain_token = None
|
||||
self.__server_encryption_token = None
|
||||
self.__device_encryption_token = None
|
||||
self.__devices = None
|
||||
self.__connected = False
|
||||
|
||||
def update_devices(self):
|
||||
response = self.request_api("/my/listdevices", "GET", [("sessiontoken", self.__session_token)])
|
||||
self.update_request_id()
|
||||
self.__devices = response["list"]
|
||||
|
||||
def list_devices(self):
|
||||
return self.__devices
|
||||
|
||||
def get_device(self, device_name=None, device_id=None):
|
||||
if not self.is_connected():
|
||||
raise (MYJDException("No connection established\n"))
|
||||
if device_id is not None:
|
||||
for device in self.__devices:
|
||||
if device["id"] == device_id:
|
||||
return Jddevice(self, device)
|
||||
elif device_name is not None:
|
||||
for device in self.__devices:
|
||||
if device["name"] == device_name:
|
||||
return Jddevice(self, device)
|
||||
raise MYJDException("Device not found\n")
|
||||
|
||||
def request_api(self, path, http_method="GET", params=None, action=None):
|
||||
data = None
|
||||
if not self.is_connected() and path != "/my/connect":
|
||||
raise (MYJDException("No connection established\n"))
|
||||
if http_method == "GET":
|
||||
query = [path + "?"]
|
||||
for param in params:
|
||||
if param[0] != "encryptedLoginSecret":
|
||||
query += ["%s=%s" % (param[0], quote(param[1]))]
|
||||
else:
|
||||
query += ["&%s=%s" % (param[0], param[1])]
|
||||
query += ["rid=" + str(self.__request_id)]
|
||||
if self.__server_encryption_token is None:
|
||||
query += ["signature=" + str(self.__signature_create(self.__login_secret, query[0] + "&".join(query[1:])))]
|
||||
else:
|
||||
query += ["signature=" + str(self.__signature_create(self.__server_encryption_token, query[0] + "&".join(query[1:])))]
|
||||
query = query[0] + "&".join(query[1:])
|
||||
encrypted_response = requests.get(self.__api_url + query)
|
||||
else:
|
||||
params_request = []
|
||||
for param in params:
|
||||
if not isinstance(param, list):
|
||||
params_request += [json.dumps(param)]
|
||||
else:
|
||||
params_request += [param]
|
||||
params_request = {"apiVer": self.__api_version, "url": path, "params": params_request, "rid": self.__request_id}
|
||||
data = json.dumps(params_request).replace('"null"', "null").replace("'null'", "null")
|
||||
encrypted_data = self.__encrypt(self.__device_encryption_token, data)
|
||||
if action is not None:
|
||||
request_url = self.__api_url + action + path
|
||||
else:
|
||||
request_url = self.__api_url + path
|
||||
encrypted_response = requests.post(request_url, headers={"Content-Type": "application/aesjson-jd; charset=utf-8"}, data=encrypted_data)
|
||||
if encrypted_response.status_code != 200:
|
||||
error_msg = json.loads(encrypted_response.text)
|
||||
msg = "\n\tSOURCE: " + error_msg["src"] + "\n\tTYPE: " + \
|
||||
error_msg["type"] + "\n------\nREQUEST_URL: " + \
|
||||
self.__api_url + path
|
||||
if http_method == "GET":
|
||||
msg += query
|
||||
msg += "\n"
|
||||
if data is not None:
|
||||
msg += "DATA:\n" + data
|
||||
raise (MYJDException(msg))
|
||||
if action is None:
|
||||
if not self.__server_encryption_token:
|
||||
response = self.__decrypt(self.__login_secret, encrypted_response.text)
|
||||
else:
|
||||
response = self.__decrypt(self.__server_encryption_token, encrypted_response.text)
|
||||
else:
|
||||
if params is not None:
|
||||
response = self.__decrypt(self.__device_encryption_token, encrypted_response.text)
|
||||
else:
|
||||
return {"data": response}
|
||||
jsondata = json.loads(response.decode('utf-8'))
|
||||
if jsondata['rid'] != self.__request_id:
|
||||
self.update_request_id()
|
||||
return None
|
||||
self.update_request_id()
|
||||
return jsondata
|
||||
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import myjdapi
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
|
||||
class cMyJDownloaderHandler:
|
||||
|
||||
def sendToMyJDownloader(self, sUrl, sMovieTitle):
|
||||
if self.__checkConfig() == False:
|
||||
cGui().showError(cConfig().getLocalizedString(30090), cConfig().getLocalizedString(30254), 5)
|
||||
return False
|
||||
|
||||
jd = myjdapi.Myjdapi()
|
||||
if jd.connect(self.__getUser(), self.__getPass()) == False:
|
||||
cGui().showError(cConfig().getLocalizedString(30090), cConfig().getLocalizedString(30255), 5)
|
||||
return False
|
||||
|
||||
if jd.update_devices() == False:
|
||||
cGui().showError(cConfig().getLocalizedString(30090), cConfig().getLocalizedString(30256), 5)
|
||||
return False
|
||||
|
||||
device = jd.get_device(self.__getDevice())
|
||||
if device.linkgrabber.add_links([{"autostart": False, "links": sUrl, "packageName": sMovieTitle}])['id'] > 0:
|
||||
cGui().showInfo(cConfig().getLocalizedString(30090), cConfig().getLocalizedString(30256), 5)
|
||||
return True
|
||||
return False
|
||||
|
||||
def __checkConfig(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [myjdownloaderHandler]: check MYJD Addon setings', LOGNOTICE)
|
||||
if cConfig().getSetting('myjd_enabled') == 'true':
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getDevice(self):
|
||||
return cConfig().getSetting('myjd_device')
|
||||
|
||||
def __getUser(self):
|
||||
return cConfig().getSetting('myjd_user')
|
||||
|
||||
def __getPass(self):
|
||||
return cConfig().getSetting('myjd_pass')
|
||||
210
plugin.video.xstream/resources/lib/handler/pluginHandler.py
Normal file
@@ -0,0 +1,210 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import xbmc
|
||||
import concurrent.futures
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from xbmc import LOGINFO as LOGNOTICE, LOGERROR, log
|
||||
from resources.lib import utils
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from urllib.parse import urlparse
|
||||
from xbmcgui import Dialog
|
||||
from xbmcvfs import translatePath
|
||||
from resources.lib.tools import platform, infoDialog, getDNS, getRepofromAddonsDB
|
||||
|
||||
|
||||
ADDON_PATH = translatePath(os.path.join('special://home/addons/', '%s'))
|
||||
|
||||
class cPluginHandler:
|
||||
def __init__(self):
|
||||
self.rootFolder = translatePath(cConfig().getAddonInfo('path'))
|
||||
self.settingsFile = os.path.join(self.rootFolder, 'resources', 'settings.xml')
|
||||
self.profilePath = translatePath(cConfig().getAddonInfo('profile'))
|
||||
self.pluginDBFile = os.path.join(self.profilePath, 'pluginDB')
|
||||
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pluginHandler]: profile folder: %s' % self.profilePath, LOGNOTICE)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pluginHandler]: root folder: %s' % self.rootFolder, LOGNOTICE)
|
||||
self.defaultFolder = os.path.join(self.rootFolder, 'sites')
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pluginHandler]: default sites folder: %s' % self.defaultFolder, LOGNOTICE)
|
||||
|
||||
def getAvailablePlugins(self):
|
||||
global globalSearchStatus
|
||||
pluginDB = self.__getPluginDB()
|
||||
update = False
|
||||
fileNames = self.__getFileNamesFromFolder(self.defaultFolder)
|
||||
|
||||
for fileName in fileNames:
|
||||
plugin = {'name': '', 'identifier': '', 'icon': '', 'domain': '', 'globalsearch': '', 'modified': 0}
|
||||
if fileName in pluginDB:
|
||||
plugin.update(pluginDB[fileName])
|
||||
|
||||
try:
|
||||
modTime = os.path.getmtime(os.path.join(self.defaultFolder, fileName + '.py'))
|
||||
except OSError:
|
||||
modTime = 0
|
||||
|
||||
try:
|
||||
globalSearchStatus = cConfig().getSetting('global_search_' + fileName)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if fileName not in pluginDB or modTime > plugin['modified'] or globalSearchStatus:
|
||||
pluginData = self.__getPluginData(fileName, self.defaultFolder)
|
||||
if pluginData:
|
||||
pluginData['globalsearch'] = globalSearchStatus
|
||||
pluginData['modified'] = modTime
|
||||
pluginDB[fileName] = pluginData
|
||||
update = True
|
||||
|
||||
deletions = []
|
||||
for pluginID in pluginDB:
|
||||
if pluginID not in fileNames:
|
||||
deletions.append(pluginID)
|
||||
|
||||
for pid in deletions:
|
||||
del pluginDB[pid]
|
||||
|
||||
if update or deletions:
|
||||
self.__updatePluginDB(pluginDB)
|
||||
|
||||
return self.getAvailablePluginsFromDB()
|
||||
|
||||
def getAvailablePluginsFromDB(self):
|
||||
plugins = []
|
||||
iconFolder = os.path.join(self.rootFolder, 'resources', 'art', 'sites')
|
||||
pluginDB = self.__getPluginDB()
|
||||
|
||||
for pluginID in pluginDB:
|
||||
plugin = pluginDB[pluginID]
|
||||
plugin['id'] = pluginID
|
||||
plugin['icon'] = os.path.join(iconFolder, plugin.get('icon', ''))
|
||||
|
||||
if cConfig().getSetting('plugin_%s' % pluginID) == 'true':
|
||||
plugins.append(plugin)
|
||||
|
||||
return plugins
|
||||
|
||||
def __updatePluginDB(self, data):
|
||||
if not os.path.exists(self.profilePath):
|
||||
os.makedirs(self.profilePath)
|
||||
with open(self.pluginDBFile, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
def __getPluginDB(self):
|
||||
if not os.path.exists(self.pluginDBFile):
|
||||
return dict()
|
||||
try:
|
||||
with open(self.pluginDBFile, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return dict()
|
||||
|
||||
def __getFileNamesFromFolder(self, sFolder):
|
||||
return [os.path.basename(f[:-3]) for f in os.listdir(sFolder) if f.endswith('.py')]
|
||||
|
||||
def __getPluginData(self, fileName, defaultFolder):
|
||||
pluginData = {}
|
||||
if defaultFolder not in sys.path:
|
||||
sys.path.append(defaultFolder)
|
||||
try:
|
||||
plugin = __import__(fileName, globals(), locals())
|
||||
pluginData['name'] = plugin.SITE_NAME
|
||||
pluginData['identifier'] = getattr(plugin, 'SITE_IDENTIFIER', '')
|
||||
pluginData['icon'] = getattr(plugin, 'SITE_ICON', '')
|
||||
pluginData['domain'] = getattr(plugin, 'DOMAIN', '')
|
||||
pluginData['globalsearch'] = getattr(plugin, 'SITE_GLOBAL_SEARCH', True)
|
||||
return pluginData
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def __getPluginDataDomain(self, fileName, defaultFolder):
|
||||
if defaultFolder not in sys.path:
|
||||
sys.path.append(defaultFolder)
|
||||
plugin = __import__(fileName, globals(), locals())
|
||||
return {
|
||||
'identifier': plugin.SITE_IDENTIFIER,
|
||||
'domain': getattr(plugin, 'DOMAIN', '')
|
||||
}
|
||||
|
||||
def checkDomain(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [checkDomain]: Query status code of the provider', LOGNOTICE)
|
||||
fileNames = self.__getFileNamesFromFolder(self.defaultFolder)
|
||||
|
||||
tasks = []
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
|
||||
for fileName in fileNames:
|
||||
try:
|
||||
pluginDataDomain = self.__getPluginDataDomain(fileName, self.defaultFolder)
|
||||
provider = pluginDataDomain['identifier']
|
||||
|
||||
if provider == 'api_all':
|
||||
continue
|
||||
|
||||
domain = cConfig().getSetting(
|
||||
'plugin_' + provider + '.domain',
|
||||
pluginDataDomain['domain']
|
||||
)
|
||||
base_link = 'http://' + domain + '/'
|
||||
|
||||
wrongDomain = ('site-maps.cc', 'www.drei.at', 'notice.cuii.info')
|
||||
if domain in wrongDomain:
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', '')
|
||||
cConfig().setSetting('plugin_' + provider + '_status', '')
|
||||
continue
|
||||
|
||||
if cConfig().getSetting('plugin_' + provider) == 'false':
|
||||
cConfig().setSetting('global_search_' + provider, 'false')
|
||||
cConfig().setSetting('plugin_' + provider + '_checkdomain', 'false')
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', '')
|
||||
cConfig().setSetting('plugin_' + provider + '_status', '')
|
||||
continue
|
||||
|
||||
if cConfig().getSetting('plugin_' + provider + '_checkdomain') == 'true':
|
||||
tasks.append(
|
||||
executor.submit(self._checkdomain, provider, base_link)
|
||||
)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for future in concurrent.futures.as_completed(tasks):
|
||||
try:
|
||||
future.result()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [checkDomain]: Domains for all available Plugins updated', LOGNOTICE)
|
||||
#infoDialog("Domain-Überprüfung aller Plugins abgeschlossen", sound=False, icon='INFO', time=6000)
|
||||
|
||||
def _checkdomain(self, provider, base_link):
|
||||
try:
|
||||
oRequest = cRequestHandler(base_link, caching=False, ignoreErrors=True)
|
||||
oRequest.request()
|
||||
status_code = int(oRequest.getStatus())
|
||||
|
||||
cConfig().setSetting('plugin_' + provider + '_status', str(status_code))
|
||||
|
||||
if 403 <= status_code <= 503:
|
||||
cConfig().setSetting('global_search_' + provider, 'false')
|
||||
|
||||
elif 300 <= status_code <= 400:
|
||||
url = oRequest.getRealUrl()
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', urlparse(url).hostname)
|
||||
cConfig().setSetting('global_search_' + provider, 'true')
|
||||
|
||||
elif status_code == 200:
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', urlparse(base_link).hostname)
|
||||
cConfig().setSetting('global_search_' + provider, 'true')
|
||||
|
||||
else:
|
||||
cConfig().setSetting('global_search_' + provider, 'false')
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', '')
|
||||
|
||||
except Exception:
|
||||
cConfig().setSetting('global_search_' + provider, 'false')
|
||||
cConfig().setSetting('plugin_' + provider + '.domain', '')
|
||||
58
plugin.video.xstream/resources/lib/handler/pyLoadHandler.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import sys
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from xbmc import LOGINFO as LOGNOTICE, log
|
||||
from urllib.request import Request, urlopen, build_opener
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode, quote_plus
|
||||
|
||||
class cPyLoadHandler:
|
||||
def __init__(self):
|
||||
self.config = cConfig()
|
||||
|
||||
def sendToPyLoad(self, sPackage, sUrl):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: PyLoad package: ' + str(sPackage) + ', ' + str(sUrl), LOGNOTICE)
|
||||
if self.__sendLinkToCore(sPackage, sUrl):
|
||||
cGui().showInfo(cConfig().getLocalizedString(30257), cConfig().getLocalizedString(30256), 5)
|
||||
else:
|
||||
cGui().showInfo(cConfig().getLocalizedString(30257), cConfig().getLocalizedString(30258), 5)
|
||||
|
||||
def __sendLinkToCore(self, sPackage, sUrl):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: Sending link...', LOGNOTICE)
|
||||
try:
|
||||
py_host = self.config.getSetting('pyload_host')
|
||||
py_port = self.config.getSetting('pyload_port')
|
||||
py_user = self.config.getSetting('pyload_user')
|
||||
py_passwd = self.config.getSetting('pyload_passwd')
|
||||
mydata = [('username', py_user), ('password', py_passwd)]
|
||||
mydata = urlencode(mydata)
|
||||
# check if host has a leading http://
|
||||
if py_host.find('http://') != 0:
|
||||
py_host = 'http://' + py_host
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: Attemting to connect to PyLoad at: ' + py_host + ':' + py_port, LOGNOTICE)
|
||||
req = Request(py_host + ':' + py_port + '/api/login', mydata)
|
||||
req.add_header("Content-type", "application/x-www-form-urlencoded")
|
||||
page = urlopen(req).read()
|
||||
page = page[1:]
|
||||
session = page[:-1]
|
||||
opener = build_opener()
|
||||
opener.addheaders.append(('Cookie', 'beaker.session.id=' + session))
|
||||
sPackage = sPackage.translate(str.maketrans('\\/:*?"<>|', '_________'))
|
||||
py_url = py_host + ':' + py_port + '/api/addPackage?name="' + quote_plus(sPackage) + '"&links=["' + quote_plus(sUrl) + '"]'
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: PyLoad API call: ' + py_url, LOGNOTICE)
|
||||
sock = opener.open(py_url).read()
|
||||
sock.close()
|
||||
return True
|
||||
except HTTPError as e:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: unable to send link: Error= ' + str(sys.exc_info()[0]), LOGNOTICE)
|
||||
log(e.code, LOGNOTICE)
|
||||
log(e.read(), LOGNOTICE)
|
||||
try:
|
||||
sock.close()
|
||||
except Exception:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [pyLoadHandler]: unable to close socket...', LOGNOTICE)
|
||||
return False
|
||||
516
plugin.video.xstream/resources/lib/handler/requestHandler.py
Normal file
@@ -0,0 +1,516 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import time
|
||||
import xbmcgui
|
||||
import re
|
||||
import os
|
||||
import hashlib
|
||||
import json
|
||||
import traceback
|
||||
import ssl
|
||||
import certifi
|
||||
import socket
|
||||
import zlib
|
||||
import http.client
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.tools import logger, cCache
|
||||
from xbmcvfs import translatePath
|
||||
|
||||
from urllib.parse import quote, urlencode, urlparse, quote_plus
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import HTTPHandler, HTTPSHandler, Request, HTTPCookieProcessor, build_opener, urlopen, HTTPRedirectHandler
|
||||
from http.cookiejar import LWPCookieJar, Cookie
|
||||
from http.client import HTTPException
|
||||
from random import choice
|
||||
|
||||
class IPHTTPSConnection(http.client.HTTPSConnection):
|
||||
def __init__(self, host, ip=None, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
|
||||
self.context = context
|
||||
# If an IP is provided, connect to it rather than the resolved host.
|
||||
self.ip = ip
|
||||
self.actual_host = host # original hostname for SNI and Host header
|
||||
super().__init__(host if not ip else ip, port, timeout=timeout, context=context)
|
||||
|
||||
def connect(self):
|
||||
# Create a socket connection to the provided IP (if any)
|
||||
if self.ip:
|
||||
self.sock = self._create_connection((self.ip, self.port), self.timeout)
|
||||
#if self._tunnel_host:
|
||||
# self._tunnel()
|
||||
# Wrap the socket with our SSL context using the actual host for SNI.
|
||||
self.sock = self.context.wrap_socket(self.sock, server_hostname=self.actual_host)
|
||||
else:
|
||||
super().connect()
|
||||
|
||||
class CustomSecureHTTPSHandler(HTTPSHandler):
|
||||
def __init__(self, ip=None):
|
||||
# Create an SSL context with certifi's CA bundle.
|
||||
context = ssl.create_default_context(cafile=certifi.where())
|
||||
# If an IP is provided, disable hostname checking (since we'll verify using SNI later).
|
||||
context.check_hostname = False if ip else True
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
self.ip = ip
|
||||
self.context = context
|
||||
super().__init__(context=context)
|
||||
|
||||
def https_open(self, req):
|
||||
# Extract the hostname from the request URL.
|
||||
parsed = urlparse(req.full_url)
|
||||
host = parsed.hostname
|
||||
# Define a connection factory that returns an IPHTTPSConnection
|
||||
def connection_factory(*args, **kwargs):
|
||||
return IPHTTPSConnection(host, ip=self.ip, timeout=req.timeout, context=self.context)
|
||||
return self.do_open(connection_factory, req)
|
||||
|
||||
|
||||
class RedirectFilter(HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, hdrs, newurl):
|
||||
if cConfig().getSetting('bypassDNSlock', 'false') != 'true':
|
||||
if 'notice.cuii' in newurl:
|
||||
xbmcgui.Dialog().ok(cConfig().getLocalizedString(30265), cConfig().getLocalizedString(30260) + '\n' + cConfig().getLocalizedString(30261))
|
||||
return None
|
||||
return HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, hdrs, newurl)
|
||||
|
||||
class cRequestHandler:
|
||||
# useful for e.g. tmdb request where multiple requests are made within a loop
|
||||
persistent_openers = {}
|
||||
|
||||
@staticmethod
|
||||
def RandomUA():
|
||||
#Random User Agents aktualisiert 08.06.2025
|
||||
FF_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0'
|
||||
OPERA_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 OPR/119.0.0.0'
|
||||
ANDROID_USER_AGENT = 'Mozilla/5.0 (Linux; Android 15; SM-S931U Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36'
|
||||
EDGE_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0'
|
||||
CHROME_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
|
||||
SAFARI_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15'
|
||||
|
||||
_User_Agents = [FF_USER_AGENT, OPERA_USER_AGENT, EDGE_USER_AGENT, CHROME_USER_AGENT, SAFARI_USER_AGENT]
|
||||
return choice(_User_Agents)
|
||||
|
||||
def __init__(self, sUrl, caching=True, ignoreErrors=False, method='GET', data=None, compression=True, jspost=False, ssl_verify=False, bypass_dns=False):
|
||||
self._sUrl = self.__cleanupUrl(sUrl)
|
||||
self._sRealUrl = ''
|
||||
self._USER_AGENT = self.RandomUA()
|
||||
self._aParameters = {}
|
||||
self._headerEntries = {}
|
||||
self._profilePath = translatePath(cConfig().getAddonInfo('profile'))
|
||||
self._cachePath = ''
|
||||
self._cookiePath = ''
|
||||
self._Status = ''
|
||||
self._sResponseHeader = ''
|
||||
self._ssl_verify = ssl_verify
|
||||
self._bypass_dns = bypass_dns
|
||||
self.ignoreDiscard(False)
|
||||
self.ignoreExpired(False)
|
||||
self.caching = caching
|
||||
self.method = method
|
||||
self.data = data
|
||||
self.ignoreErrors = ignoreErrors
|
||||
self.compression = compression
|
||||
self.jspost = jspost
|
||||
self.cacheTime = int(cConfig().getSetting('cacheTime', 360)) *60 # 360 Minuten * 60 = 6 Stunden Cachetime
|
||||
self.requestTimeout = int(cConfig().getSetting('requestTimeout', 10))
|
||||
self.bypassDNSlock = (cConfig().getSetting('bypassDNSlock', 'false') == 'true')
|
||||
self.removeBreakLines(True)
|
||||
self.removeNewLines(True)
|
||||
self.__setDefaultHeader()
|
||||
self.__setCachePath()
|
||||
self.__setCookiePath()
|
||||
self.isMemoryCacheActive = (cConfig().getSetting('volatileHtmlCache', 'false') == 'true')
|
||||
if self.isMemoryCacheActive:
|
||||
self._memCache = cCache()
|
||||
|
||||
socket.setdefaulttimeout(self.requestTimeout)
|
||||
|
||||
def getStatus(self):
|
||||
return self._Status
|
||||
|
||||
def removeNewLines(self, bRemoveNewLines):
|
||||
self.__bRemoveNewLines = bRemoveNewLines
|
||||
|
||||
def removeBreakLines(self, bRemoveBreakLines):
|
||||
self.__bRemoveBreakLines = bRemoveBreakLines
|
||||
|
||||
def addHeaderEntry(self, sHeaderKey, sHeaderValue):
|
||||
self._headerEntries[sHeaderKey] = sHeaderValue
|
||||
|
||||
def getHeaderEntry(self, sHeaderKey):
|
||||
if sHeaderKey in self._headerEntries:
|
||||
return self._headerEntries[sHeaderKey]
|
||||
|
||||
def addParameters(self, key, value, Quote=False):
|
||||
self._aParameters[key] = value if not Quote else quote(str(value))
|
||||
|
||||
def getResponseHeader(self):
|
||||
return self._sResponseHeader
|
||||
|
||||
def getRealUrl(self):
|
||||
return self._sRealUrl
|
||||
|
||||
def getRequestUri(self):
|
||||
return self._sUrl + '?' + urlencode(self._aParameters)
|
||||
|
||||
def __setDefaultHeader(self):
|
||||
self.addHeaderEntry('User-Agent', self._USER_AGENT)
|
||||
self.addHeaderEntry('Accept-Language', 'de,en-US;q=0.7,en;q=0.3')
|
||||
self.addHeaderEntry('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8')
|
||||
if self.compression:
|
||||
self.addHeaderEntry('Accept-Encoding', 'gzip, deflate')
|
||||
self.addHeaderEntry('Connection', 'keep-alive')
|
||||
self.addHeaderEntry('Keep-Alive', 'timeout=5')
|
||||
|
||||
@staticmethod
|
||||
def __getDefaultHandler(ssl_verify, ip=None):
|
||||
if ip:
|
||||
return [CustomSecureHTTPSHandler(ip=ip)]
|
||||
elif ssl_verify:
|
||||
return [CustomSecureHTTPSHandler()]
|
||||
else:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
return [HTTPSHandler(context=ssl_context)]
|
||||
|
||||
@staticmethod
|
||||
def __cleanupUrl(url):
|
||||
#p = urlparse(url)
|
||||
#if p.query:
|
||||
# query = quote_plus(p.query).replace('%3D', '=').replace('%26', '&')
|
||||
# p = p._replace(query=p.query.replace(p.query, query))
|
||||
#else:
|
||||
# path = quote_plus(p.path).replace('%2F', '/').replace('%26', '&').replace('%3D', '=')
|
||||
# p = p._replace(path=p.path.replace(p.path, path))
|
||||
#return p.geturl()
|
||||
return url
|
||||
|
||||
def request(self):
|
||||
if self.caching and self.cacheTime > 0 and self.method == 'GET' and self.data is None:
|
||||
if self.isMemoryCacheActive:
|
||||
sContent = self.__readVolatileCache(self.getRequestUri(), self.cacheTime)
|
||||
else:
|
||||
sContent = self.__readPersistentCache(self.getRequestUri())
|
||||
if sContent:
|
||||
self._Status = '200'
|
||||
return sContent
|
||||
else:
|
||||
logger.info('-> [requestHandler]: read html for %s' % self.getRequestUri())
|
||||
|
||||
# nur ausführen wenn der übergabeparameter und die konfiguration passen
|
||||
if self._bypass_dns and self.bypassDNSlock:
|
||||
### DNS lock bypass
|
||||
ip_override = self.__doh_request(self._sUrl)
|
||||
### DNS lock bypass
|
||||
else:
|
||||
ip_override = None
|
||||
|
||||
cookieJar = LWPCookieJar(filename=self._cookiePath)
|
||||
try:
|
||||
cookieJar.load(ignore_discard=self.__bIgnoreDiscard, ignore_expires=self.__bIgnoreExpired)
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
|
||||
domain = urlparse(self._sUrl).netloc
|
||||
if domain in cRequestHandler.persistent_openers:
|
||||
opener = cRequestHandler.persistent_openers[domain]
|
||||
else:
|
||||
handlers = self.__getDefaultHandler(self._ssl_verify, ip_override)
|
||||
handlers += [HTTPHandler(), HTTPCookieProcessor(cookiejar=cookieJar), RedirectFilter()]
|
||||
opener = build_opener(*handlers)
|
||||
cRequestHandler.persistent_openers[domain] = opener
|
||||
|
||||
# Prepare parameters for GET/POST
|
||||
if self.method == 'POST':
|
||||
if self.data is not None:
|
||||
if isinstance(self.data, dict):
|
||||
# Default: form data
|
||||
sParameters = urlencode(self.data).encode()
|
||||
elif isinstance(self.data, str):
|
||||
sParameters = self.data.encode()
|
||||
else:
|
||||
sParameters = self.data
|
||||
else:
|
||||
sParameters = None
|
||||
else:
|
||||
sParameters = json.dumps(self._aParameters).encode() if self.jspost else urlencode(self._aParameters, True).encode()
|
||||
if len(sParameters) == 0:
|
||||
sParameters = None
|
||||
|
||||
oRequest = Request(self._sUrl, sParameters if sParameters and len(sParameters) > 0 else None)
|
||||
|
||||
for key, value in self._headerEntries.items():
|
||||
oRequest.add_header(key, value)
|
||||
|
||||
if self.method == 'POST' and 'Content-Type' not in self._headerEntries:
|
||||
oRequest.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
elif self.jspost:
|
||||
oRequest.add_header('Content-Type', 'application/json')
|
||||
|
||||
cookieJar.add_cookie_header(oRequest)
|
||||
|
||||
try:
|
||||
oResponse = opener.open(oRequest)
|
||||
except HTTPError as e:
|
||||
if e.code >= 400:
|
||||
self._Status = str(e.code)
|
||||
data = e.fp.read()
|
||||
if 'DDOS-GUARD' in str(data):
|
||||
opener = build_opener(HTTPCookieProcessor(cookieJar))
|
||||
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
|
||||
response = opener.open('https://check.ddos-guard.net/check.js')
|
||||
content = response.read().decode('utf-8', 'replace')
|
||||
url2 = re.findall("Image.*?'([^']+)'; new", content)
|
||||
url3 = urlparse(self._sUrl)
|
||||
url3 = '%s://%s/%s' % (url3.scheme, url3.netloc, url2[0])
|
||||
opener = build_opener(HTTPCookieProcessor(cookieJar))
|
||||
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
|
||||
opener.open(url3).read()
|
||||
opener = build_opener(HTTPCookieProcessor(cookieJar))
|
||||
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
|
||||
oResponse = opener.open(self._sUrl, sParameters if len(sParameters) > 0 else None)
|
||||
if not oResponse:
|
||||
logger.error(' -> [requestHandler]: Failed DDOS-GUARD active: ' + self._sUrl)
|
||||
return 'DDOS GUARD SCHUTZ'
|
||||
elif 'cloudflare' in str(e.headers):
|
||||
if not self.ignoreErrors:
|
||||
# Angepasste, nutzerfreundliche Cloudflare Meldung
|
||||
msg = 'Die angeforderte Seite (%s) ist durch Cloudflare geschützt.' % urlparse(self._sUrl).netloc
|
||||
msg += '\nDer Zugriff ist deshalb blockiert.'
|
||||
msg += '\n\nBitte versuchen Sie es später erneut oder prüfen Sie die Webseite im Browser.'
|
||||
xbmcgui.Dialog().ok('Cloudflare Schutz aktiv', msg)
|
||||
logger.error(' -> [requestHandler]: Failed Cloudflare active: ' + self._sUrl)
|
||||
return 'CLOUDFLARE-SCHUTZ AKTIV' # Meldung geht als "e.doc" in die exception nach default.py
|
||||
else:
|
||||
if not self.ignoreErrors:
|
||||
xbmcgui.Dialog().ok('xStream', cConfig().getLocalizedString(30259) + ' {0} {1}'.format(self._sUrl, str(e)))
|
||||
logger.error(' -> [requestHandler]: HTTPError ' + str(e) + ' Url: ' + self._sUrl)
|
||||
return 'SEITE NICHT ERREICHBAR'
|
||||
else:
|
||||
if not self.ignoreErrors:
|
||||
xbmcgui.Dialog().ok('xStream', cConfig().getLocalizedString(30259) + ' {0} {1}'.format(self._sUrl, str(e)))
|
||||
logger.error(' -> [requestHandler]: HTTPError ' + str(e) + ' Url: ' + self._sUrl)
|
||||
return 'SEITE NICHT ERREICHBAR'
|
||||
except URLError as e:
|
||||
if not self.ignoreErrors:
|
||||
xbmcgui.Dialog().ok('xStream', str(e.reason))
|
||||
logger.error(' -> [requestHandler]: URLError ' + str(e.reason) + ' Url: ' + self._sUrl)
|
||||
return 'URL FEHLER'
|
||||
except HTTPException as e:
|
||||
if not self.ignoreErrors:
|
||||
xbmcgui.Dialog().ok('xStream', str(e))
|
||||
logger.error(' -> [requestHandler]: HTTPException ' + str(e) + ' Url: ' + self._sUrl)
|
||||
return 'TIMEOUT'
|
||||
|
||||
self._sResponseHeader = oResponse.info()
|
||||
|
||||
content_encoding = self._sResponseHeader.get('Content-Encoding', '').lower()
|
||||
if content_encoding:
|
||||
raw_content = oResponse.read()
|
||||
if content_encoding == 'gzip':
|
||||
decompressed = zlib.decompress(raw_content, wbits=zlib.MAX_WBITS | 16)
|
||||
elif content_encoding == 'deflate':
|
||||
decompressed = zlib.decompress(raw_content, wbits=-zlib.MAX_WBITS)
|
||||
else:
|
||||
decompressed = raw_content
|
||||
sContent = decompressed.decode('utf-8', 'replace')
|
||||
else:
|
||||
sContent = oResponse.read().decode('utf-8', 'replace')
|
||||
|
||||
if 'lazingfast' in sContent:
|
||||
bf = cBF().resolve(self._sUrl, sContent, cookieJar, self._USER_AGENT, sParameters)
|
||||
if bf:
|
||||
sContent = bf
|
||||
else:
|
||||
logger.error(' -> [requestHandler]: Failed Blazingfast active: ' + self._sUrl)
|
||||
|
||||
try:
|
||||
cookieJar.save(ignore_discard=self.__bIgnoreDiscard, ignore_expires=self.__bIgnoreExpired)
|
||||
except Exception as e:
|
||||
logger.error(' -> [requestHandler]: Failed save cookie: %s' % e)
|
||||
|
||||
self._sRealUrl = oResponse.geturl()
|
||||
self._Status = oResponse.getcode() if self._sUrl == self._sRealUrl else '301'
|
||||
|
||||
if self.__bRemoveNewLines:
|
||||
sContent = sContent.replace('\n', '').replace('\r\t', '')
|
||||
if self.__bRemoveBreakLines:
|
||||
sContent = sContent.replace(' ', '')
|
||||
|
||||
if self.caching and self.cacheTime > 0 and self.method == 'GET' and self.data is None:
|
||||
if self.isMemoryCacheActive:
|
||||
self.__writeVolatileCache(self.getRequestUri(), sContent)
|
||||
else:
|
||||
self.__writePersistentCache(self.getRequestUri(), sContent)
|
||||
|
||||
return sContent
|
||||
|
||||
def __setCookiePath(self):
|
||||
cookieFile = os.path.join(self._profilePath, 'cookies')
|
||||
if not os.path.exists(cookieFile):
|
||||
os.makedirs(cookieFile)
|
||||
if 'dummy' not in self._sUrl:
|
||||
cookieFile = os.path.join(cookieFile, urlparse(self._sUrl).netloc.replace('.', '_') + '.txt')
|
||||
if not os.path.exists(cookieFile):
|
||||
open(cookieFile, 'w').close()
|
||||
self._cookiePath = cookieFile
|
||||
|
||||
def getCookie(self, sCookieName, sDomain=''):
|
||||
cookieJar = LWPCookieJar()
|
||||
try:
|
||||
cookieJar.load(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
for entry in cookieJar:
|
||||
if entry.name == sCookieName:
|
||||
if sDomain == '':
|
||||
return entry
|
||||
elif entry.domain == sDomain:
|
||||
return entry
|
||||
return False
|
||||
|
||||
def setCookie(self, oCookie):
|
||||
cookieJar = LWPCookieJar()
|
||||
try:
|
||||
cookieJar.load(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
|
||||
cookieJar.set_cookie(oCookie)
|
||||
cookieJar.save(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
def ignoreDiscard(self, bIgnoreDiscard):
|
||||
self.__bIgnoreDiscard = bIgnoreDiscard
|
||||
|
||||
def ignoreExpired(self, bIgnoreExpired):
|
||||
self.__bIgnoreExpired = bIgnoreExpired
|
||||
|
||||
def __doh_request(self, url, doh_server="https://cloudflare-dns.com/dns-query"):
|
||||
# Parse the URL
|
||||
parsed_url = urlparse(url)
|
||||
hostname = parsed_url.hostname
|
||||
key = 'doh_request' + hostname
|
||||
|
||||
if self.isMemoryCacheActive and self.cacheTime > 0:
|
||||
ip_address = self.__readVolatileCache(key, self.cacheTime)
|
||||
if ip_address:
|
||||
return ip_address
|
||||
|
||||
params = urlencode({"name": hostname, "type": "A"})
|
||||
doh_url = f"{doh_server}?{params}"
|
||||
req = Request(doh_url)
|
||||
req.add_header("Accept", "application/dns-json")
|
||||
|
||||
try:
|
||||
response = urlopen(req, timeout=5)
|
||||
response_text = response.read().decode("utf-8", "replace")
|
||||
dns_response = json.loads(response_text)
|
||||
if "Answer" not in dns_response:
|
||||
raise Exception("Invalid DNS response")
|
||||
ip_address = dns_response["Answer"][0]["data"]
|
||||
if self.isMemoryCacheActive and self.cacheTime > 0:
|
||||
self.__writeVolatileCache(key, ip_address)
|
||||
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
logger.error(' -> [requestHandler]: DNS query failed: %s' % e)
|
||||
return None
|
||||
|
||||
def __setCachePath(self):
|
||||
cache = os.path.join(self._profilePath, 'htmlcache')
|
||||
if not os.path.exists(cache):
|
||||
os.makedirs(cache)
|
||||
self._cachePath = cache
|
||||
|
||||
def __readPersistentCache(self, url):
|
||||
h = hashlib.md5(url.encode('utf8')).hexdigest()
|
||||
cacheFile = os.path.join(self._cachePath, h)
|
||||
fileAge = self.getFileAge(cacheFile)
|
||||
if 0 < fileAge < self.cacheTime:
|
||||
try:
|
||||
with open(cacheFile, 'rb') as f:
|
||||
content = f.read().decode('utf8')
|
||||
except Exception:
|
||||
logger.error(' -> [requestHandler]: Could not read Cache')
|
||||
if content:
|
||||
logger.info(' -> [requestHandler]: read html for %s from cache' % url)
|
||||
return content
|
||||
return None
|
||||
|
||||
def __writePersistentCache(self, url, content):
|
||||
try:
|
||||
h = hashlib.md5(url.encode('utf8')).hexdigest()
|
||||
with open(os.path.join(self._cachePath, h), 'wb') as f:
|
||||
f.write(content.encode('utf8'))
|
||||
except Exception:
|
||||
logger.error(' -> [requestHandler]: Could not write Cache')
|
||||
|
||||
def __writeVolatileCache(self, url, content):
|
||||
self._memCache.set(hashlib.md5(url.encode('utf8')).hexdigest(), content)
|
||||
|
||||
def __readVolatileCache(self, url, cache_time):
|
||||
entry = self._memCache.get(hashlib.md5(url.encode('utf8')).hexdigest(), cache_time)
|
||||
if entry:
|
||||
logger.info('-> [requestHandler]: read html for %s from cache' % url)
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def getFileAge(cacheFile):
|
||||
try:
|
||||
return time.time() - os.stat(cacheFile).st_mtime
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def clearCache(self):
|
||||
# clear volatile cache
|
||||
if self.isMemoryCacheActive:
|
||||
self._memCache.clear()
|
||||
cRequestHandler.persistent_openers.clear()
|
||||
|
||||
# clear persistent cache
|
||||
files = os.listdir(self._cachePath)
|
||||
for file in files:
|
||||
os.remove(os.path.join(self._cachePath, file))
|
||||
xbmcgui.Dialog().notification('xStream', cConfig().getLocalizedString(30405), xbmcgui.NOTIFICATION_INFO, 100, False)
|
||||
|
||||
|
||||
class cBF:
|
||||
def resolve(self, url, html, cookie_jar, user_agent, sParameters):
|
||||
page = urlparse(url).scheme + '://' + urlparse(url).netloc
|
||||
j = re.compile('<script[^>]src="([^"]+)').findall(html)
|
||||
if j:
|
||||
opener = build_opener(HTTPCookieProcessor(cookie_jar))
|
||||
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
|
||||
opener.open(page + j[0])
|
||||
a = re.compile(r'xhr\.open\("GET","([^,]+)",').findall(html)
|
||||
if a:
|
||||
import random
|
||||
aespage = page + a[0].replace('" + ww +"', str(random.randint(700, 1500)))
|
||||
opener = build_opener(HTTPCookieProcessor(cookie_jar))
|
||||
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
|
||||
html = opener.open(aespage).read().decode('utf-8', 'replace')
|
||||
cval = self.aes_decode(html)
|
||||
cdata = re.compile('cookie="([^="]+).*?domain[^>]=([^;]+)').findall(html)
|
||||
if cval and cdata:
|
||||
c = Cookie(version=0, name=cdata[0][0], value=cval, port=None, port_specified=False, domain=cdata[0][1], domain_specified=True, domain_initial_dot=False, path="/", path_specified=True, secure=False, expires=time.time() + 21600, discard=False, comment=None, comment_url=None, rest={})
|
||||
cookie_jar.set_cookie(c)
|
||||
opener = build_opener(HTTPCookieProcessor(cookie_jar))
|
||||
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
|
||||
return opener.open(url, sParameters if len(sParameters) > 0 else None).read().decode('utf-8', 'replace')
|
||||
|
||||
@staticmethod
|
||||
def aes_decode(html):
|
||||
try:
|
||||
import pyaes
|
||||
keys = re.compile(r'toNumbers\("([^"]+)"').findall(html)
|
||||
if keys:
|
||||
from binascii import hexlify, unhexlify
|
||||
msg = unhexlify(keys[2])
|
||||
key = unhexlify(keys[0])
|
||||
iv = unhexlify(keys[1])
|
||||
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
|
||||
plain_text = decrypter.feed(msg)
|
||||
plain_text += decrypter.feed()
|
||||
return hexlify(plain_text).decode()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
4
plugin.video.xstream/resources/lib/jsnprotect.py
Normal file
146
plugin.video.xstream/resources/lib/jsunpacker.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
"""
|
||||
urlresolver XBMC Addon
|
||||
Copyright (C) 2013 Bstrdsmkr
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Adapted for use in xbmc from:
|
||||
https://github.com/einars/js-beautify/blob/master/python/jsbeautifier/unpackers/packer.py
|
||||
|
||||
Unpacker for Dean Edward's p.a.c.k.e.r, a part of javascript beautifier
|
||||
by Einar Lielmanis <einar@jsbeautifier.org>
|
||||
|
||||
written by Stefano Sanfilippo <a.little.coder@gmail.com>
|
||||
|
||||
usage:
|
||||
|
||||
if detect(some_string):
|
||||
unpacked = unpack(some_string)
|
||||
|
||||
Unpacker for Dean Edward's p.a.c.k.e.r
|
||||
"""
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
def detect(source):
|
||||
"""Detects whether `source` is P.A.C.K.E.R. coded."""
|
||||
return source.replace(' ', '').startswith('eval(function(p,a,c,k,e,')
|
||||
|
||||
def unpack(source):
|
||||
"""Unpacks P.A.C.K.E.R. packed js code."""
|
||||
payload, symtab, radix, count = _filterargs(source)
|
||||
|
||||
if count != len(symtab):
|
||||
raise UnpackingError('Malformed p.a.c.k.e.r. symtab.')
|
||||
|
||||
try:
|
||||
unbase = Unbaser(radix)
|
||||
except TypeError:
|
||||
raise UnpackingError('Unknown p.a.c.k.e.r. encoding.')
|
||||
|
||||
def lookup(match):
|
||||
"""Look up symbols in the synthetic symtab."""
|
||||
word = match.group(0)
|
||||
return symtab[unbase(word)] or word
|
||||
|
||||
source = re.sub(r'\b\w+\b', lookup, payload)
|
||||
return _replacestrings(source)
|
||||
|
||||
def _filterargs(source):
|
||||
"""Juice from a source file the four args needed by decoder."""
|
||||
juicers = [ (r"}\('(.*)', *(\d+), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)"),
|
||||
(r"}\('(.*)', *(\d+), *(\d+), *'(.*)'\.split\('\|'\)"),
|
||||
]
|
||||
for juicer in juicers:
|
||||
args = re.search(juicer, source, re.DOTALL)
|
||||
if args:
|
||||
a = args.groups()
|
||||
try:
|
||||
return a[0], a[3].split('|'), int(a[1]), int(a[2])
|
||||
except ValueError:
|
||||
raise UnpackingError('Corrupted p.a.c.k.e.r. data.')
|
||||
|
||||
# could not find a satisfying regex
|
||||
raise UnpackingError('Could not make sense of p.a.c.k.e.r data (unexpected code structure)')
|
||||
|
||||
def _replacestrings(source):
|
||||
"""Strip string lookup table (list) and replace values in source."""
|
||||
match = re.search(r'var *(_\w+)\=\["(.*?)"\];', source, re.DOTALL)
|
||||
|
||||
if match:
|
||||
varname, strings = match.groups()
|
||||
startpoint = len(match.group(0))
|
||||
lookup = strings.split('","')
|
||||
variable = '%s[%%d]' % varname
|
||||
for index, value in enumerate(lookup):
|
||||
source = source.replace(variable % index, '"%s"' % value)
|
||||
return source[startpoint:]
|
||||
return source
|
||||
|
||||
|
||||
class Unbaser(object):
|
||||
"""Functor for a given base. Will efficiently convert
|
||||
strings to natural numbers."""
|
||||
ALPHABET = {
|
||||
56: '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz',
|
||||
59: '0123456789abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
64: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/',
|
||||
95: (' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
r'[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
}
|
||||
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
# If base can be handled by int() builtin, let it do it for us
|
||||
if 2 <= base <= 36:
|
||||
self.unbase = lambda string: int(string, base)
|
||||
else:
|
||||
# Build conversion dictionary cache
|
||||
try:
|
||||
self.ALPHABET = self.ALPHABET[base] if base in self.ALPHABET else self.ALPHABET[64][0:base]
|
||||
self.dictionary = dict((cipher, index) for
|
||||
index, cipher in enumerate(self.ALPHABET))
|
||||
except KeyError:
|
||||
raise TypeError('Unsupported base encoding.')
|
||||
|
||||
self.unbase = self._dictunbaser
|
||||
|
||||
def __call__(self, string):
|
||||
return self.unbase(string)
|
||||
|
||||
def _dictunbaser(self, string):
|
||||
"""Decodes a value to an integer."""
|
||||
ret = 0
|
||||
for index, cipher in enumerate(string[::-1]):
|
||||
ret += (self.base ** index) * self.dictionary[cipher]
|
||||
return ret
|
||||
|
||||
|
||||
class UnpackingError(Exception):
|
||||
"""Badly packed source or general error. Argument is a
|
||||
meaningful description."""
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test = '''eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('4(\'30\').2z({2y:\'5://a.8.7/i/z/y/w.2x\',2w:{b:\'2v\',19:\'<p><u><2 d="20" c="#17">2u 19.</2></u><16/><u><2 d="18" c="#15">2t 2s 2r 2q.</2></u></p>\',2p:\'<p><u><2 d="20" c="#17">2o 2n b.</2></u><16/><u><2 d="18" c="#15">2m 2l 2k 2j.</2></u></p>\',},2i:\'2h\',2g:[{14:"11",b:"5://a.8.7/2f/13.12"},{14:"2e",b:"5://a.8.7/2d/13.12"},],2c:"11",2b:[{10:\'2a\',29:\'5://v.8.7/t-m/m.28\'},{10:\'27\'}],26:{\'25-3\':{\'24\':{\'23\':22,\'21\':\'5://a.8.7/i/z/y/\',\'1z\':\'w\',\'1y\':\'1x\'}}},s:\'5://v.8.7/t-m/s/1w.1v\',1u:"1t",1s:"1r",1q:\'1p\',1o:"1n",1m:"1l",1k:\'5\',1j:\'o\',});l e;l k=0;l 6=0;4().1i(9(x){f(6>0)k+=x.r-6;6=x.r;f(q!=0&&k>=q){6=-1;4().1h();4().1g(o);$(\'#1f\').j();$(\'h.g\').j()}});4().1e(9(x){6=-1});4().1d(9(x){n(x)});4().1c(9(){$(\'h.g\').j()});9 n(x){$(\'h.g\').1b();f(e)1a;e=1;}',36,109,'||font||jwplayer|http|p0102895|me|vidto|function|edge3|file|color|size|vvplay|if|video_ad|div||show|tt102895|var|player|doPlay|false||21600|position|skin|test||static|1y7okrqkv4ji||00020|01|type|360p|mp4|video|label|FFFFFF|br|FF0000||deleted|return|hide|onComplete|onPlay|onSeek|play_limit_box|setFullscreen|stop|onTime|dock|provider|391|height|650|width|over|controlbar|5110|duration|uniform|stretching|zip|stormtrooper|213|frequency|prefix||path|true|enabled|preview|timeslidertooltipplugin|plugins|html5|swf|src|flash|modes|hd_default|3bjhohfxpiqwws4phvqtsnolxocychumk274dsnkblz6sfgq6uz6zt77gxia|240p|3bjhohfxpiqwws4phvqtsnolxocychumk274dsnkba36sfgq6uzy3tv2oidq|hd|original|ratio|broken|is|link|Your|such|No|nofile|more|any|availabe|Not|File|OK|previw|jpg|image|setup|flvplayer'.split('|')))'''
|
||||
# test = '''eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('y.x(A(\'%0%f%b%9%1%d%8%8%o%e%B%c%0%e%d%0%f%w%1%7%3%2%p%d%1%n%2%1%c%0%t%0%f%7%8%8%d%5%6%1%7%e%b%l%7%1%2%e%9%q%c%0%6%1%z%2%0%f%b%1%9%c%0%s%6%6%l%G%4%4%5%5%5%k%b%7%5%8%o%i%2%k%6%i%4%2%3%p%2%n%4%5%7%6%9%s%4%j%q%a%h%a%3%a%E%a%3%D%H%9%K%C%I%m%r%g%h%L%v%g%u%F%r%g%3%J%3%j%3%m%h%4\'));',48,48,'22|72|65|6d|2f|77|74|61|6c|63|4e|73|3d|6f|6e|20|4d|32|76|59|2e|70|51|64|69|62|79|31|68|30|7a|34|66|write|document|75|unescape|67|4f|5a|57|55|3a|44|47|4a|78|49'.split('|'),0,{}))'''
|
||||
# test = '''eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('x.w(z(\'%1%f%9%b%0%d%7%7%m%e%A%c%1%e%d%1%f%v%0%3%i%2%o%d%0%s%2%0%c%1%q%1%f%3%7%7%d%6%5%0%3%e%9%l%3%0%2%e%b%g%c%1%5%0%y%2%1%f%9%0%b%c%1%r%5%5%l%E%4%4%6%6%6%n%9%3%6%7%m%k%2%n%5%k%4%2%i%o%2%s%4%6%3%5%b%r%4%8%D%h%C%a%F%8%H%B%I%h%i%a%g%8%u%a%q%j%t%j%g%8%t%h%p%j%p%a%G%4\'));',45,45,'72|22|65|61|2f|74|77|6c|5a|73|55|63|3d|6f|6e|20|79|59|6d|4d|76|70|69|2e|62|7a|30|68|64|44|54|66|write|document|75|unescape|67|51|32|6a|3a|35|5f|47|34'.split('|'),0,{}))'''
|
||||
test = '''eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('q.r(s(\'%h%t%a%p%u%6%c%n%0%5%l%4%2%4%7%j%0%8%1%o%b%3%7%m%1%8%a%7%b%3%d%6%1%f%0%v%1%5%D%9%0%5%c%g%0%4%A%9%0%f%k%z%2%8%1%C%2%i%d%6%2%3%k%j%2%3%y%e%x%w%g%B%E%F%i%h%e\'));',42,42,'5a|4d|4f|54|6a|44|33|6b|57|7a|56|4e|68|55|3e|47|69|65|6d|32|45|46|31|6f|30|75|document|write|unescape|6e|62|6c|2f|3c|22|79|63|66|78|59|72|61'.split('|'),0,{}))'''
|
||||
print (unpack(test))
|
||||
87
plugin.video.xstream/resources/lib/player.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import xbmc
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib.config import cConfig
|
||||
from xbmc import LOGINFO as LOGNOTICE, LOGERROR, log
|
||||
|
||||
class XstreamPlayer(xbmc.Player):
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmc.Player.__init__(self, *args, **kwargs)
|
||||
self.streamFinished = False
|
||||
self.streamSuccess = True
|
||||
self.playedTime = 0
|
||||
self.totalTime = 999999
|
||||
self.from_global_search = False # Track if started from Global Search
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: player instance created', LOGNOTICE)
|
||||
|
||||
def onPlayBackStarted(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: starting Playback', LOGNOTICE)
|
||||
try:
|
||||
self.totalTime = self.getTotalTime()
|
||||
except:
|
||||
self.totalTime = 999999
|
||||
|
||||
# Detect if playback started from Global Search
|
||||
try:
|
||||
path = xbmc.getInfoLabel('Container.FolderPath')
|
||||
if path:
|
||||
low = path.lower()
|
||||
keywords = [
|
||||
'function=globalsearch',
|
||||
'site=globalsearch',
|
||||
'function=searchalter',
|
||||
'function=searchtmdb'
|
||||
]
|
||||
if any(kw in low for kw in keywords):
|
||||
self.from_global_search = True
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: Detected Global Search context', LOGNOTICE)
|
||||
except:
|
||||
pass
|
||||
|
||||
def onPlayBackStopped(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: Playback stopped', LOGNOTICE)
|
||||
if self.playedTime == 0 and self.totalTime == 999999:
|
||||
self.streamSuccess = False
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: Kodi failed to open stream', LOGERROR)
|
||||
self.streamFinished = True
|
||||
|
||||
# After playback ends, if we came from Global Search → return to main menu
|
||||
if self.from_global_search:
|
||||
try:
|
||||
xbmc.executebuiltin('Container.Update(plugin://plugin.video.xstream/)')
|
||||
log('xStream -> [player]: Returning to addon main menu after Global Search', LOGNOTICE)
|
||||
except Exception as e:
|
||||
log('xStream -> [player]: Failed to return to main menu: %s' % str(e), LOGERROR)
|
||||
|
||||
def onPlayBackEnded(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: Playback completed', LOGNOTICE)
|
||||
self.onPlayBackStopped()
|
||||
|
||||
|
||||
class cPlayer:
|
||||
def clearPlayList(self):
|
||||
oPlaylist = self.__getPlayList()
|
||||
oPlaylist.clear()
|
||||
|
||||
def __getPlayList(self):
|
||||
return xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
|
||||
def addItemToPlaylist(self, oGuiElement):
|
||||
oListItem = cGui().createListItem(oGuiElement)
|
||||
self.__addItemToPlaylist(oGuiElement, oListItem)
|
||||
|
||||
def __addItemToPlaylist(self, oGuiElement, oListItem):
|
||||
oPlaylist = self.__getPlayList()
|
||||
oPlaylist.add(oGuiElement.getMediaUrl(), oListItem)
|
||||
|
||||
def startPlayer(self):
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [player]: start player', LOGNOTICE)
|
||||
xbmcPlayer = XstreamPlayer()
|
||||
monitor = xbmc.Monitor()
|
||||
while (not monitor.abortRequested()) & (not xbmcPlayer.streamFinished):
|
||||
if xbmcPlayer.isPlayingVideo():
|
||||
xbmcPlayer.playedTime = xbmcPlayer.getTime()
|
||||
monitor.waitForAbort(10)
|
||||
return xbmcPlayer.streamSuccess
|
||||
356
plugin.video.xstream/resources/lib/tmdb.py
Normal file
@@ -0,0 +1,356 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.config import cConfig
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
class cTMDB:
|
||||
TMDB_GENRES = {12: 'Abenteuer', 14: 'Fantasy', 16: 'Animation', 18: 'Drama', 27: 'Horror', 28: 'Action', 35: 'Komödie', 36: 'Historie', 37: 'Western', 53: 'Thriller', 80: 'Krimi', 99: 'Dokumentarfilm', 878: 'Science Fiction', 9648: 'Mystery', 10402: 'Musik', 10749: 'Liebesfilm', 10751: 'Familie', 10752: 'Kriegsfilm', 10759: 'Action & Adventure', 10762: 'Kids', 10763: 'News', 10764: 'Reality', 10765: 'Sci-Fi & Fantasy', 10766: 'Soap', 10767: 'Talk', 10768: 'War & Politics', 10770: 'TV-Film'}
|
||||
URL = 'https://api.themoviedb.org/3/'
|
||||
URL_TRAILER = 'plugin://plugin.video.youtube/play/?video_id=%s'
|
||||
TMDB_LANGUAGE = cConfig().getSetting('tmdb_lang')
|
||||
|
||||
def __init__(self, api_key='', lang=TMDB_LANGUAGE):
|
||||
self.api_key = '86dd18b04874d9c94afadde7993d94e3'
|
||||
self.lang = lang
|
||||
self.poster = 'https://image.tmdb.org/t/p/%s' % cConfig().getSetting('poster_tmdb')
|
||||
self.fanart = 'https://image.tmdb.org/t/p/%s' % cConfig().getSetting('backdrop_tmdb')
|
||||
|
||||
|
||||
def search_movie_name(self, name, year='', page=1, advanced='false'):
|
||||
name = re.sub(' +', ' ', name)
|
||||
if year:
|
||||
# term = quote_plus(name) + '&year=' + year
|
||||
name = re.sub(year, ' ', name) #Wenn das Jahr im Namen auftaucht dann das Jahr löschen
|
||||
#else:
|
||||
# term = quote_plus(name)
|
||||
term = quote_plus(name)
|
||||
meta = self._call('search/movie', 'query=' + term + '&page=' + str(page))
|
||||
if 'errors' not in meta and 'status_code' not in meta:
|
||||
#if 'total_results' in meta and meta['total_results'] == 0 and year:
|
||||
# meta = self.search_movie_name(name, '', advanced=advanced)
|
||||
if 'total_results' in meta and meta['total_results'] != 0:
|
||||
movie = ''
|
||||
if meta['total_results'] == 1:
|
||||
movie = meta['results'][0]
|
||||
else:
|
||||
for searchMovie in meta['results']:
|
||||
try: # Exception Handling notwendig da TMDb in seltenen Fällen keine genre_ids mitliefert
|
||||
if searchMovie['genre_ids'] and 99 not in searchMovie['genre_ids']:
|
||||
if searchMovie['title'].lower() == name.lower():
|
||||
movie = searchMovie
|
||||
break
|
||||
except Exception:
|
||||
break
|
||||
if not movie:
|
||||
for searchMovie in meta['results']:
|
||||
if searchMovie['genre_ids'] and 99 not in searchMovie['genre_ids']:
|
||||
if year:
|
||||
if 'release_date' in searchMovie and searchMovie['release_date']:
|
||||
release_date = searchMovie['release_date']
|
||||
yy = release_date[:4]
|
||||
if int(year) - int(yy) > 1:
|
||||
continue
|
||||
movie = searchMovie
|
||||
break
|
||||
if not movie:
|
||||
movie = meta['results'][0]
|
||||
if advanced == 'true':
|
||||
tmdb_id = movie['id']
|
||||
meta = self.search_movie_id(tmdb_id)
|
||||
else:
|
||||
meta = movie
|
||||
else:
|
||||
meta = {}
|
||||
return meta
|
||||
|
||||
def search_movie_id(self, movie_id, append_to_response='append_to_response=trailers,credits'):
|
||||
result = self._call('movie/' + str(movie_id), append_to_response)
|
||||
result['tmdb_id'] = movie_id
|
||||
return result
|
||||
|
||||
def search_tvshow_name(self, name, year='', page=1, genre='', advanced='false'):
|
||||
name = name.lower()
|
||||
if '- staffel' in name:
|
||||
name = re.sub(r'\s-\s\wtaffel[^>]([1-9\-]+)', '', name)
|
||||
elif 'staffel' in name:
|
||||
name = re.sub(r'\s\wtaffel[^>]([1-9\-]+)', '', name)
|
||||
if year:
|
||||
# term = quote_plus(name) + '&year=' + year
|
||||
name = re.sub(year, ' ', name) #Wenn das Jahr im Namen auftaucht dann das Jahr löschen
|
||||
#else:
|
||||
# term = quote_plus(name)
|
||||
term = quote_plus(name)
|
||||
meta = self._call('search/tv', 'query=' + term + '&page=' + str(page))
|
||||
if 'errors' not in meta and 'status_code' not in meta:
|
||||
#if 'total_results' in meta and meta['total_results'] == 0 and year:
|
||||
# meta = self.search_tvshow_name(name, '', advanced=advanced)
|
||||
if 'total_results' in meta and meta['total_results'] != 0:
|
||||
movie = ''
|
||||
if meta['total_results'] == 1:
|
||||
movie = meta['results'][0]
|
||||
else:
|
||||
for searchMovie in meta['results']:
|
||||
if genre == '' or genre in searchMovie['genre_ids']:
|
||||
movieName = searchMovie['name']
|
||||
if movieName.lower() == name.lower():
|
||||
movie = searchMovie
|
||||
break
|
||||
if not movie:
|
||||
for searchMovie in meta['results']:
|
||||
if genre and genre in searchMovie['genre_ids']:
|
||||
if year:
|
||||
if 'release_date' in searchMovie and searchMovie['release_date']:
|
||||
release_date = searchMovie['release_date']
|
||||
yy = release_date[:4]
|
||||
if int(year) - int(yy) > 1:
|
||||
continue
|
||||
movie = searchMovie
|
||||
break
|
||||
if not movie:
|
||||
movie = meta['results'][0]
|
||||
if advanced == 'true':
|
||||
tmdb_id = movie['id']
|
||||
meta = self.search_tvshow_id(tmdb_id)
|
||||
else:
|
||||
meta = movie
|
||||
else:
|
||||
meta = {}
|
||||
return meta
|
||||
|
||||
def search_tvshow_id(self, show_id, append_to_response='append_to_response=external_ids,videos,credits'):
|
||||
result = self._call('tv/' + str(show_id), append_to_response)
|
||||
result['tmdb_id'] = show_id
|
||||
return result
|
||||
|
||||
def get_meta(self, media_type, name, imdb_id='', tmdb_id='', year='', season='', episode='', advanced='false'):
|
||||
name = re.sub(' +', ' ', name)
|
||||
meta = {}
|
||||
if media_type == 'movie':
|
||||
if tmdb_id:
|
||||
meta = self.search_movie_id(tmdb_id)
|
||||
elif name:
|
||||
meta = self.search_movie_name(name, year, advanced=advanced)
|
||||
elif media_type == 'tvshow':
|
||||
if tmdb_id:
|
||||
meta = self.search_tvshow_id(tmdb_id)
|
||||
elif name:
|
||||
meta = self.search_tvshow_name(name, year, advanced=advanced)
|
||||
if meta and 'id' in meta:
|
||||
meta = self._format(meta, name)
|
||||
return meta
|
||||
|
||||
def getUrl(self, url, page=1, term=''):
|
||||
try:
|
||||
if term:
|
||||
term = term + '&page=' + str(page)
|
||||
else:
|
||||
term = 'page=' + str(page)
|
||||
result = self._call(url, term)
|
||||
except Exception:
|
||||
return False
|
||||
return result
|
||||
|
||||
def _call(self, action, append_to_response=''):
|
||||
url = '%s%s?language=%s&api_key=%s' % (self.URL, action, self.lang, self.api_key)
|
||||
if append_to_response:
|
||||
url += '&%s' % append_to_response
|
||||
if 'person' in url:
|
||||
url = url.replace('&page=', '')
|
||||
oRequestHandler = cRequestHandler(url, ignoreErrors=True)
|
||||
name = oRequestHandler.request()
|
||||
try:
|
||||
data = json.loads(name)
|
||||
except Exception:
|
||||
return {}
|
||||
if 'status_code' in data and data['status_code'] == 34:
|
||||
return {}
|
||||
return data
|
||||
|
||||
def getGenresFromIDs(self, genresID):
|
||||
sGenres = []
|
||||
for gid in genresID:
|
||||
genre = self.TMDB_GENRES.get(gid)
|
||||
if genre:
|
||||
sGenres.append(genre)
|
||||
return sGenres
|
||||
|
||||
def getLanguage(self, Language):
|
||||
iso_639 = {'en': 'English', 'de': 'German', 'fr': 'French', 'it': 'Italian', 'nl': 'Nederlands', 'sv': 'Swedish', 'cs': 'Czech', 'da': 'Danish', 'fi': 'Finnish', 'pl': 'Polish', 'es': 'Spanish', 'el': 'Greek', 'tr': 'Turkish', 'uk': 'Ukrainian', 'ru': 'Russian', 'kn': 'Kannada', 'ga': 'Irish', 'hr': 'Croatian', 'hu': 'Hungarian', 'ja': 'Japanese', 'no': 'Norwegian', 'id': 'Indonesian', 'ko': 'Korean', 'pt': 'Portuguese', 'lv': 'Latvian', 'lt': 'Lithuanian', 'ro': 'Romanian', 'sk': 'Slovak', 'sl': 'Slovenian', 'sq': 'Albanian', 'sr': 'Serbian', 'th': 'Thai', 'vi': 'Vietnamese', 'bg': 'Bulgarian', 'fa': 'Persian', 'hy': 'Armenian', 'ka': 'Georgian', 'ar': 'Arabic', 'af': 'Afrikaans', 'bs': 'Bosnian', 'zh': 'Chinese', 'cn': 'Chinese', 'hi': 'Hindi'}
|
||||
if Language in iso_639:
|
||||
return iso_639[Language]
|
||||
else:
|
||||
return Language
|
||||
|
||||
def get_meta_episodes(self, media_type, name, tmdb_id='', season='', episode='', advanced='false'):
|
||||
meta = {}
|
||||
if media_type == 'episode' and tmdb_id and season and episode:
|
||||
url = '%stv/%s/season/%s?api_key=%s&language=de' % (self.URL, tmdb_id, season, self.api_key)
|
||||
Data = cRequestHandler(url, ignoreErrors=True).request()
|
||||
if Data:
|
||||
try:
|
||||
meta = json.loads(Data)
|
||||
if 'status_code' in meta and meta['status_code'] == 34:
|
||||
meta = {}
|
||||
except Exception:
|
||||
meta = {}
|
||||
if 'episodes' in meta:
|
||||
for e in meta['episodes']:
|
||||
if 'episode_number':
|
||||
if e['episode_number'] == int(episode):
|
||||
return self._format_episodes(e, name)
|
||||
else:
|
||||
return {}
|
||||
|
||||
def _format_episodes(self, meta, name):
|
||||
_meta = {}
|
||||
if 'air_date' in meta and meta['air_date']:
|
||||
_meta['aired'] = meta['air_date']
|
||||
if 'episode_number' in meta and meta['episode_number']:
|
||||
_meta['episode'] = meta['episode_number']
|
||||
if 'name' in meta and meta['name']:
|
||||
_meta['title'] = meta['name']
|
||||
if 'overview' in meta and meta['overview']:
|
||||
_meta['plot'] = meta['overview']
|
||||
if 'production_code' in meta and meta['production_code']:
|
||||
_meta['code'] = str(meta['production_code'])
|
||||
if 'season_number' in meta and meta['season_number']:
|
||||
_meta['season'] = meta['season_number']
|
||||
if 'still_path' in meta and meta['still_path']:
|
||||
_meta['cover_url'] = self.poster + meta['still_path']
|
||||
if 'vote_average' in meta and meta['vote_average']:
|
||||
_meta['rating'] = meta['vote_average']
|
||||
if 'vote_count' in meta and meta['vote_count']:
|
||||
_meta['votes'] = meta['vote_count']
|
||||
if 'crew' in meta and meta['crew']:
|
||||
_meta['writer'] = ''
|
||||
_meta['director'] = ''
|
||||
|
||||
for crew in meta['crew']:
|
||||
if crew['department'] == 'Directing':
|
||||
if _meta['director'] != '':
|
||||
_meta['director'] += ' / '
|
||||
_meta['director'] += '%s: %s' % (crew['job'], crew['name'])
|
||||
elif crew['department'] == 'Writing':
|
||||
if _meta['writer'] != '':
|
||||
_meta['writer'] += ' / '
|
||||
_meta['writer'] += '%s: %s' % (crew['job'], crew['name'])
|
||||
if 'guest_stars' in meta and meta['guest_stars']:
|
||||
licast = []
|
||||
for c in meta['guest_stars']:
|
||||
licast.append((c['name'], c['character'], self.poster + str(c['profile_path'])))
|
||||
_meta['cast'] = licast
|
||||
return _meta
|
||||
|
||||
def _format(self, meta, name):
|
||||
_meta = {}
|
||||
_meta['genre'] = ''
|
||||
if 'id' in meta:
|
||||
_meta['tmdb_id'] = meta['id']
|
||||
if 'backdrop_path' in meta and meta['backdrop_path']:
|
||||
_meta['backdrop_url'] = self.fanart + str(meta['backdrop_path'])
|
||||
if 'original_language' in meta and meta['original_language']:
|
||||
_meta['country'] = self.getLanguage(meta['original_language'])
|
||||
if 'original_title' in meta and meta['original_title']:
|
||||
_meta['originaltitle'] = meta['original_title']
|
||||
elif 'original_name' in meta and meta['original_name']:
|
||||
_meta['originaltitle'] = meta['original_name']
|
||||
if 'overview' in meta and meta['overview']:
|
||||
_meta['plot'] = meta['overview']
|
||||
if 'poster_path' in meta and meta['poster_path']:
|
||||
_meta['cover_url'] = self.poster + str(meta['poster_path'])
|
||||
if 'release_date' in meta and meta['release_date']:
|
||||
_meta['premiered'] = meta['release_date']
|
||||
elif 'first_air_date' in meta and meta['first_air_date']:
|
||||
_meta['premiered'] = meta['first_air_date']
|
||||
if 'premiered' in _meta and _meta['premiered'] and len(_meta['premiered']) == 10:
|
||||
_meta['year'] = int(_meta['premiered'][:4])
|
||||
if 'budget' in meta and meta['budget']:
|
||||
_meta['budget'] = '{:,} $'.format(meta['budget'])
|
||||
if 'revenue' in meta and meta['revenue']:
|
||||
_meta['revenue'] = '{:,} $'.format(meta['revenue'])
|
||||
if 'status' in meta and meta['status']:
|
||||
_meta['status'] = meta['status']
|
||||
duration = 0
|
||||
if 'runtime' in meta and meta['runtime']:
|
||||
duration = int(meta['runtime'])
|
||||
elif 'episode_run_time' in meta and meta['episode_run_time']:
|
||||
duration = int(meta['episode_run_time'][0])
|
||||
if duration > 1:
|
||||
_meta['duration'] = duration
|
||||
if 'tagline' in meta and meta['tagline']:
|
||||
_meta['tagline'] = meta['tagline']
|
||||
if 'vote_average' in meta and meta['vote_average']:
|
||||
_meta['rating'] = meta['vote_average']
|
||||
if 'vote_count' in meta and meta['vote_count']:
|
||||
_meta['votes'] = meta['vote_count']
|
||||
if 'genres' in meta and meta['genres']:
|
||||
for genre in meta['genres']:
|
||||
if _meta['genre'] == '':
|
||||
_meta['genre'] += genre['name']
|
||||
else:
|
||||
_meta['genre'] += ' / ' + genre['name']
|
||||
elif 'genre_ids' in meta and meta['genre_ids']:
|
||||
genres = self.getGenresFromIDs(meta['genre_ids'])
|
||||
for genre in genres:
|
||||
if _meta['genre'] == '':
|
||||
_meta['genre'] += genre
|
||||
else:
|
||||
_meta['genre'] += ' / ' + genre
|
||||
if 'production_companies' in meta and meta['production_companies']:
|
||||
_meta['studio'] = ''
|
||||
for studio in meta['production_companies']:
|
||||
if _meta['studio'] == '':
|
||||
_meta['studio'] += studio['name']
|
||||
else:
|
||||
_meta['studio'] += ' / ' + studio['name']
|
||||
if 'credits' in meta and meta['credits']:
|
||||
strmeta = str(meta['credits'])
|
||||
listCredits = eval(strmeta)
|
||||
casts = listCredits['cast']
|
||||
crews = []
|
||||
if len(casts) > 0:
|
||||
licast = []
|
||||
if 'crew' in listCredits:
|
||||
crews = listCredits['crew']
|
||||
if len(crews) > 0:
|
||||
_meta['credits'] = "{'cast': " + str(casts) + ", 'crew': " + str(crews) + "}"
|
||||
for cast in casts:
|
||||
licast.append((cast['name'], cast['character'], self.poster + str(cast['profile_path']), str(cast['id'])))
|
||||
_meta['cast'] = licast
|
||||
else:
|
||||
_meta['credits'] = "{'cast': " + str(casts) + '}'
|
||||
if len(crews) > 0:
|
||||
_meta['writer'] = ''
|
||||
for crew in crews:
|
||||
if crew['job'] == 'Director':
|
||||
_meta['director'] = crew['name']
|
||||
elif crew['department'] == 'Writing':
|
||||
if _meta['writer'] != '':
|
||||
_meta['writer'] += ' / '
|
||||
_meta['writer'] += '%s: %s' % (crew['job'], crew['name'])
|
||||
elif crew['department'] == 'Production' and 'Producer' in crew['job']:
|
||||
if _meta['writer'] != '':
|
||||
_meta['writer'] += ' / '
|
||||
_meta['writer'] += '%s: %s' % (crew['job'], crew['name'])
|
||||
if 'trailers' in meta and meta['trailers']:
|
||||
if 'youtube' in meta['trailers']:
|
||||
trailers = ''
|
||||
for t in meta['trailers']['youtube']:
|
||||
if t['type'] == 'Trailer':
|
||||
trailers = self.URL_TRAILER % t['source']
|
||||
if trailers:
|
||||
_meta['trailer'] = trailers
|
||||
elif 'videos' in meta and meta['videos']:
|
||||
if 'results' in meta['videos']:
|
||||
trailers = ''
|
||||
for t in meta['videos']['results']:
|
||||
if t['type'] == 'Trailer' and t['site'] == 'YouTube':
|
||||
trailers = self.URL_TRAILER % t['key']
|
||||
if trailers:
|
||||
_meta['trailer'] = trailers
|
||||
return _meta
|
||||
222
plugin.video.xstream/resources/lib/tmdbinfo.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import xbmc
|
||||
import time
|
||||
import xbmcgui
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.tmdb import cTMDB
|
||||
from datetime import date, datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
def WindowsBoxes(sTitle, sFileName, metaType, year=''):
|
||||
try:
|
||||
meta = cTMDB().get_meta(metaType, sFileName, tmdb_id=xbmc.getInfoLabel('ListItem.Property(TmdbId)'), year=year, advanced='true')
|
||||
try:
|
||||
meta['plot'] = str(meta['plot'].encode('latin-1'), 'utf-8')
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
print("TMDB - error")
|
||||
pass
|
||||
|
||||
if 'tmdb_id' not in meta:
|
||||
xbmc.executebuiltin("Notification(TMDB, Kein Eintrag gefunden, 1000, '')")
|
||||
return
|
||||
if 'premiered' in meta and meta['premiered']:
|
||||
releaseDate = datetime(*(time.strptime(meta['premiered'], '%Y-%m-%d')[0:6]))
|
||||
meta['releaseDate'] = releaseDate.strftime('%d/%m/%Y')
|
||||
else:
|
||||
meta['releaseDate'] = '-'
|
||||
if 'duration' in meta and meta['duration']:
|
||||
duration = meta['duration']
|
||||
durationH = duration // 60
|
||||
meta['durationH'] = durationH
|
||||
meta['durationM'] = '{:02d}'.format(int(duration - 60 * durationH))
|
||||
else:
|
||||
meta['durationH'] = 0
|
||||
meta['durationM'] = 0
|
||||
|
||||
class XMLDialog(xbmcgui.WindowXMLDialog):
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self)
|
||||
pass
|
||||
|
||||
def onInit(self):
|
||||
self.setProperty('color', cConfig().getSetting('Color'))
|
||||
self.poster = 'https://image.tmdb.org/t/p/%s' % cConfig().getSetting('poster_tmdb')
|
||||
self.none_poster = 'https://eu.ui-avatars.com/api/?background=000&size=512&name=%s&color=FFF&font-size=0.33'
|
||||
if 'trailer' in meta:
|
||||
self.setProperty('isTrailer', 'true')
|
||||
self.setFocusId(9000)
|
||||
if 'credits' in meta and meta['credits']:
|
||||
cast = []
|
||||
crew = []
|
||||
try:
|
||||
data = eval(str(meta['credits'].encode('latin-1'), 'utf-8'))
|
||||
except Exception:
|
||||
data = eval(str(meta['credits']))
|
||||
|
||||
listitems = []
|
||||
if 'cast' in data and data['cast']:
|
||||
for i in data['cast']:
|
||||
slabel = i['name']
|
||||
slabel2 = i['character']
|
||||
if i['profile_path']:
|
||||
sicon = self.poster + str(i['profile_path'])
|
||||
else:
|
||||
sicon = self.none_poster % slabel
|
||||
sid = i['id']
|
||||
listitem_ = xbmcgui.ListItem(label=slabel, label2=slabel2)
|
||||
listitem_.setProperty('id', str(sid))
|
||||
listitem_.setArt({'icon': sicon})
|
||||
listitems.append(listitem_)
|
||||
cast.append(slabel)
|
||||
self.getControl(50).addItems(listitems)
|
||||
|
||||
listitems2 = []
|
||||
if 'crew' in data and data['crew']:
|
||||
for i in data['crew']:
|
||||
slabel = i['name']
|
||||
slabel2 = i['job']
|
||||
if i['profile_path']:
|
||||
sicon = self.poster + str(i['profile_path'])
|
||||
else:
|
||||
sicon = self.none_poster % slabel
|
||||
sid = i['id']
|
||||
listitem_ = xbmcgui.ListItem(label=slabel, label2=slabel2)
|
||||
listitem_.setProperty('id', str(sid))
|
||||
listitem_.setArt({'icon': sicon})
|
||||
listitems2.append(listitem_)
|
||||
crew.append(slabel)
|
||||
self.getControl(5200).addItems(listitems2)
|
||||
|
||||
meta['title'] = sTitle
|
||||
if 'rating' not in meta or meta['rating'] == 0:
|
||||
meta['rating'] = '-'
|
||||
if 'votes' not in meta or meta['votes'] == '0':
|
||||
meta['votes'] = '-'
|
||||
|
||||
for prop in meta:
|
||||
try:
|
||||
if isinstance(meta[prop], str):
|
||||
self.setProperty(prop, meta[prop])
|
||||
else:
|
||||
self.setProperty(prop, str(meta[prop]))
|
||||
except Exception:
|
||||
if isinstance(meta[prop], str):
|
||||
self.setProperty(prop, meta[prop])
|
||||
else:
|
||||
self.setProperty(prop, str(meta[prop]))
|
||||
|
||||
def credit(self, meta='', control=''):
|
||||
listitems = []
|
||||
if not meta:
|
||||
meta = {}
|
||||
for i in meta:
|
||||
if 'title' in i and i['title']:
|
||||
sTitle = i['title']
|
||||
elif 'name' in i and i['name']:
|
||||
sTitle = i['name']
|
||||
if i['poster_path']:
|
||||
sThumbnail = self.poster + str(i['poster_path'])
|
||||
else:
|
||||
sThumbnail = self.none_poster % sTitle
|
||||
listitem_ = xbmcgui.ListItem(label=sTitle)
|
||||
listitem_.setArt({'icon': sThumbnail})
|
||||
listitems.append(listitem_)
|
||||
self.getControl(control).addItems(listitems)
|
||||
|
||||
def onClick(self, controlId):
|
||||
if controlId == 11:
|
||||
if metaType == 'movie':
|
||||
sUrl = 'movie/%s/videos' % str(self.getProperty('tmdb_id'))
|
||||
else:
|
||||
sUrl = 'tv/%s/videos' % str(self.getProperty('tmdb_id'))
|
||||
meta = cTMDB().getUrl(sUrl)
|
||||
name = []
|
||||
url = []
|
||||
for result in meta['results']:
|
||||
name.append((result['name']))
|
||||
url.append((result['key']))
|
||||
index = xbmcgui.Dialog().select('Trailer/Teaser', name)
|
||||
if index > -1:
|
||||
self.close()
|
||||
YT = 'plugin://plugin.video.youtube/play/?video_id=%s' % url[index]
|
||||
return xbmc.executebuiltin('RunPlugin(%s)' % YT)
|
||||
elif controlId == 30:
|
||||
self.close()
|
||||
return
|
||||
elif controlId == 50 or controlId == 5200:
|
||||
item = self.getControl(controlId).getSelectedItem()
|
||||
sid = item.getProperty('id')
|
||||
sUrl = 'person/' + str(sid)
|
||||
try:
|
||||
meta = cTMDB().getUrl(sUrl, '', "append_to_response=movie_credits,tv_credits")
|
||||
meta_credits = meta['movie_credits']['cast']
|
||||
self.credit(meta_credits, 5215)
|
||||
sTitle = meta['name']
|
||||
if not meta['deathday']:
|
||||
today = date.today()
|
||||
try:
|
||||
birthday = datetime(*(time.strptime(meta['birthday'], '%Y-%m-%d')[0:6]))
|
||||
age = today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))
|
||||
age = '%s Jahre' % age
|
||||
except Exception:
|
||||
age = ''
|
||||
else:
|
||||
age = meta['deathday']
|
||||
self.setProperty('Person_name', sTitle)
|
||||
self.setProperty('Person_birthday', meta['birthday'])
|
||||
self.setProperty('Person_place_of_birth', meta['place_of_birth'])
|
||||
self.setProperty('Person_deathday', str(age))
|
||||
self.setProperty('Person_biography', meta['biography'])
|
||||
self.setFocusId(9000)
|
||||
except Exception:
|
||||
return
|
||||
self.setProperty('xstream_menu', 'Person')
|
||||
elif controlId == 9:
|
||||
sid = self.getProperty('tmdb_id')
|
||||
if metaType == 'movie':
|
||||
sUrl_simil = 'movie/%s/similar' % str(sid)
|
||||
sUrl_recom = 'movie/%s/recommendations' % str(sid)
|
||||
else:
|
||||
sUrl_simil = 'tv/%s/similar' % str(sid)
|
||||
sUrl_recom = 'tv/%s/recommendations' % str(sid)
|
||||
try:
|
||||
meta = cTMDB().getUrl(sUrl_simil)
|
||||
meta = meta['results']
|
||||
self.credit(meta, 5205)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
meta = cTMDB().getUrl(sUrl_recom)
|
||||
meta = meta['results']
|
||||
self.credit(meta, 5210)
|
||||
except Exception:
|
||||
return
|
||||
elif controlId == 5215 or controlId == 5205 or controlId == 5210:
|
||||
item = self.getControl(controlId).getSelectedItem()
|
||||
self.close()
|
||||
xbmc.executebuiltin("Container.Update(%s?function=searchTMDB&%s)" % ('plugin://plugin.video.xstream/', urlencode({'searchTitle': item.getLabel()})))
|
||||
return
|
||||
|
||||
def onFocus(self, controlId):
|
||||
self.controlId = controlId
|
||||
|
||||
def _close_dialog(self):
|
||||
self.close()
|
||||
|
||||
def onAction(self, action):
|
||||
if action.getId() in (104, 105, 1, 2):
|
||||
return
|
||||
if action.getId() in (9, 10, 11, 30, 92, 216, 247, 257, 275, 61467, 61448):
|
||||
self.close()
|
||||
|
||||
# kasi
|
||||
path = 'special://home/addons/%s' % cConfig().getAddonInfo('id')
|
||||
wd = XMLDialog('info.xml', path, 'default', '720p')
|
||||
wd.doModal()
|
||||
del wd
|
||||
429
plugin.video.xstream/resources/lib/tools.py
Normal file
@@ -0,0 +1,429 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import hashlib
|
||||
import re
|
||||
import os
|
||||
import time, pyaes
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from xbmcvfs import translatePath
|
||||
from resources.lib.config import cConfig
|
||||
from urllib.parse import quote, unquote, quote_plus, unquote_plus, urlparse
|
||||
from html.entities import name2codepoint
|
||||
from difflib import SequenceMatcher
|
||||
from functools import lru_cache
|
||||
from os import path, chdir
|
||||
|
||||
# Aufgeführte Plattformen zum Anzeigen der Systemplattform
|
||||
def platform():
|
||||
if xbmc.getCondVisibility('system.platform.android'):
|
||||
return 'Android'
|
||||
elif xbmc.getCondVisibility('system.platform.linux'):
|
||||
return 'Linux'
|
||||
elif xbmc.getCondVisibility('system.platform.linux.Raspberrypi'):
|
||||
return 'Linux/RPi'
|
||||
elif xbmc.getCondVisibility('system.platform.windows'):
|
||||
return 'Windows'
|
||||
elif xbmc.getCondVisibility('system.platform.uwp'):
|
||||
return 'Windows UWP'
|
||||
elif xbmc.getCondVisibility('system.platform.osx'):
|
||||
return 'OSX'
|
||||
elif xbmc.getCondVisibility('system.platform.atv2'):
|
||||
return 'ATV2'
|
||||
elif xbmc.getCondVisibility('system.platform.ios'):
|
||||
return 'iOS'
|
||||
elif xbmc.getCondVisibility('system.platform.darwin'):
|
||||
return 'iOS'
|
||||
elif xbmc.getCondVisibility('system.platform.xbox'):
|
||||
return 'XBOX'
|
||||
elif xbmc.getCondVisibility('System.HasAddon(service.coreelec.settings)'):
|
||||
return 'CoreElec'
|
||||
elif xbmc.getCondVisibility('System.HasAddon(service.libreelec.settings)'):
|
||||
return 'LibreElec'
|
||||
elif xbmc.getCondVisibility('System.HasAddon(service.osmc.settings)'):
|
||||
return 'OSMC'
|
||||
|
||||
|
||||
# zeigt nach Update den Changelog als Popup an
|
||||
def changelog():
|
||||
CHANGELOG_PATH = translatePath(os.path.join('special://home/addons/' + cConfig().getAddonInfo('id') + '/', 'changelog.txt'))
|
||||
version = cConfig().getAddonInfo('version')
|
||||
if cConfig().getSetting('changelog_version') == version or not os.path.isfile(CHANGELOG_PATH):
|
||||
return
|
||||
cConfig().setSetting('changelog_version', version)
|
||||
heading = cConfig().getLocalizedString(30275)
|
||||
with open(CHANGELOG_PATH, mode='r', encoding='utf-8') as f:
|
||||
cl_lines = f.readlines()
|
||||
announce = ''
|
||||
for line in cl_lines:
|
||||
announce += line
|
||||
textBox(heading, announce)
|
||||
|
||||
|
||||
# zeigt die Entwickler Optionen Warnung als Popup an
|
||||
def devWarning():
|
||||
POPUP_PATH = translatePath(os.path.join('special://home/addons/' + cConfig().getAddonInfo('id') + '/resources/popup', 'devWarning.txt'))
|
||||
heading = cConfig().getLocalizedString(30322)
|
||||
with open(POPUP_PATH, mode='r', encoding='utf-8') as f:
|
||||
cl_lines = f.readlines()
|
||||
announce = ''
|
||||
for line in cl_lines:
|
||||
announce += line
|
||||
textBox(heading, announce)
|
||||
|
||||
|
||||
# Erstellt eine Textbox
|
||||
def textBox(heading, announce):
|
||||
class TextBox():
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.WINDOW = 10147
|
||||
self.CONTROL_LABEL = 1
|
||||
self.CONTROL_TEXTBOX = 5
|
||||
xbmc.executebuiltin("ActivateWindow(%d)" % (self.WINDOW, ))
|
||||
self.win = xbmcgui.Window(self.WINDOW)
|
||||
xbmc.sleep(500)
|
||||
self.setControls()
|
||||
|
||||
def setControls(self):
|
||||
self.win.getControl(self.CONTROL_LABEL).setLabel(heading)
|
||||
try:
|
||||
f = open(announce)
|
||||
text = f.read()
|
||||
except:
|
||||
text = announce
|
||||
self.win.getControl(self.CONTROL_TEXTBOX).setText(str(text))
|
||||
return
|
||||
|
||||
TextBox()
|
||||
while xbmc.getCondVisibility('Window.IsVisible(10147)'):
|
||||
xbmc.sleep(500)
|
||||
|
||||
|
||||
# Info Meldung im Kodi
|
||||
def infoDialog(message, heading=cConfig().getAddonInfo('name'), icon='', time=5000, sound=False):
|
||||
if icon == '': icon = cConfig().getAddonInfo('icon')
|
||||
elif icon == 'INFO': icon = xbmcgui.NOTIFICATION_INFO
|
||||
elif icon == 'WARNING': icon = xbmcgui.NOTIFICATION_WARNING
|
||||
elif icon == 'ERROR': icon = xbmcgui.NOTIFICATION_ERROR
|
||||
xbmcgui.Dialog().notification(heading, message, icon, time, sound=sound)
|
||||
|
||||
|
||||
class cParser:
|
||||
@staticmethod
|
||||
def _get_compiled_pattern(pattern, flags=0):
|
||||
return re.compile(pattern, flags)
|
||||
|
||||
@staticmethod
|
||||
def _replaceSpecialCharacters(s):
|
||||
try:
|
||||
# Umlaute Unicode konvertieren
|
||||
for t in (('\\/', '/'), ('&', '&'), ('\\u00c4', 'Ä'), ('\\u00e4', 'ä'),
|
||||
('\\u00d6', 'Ö'), ('\\u00f6', 'ö'), ('\\u00dc', 'Ü'), ('\\u00fc', 'ü'),
|
||||
('\\u00df', 'ß'), ('\\u2013', '-'), ('\\u00b2', '²'), ('\\u00b3', '³'),
|
||||
('\\u00e9', 'é'), ('\\u2018', '‘'), ('\\u201e', '„'), ('\\u201c', '“'),
|
||||
('\\u00c9', 'É'), ('\\u2026', '...'), ('\\u202f', 'h'), ('\\u2019', '’'),
|
||||
('\\u0308', '̈'), ('\\u00e8', 'è'), ('#038;', ''), ('\\u00f8', 'ø'),
|
||||
('/', '/'), ('\\u00e1', 'á'), ('–', '-'), ('“', '“'), ('„', '„'),
|
||||
('’', '’'), ('…', '…'), ('\\u00bc', '¼'), ('\\u00bd', '½'), ('\\u00be', '¾'),
|
||||
('\\u2153', '⅓'), ('\\u002A', '*')):
|
||||
s = s.replace(*t)
|
||||
|
||||
# Umlaute HTML konvertieren
|
||||
for h in (('\\/', '/'), ('&', '&'), (''', "'"), ("'", "'"),
|
||||
('Ä', 'Ä'), ('ä', 'ä'), ('Ö', 'Ö'), ('ö', 'ö'),
|
||||
('Ü', 'Ü'), ('ü', 'ü'), ('ß', 'ß') , ('²', '²'),
|
||||
('Ü', '³'), ('¼', '¼'), ('½', '½'), ('¾', '¾'),
|
||||
('⅓', '⅓'), ('∗', '*')):
|
||||
s = s.replace(*h)
|
||||
except:
|
||||
pass
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def parseSingleResult(sHtmlContent, pattern, ignoreCase=False):
|
||||
if sHtmlContent:
|
||||
flags = re.S | re.M
|
||||
if ignoreCase:
|
||||
flags |= re.I
|
||||
|
||||
matches = cParser._get_compiled_pattern(pattern, flags).search(sHtmlContent)
|
||||
|
||||
if matches:
|
||||
# Check if there's at least one capturing group
|
||||
if matches.lastindex is not None and matches.lastindex >= 1:
|
||||
return True, cParser._replaceSpecialCharacters(matches.group(1))
|
||||
else:
|
||||
# fallback to the entire match if no group was captured
|
||||
return True, cParser._replaceSpecialCharacters(matches.group(0))
|
||||
return False, None
|
||||
|
||||
@staticmethod
|
||||
def parse(sHtmlContent, pattern, iMinFoundValue=1, ignoreCase=False):
|
||||
if sHtmlContent:
|
||||
flags = re.DOTALL
|
||||
if ignoreCase:
|
||||
flags |= re.I
|
||||
|
||||
aMatches = cParser._get_compiled_pattern(pattern, flags).findall(sHtmlContent)
|
||||
|
||||
if len(aMatches) >= iMinFoundValue:
|
||||
# handle both single strings and tuples of matches
|
||||
if isinstance(aMatches[0], tuple):
|
||||
# Process each string in tuple
|
||||
aMatches = [tuple(cParser._replaceSpecialCharacters(x) if isinstance(x, str) and x is not None else '' for x in match) for match in aMatches]
|
||||
else:
|
||||
# Process single strings
|
||||
aMatches = [cParser._replaceSpecialCharacters(x) if isinstance(x, str) and x is not None else '' for x in aMatches]
|
||||
|
||||
return True, aMatches
|
||||
return False, None
|
||||
|
||||
@staticmethod
|
||||
def replace(pattern, sReplaceString, sValue):
|
||||
return cParser._get_compiled_pattern(pattern).sub(sReplaceString, sValue)
|
||||
|
||||
@staticmethod
|
||||
def search(pattern, sValue, ignoreCase=True):
|
||||
flags = 0
|
||||
if ignoreCase:
|
||||
flags = re.IGNORECASE
|
||||
return cParser._get_compiled_pattern(pattern, flags).search(sValue)
|
||||
|
||||
@staticmethod
|
||||
def escape(sValue):
|
||||
return re.escape(sValue)
|
||||
|
||||
@staticmethod
|
||||
def getNumberFromString(sValue):
|
||||
aMatches = re.compile(r'\d+').findall(sValue)
|
||||
if len(aMatches) > 0:
|
||||
return int(aMatches[0])
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def urlparse(sUrl):
|
||||
return urlparse(sUrl.replace('www.', '')).netloc.title()
|
||||
|
||||
@staticmethod
|
||||
def urlDecode(sUrl):
|
||||
return unquote(sUrl)
|
||||
|
||||
@staticmethod
|
||||
def urlEncode(sUrl, safe=''):
|
||||
return quote(sUrl, safe)
|
||||
|
||||
@staticmethod
|
||||
def quote(sUrl):
|
||||
return quote(sUrl)
|
||||
|
||||
@staticmethod
|
||||
def unquotePlus(sUrl):
|
||||
return unquote_plus(sUrl)
|
||||
|
||||
@staticmethod
|
||||
def quotePlus(sUrl):
|
||||
return quote_plus(sUrl)
|
||||
|
||||
@staticmethod
|
||||
def B64decode(text):
|
||||
import base64
|
||||
return base64.b64decode(text).decode('utf-8')
|
||||
|
||||
|
||||
# xStream interner Log
|
||||
class logger:
|
||||
@staticmethod
|
||||
def info(sInfo):
|
||||
logger.__writeLog(sInfo, cLogLevel=xbmc.LOGINFO)
|
||||
|
||||
@staticmethod
|
||||
def debug(sInfo):
|
||||
logger.__writeLog(sInfo, cLogLevel=xbmc.LOGDEBUG)
|
||||
|
||||
@staticmethod
|
||||
def warning(sInfo):
|
||||
logger.__writeLog(sInfo, cLogLevel=xbmc.LOGWARNING)
|
||||
|
||||
@staticmethod
|
||||
def error(sInfo):
|
||||
logger.__writeLog(sInfo, cLogLevel=xbmc.LOGERROR)
|
||||
|
||||
@staticmethod
|
||||
def fatal(sInfo):
|
||||
logger.__writeLog(sInfo, cLogLevel=xbmc.LOGFATAL)
|
||||
|
||||
@staticmethod
|
||||
def __writeLog(sLog, cLogLevel=xbmc.LOGDEBUG):
|
||||
params = ParameterHandler()
|
||||
try:
|
||||
if params.exist('site'):
|
||||
site = params.getValue('site')
|
||||
sLog = "[%s] -> [%s]: %s" % (cConfig().getAddonInfo('name'), site, sLog)
|
||||
else:
|
||||
sLog = "[%s] %s" % (cConfig().getAddonInfo('name'), sLog)
|
||||
xbmc.log(sLog, cLogLevel)
|
||||
except Exception as e:
|
||||
xbmc.log('Logging Failure: %s' % e, cLogLevel)
|
||||
pass
|
||||
|
||||
|
||||
class cUtil:
|
||||
@staticmethod
|
||||
def removeHtmlTags(sValue, sReplace=''):
|
||||
return re.compile(r'<.*?>').sub(sReplace, sValue)
|
||||
|
||||
@staticmethod
|
||||
def unescape(text):
|
||||
# edit kasi 2024-11-26 so für py2/py3 oder für nur py3 unichr ersetzen durch chr
|
||||
try: unichr
|
||||
except NameError: unichr = chr
|
||||
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if not text.endswith(';'): text += ';'
|
||||
if text[:2] == '&#':
|
||||
try:
|
||||
if text[:3] == '&#x':
|
||||
return unichr(int(text[3:-1], 16))
|
||||
else:
|
||||
return unichr(int(text[2:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
text = unichr(name2codepoint[text[1:-1]])
|
||||
except KeyError:
|
||||
pass
|
||||
return text
|
||||
|
||||
if isinstance(text, str):
|
||||
try:
|
||||
text = text.decode('utf-8')
|
||||
except Exception:
|
||||
try:
|
||||
text = text.decode('utf-8', 'ignore')
|
||||
except Exception:
|
||||
pass
|
||||
return re.compile('&(\\w+;|#x?\\d+;?)').sub(fixup, text.strip())
|
||||
|
||||
@staticmethod
|
||||
def cleanse_text(text):
|
||||
if text is None: text = ''
|
||||
text = cUtil.removeHtmlTags(text)
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def evp_decode(cipher_text, passphrase, salt=None):
|
||||
if not salt:
|
||||
salt = cipher_text[8:16]
|
||||
cipher_text = cipher_text[16:]
|
||||
key, iv = cUtil.evpKDF(passphrase, salt)
|
||||
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
|
||||
plain_text = decrypter.feed(cipher_text)
|
||||
plain_text += decrypter.feed()
|
||||
return plain_text.decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def evpKDF(pwd, salt, key_size=32, iv_size=16):
|
||||
temp = b''
|
||||
fd = temp
|
||||
while len(fd) < key_size + iv_size:
|
||||
h = hashlib.md5()
|
||||
h.update(temp + pwd + salt)
|
||||
temp = h.digest()
|
||||
fd += temp
|
||||
key = fd[0:key_size]
|
||||
iv = fd[key_size:key_size + iv_size]
|
||||
return key, iv
|
||||
|
||||
@staticmethod
|
||||
def isSimilar(sSearch, sText, threshold=0.9):
|
||||
return (SequenceMatcher(None, sSearch, sText).ratio() >= threshold)
|
||||
|
||||
@staticmethod
|
||||
@lru_cache(maxsize=200000)
|
||||
def get_seq_match_ratio(token1, token2):
|
||||
return SequenceMatcher(None, token1, token2).ratio()
|
||||
|
||||
@staticmethod
|
||||
def isSimilarByToken(sSearch, sText, threshold=0.9):
|
||||
tokens_sSearch = sSearch.split()
|
||||
tokens_sText = sText.split()
|
||||
|
||||
if not tokens_sSearch:
|
||||
return False
|
||||
|
||||
# get_ratio = lambda a, b: SequenceMatcher(None, a, b).ratio()
|
||||
best_ratios = [
|
||||
max(cUtil.get_seq_match_ratio(token, token2) for token2 in tokens_sText)
|
||||
for token in tokens_sSearch
|
||||
]
|
||||
return (sum(best_ratios) / len(best_ratios)) >= threshold
|
||||
|
||||
def valid_email(email): #ToDo: Funktion in Settings / Konten aktivieren
|
||||
# Überprüfen der EMail-Adresse mit dem Muster
|
||||
if re.compile(r'^[\w\.-]+@[\w\.-]+\.\w+$').match(email):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getDNS(dns):
|
||||
status = 'Beschäftigt'
|
||||
loop = 1
|
||||
while status == 'Beschäftigt':
|
||||
if loop == 20:
|
||||
break
|
||||
status = xbmc.getInfoLabel(dns)
|
||||
xbmc.sleep(20)
|
||||
loop += 1
|
||||
return status
|
||||
|
||||
def getRepofromAddonsDB(addonID):
|
||||
from sqlite3 import dbapi2 as database
|
||||
from glob import glob
|
||||
chdir(path.join(translatePath('special://database/')))
|
||||
addonsDB = path.join(translatePath('special://database/'), sorted(glob("Addons*.db"), reverse=True)[0])
|
||||
dbcon = database.connect(addonsDB)
|
||||
dbcur = dbcon.cursor()
|
||||
select = ("SELECT origin FROM installed WHERE addonID = '%s'") % addonID
|
||||
dbcur.execute(select)
|
||||
match = dbcur.fetchone()
|
||||
dbcon.close()
|
||||
if match and len(match) > 0:
|
||||
repo = match[0]
|
||||
else:
|
||||
repo = ''
|
||||
return repo
|
||||
|
||||
|
||||
class cCache(object):
|
||||
_win = None
|
||||
|
||||
def __init__(self):
|
||||
# see https://kodi.wiki/view/Window_IDs
|
||||
self._win = xbmcgui.Window(10000)
|
||||
|
||||
def __del__(self):
|
||||
del self._win
|
||||
|
||||
def get(self, key, cache_time):
|
||||
cachedata = self._win.getProperty(key)
|
||||
|
||||
if cachedata:
|
||||
cachedata = eval(cachedata)
|
||||
if time.time() - cachedata[0] < cache_time or cache_time < 0:
|
||||
return cachedata[1]
|
||||
else:
|
||||
self._win.clearProperty(key)
|
||||
|
||||
return None
|
||||
|
||||
def set(self, key, data):
|
||||
self._win.setProperty(key, repr((time.time(), data)))
|
||||
|
||||
def clear(self):
|
||||
self._win.clearProperties()
|
||||
312
plugin.video.xstream/resources/lib/updateManager.py
Normal file
@@ -0,0 +1,312 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import json
|
||||
import requests
|
||||
import zipfile
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from xbmcgui import Dialog
|
||||
from resources.lib.config import cConfig
|
||||
from xbmc import LOGINFO as LOGNOTICE, LOGERROR, LOGWARNING, log, executebuiltin
|
||||
from xbmcvfs import translatePath
|
||||
|
||||
|
||||
# Resolver
|
||||
def resolverUpdate(silent=False):
|
||||
# Nightly Branch
|
||||
if cConfig().getSetting('resolver.branch') == 'nightly':
|
||||
username = 'fetchdevteam'
|
||||
resolve_dir = 'snipsolver'
|
||||
resolve_id = 'script.module.resolveurl'
|
||||
# Abfrage aus den Einstellungen welcher Branch
|
||||
branch = 'nightly'
|
||||
token = ''
|
||||
|
||||
try:
|
||||
return UpdateResolve(username, resolve_dir, resolve_id, branch, token, silent)
|
||||
except Exception as e:
|
||||
log(' -> [updateManager]: Exception Raised: %s' % str(e), LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + resolve_id + cConfig().getLocalizedString(30157))
|
||||
return
|
||||
else:
|
||||
# Release Branch https://github.com/Gujal00/ResolveURL
|
||||
username = 'Gujal00'
|
||||
resolve_dir = 'ResolveURL'
|
||||
resolve_id = 'script.module.resolveurl'
|
||||
# Abfrage aus den Einstellungen welcher Branch
|
||||
branch = 'master'
|
||||
token = ''
|
||||
|
||||
try:
|
||||
return UpdateResolve(username, resolve_dir, resolve_id, branch, token, silent)
|
||||
except Exception as e:
|
||||
log(' -> [updateManager]: Exception Raised: %s' % str(e), LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + resolve_id + cConfig().getLocalizedString(30157))
|
||||
return
|
||||
|
||||
|
||||
# xStream Dev
|
||||
def xStreamDevUpdate(silent=False):
|
||||
username = cConfig().getSettingString('xstream.dev.username')
|
||||
plugin_id = cConfig().getSettingString('xstream.dev.id')
|
||||
branch = cConfig().getSettingString('xstream.dev.branch')
|
||||
token = cConfig().getSettingString('xstream.dev.token')
|
||||
try:
|
||||
return Update(username, plugin_id, branch, token, silent)
|
||||
except Exception as e:
|
||||
log(' -> [updateManager]: Exception Raised: %s' % str(e), LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + plugin_id + cConfig().getLocalizedString(30157))
|
||||
return False
|
||||
|
||||
# Update Resolver
|
||||
def UpdateResolve(username, resolve_dir, resolve_id, branch, token, silent):
|
||||
REMOTE_PLUGIN_COMMITS = "https://api.github.com/repos/%s/%s/commits/%s" % (username, resolve_dir, branch) # Github Commits
|
||||
REMOTE_PLUGIN_DOWNLOADS = "https://api.github.com/repos/%s/%s/zipball/%s" % (username, resolve_dir, branch) # Github Downloads
|
||||
PACKAGES_PATH = translatePath(os.path.join('special://home/addons/packages/')) # Packages Ordner für Downloads
|
||||
ADDON_PATH = translatePath(os.path.join('special://home/addons/packages/', '%s') % resolve_id) # Addon Ordner in Packages
|
||||
INSTALL_PATH = translatePath(os.path.join('special://home/addons/', '%s') % resolve_id) # Installation Ordner
|
||||
|
||||
auth = HTTPBasicAuth(username, token)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Search for updates.' % resolve_id, LOGNOTICE)
|
||||
try:
|
||||
ADDON_DIR = translatePath(os.path.join('special://userdata/addon_data/', '%s') % resolve_id) # Pfad von ResolveURL Daten
|
||||
LOCAL_PLUGIN_VERSION = os.path.join(ADDON_DIR, "update_sha") # Pfad der update.sha in den ResolveURL Daten
|
||||
LOCAL_FILE_NAME_PLUGIN = os.path.join(ADDON_DIR, 'update-' + resolve_id + '.zip')
|
||||
if not os.path.exists(ADDON_DIR): os.mkdir(ADDON_DIR)
|
||||
|
||||
if cConfig().getSetting('enforceUpdate') == 'true':
|
||||
if os.path.exists(LOCAL_PLUGIN_VERSION): os.remove(LOCAL_PLUGIN_VERSION)
|
||||
|
||||
commitXML = _getXmlString(REMOTE_PLUGIN_COMMITS, auth) # Commit Update
|
||||
if commitXML:
|
||||
isTrue = commitUpdate(commitXML, LOCAL_PLUGIN_VERSION, REMOTE_PLUGIN_DOWNLOADS, PACKAGES_PATH, resolve_dir, LOCAL_FILE_NAME_PLUGIN, silent, auth)
|
||||
|
||||
if isTrue is True:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - download new update.' % resolve_id, LOGNOTICE)
|
||||
shutil.make_archive(ADDON_PATH, 'zip', ADDON_PATH)
|
||||
shutil.unpack_archive(ADDON_PATH + '.zip', INSTALL_PATH)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - install new update.' % resolve_id, LOGNOTICE)
|
||||
if os.path.exists(ADDON_PATH + '.zip'): os.remove(ADDON_PATH + '.zip')
|
||||
if silent is False: Dialog().ok(cConfig().getLocalizedString(30166), cConfig().getLocalizedString(30158) + resolve_id + cConfig().getLocalizedString(30159))
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - update completed.' % resolve_id, LOGNOTICE)
|
||||
return True
|
||||
elif isTrue is None:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - no update available.' % resolve_id, LOGNOTICE)
|
||||
if silent is False: Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30160) + resolve_id + cConfig().getLocalizedString(30161))
|
||||
return None
|
||||
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Error updating!' % resolve_id, LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + resolve_id + cConfig().getLocalizedString(30157))
|
||||
return False
|
||||
except:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Error updating!' % resolve_id, LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + resolve_id + cConfig().getLocalizedString(30157))
|
||||
|
||||
# xStream Update
|
||||
def Update(username, plugin_id, branch, token, silent):
|
||||
REMOTE_PLUGIN_COMMITS = "https://api.github.com/repos/%s/%s/commits/%s" % (username, plugin_id, branch)
|
||||
REMOTE_PLUGIN_DOWNLOADS = "https://api.github.com/repos/%s/%s/zipball/%s" % (username, plugin_id, branch)
|
||||
auth = HTTPBasicAuth(username, token)
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Search for updates.' % plugin_id, LOGNOTICE)
|
||||
try:
|
||||
ADDON_DIR = translatePath(os.path.join('special://userdata/addon_data/', '%s') % plugin_id)
|
||||
LOCAL_PLUGIN_VERSION = os.path.join(ADDON_DIR, "update_sha")
|
||||
LOCAL_FILE_NAME_PLUGIN = os.path.join(ADDON_DIR, 'update-' + plugin_id + '.zip')
|
||||
if not os.path.exists(ADDON_DIR): os.mkdir(ADDON_DIR)
|
||||
# ka - Update erzwingen
|
||||
if cConfig().getSetting('enforceUpdate') == 'true':
|
||||
if os.path.exists(LOCAL_PLUGIN_VERSION): os.remove(LOCAL_PLUGIN_VERSION)
|
||||
|
||||
path = translatePath(os.path.join('special://home/addons/', '%s') % plugin_id)
|
||||
commitXML = _getXmlString(REMOTE_PLUGIN_COMMITS, auth)
|
||||
if commitXML:
|
||||
isTrue = commitUpdate(commitXML, LOCAL_PLUGIN_VERSION, REMOTE_PLUGIN_DOWNLOADS, path, plugin_id,
|
||||
LOCAL_FILE_NAME_PLUGIN, silent, auth)
|
||||
if isTrue is True:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - download new update.' % plugin_id, LOGNOTICE)
|
||||
if silent is False: Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30158) + plugin_id + cConfig().getLocalizedString(30159))
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager] %s: - install new update.' % plugin_id, LOGNOTICE)
|
||||
return True
|
||||
elif isTrue is None:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - no update available.' % plugin_id, LOGNOTICE)
|
||||
if silent is False: Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30160) + plugin_id + cConfig().getLocalizedString(30161))
|
||||
return None
|
||||
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Error updating!' % plugin_id, LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + plugin_id + cConfig().getLocalizedString(30157))
|
||||
return False
|
||||
except:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Error updating!' % plugin_id, LOGERROR)
|
||||
Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30156) + plugin_id + cConfig().getLocalizedString(30157))
|
||||
|
||||
|
||||
def commitUpdate(onlineFile, offlineFile, downloadLink, LocalDir, plugin_id, localFileName, silent, auth):
|
||||
try:
|
||||
jsData = json.loads(onlineFile)
|
||||
if not os.path.exists(offlineFile) or open(offlineFile).read() != jsData['sha']:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: %s: - Start updating!' % plugin_id, LOGNOTICE)
|
||||
isTrue = doUpdate(LocalDir, downloadLink, plugin_id, localFileName, auth)
|
||||
if isTrue is True:
|
||||
try:
|
||||
open(offlineFile, 'w').write(jsData['sha'])
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return None
|
||||
except Exception:
|
||||
os.remove(offlineFile)
|
||||
log(' -> [updateManager]: RateLimit reached')
|
||||
return False
|
||||
|
||||
|
||||
def doUpdate(LocalDir, REMOTE_PATH, Title, localFileName, auth):
|
||||
try:
|
||||
response = requests.get(REMOTE_PATH, auth=auth) # verify=False,
|
||||
if response.status_code == 200:
|
||||
open(localFileName, "wb").write(response.content)
|
||||
else:
|
||||
return False
|
||||
updateFile = zipfile.ZipFile(localFileName)
|
||||
removeFilesNotInRepo(updateFile, LocalDir)
|
||||
for index, n in enumerate(updateFile.namelist()):
|
||||
if n[-1] != "/":
|
||||
dest = os.path.join(LocalDir, "/".join(n.split("/")[1:]))
|
||||
destdir = os.path.dirname(dest)
|
||||
if not os.path.isdir(destdir):
|
||||
os.makedirs(destdir)
|
||||
data = updateFile.read(n)
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
f = open(dest, 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
updateFile.close()
|
||||
os.remove(localFileName)
|
||||
executebuiltin("UpdateLocalAddons()")
|
||||
return True
|
||||
except:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: doUpdate not possible due download error')
|
||||
return False
|
||||
|
||||
|
||||
def removeFilesNotInRepo(updateFile, LocalDir):
|
||||
ignored_files = ['settings.xml', 'aniworld.py', 'aniworld.png']
|
||||
updateFileNameList = [i.split("/")[-1] for i in updateFile.namelist()]
|
||||
|
||||
for root, dirs, files in os.walk(LocalDir):
|
||||
if ".git" in root or "pydev" in root or ".idea" in root:
|
||||
continue
|
||||
else:
|
||||
for file in files:
|
||||
if file in ignored_files:
|
||||
continue
|
||||
if file not in updateFileNameList:
|
||||
os.remove(os.path.join(root, file))
|
||||
|
||||
|
||||
def _getXmlString(xml_url, auth):
|
||||
try:
|
||||
xmlString = requests.get(xml_url, auth=auth).content # verify=False,
|
||||
if "sha" in json.loads(xmlString):
|
||||
return xmlString
|
||||
else:
|
||||
log(cConfig().getLocalizedString(30166) + ' -> [updateManager]: Update-URL incorrect or bad credentials')
|
||||
except Exception as e:
|
||||
log(e)
|
||||
|
||||
|
||||
# todo Verzeichnis packen -für zukünftige Erweiterung "Backup"
|
||||
def zipfolder(foldername, target_dir):
|
||||
zipobj = zipfile.ZipFile(foldername + '.zip', 'w', zipfile.ZIP_DEFLATED)
|
||||
rootlen = len(target_dir) + 1
|
||||
for base, dirs, files in os.walk(target_dir):
|
||||
for file in files:
|
||||
fn = os.path.join(base, file)
|
||||
zipobj.write(fn, fn[rootlen:])
|
||||
zipobj.close()
|
||||
|
||||
|
||||
def devUpdates(): # für manuelles Updates vorgesehen
|
||||
try:
|
||||
resolverupdate = False # Resolver Update
|
||||
#pluginupdate = False # xStream Update
|
||||
# Einleitungstext
|
||||
#if Dialog().ok(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30152)):
|
||||
# Abfrage welches Plugin aktualisiert werden soll (kann erweitert werden)
|
||||
# options = [cConfig().getLocalizedString(30153),
|
||||
# cConfig().getLocalizedString(30096) + ' ' + cConfig().getLocalizedString(30154),
|
||||
# cConfig().getLocalizedString(30030) + ' ' + cConfig().getLocalizedString(30154)]
|
||||
# result = Dialog().select(cConfig().getLocalizedString(30151), options)
|
||||
#else:
|
||||
#return False
|
||||
|
||||
#if result == -1: # Abbrechen
|
||||
#return False
|
||||
|
||||
#elif result == 0: # Alle Addons aktualisieren
|
||||
# Abfrage ob ResolveURL Release oder Nightly Branch (kann erweitert werden)
|
||||
result = Dialog().yesno(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30268), yeslabel='Nightly', nolabel='Release')
|
||||
|
||||
if result == 0:
|
||||
cConfig().setSetting('resolver.branch', 'release')
|
||||
elif result == 1:
|
||||
cConfig().setSetting('resolver.branch', 'nightly')
|
||||
|
||||
# Voreinstellung beendet
|
||||
if Dialog().yesno(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30269), yeslabel=cConfig().getLocalizedString(30162), nolabel=cConfig().getLocalizedString(30163)):
|
||||
# Updates ausführen
|
||||
#pluginupdate = True
|
||||
resolverupdate = True
|
||||
else:
|
||||
return False
|
||||
|
||||
#elif result == 1: # xStream aktualisieren
|
||||
# if Dialog().yesno(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30269),
|
||||
# yeslabel=cConfig().getLocalizedString(30162),
|
||||
# nolabel=cConfig().getLocalizedString(30163)):
|
||||
# # Updates ausführen
|
||||
# pluginupdate = True
|
||||
# else:
|
||||
# return False
|
||||
|
||||
#elif result == 2: # Resolver aktualisieren
|
||||
# # Abfrage ob ResolveURL Release oder Nightly Branch (kann erweitert werden)
|
||||
# result = Dialog().yesno(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30268), yeslabel='Nightly',
|
||||
# nolabel='Release')
|
||||
|
||||
# if result == 0:
|
||||
# cCOnfig().setSetting('resolver.branch', 'release')
|
||||
# elif result == 1:
|
||||
# cConfig().setSetting('resolver.branch', 'nightly')
|
||||
|
||||
# # Voreinstellung beendet
|
||||
# if Dialog().yesno(cConfig().getLocalizedString(30151), cConfig().getLocalizedString(30269),
|
||||
# yeslabel=cConfig().getLocalizedString(30162),
|
||||
# nolabel=cConfig().getLocalizedString(30163)):
|
||||
# # Updates ausführen
|
||||
# resolverupdate = True
|
||||
# else:
|
||||
# return False
|
||||
|
||||
#if pluginupdate is True:
|
||||
#try:
|
||||
#xStreamUpdate(False)
|
||||
#except:
|
||||
#pass
|
||||
if resolverupdate is True:
|
||||
try:
|
||||
resolverUpdate(False)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Zurücksetzten der Update.sha
|
||||
if cConfig().getSetting('enforceUpdate') == 'true': cConfig().setSetting('enforceUpdate', 'false')
|
||||
return
|
||||
except Exception as e:
|
||||
log(e)
|
||||
149
plugin.video.xstream/resources/lib/utils.py
Normal file
@@ -0,0 +1,149 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
import xbmcgui, xbmcvfs
|
||||
from xbmcvfs import translatePath
|
||||
from urllib.request import urlretrieve
|
||||
from resources.lib.config import cConfig
|
||||
|
||||
progressDialog = xbmcgui.DialogProgress()
|
||||
|
||||
|
||||
def download_url(url, dest, dp=None):
|
||||
# download_url(url, src, dp=[None / True / False / Dialog])
|
||||
if dp == None or dp == True:
|
||||
dp = progressDialog
|
||||
dp.create("URL Downloader", " \n Downloading File: [B]%s[/B]" % url.split('/')[-1])
|
||||
elif dp == False:
|
||||
return urlretrieve(url, dest)
|
||||
try:
|
||||
dp.update(0)
|
||||
urlretrieve(url, dest, lambda nb, bs, fs, url=url: _pbhook(nb, bs, fs, dp))
|
||||
dp.close()
|
||||
except:
|
||||
urlretrieve(url, dest)
|
||||
|
||||
def _pbhook(numblocks, blocksize, filesize, dp):
|
||||
try:
|
||||
percent = min((numblocks * blocksize * 100) / filesize, 100)
|
||||
dp.update(int(percent))
|
||||
except:
|
||||
percent = 100
|
||||
dp.update(percent)
|
||||
if dp.iscanceled():
|
||||
dp.close()
|
||||
raise Exception("Canceled")
|
||||
|
||||
|
||||
def unzip_recursive(path, dirs, dest):
|
||||
for directory in dirs:
|
||||
dirs_dir = os.path.join(path, directory)
|
||||
dest_dir = os.path.join(dest, directory)
|
||||
xbmcvfs.mkdir(dest_dir)
|
||||
dirs2, files = xbmcvfs.listdir(dirs_dir)
|
||||
if dirs2:
|
||||
unzip_recursive(dirs_dir, dirs2, dest_dir)
|
||||
for file in files:
|
||||
unzip_file(os.path.join(dirs_dir, file), os.path.join(dest_dir, file))
|
||||
|
||||
def unzip_file(path, dest):
|
||||
''' Unzip specific file. Path should start with zip:// '''
|
||||
xbmcvfs.copy(path, dest)
|
||||
|
||||
def unzip(path, dest, folder=None):
|
||||
try:
|
||||
with zipfile.ZipFile(path, 'r') as zip:
|
||||
zip.extractall(dest)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_zip_directory(path, folder):
|
||||
dirs, files = xbmcvfs.listdir(path)
|
||||
if folder in dirs:
|
||||
return os.path.join(path, folder)
|
||||
for directory in dirs:
|
||||
result = get_zip_directory(os.path.join(path, directory), folder)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
||||
def remove_dir(folder):
|
||||
import os, shutil, stat
|
||||
for filename in os.listdir(folder):
|
||||
file_path = os.path.join(folder, filename)
|
||||
try:
|
||||
if os.path.isfile(file_path) or os.path.islink(file_path):
|
||||
if os.path.isfile(file_path): os.chmod(file_path, stat.S_IWRITE)
|
||||
os.unlink(file_path)
|
||||
elif os.path.isdir(file_path):
|
||||
shutil.rmtree(file_path)
|
||||
except Exception as e:
|
||||
print('Failed to delete %s. Reason: %s' % (file_path, e))
|
||||
|
||||
|
||||
def countdown(bKill=False):
|
||||
from xbmcgui import DialogProgress
|
||||
from xbmc import executebuiltin, Monitor
|
||||
|
||||
cConfig().setSetting('xs_logo', 'true') # wird noch nicht ausgewertet
|
||||
executebuiltin("Dialog.Close(all)")
|
||||
executebuiltin("ActivateWindow(Home)")
|
||||
|
||||
seconds = 5
|
||||
percentage = 100
|
||||
monitor = Monitor()
|
||||
pDialog = DialogProgress()
|
||||
pDialog.create(cConfig().getAddonInfo('name') + ' Manipulation')
|
||||
# while not monitor.abortRequested() and percentage > 0:
|
||||
while percentage > 0:
|
||||
# percentage -= 20
|
||||
# secondsTxt = "seconds" if seconds > 1 else "second"
|
||||
# pDialog.update(percentage, f"Kodi wird in {seconds} {secondsTxt} beendet.")
|
||||
pDialog.update(percentage, f"{cConfig().getAddonInfo('name')} bzw. Kodi wird in wenigen Sekunden beendet.")
|
||||
seconds -= 1
|
||||
percentage -= 20
|
||||
if monitor.waitForAbort(1): dummy =''
|
||||
#if monitor.waitForAbort(1): break
|
||||
#if pDialog.iscanceled(): return True
|
||||
pDialog.close()
|
||||
|
||||
## Addon deaktivieren & Kodi beenden
|
||||
if not bKill:
|
||||
from xbmc import executeJSONRPC
|
||||
addonId = cConfig().getAddonInfo('id')
|
||||
for addonId in ('plugin.video.xstream', 'plugin.video.xship', 'repository.xstream', 'repository.xship'):
|
||||
try:
|
||||
executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.SetAddonEnabled","id":1,"params":{"addonid":"%s", "enabled":false}}' % addonId)
|
||||
except:
|
||||
continue
|
||||
# Kodi beenden
|
||||
executebuiltin('Quit')
|
||||
exit()
|
||||
|
||||
def kill(): # Löschfunktion
|
||||
countdown(True)
|
||||
from os import path
|
||||
from xbmc import executebuiltin, executeJSONRPC
|
||||
try: from xbmcvfs import translatePath
|
||||
except: from xbmc import translatePath
|
||||
|
||||
addonId = cConfig().getAddonInfo('id')
|
||||
for addonId in ('plugin.video.xstream', 'plugin.video.xship', 'repository.xstream', 'repository.xship'):
|
||||
try:
|
||||
addonPath = translatePath('special://home/addons/%s') % addonId
|
||||
addonProfilePath = translatePath('special://profile/addon_data/%s') % addonId
|
||||
executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.SetAddonEnabled","id":1,"params":{"addonid":"%s", "enabled":false}}' % addonId)
|
||||
if path.exists(addonPath): remove_dir(addonPath)
|
||||
if path.exists(addonProfilePath): remove_dir(addonProfilePath)
|
||||
except:
|
||||
pass
|
||||
# Kodi beenden
|
||||
executebuiltin('Quit')
|
||||
exit()
|
||||
|
||||
# # Todo - soll mal Hilfefunktion werden
|
||||
def help():
|
||||
return 'OK' # Platzhalter
|
||||
|
||||
69
plugin.video.xstream/resources/lib/youtube_fix.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
|
||||
import xbmcgui, sys, xbmc, xbmcvfs
|
||||
from resources.lib.config import cConfig
|
||||
|
||||
######################## Youtube Mod ############################
|
||||
|
||||
storedb=xbmcvfs.translatePath('special://home/userdata/addon_data/plugin.video.youtube/api_keys.json')
|
||||
providerpath=xbmcvfs.translatePath('special://home/addons/plugin.video.youtube/resources/lib/youtube_plugin/youtube/provider.py')
|
||||
youtubepath=xbmcvfs.translatePath('special://home/addons/plugin.video.youtube/resources/lib/youtube_plugin/youtube/client/youtube.py')
|
||||
|
||||
def YT():
|
||||
try:
|
||||
apikey=cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
except:
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % 'plugin.video.youtube')
|
||||
sys.exit()
|
||||
|
||||
addon_id='plugin.video.xstream'
|
||||
api_key='AIzaSyDnlJ0e_CZlLoZm7CMNnO41xInZgVFyObo'
|
||||
client_id='869922081769-d392du3vu6c8cpmtll11rpd7f09deu1n.apps.googleusercontent.com'
|
||||
client_secret='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'
|
||||
|
||||
if apikey == '' or apikey == None:
|
||||
try:
|
||||
with open(providerpath,'r') as f1:
|
||||
f1x=f1.read()
|
||||
if not 'mr-evil1' in f1x:
|
||||
try:
|
||||
xbmc.sleep(1000)
|
||||
with open(providerpath,'w' ) as f1y:
|
||||
f1y.write('# -*- coding: utf-8 -*-\n#mr-evil1\n'+f1x.replace(""" keys_changed = access_manager.dev_keys_changed(
|
||||
dev_id, dev_keys['key'], dev_keys['id'], dev_keys['secret']
|
||||
)""",""" try:
|
||||
keys_changed = access_manager.dev_keys_changed(dev_id, dev_keys['key'], dev_keys['id'], dev_keys['secret'])
|
||||
except:
|
||||
keys_changed = access_manager.dev_keys_changed(str(dev_id), str(dev_keys['key']), str(dev_keys['id']), str(dev_keys['secret']))""").replace(""" if self._api_check.changed:
|
||||
context.log_warning('API key set changed: Resetting client'
|
||||
' and updating access token')
|
||||
self.reset_client()
|
||||
access_tokens = []
|
||||
refresh_tokens = []
|
||||
access_manager.update_access_token(
|
||||
dev_id, access_tokens, -1, refresh_tokens,
|
||||
)""",""" try:
|
||||
if self._api_check.changed:
|
||||
context.log_warning('API key set changed: Resetting client'
|
||||
' and updating access token')
|
||||
self.reset_client()
|
||||
access_tokens = []
|
||||
refresh_tokens = []
|
||||
access_manager.update_access_token(
|
||||
dev_id, access_tokens, -1, refresh_tokens,
|
||||
)
|
||||
except:pass"""))
|
||||
except:xbmcgui.Dialog().ok('','FEHLER1')
|
||||
with open(youtubepath,'r') as f2:
|
||||
f2x=f2.read()
|
||||
if not 'mr-evil1' in f2x:
|
||||
try:
|
||||
xbmc.sleep(1000)
|
||||
with open(youtubepath,'w' ) as f2y:
|
||||
f2y.write('# -*- coding: utf-8 -*-\n#mr-evil1\n'+f2x.replace("""log_params['key'] = '...'.join((key[:3], key[-3:]))""","""#log_params['key'] = '...'.join((key[:3], key[-3:]))"""))
|
||||
except:xbmcgui.Dialog().ok('','FEHLER2')
|
||||
import youtube_registration
|
||||
youtube_registration.register_api_keys(addon_id,api_key,client_id,client_secret)
|
||||
except:pass
|
||||
11
plugin.video.xstream/resources/popup/devWarning.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
[COLOR red]
|
||||
[B]Warnung vor Missbrauch dieser Entwickler Optionen[/B]
|
||||
[I]
|
||||
Diese Einstellungen sind nur für Entwickler gedacht, aktivieren
|
||||
Sie die Optionen nur wenn Sie vom xStream-Team Zugangsdaten
|
||||
erhalten haben !!!
|
||||
|
||||
Bei Aktivierung mit falschen Zugangsdaten kann die bestehende
|
||||
Installation beschädigt werden!
|
||||
[/I]
|
||||
[/COLOR]
|
||||
1665
plugin.video.xstream/resources/settings.xml
Normal file
982
plugin.video.xstream/resources/skins/default/720p/info.xml
Normal file
@@ -0,0 +1,982 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window id="130">
|
||||
<defaultcontrol always="true">9000</defaultcontrol>
|
||||
<onload>SetProperty(xstream_menu,Info)</onload>
|
||||
<onunload>SetProperty(xstream_menu,Info)</onunload>
|
||||
<allowoverlay>no</allowoverlay>
|
||||
<controls>
|
||||
<control type="group">
|
||||
<visible>!Window.IsVisible(FileBrowser)</visible>
|
||||
<animation type="WindowOpen">
|
||||
<effect type="rotatex" start="1280,0" end="0,0" time="950" tween="cubic" easing="out"/>
|
||||
<effect type="fade" start="0" end="100000" time="900" delay="500"/>
|
||||
</animation>
|
||||
<animation type="WindowClose">
|
||||
<effect type="slide" start="0,0" end="1100,0" time="800" tween="back" easing="in"/>
|
||||
<effect type="fade" start="100" end="0" time="800" delay="400"/>
|
||||
</animation>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<right>0</right>
|
||||
<top>0</top>
|
||||
<bottom>0</bottom>
|
||||
<texture>black-back.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<fadetime>200</fadetime>
|
||||
<texture fallback="black-back.png">$INFO[Window.Property(backdrop_url)]</texture>
|
||||
<aspectratio align="center" aligny="center" scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<right>0</right>
|
||||
<top>0</top>
|
||||
<bottom>0</bottom>
|
||||
<texture colordiffuse="CC000a12">bg.png</texture>
|
||||
</control>
|
||||
<control type="group" id="49">
|
||||
<left>0</left>
|
||||
<align>left</align>
|
||||
<control type="group">
|
||||
<top>0</top>
|
||||
<left>0</left>
|
||||
<width>468</width>
|
||||
<control type="image">
|
||||
<aspectratio align="left">scale</aspectratio>
|
||||
<texture colordiffuse="$INFO[Window.Property(color)]">focus.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<width>24</width>
|
||||
<left>444</left>
|
||||
<top>0</top>
|
||||
<texture flipx="false">shadow-line.png</texture>
|
||||
</control>
|
||||
<control type="group">
|
||||
<top>118</top>
|
||||
<height>568</height>
|
||||
<width>390</width>
|
||||
<left>39</left>
|
||||
<control type="button" id="30">
|
||||
<onup>9000</onup>
|
||||
<ondown>9000</ondown>
|
||||
<onleft>9000</onleft>
|
||||
<onright>9000</onright>
|
||||
<textoffsetx>5</textoffsetx>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<left>14</left>
|
||||
<top>-75</top>
|
||||
<bottom>-20</bottom>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus colordiffuse="D0FFFFFF">linefocus.png</texturenofocus>
|
||||
<texturefocus colordiffuse="$INFO[Window.Property(color)]">linefocus.png</texturefocus>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<label>Close</label>
|
||||
<onclick>Action(Close)</onclick>
|
||||
<visible>!Player.HasMedia</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>578</height>
|
||||
<width>400</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="D0000000">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Window.Property(cover_url)]</texture>
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<visible>!Control.HasFocus(50) + !Control.HasFocus(5200) + !Control.HasFocus(5205) + !Control.HasFocus(5210) + !Control.HasFocus(5215)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Container(50).ListItem.Icon]</texture>
|
||||
<aspectratio align="center" aligny="center">scale</aspectratio>
|
||||
<visible>Control.HasFocus(50) + !Container(50).OnNext + !Container(50).OnPrevious</visible>
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
</control>
|
||||
<control type="image">
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Container(5200).ListItem.icon]</texture>
|
||||
<aspectratio align="center" aligny="center">scale</aspectratio>
|
||||
<visible>Control.HasFocus(5200) + !Container(5200).OnNext + !Container(5200).OnPrevious</visible>
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
</control>
|
||||
<control type="image">
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Container(5205).ListItem.Icon]</texture>
|
||||
<aspectratio align="center" aligny="center">scale</aspectratio>
|
||||
<visible>Control.HasFocus(5205) + !Container(5205).OnNext + !Container(5205).OnPrevious</visible>
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
</control>
|
||||
<control type="image">
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Container(5210).ListItem.icon]</texture>
|
||||
<aspectratio align="center" aligny="center">scale</aspectratio>
|
||||
<visible>Control.HasFocus(5210) + !Container(5210).OnNext + !Container(5210).OnPrevious</visible>
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
</control>
|
||||
<control type="image">
|
||||
<bordersize>15</bordersize>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
<texture background="true" diffuse="poster-big.png" fallback="no-poster2.png">$INFO[Container(5215).ListItem.icon]</texture>
|
||||
<aspectratio align="center" aligny="center">scale</aspectratio>
|
||||
<visible>Control.HasFocus(5215) + !Container(5215).OnNext + !Container(5215).OnPrevious</visible>
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
<control type="group">
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
<animation center="auto" easing="inout" effect="fade" end="0" start="100" time="400" tween="sine">Hidden</animation>
|
||||
<top>52</top>
|
||||
<left>522</left>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Syno) + !Player.HasMedia</visible>
|
||||
<control type="label" id="1">
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>[B][UPPERCASE]$INFO[Window.Property(title)][/UPPERCASE][/B]</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>30</top>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<label>$INFO[Window.Property(year),, •]$INFO[Window.Property(durationH), ,h]$INFO[Window.Property(durationM),, •] ($INFO[Window.Property(rating)] / $INFO[Window.Property(votes)])</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="textbox" id="400">
|
||||
<top>83</top>
|
||||
<left>0</left>
|
||||
<width>700</width>
|
||||
<height>480</height>
|
||||
<font>font20</font>
|
||||
<align>justify</align>
|
||||
<textcolor>white</textcolor>
|
||||
<pagecontrol>61</pagecontrol>
|
||||
<label>$INFO[Window.Property(Plot)]</label>
|
||||
<autoscroll time="3000" delay="4000" repeat="5000">!Control.HasFocus(61)</autoscroll>
|
||||
</control>
|
||||
</control>
|
||||
<control type="group">
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
<animation center="auto" easing="inout" effect="fade" end="0" start="100" time="400" tween="sine">Hidden</animation>
|
||||
<top>52</top>
|
||||
<left>522</left>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Info) + !Player.HasMedia</visible>
|
||||
<control type="label" id="1">
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>[B][UPPERCASE]$INFO[Window.Property(title)][/UPPERCASE][/B]</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>30</top>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<label>$INFO[Window.Property(year),, •]$INFO[Window.Property(durationH), ,h]$INFO[Window.Property(durationM),, •] ($INFO[Window.Property(rating)] / $INFO[Window.Property(votes)])</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="textbox" id="400">
|
||||
<top>83</top>
|
||||
<left>0</left>
|
||||
<width>700</width>
|
||||
<height>180</height>
|
||||
<font>font12</font>
|
||||
<align>justify</align>
|
||||
<textcolor>white</textcolor>
|
||||
<pagecontrol>61</pagecontrol>
|
||||
<label>$INFO[Window.Property(Plot)]</label>
|
||||
<autoscroll time="3000" delay="4000" repeat="5000">!Control.HasFocus(61)</autoscroll>
|
||||
</control>
|
||||
<control type="list">
|
||||
<left>0</left>
|
||||
<top>280</top>
|
||||
<width>700</width>
|
||||
<height>400</height>
|
||||
<onleft>49</onleft>
|
||||
<onright>49</onright>
|
||||
<onup>9000</onup>
|
||||
<ondown>61</ondown>
|
||||
<pagecontrol>-</pagecontrol>
|
||||
<orientation>vertical</orientation>
|
||||
<itemlayout width="1200" height="32">
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<height>20</height>
|
||||
<width>200</width>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
<scroll>false</scroll>
|
||||
</control>
|
||||
<control type="label">
|
||||
<height>20</height>
|
||||
<left>150</left>
|
||||
<width>500</width>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
<scroll>false</scroll>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout width="1200" height="32">
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<height>20</height>
|
||||
<width>200</width>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<info>ListItem.Label</info>
|
||||
<scroll>false</scroll>
|
||||
</control>
|
||||
<control type="label">
|
||||
<height>20</height>
|
||||
<left>150</left>
|
||||
<width>500</width>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
<scroll>True</scroll>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
<content>
|
||||
<item>
|
||||
<label>[B]Director[/B]</label>
|
||||
<label2>$INFO[Window.Property(director)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Writer[/B]</label>
|
||||
<label2>$INFO[Window.Property(Writer)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Genre[/B]</label>
|
||||
<label2>$INFO[Window.Property(Genre)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Studio[/B]</label>
|
||||
<label2>$INFO[Window.Property(studio)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Original Title[/B]</label>
|
||||
<label2>$INFO[Window.Property(originaltitle)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Original Language[/B]</label>
|
||||
<label2>$INFO[Window.Property(country)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Tagline[/B]</label>
|
||||
<label2>$INFO[Window.Property(tagline)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Budget / Revenue[/B]</label>
|
||||
<label2>$INFO[Window.Property(budget)] / $INFO[Window.Property(revenue)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
<item>
|
||||
<label>[B]Premiere[/B]</label>
|
||||
<label2>$INFO[Window.Property(releaseDate)]</label2>
|
||||
<onclick>noop</onclick>
|
||||
</item>
|
||||
</content>
|
||||
</control>
|
||||
</control>
|
||||
<control type="group">
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
<animation center="auto" easing="inout" effect="fade" end="0" start="100" time="400" tween="sine">Hidden</animation>
|
||||
<top>52</top>
|
||||
<left>522</left>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Person) + !Player.HasMedia</visible>
|
||||
<control type="label" id="1">
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>[B][UPPERCASE]$INFO[Window.Property(Person_name)][/UPPERCASE][/B]</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>30</top>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<label>$INFO[Window.Property(Person_birthday),, •]$INFO[Window.Property(Person_deathday), , •] $INFO[Window.Property(Person_place_of_birth)]</label>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="textbox" id="400">
|
||||
<top>83</top>
|
||||
<left>0</left>
|
||||
<width>700</width>
|
||||
<height>223</height>
|
||||
<font>font12</font>
|
||||
<align>justify</align>
|
||||
<textcolor>white</textcolor>
|
||||
<pagecontrol>61</pagecontrol>
|
||||
<label>$INFO[Window.Property(Person_biography)]</label>
|
||||
<autoscroll time="3000" delay="4000" repeat="5000">!Control.HasFocus(61)</autoscroll>
|
||||
</control>
|
||||
<control type="label" id="1">
|
||||
<left>0</left>
|
||||
<top>330</top>
|
||||
<width>820</width>
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>Spielt in: $INFO[Container(5215).ListItem.Label]</label>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="wraplist" id="5215">
|
||||
<left>-17</left>
|
||||
<top>346</top>
|
||||
<width>820</width>
|
||||
<height>700</height>
|
||||
<onleft>5200</onleft>
|
||||
<onright>5200</onright>
|
||||
<onup>50</onup>
|
||||
<ondown>9000</ondown>
|
||||
<align>left</align>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>0</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>0</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-9</left>
|
||||
<top>-9</top>
|
||||
<height>248</height>
|
||||
<width>171</width>
|
||||
<bordersize>20</bordersize>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
<animation center="auto" easing="inout" effect="zoom" end="100" start="80" time="400" tween="back">Focus</animation>
|
||||
<visible>Control.HasFocus(5215)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
</control>
|
||||
<control type="group">
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
<animation center="auto" easing="inout" effect="fade" end="0" start="100" time="400" tween="sine">Hidden</animation>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Cast) + !Player.HasMedia</visible>
|
||||
<onclick>SetProperty(xstream_menu,Person)</onclick>
|
||||
<control type="label" id="1">
|
||||
<left>520</left>
|
||||
<top>130</top>
|
||||
<width>820</width>
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>Casting: $INFO[Container(50).ListItem.Label] / $INFO[Container(50).ListItem.Label2]</label>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="label" id="1">
|
||||
<left>520</left>
|
||||
<top>382</top>
|
||||
<width>820</width>
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>Director: $INFO[Container(5200).ListItem.Label] / $INFO[Container(5200).ListItem.Label2]</label>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="Wraplist" id="50">
|
||||
<left>465</left>
|
||||
<top>145</top>
|
||||
<width>820</width>
|
||||
<height>700</height>
|
||||
<onleft>50</onleft>
|
||||
<onright>50</onright>
|
||||
<onup>9000</onup>
|
||||
<ondown>5200</ondown>
|
||||
<align>left</align>
|
||||
<onclick>SetProperty(xstream_menu,Person)</onclick>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<top>-5</top>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-9</left>
|
||||
<top>-9</top>
|
||||
<height>248</height>
|
||||
<width>171</width>
|
||||
<bordersize>20</bordersize>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
<animation center="auto" easing="inout" effect="zoom" end="100" start="80" time="400" tween="back">Focus</animation>
|
||||
<visible>Control.HasFocus(50)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<control type="wraplist" id="5200">
|
||||
<left>465</left>
|
||||
<top>398</top>
|
||||
<width>820</width>
|
||||
<height>700</height>
|
||||
<onleft>5200</onleft>
|
||||
<onright>5200</onright>
|
||||
<onup>50</onup>
|
||||
<ondown>9000</ondown>
|
||||
<align>left</align>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55FFFFFF">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55FFFFFF">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-9</left>
|
||||
<top>-9</top>
|
||||
<height>248</height>
|
||||
<width>171</width>
|
||||
<bordersize>20</bordersize>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
<animation center="auto" easing="inout" effect="zoom" end="100" start="80" time="400" tween="back">Focus</animation>
|
||||
<visible>Control.HasFocus(5200)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
</control>
|
||||
<control type="group" id="52200">
|
||||
<animation center="auto" easing="inout" effect="fade" end="100" start="0" time="400" tween="sine">Visible</animation>
|
||||
<animation center="auto" easing="inout" effect="fade" end="0" start="100" time="400" tween="sine">Hidden</animation>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Simil) + !Player.HasMedia</visible>
|
||||
<control type="label">
|
||||
<left>520</left>
|
||||
<top>130</top>
|
||||
<width>820</width>
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>Similar: $INFO[Container(5205).ListItem.Label]</label>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="label" id="1">
|
||||
<left>520</left>
|
||||
<top>382</top>
|
||||
<width>820</width>
|
||||
<height>20</height>
|
||||
<font>font12</font>
|
||||
<label>Recommend: $INFO[Container(5210).ListItem.Label]</label>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
<control type="wraplist" id="5205">
|
||||
<left>465</left>
|
||||
<top>145</top>
|
||||
<width>820</width>
|
||||
<height>700</height>
|
||||
<onleft>5205</onleft>
|
||||
<onright>5205</onright>
|
||||
<onup>9000</onup>
|
||||
<ondown>5210</ondown>
|
||||
<align>left</align>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55FFFFFF">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-9</left>
|
||||
<top>-9</top>
|
||||
<height>248</height>
|
||||
<width>171</width>
|
||||
<bordersize>20</bordersize>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
<animation center="auto" easing="inout" effect="zoom" end="100" start="80" time="400" tween="back">Focus</animation>
|
||||
<visible>Control.HasFocus(5205)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<control type="wraplist" id="5210">
|
||||
<left>465</left>
|
||||
<top>398</top>
|
||||
<width>820</width>
|
||||
<height>700</height>
|
||||
<onleft>5210</onleft>
|
||||
<onright>5210</onright>
|
||||
<onup>5205</onup>
|
||||
<ondown>9000</ondown>
|
||||
<align>left</align>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55ffffff">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="230" width="153">
|
||||
<control type="group">
|
||||
<height>230</height>
|
||||
<width>153</width>
|
||||
<left>40</left>
|
||||
<control type="image">
|
||||
<left>-5</left>
|
||||
<top>-5</top>
|
||||
<height>240</height>
|
||||
<width>163</width>
|
||||
<bordersize>20</bordersize>
|
||||
<bordertexture border="30" colordiffuse="55FFFFFF">shadow-20.png</bordertexture>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>-9</left>
|
||||
<top>-9</top>
|
||||
<height>248</height>
|
||||
<width>171</width>
|
||||
<bordersize>20</bordersize>
|
||||
<texture border="30" colordiffuse="$INFO[Window.Property(color)]">box.png</texture>
|
||||
<animation center="auto" easing="inout" effect="zoom" end="100" start="80" time="400" tween="back">Focus</animation>
|
||||
<visible>Control.HasFocus(5210)</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<bordersize>15</bordersize>
|
||||
<texture background="true" diffuse="poster-small.png" fallback="no-poster2.png">$INFO[ListItem.icon]</texture>
|
||||
<aspectratio scalediffuse="false">scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>25</left>
|
||||
<top>170</top>
|
||||
<width>110</width>
|
||||
<height>40</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<scroll>false</scroll>
|
||||
<label>$INFO[ListItem.Label][CR]$INFO[ListItem.Label2]</label>
|
||||
</control>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
</control>
|
||||
<control type="grouplist" id="9000">
|
||||
<left>520</left>
|
||||
<top>630</top>
|
||||
<height>300</height>
|
||||
<width>750</width>
|
||||
<itemgap>20</itemgap>
|
||||
<align>left</align>
|
||||
<orientation>horizontal</orientation>
|
||||
<onleft>30</onleft>
|
||||
<onright>30</onright>
|
||||
<onup condition="String.IsEqual(Window.Property(xstream_menu),Cast)">5200</onup>
|
||||
<onup condition="String.IsEqual(Window.Property(xstream_menu),Simil)">5210</onup>
|
||||
<onup condition="String.IsEqual(Window.Property(xstream_menu),Person)">5215</onup>
|
||||
<onup condition="String.IsEqual(Window.Property(xstream_menu),Info)">30</onup>
|
||||
<visible allowhiddenfocus="true">!Control.IsVisible(35)</visible>
|
||||
<control type="button" id="5">
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<label>Info</label>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus border="10" diffuse="box.png" colordiffuse="D0FFFFFF">focus.png</texturenofocus>
|
||||
<texturefocus border="10" diffuse="box.png" colordiffuse="$INFO[Window.Property(color)]">focus.png</texturefocus>
|
||||
<onclick>SetProperty(xstream_menu,Info)</onclick>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<visible>!String.IsEqual(Window.Property(xstream_menu),Info)</visible>
|
||||
<visible>!Player.HasMedia</visible>
|
||||
</control>
|
||||
<control type="button" id="6">
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<label>Info</label>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus border="10" diffuse="box.png" colordiffuse="D0FFFFFF">focus.png</texturenofocus>
|
||||
<texturefocus border="10" diffuse="box.png" colordiffuse="$INFO[Window.Property(color)]">focus.png</texturefocus>
|
||||
<onclick>SetProperty(xstream_menu,Syno)</onclick>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<visible>String.IsEqual(Window.Property(xstream_menu),Info)</visible>
|
||||
<visible>!Player.HasMedia</visible>
|
||||
</control>
|
||||
<control type="button" id="7">
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<label>Cast</label>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus border="10" diffuse="box.png" colordiffuse="D0FFFFFF">focus.png</texturenofocus>
|
||||
<texturefocus border="10" diffuse="box.png" colordiffuse="$INFO[Window.Property(color)]">focus.png</texturefocus>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<onclick>SetProperty(xstream_menu,Cast)</onclick>
|
||||
<visible>!Player.HasMedia</visible>
|
||||
</control>
|
||||
<control type="button" id="9">
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<label>Similar</label>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus border="10" diffuse="box.png" colordiffuse="D0FFFFFF">focus.png</texturenofocus>
|
||||
<texturefocus border="10" diffuse="box.png" colordiffuse="$INFO[Window.Property(color)]">focus.png</texturefocus>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<onclick>SetProperty(xstream_menu,Simil)</onclick>
|
||||
<visible>!Player.HasMedia</visible>
|
||||
</control>
|
||||
<control type="button" id="11">
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
<height>40</height>
|
||||
<width>auto</width>
|
||||
<label>Trailer</label>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<focusedcolor>FFFFFFFF</focusedcolor>
|
||||
<texturenofocus border="10" diffuse="box.png" colordiffuse="D0FFFFFF">focus.png</texturenofocus>
|
||||
<texturefocus border="10" diffuse="box.png" colordiffuse="$INFO[Window.Property(color)]">focus.png</texturefocus>
|
||||
<animation reversible="false" center="auto" easing="inout" effect="zoom" end="100" start="90" time="400" tween="back">Focus</animation>
|
||||
<visible>String.isEqual(Window.Property(isTrailer),true)</visible>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
</controls>
|
||||
</window>
|
||||
BIN
plugin.video.xstream/resources/skins/default/media/bg.png
Normal file
|
After Width: | Height: | Size: 956 B |
|
After Width: | Height: | Size: 280 B |
BIN
plugin.video.xstream/resources/skins/default/media/box.png
Normal file
|
After Width: | Height: | Size: 333 B |
BIN
plugin.video.xstream/resources/skins/default/media/focus.png
Normal file
|
After Width: | Height: | Size: 956 B |
BIN
plugin.video.xstream/resources/skins/default/media/linefocus.png
Normal file
|
After Width: | Height: | Size: 964 B |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
BIN
plugin.video.xstream/resources/skins/default/media/shadow-20.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1006 B |
126
plugin.video.xstream/service.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import xbmc
|
||||
import time
|
||||
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib import tools
|
||||
from xbmc import LOGERROR, LOGDEBUG, log
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.handler.pluginHandler import cPluginHandler
|
||||
from resources.lib import updateManager
|
||||
from resources.lib.utils import translatePath
|
||||
from resources.lib.tools import cCache
|
||||
from resources.lib.tools import infoDialog
|
||||
|
||||
|
||||
# ResolverUrl Addon Data
|
||||
RESOLVE_ADDON_DATA_PATH = translatePath(os.path.join('special://home/userdata/addon_data/script.module.resolveurl'))
|
||||
|
||||
# Pfad der update.sha
|
||||
RESOLVE_SHA = os.path.join(translatePath(RESOLVE_ADDON_DATA_PATH), "update_sha")
|
||||
|
||||
# xStream Installationspfad
|
||||
ADDON_PATH = translatePath(os.path.join('special://home/addons/', '%s'))
|
||||
|
||||
# Aktiviere xStream Addon
|
||||
def enableAddon(ADDONID):
|
||||
struktur = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.GetAddonDetails","id":1,"params": {"addonid":"%s", "properties": ["enabled"]}}' % ADDONID))
|
||||
if 'error' in struktur or struktur["result"]["addon"]["enabled"] != True:
|
||||
count = 0
|
||||
while True:
|
||||
if count == 5: break
|
||||
count += 1
|
||||
xbmc.executebuiltin('EnableAddon(%s)' % (ADDONID))
|
||||
xbmc.executebuiltin('SendClick(11)')
|
||||
xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.SetAddonEnabled","id":1,"params":{"addonid":"%s", "enabled":true}}' % ADDONID)
|
||||
xbmc.sleep(500)
|
||||
try:
|
||||
struktur = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.GetAddonDetails","id":1,"params": {"addonid":"%s", "properties": ["enabled"]}}' % ADDONID))
|
||||
if struktur["result"]["addon"]["enabled"] == True: break
|
||||
except:
|
||||
pass
|
||||
|
||||
# Überprüfe Abhängigkeiten
|
||||
def checkDependence(ADDONID):
|
||||
isdebug = True
|
||||
if isdebug:
|
||||
log(__name__ + ' - %s - checkDependence ' % ADDONID, LOGDEBUG)
|
||||
try:
|
||||
addon_xml = os.path.join(ADDON_PATH % ADDONID, 'addon.xml')
|
||||
with open(addon_xml, 'rb') as f:
|
||||
xml = f.read()
|
||||
pattern = '(import.*?addon[^/]+)'
|
||||
allDependence = re.findall(pattern, str(xml))
|
||||
for i in allDependence:
|
||||
try:
|
||||
if 'optional' in i or 'xbmc.python' in i: continue
|
||||
pattern = 'import.*?"([^"]+)'
|
||||
IDdoADDON = re.search(pattern, i).group(1)
|
||||
if os.path.exists(ADDON_PATH % IDdoADDON) == True and cConfig().getSetting('enforceUpdate') != 'true':
|
||||
enableAddon(IDdoADDON)
|
||||
else:
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % (IDdoADDON))
|
||||
xbmc.executebuiltin('SendClick(11)')
|
||||
enableAddon(IDdoADDON)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
log(__name__ + ' %s - Exception ' % e, LOGERROR)
|
||||
|
||||
def delHtmlCache():
|
||||
# Html Cache beim KodiStart nach (X) Tage löschen
|
||||
deltaDay = int(cConfig().getSetting('cacheDeltaDay', 2))
|
||||
deltaTime = 60*60*24*deltaDay # Tage
|
||||
currentTime = int(time.time())
|
||||
# alle x Tage
|
||||
if currentTime >= int(cConfig().getSetting('lastdelhtml', 0)) + deltaTime:
|
||||
cRequestHandler('').clearCache() # Cache löschen
|
||||
cConfig().setSetting('lastdelhtml', str(currentTime))
|
||||
|
||||
|
||||
def main():
|
||||
cCache().set(cConfig().getAddonInfo('id') + '_main', 'running')
|
||||
# Starte Resolver Update wenn auf Github verfügbar
|
||||
if os.path.isfile(RESOLVE_SHA) == False or cConfig().getSetting('githubUpdateResolver') == 'true' or cConfig().getSetting('enforceUpdate') == 'true':
|
||||
status2 = updateManager.resolverUpdate(True)
|
||||
if cConfig().getSetting('update.notification') == 'full': # Benachrichtigung Resolver vollständig
|
||||
infoDialog(cConfig().getLocalizedString(30112), sound=False, icon='INFO', time=10000) # Suche Updates
|
||||
if status2 == True: infoDialog('Resolver ' + cConfig().getSetting('resolver.branch') + cConfig().getLocalizedString(30116), sound=False, icon='INFO', time=6000)
|
||||
if status2 == False: infoDialog(cConfig().getLocalizedString(30117), sound=True, icon='ERROR')
|
||||
if status2 == None: infoDialog(cConfig().getLocalizedString(30118), sound=False, icon='INFO', time=6000)
|
||||
if cConfig().getSetting('enforceUpdate') == 'true': cConfig().setSetting('enforceUpdate', 'false')
|
||||
else:
|
||||
if status2 == True: infoDialog('Resolver ' + cConfig().getSetting('resolver.branch') + cConfig().getLocalizedString(30116), sound=False, icon='INFO', time=6000)
|
||||
if status2 == False: infoDialog(cConfig().getLocalizedString(30117), sound=True, icon='ERROR')
|
||||
if cConfig().getSetting('enforceUpdate') == 'true': cConfig().setSetting('enforceUpdate', 'false')
|
||||
|
||||
# Startet Überprüfung der Abhängigkeiten
|
||||
checkDependence('plugin.video.xstream')
|
||||
|
||||
# Startet Domain Überprüfung und schreibt diese in die settings.xml
|
||||
cPluginHandler().checkDomain()
|
||||
|
||||
# Wenn neue settings vorhanden oder geändert in addon_data dann starte Pluginhandler und aktualisiere die PluginDB um Daten von checkDomain mit aufzunehmen
|
||||
try:
|
||||
if cConfig().getSetting('newSetting') == 'true':
|
||||
cPluginHandler().getAvailablePlugins()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# getAvailablePlugins must be finished before the main menu can be started!
|
||||
cCache().set(cConfig().getAddonInfo('id') + '_main', 'finished')
|
||||
|
||||
# Changelog Popup in den "settings.xml" ein bzw. aus schaltbar
|
||||
if cConfig().getSetting('popup.update.notification') == 'true':
|
||||
tools.changelog()
|
||||
|
||||
# Html Cache beim KodiStart nach (X) Tage löschen
|
||||
delHtmlCache()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
514
plugin.video.xstream/sites/aniworld.py
Normal file
@@ -0,0 +1,514 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
#Always pay attention to the translations in the menu!
|
||||
# Sprachauswahl für Hoster enthalten.
|
||||
# Ajax Suchfunktion enthalten.
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showValue: 24 Stunden
|
||||
# showAllSeries: 24 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
# SSsearch: 24 Stunden
|
||||
|
||||
# 2022-12-06 Heptamer - Suchfunktion überarbeitet
|
||||
|
||||
import xbmcgui
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser, cUtil
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
SITE_IDENTIFIER = 'aniworld'
|
||||
SITE_NAME = 'AniWorld'
|
||||
SITE_ICON = 'aniworld.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
# URL_MAIN = 'https://aniworld.to'
|
||||
URL_SERIES = URL_MAIN + '/animes'
|
||||
URL_POPULAR = URL_MAIN + '/beliebte-animes'
|
||||
URL_NEW_EPISODES = URL_MAIN + '/neue-episoden'
|
||||
URL_LOGIN = URL_MAIN + '/login'
|
||||
REFERER = 'https://' + DOMAIN
|
||||
|
||||
#
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
username = cConfig().getSetting('aniworld.user') # Username
|
||||
password = cConfig().getSetting('aniworld.pass') # Password
|
||||
if username == '' or password == '': # If no username and password were set, close the plugin!
|
||||
xbmcgui.Dialog().ok(cConfig().getLocalizedString(30241), cConfig().getLocalizedString(30263)) # Info Dialog!
|
||||
else:
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30518), SITE_IDENTIFIER, 'showAllSeries'), params) # All Series
|
||||
params.setParam('sUrl', URL_NEW_EPISODES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30516), SITE_IDENTIFIER, 'showNewEpisodes'), params) # New Episodes
|
||||
params.setParam('sUrl', URL_POPULAR)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30519), SITE_IDENTIFIER, 'showEntries'), params) # Popular Series
|
||||
params.setParam('sUrl', URL_MAIN)
|
||||
params.setParam('sCont', 'catalogNav')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showValue'), params) # From A-Z
|
||||
params.setParam('sCont', 'homeContentGenresList')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showValue'), params) # Genre
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showValue():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
isMatch, sContainer = cParser.parseSingleResult(sHtmlContent, '<ul[^>]*class="%s"[^>]*>(.*?)<\\/ul>' % params.getValue('sCont'))
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sContainer, r'<li>\s*<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>\s*<\/li>')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
sUrl = sUrl if sUrl.startswith('http') else URL_MAIN + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showAllSeries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<a[^>]*href="(\\/anime\\/[^"]*)"[^>]*>(.*?)</a>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + sUrl)
|
||||
params.setParam('TVShowTitle', sName)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showNewEpisodes(entryUrl=False, sGui=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl:
|
||||
entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 4 # HTML Cache Zeit 4 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<div[^>]*class="col-md-[^"]*"[^>]*>\s*<a[^>]*href="([^"]*)"[^>]*>\s*<strong>([^<]+)</strong>\s*<span[^>]*>([^<]+)</span>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sInfo in aResult:
|
||||
sMovieTitle = sName + ' ' + sInfo
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
oGuiElement.setTitle(sMovieTitle)
|
||||
params.setParam('sUrl', URL_MAIN + sUrl)
|
||||
params.setParam('TVShowTitle', sMovieTitle)
|
||||
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl:
|
||||
entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # HTML Cache Zeit 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
#Aufbau pattern
|
||||
#'<div[^>]*class="col-md-[^"]*"[^>]*>.*?' # start element
|
||||
#'<a[^>]*href="([^"]*)"[^>]*>.*?' # url
|
||||
#'<img[^>]*src="([^"]*)"[^>]*>.*?' # thumbnail
|
||||
#'<h3>(.*?)<span[^>]*class="paragraph-end">.*?' # title
|
||||
#'<\\/div>' # end element
|
||||
pattern = '<div[^>]*class="col-md-[^"]*"[^>]*>.*?<a[^>]*href="([^"]*)"[^>]*>.*?<img[^>]*src="([^"]*)"[^>]*>.*?<h3>(.*?)<span[^>]*class="paragraph-end">.*?</div>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sThumbnail, sName in aResult:
|
||||
if sThumbnail.startswith('/'):
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + sUrl)
|
||||
params.setParam('TVShowTitle', sName)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
pattern = 'pagination">.*?<a href="([^"]+)">></a>.*?</a></div>'
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatchNextPage:
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showSeasons():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
sTVShowTitle = params.getValue('TVShowTitle')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<div[^>]*class="hosterSiteDirectNav"[^>]*>.*?<ul>(.*?)</ul>'
|
||||
isMatch, sContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
pattern = '<a[^>]*href="([^"]*)"[^>]*title="([^"]*)"[^>]*>(.*?)</a>.*?'
|
||||
isMatch, aResult = cParser.parse(sContainer, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, '<p[^>]*data-full-description="(.*?)"[^>]*>')
|
||||
isThumbnail, sThumbnail = cParser.parseSingleResult(sHtmlContent, '<div[^>]*class="seriesCoverBox"[^>]*>.*?<img[^>]*src="([^"]*)"[^>]*>')
|
||||
if isThumbnail:
|
||||
if sThumbnail.startswith('/'):
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sNr in aResult:
|
||||
isMovie = sUrl.endswith('filme')
|
||||
if 'Alle Filme' in sName:
|
||||
sName = 'Filme'
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showEpisodes')
|
||||
oGuiElement.setMediaType('season' if not isMovie else 'movie')
|
||||
if isThumbnail:
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
if not isMovie:
|
||||
oGuiElement.setTVShowTitle(sTVShowTitle)
|
||||
oGuiElement.setSeason(sNr)
|
||||
params.setParam('sSeason', sNr)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('sUrl', URL_MAIN + sUrl)
|
||||
cGui().addFolder(oGuiElement, params, True, total)
|
||||
cGui().setView('seasons')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
sTVShowTitle = params.getValue('TVShowTitle')
|
||||
sSeason = params.getValue('sSeason')
|
||||
sThumbnail = params.getValue('sThumbnail')
|
||||
if not sSeason:
|
||||
sSeason = '0'
|
||||
isMovieList = sUrl.endswith('filme')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 4 # HTML Cache Zeit 4 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<table[^>]*class="seasonEpisodesList"[^>]*>(.*?)</table>'
|
||||
isMatch, sContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
if isMovieList == True:
|
||||
pattern = r'<tr[^>]*data-episode-season-id="(\d+).*?<a href="([^"]+)">\s([^<]+).*?<strong>([^<]+)'
|
||||
isMatch, aResult = cParser.parse(sContainer, pattern)
|
||||
if not isMatch:
|
||||
pattern = r'<tr[^>]*data-episode-season-id="(\d+).*?<a href="([^"]+)">\s([^<]+).*?<span>([^<]+)'
|
||||
isMatch, aResult = cParser.parse(sContainer, pattern)
|
||||
else:
|
||||
pattern = r'<tr[^>]*data-episode-season-id="(\d+).*?<a href="([^"]+).*?(?:<strong>(.*?)</strong>.*?)?(?:<span>(.*?)</span>.*?)?<'
|
||||
isMatch, aResult = cParser.parse(sContainer, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, '<p[^>]*data-full-description="(.*?)"[^>]*>')
|
||||
total = len(aResult)
|
||||
for sID, sUrl2, sNameGer, sNameEng in aResult:
|
||||
sName = '%d - ' % int(sID)
|
||||
if isMovieList == True:
|
||||
sName += sNameGer + '- ' + sNameEng
|
||||
else:
|
||||
sName += sNameGer if sNameGer else sNameEng
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setMediaType('episode' if not isMovieList else 'movie')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
if not isMovieList:
|
||||
oGuiElement.setSeason(sSeason)
|
||||
oGuiElement.setEpisode(int(sID))
|
||||
oGuiElement.setTVShowTitle(sTVShowTitle)
|
||||
params.setParam('sUrl', URL_MAIN + sUrl2)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
cGui().setView('episodes' if not isMovieList else 'movies')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('sUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
if cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain') == 'www.aniworld.info':
|
||||
pattern = r'<li[^>]*episodeLink([^"]+)"\sdata-lang-key="([^"]+).*?data-link-target="([^"]+).*?<h4>([^<]+)<([^>]+)'
|
||||
pattern2 = r'itemprop="keywords".content=".*?Season...([^"]+).S.*?' # HD Kennzeichen
|
||||
# data-lang-key="1" Deutsch
|
||||
# data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
# data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
aResult2 = cParser.parse(sHtmlContent, pattern2) # pattern 2 auslesen
|
||||
if isMatch:
|
||||
for sID, sLang, sUrl, sName, sQuality in aResult:
|
||||
# Die Funktion gibt 2 werte zurück!
|
||||
# element 1 aus array "[0]" True bzw. False
|
||||
# element 2 aus array "[1]" Name von domain / hoster - wird hier nicht gebraucht!
|
||||
sUrl = sUrl.replace('/dl/2010', '/redirect/' + sID)
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
if '2' in sLang: # data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
continue
|
||||
elif '3' in sLang: # data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
continue
|
||||
elif sLang == '1': # data-lang-key="1" Deutsch
|
||||
sLang = '(DE)' # Anzeige der Sprache Deutsch
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
cGui().showLanguage() # Kein Eintrag in der ausgewählten Sprache verfügbar
|
||||
continue
|
||||
if sLanguage == '3': # Voreingestellte Sprache Japanisch in settings.xml
|
||||
if '1' in sLang: # data-lang-key="1" Deutsch
|
||||
continue
|
||||
elif sLang == '3': # data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
sLang = '(JPN) Sub: (DE)' # Anzeige der Sprache Japanisch mit deutschen Untertitel
|
||||
elif sLang == '2': # data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
sLang = '(JPN) Sub: (EN)' # Anzeige der Sprache Japanisch mit englischen Untertitel
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if sLang == '1': # data-lang-key="1"
|
||||
sLang = '(DE)' # Anzeige der Sprache
|
||||
elif sLang == '3': # data-lang-key="3"
|
||||
sLang = '(JPN) Sub: (DE)' # Anzeige der Sprache Japanisch mit deutschen Untertitel
|
||||
elif sLang == '2': # data-lang-key="2"
|
||||
sLang = '(JPN) Sub: (EN)' # Anzeige der Sprache Japanisch mit englischen Untertitel
|
||||
if 'HD' in aResult2[1]: # Prüfen ob tuple aResult2 das Kennzeichen HD enthält, dann übersteuern
|
||||
sQuality = '720'
|
||||
else:
|
||||
sQuality = '480'
|
||||
# Ab hier wird der sName mit abgefragt z.B:
|
||||
# aus dem Log [serienstream]: ['/redirect/12286260', 'VOE']
|
||||
# hier ist die sUrl = '/redirect/12286260' und der sName 'VOE'
|
||||
# hoster.py 194
|
||||
hoster = {'link': [sUrl, sName], 'name': sName, 'displayedName': '%s [I]%s [%sp][/I]' % (sName, sLang, sQuality), 'quality': sQuality, 'languageCode': sLang} # Language Code für hoster.py Sprache Prio
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
if not hosters:
|
||||
cGui().showLanguage()
|
||||
return hosters
|
||||
else:
|
||||
pattern = '<li[^>]*data-lang-key="([^"]+).*?data-link-target="([^"]+).*?<h4>([^<]+)<([^>]+)'
|
||||
pattern2 = 'itemprop="keywords".content=".*?Season...([^"]+).S.*?' # HD Kennzeichen
|
||||
# data-lang-key="1" Deutsch
|
||||
# data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
# data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
aResult2 = cParser.parse(sHtmlContent, pattern2) # pattern 2 auslesen
|
||||
if isMatch:
|
||||
for sLang, sUrl, sName, sQuality in aResult:
|
||||
# Die Funktion gibt 2 werte zurück!
|
||||
# element 1 aus array "[0]" True bzw. False
|
||||
# element 2 aus array "[1]" Name von domain / hoster - wird hier nicht gebraucht!
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
if '2' in sLang: # data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
continue
|
||||
elif '3' in sLang: # data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
continue
|
||||
elif sLang == '1': # data-lang-key="1" Deutsch
|
||||
sLang = '(DE)' # Anzeige der Sprache Deutsch
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
cGui().showLanguage() # Kein Eintrag in der ausgewählten Sprache verfügbar
|
||||
continue
|
||||
if sLanguage == '3': # Voreingestellte Sprache Japanisch in settings.xml
|
||||
if '1' in sLang: # data-lang-key="1" Deutsch
|
||||
continue
|
||||
elif sLang == '3': # data-lang-key="3" Japanisch mit deutschen Untertitel
|
||||
sLang = '(JPN) Sub: (DE)' # Anzeige der Sprache Japanisch mit deutschen Untertitel
|
||||
elif sLang == '2': # data-lang-key="2" Japanisch mit englischen Untertitel
|
||||
sLang = '(JPN) Sub: (EN)' # Anzeige der Sprache Japanisch mit englischen Untertitel
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if sLang == '1': # data-lang-key="1"
|
||||
sLang = '(DE)' # Anzeige der Sprache
|
||||
elif sLang == '3': # data-lang-key="3"
|
||||
sLang = '(JPN) Sub: (DE)' # Anzeige der Sprache Japanisch mit deutschen Untertitel
|
||||
elif sLang == '2': # data-lang-key="2"
|
||||
sLang = '(JPN) Sub: (EN)' # Anzeige der Sprache Japanisch mit englischen Untertitel
|
||||
if 'HD' in aResult2[1]: # Prüfen ob tuple aResult2 das Kennzeichen HD enthält, dann übersteuern
|
||||
sQuality = '720'
|
||||
else:
|
||||
sQuality = '480'
|
||||
# Ab hier wird der sName mit abgefragt z.B:
|
||||
# aus dem Log [serienstream]: ['/redirect/12286260', 'VOE']
|
||||
# hier ist die sUrl = '/redirect/12286260' und der sName 'VOE'
|
||||
# hoster.py 194
|
||||
hoster = {'link': [sUrl, sName], 'name': sName, 'displayedName': '%s [I]%s [%sp][/I]' % (sName, sLang, sQuality), 'quality': sQuality, 'languageCode': sLang} # Language Code für hoster.py Sprache Prio
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
if not hosters:
|
||||
cGui().showLanguage()
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(hUrl):
|
||||
if type(hUrl) == str: hUrl = eval(hUrl)
|
||||
username = cConfig().getSetting('aniworld.user')
|
||||
password = cConfig().getSetting('aniworld.pass')
|
||||
Handler = cRequestHandler(URL_LOGIN, caching=False)
|
||||
Handler.addHeaderEntry('Upgrade-Insecure-Requests', '1')
|
||||
Handler.addHeaderEntry('Referer', ParameterHandler().getValue('entryUrl'))
|
||||
Handler.addParameters('email', username)
|
||||
Handler.addParameters('password', password)
|
||||
Handler.request()
|
||||
Request = cRequestHandler(URL_MAIN + hUrl[0], caching=False)
|
||||
Request.addHeaderEntry('Referer', ParameterHandler().getValue('entryUrl'))
|
||||
Request.addHeaderEntry('Upgrade-Insecure-Requests', '1')
|
||||
Request.request()
|
||||
sUrl = Request.getRealUrl()
|
||||
|
||||
if 'voe' in hUrl[1].lower():
|
||||
isBlocked, sDomain = cConfig().isBlockedHoster(sUrl) # Die funktion gibt 2 werte zurück!
|
||||
if isBlocked: # Voe Pseudo sDomain nicht bekannt in resolveUrl
|
||||
sUrl = sUrl.replace(sDomain, 'voe.sx')
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
SSsearch(oGui, sSearchText)
|
||||
|
||||
|
||||
def SSsearch(sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
params.getValue('sSearchText')
|
||||
oRequest = cRequestHandler(URL_SERIES, caching=True, ignoreErrors=(sGui is not False))
|
||||
oRequest.addHeaderEntry('X-Requested-With', 'XMLHttpRequest')
|
||||
oRequest.addHeaderEntry('Referer', REFERER + '/animes')
|
||||
oRequest.addHeaderEntry('Origin', REFERER)
|
||||
oRequest.addHeaderEntry('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
|
||||
oRequest.addHeaderEntry('Upgrade-Insecure-Requests', '1')
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
|
||||
if not sHtmlContent:
|
||||
return
|
||||
|
||||
sst = sSearchText.lower()
|
||||
|
||||
pattern = r'<li><a data.+?href="([^"]+)".+?">(.*?)\<\/a><\/l' #link - title
|
||||
|
||||
aResult = cParser.parse(sHtmlContent, pattern)
|
||||
|
||||
if not aResult[0]:
|
||||
oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult[1])
|
||||
for link, title in aResult[1]:
|
||||
titleLow = title.lower()
|
||||
if not sst in titleLow and not cUtil.isSimilarByToken(sst, titleLow):
|
||||
continue
|
||||
else:
|
||||
#get images thumb / descr pro call. (optional)
|
||||
try:
|
||||
sThumbnail, sDescription = getMetaInfo(link, title)
|
||||
oGuiElement = cGuiElement(title, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setThumbnail(URL_MAIN + sThumbnail)
|
||||
oGuiElement.setDescription(sDescription)
|
||||
oGuiElement.setTVShowTitle(title)
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + link)
|
||||
params.setParam('sName', title)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
except Exception:
|
||||
oGuiElement = cGuiElement(title, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setTVShowTitle(title)
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + link)
|
||||
params.setParam('sName', title)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
|
||||
|
||||
def getMetaInfo(link, title): # Setzen von Metadata in Suche:
|
||||
oGui = cGui()
|
||||
oRequest = cRequestHandler(URL_MAIN + link, caching=False)
|
||||
oRequest.addHeaderEntry('X-Requested-With', 'XMLHttpRequest')
|
||||
oRequest.addHeaderEntry('Referer', REFERER + '/animes')
|
||||
oRequest.addHeaderEntry('Origin', REFERER)
|
||||
|
||||
#GET CONTENT OF HTML
|
||||
sHtmlContent = oRequest.request()
|
||||
if not sHtmlContent:
|
||||
return
|
||||
|
||||
pattern = r'seriesCoverBox">.*?<img src="([^"]+)"\ al.+?data-full-description="([^"]+)"' #img , descr
|
||||
|
||||
aResult = cParser.parse(sHtmlContent, pattern)
|
||||
|
||||
if not aResult[0]:
|
||||
return
|
||||
|
||||
for sImg, sDescr in aResult[1]:
|
||||
return sImg, sDescr
|
||||
601
plugin.video.xstream/sites/api_all.py
Normal file
@@ -0,0 +1,601 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Always pay attention to the translations in the menu!
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showGenre: 48 Stunden
|
||||
# showEntries: 6 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
|
||||
import re
|
||||
import xbmcgui
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser, cUtil
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from json import loads
|
||||
from datetime import datetime
|
||||
|
||||
# Globale Variable für die JSON-Daten
|
||||
apiJson = None
|
||||
|
||||
# Domain Abfrage ###
|
||||
|
||||
SITE_NAME = 'API Suchmaschine'
|
||||
SITE_ICON = 'api.png'
|
||||
SITE_IDENTIFIER = 'api_all'
|
||||
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'movie4k.sx')
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
ORIGIN = 'https://' + DOMAIN + '/'
|
||||
REFERER = ORIGIN + '/'
|
||||
|
||||
URL_API = 'https://' + DOMAIN
|
||||
URL_MAIN = URL_API + '/data/browse/?lang=%s&type=%s&order_by=%s&page=%s'
|
||||
URL_SEARCH = URL_API + '/data/browse/?lang=%s&order_by=%s&page=%s&limit=0'
|
||||
URL_THUMBNAIL = 'https://image.tmdb.org/t/p/w300%s'
|
||||
URL_WATCH = URL_API + '/data/watch/?_id=%s'
|
||||
|
||||
URL_GENRE = URL_API + '/data/browse/?lang=%s&type=%s&order_by=%s&genre=%s&page=%s'
|
||||
URL_CAST = URL_API + '/data/browse/?lang=%s&type=%s&order_by=%s&cast=%s&page=%s'
|
||||
URL_YEAR = URL_API + '/data/browse/?lang=%s&type=%s&order_by=%s&year=%s&page=%s'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
|
||||
def load():
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
# Änderung des Sprachcodes nach voreigestellter Sprache
|
||||
if sLanguage == '0': # prefLang Alle Sprachen
|
||||
sLang = 'all'
|
||||
if sLanguage == '1': # prefLang Deutsch
|
||||
sLang = '2'
|
||||
if sLanguage == '2': # prefLang Englisch
|
||||
sLang = '3'
|
||||
elif sLanguage == '3': # prefLang Japanisch
|
||||
sLang = cGui().showLanguage()
|
||||
return
|
||||
params.setParam('sLanguage', sLang)
|
||||
|
||||
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30502), SITE_IDENTIFIER, 'showMovieMenu'), params) # Movies
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30551), SITE_IDENTIFIER, 'showGenreMMenu'), params) # Movies Genre
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30511), SITE_IDENTIFIER, 'showSeriesMenu'), params) # Series
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30529), SITE_IDENTIFIER, 'showGenreSMenu'), params) # Series Genre
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30508), SITE_IDENTIFIER, 'showYearsMenu'), params) # Years
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30553), SITE_IDENTIFIER, 'showSearchActor'), params) # Cast
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _cleanTitle(sTitle):
|
||||
sTitle = re.sub("[\xE4]", 'ae', sTitle)
|
||||
sTitle = re.sub("[\xFC]", 'ue', sTitle)
|
||||
sTitle = re.sub("[\xF6]", 'oe', sTitle)
|
||||
sTitle = re.sub("[\xC4]", 'Ae', sTitle)
|
||||
sTitle = re.sub("[\xDC]", 'Ue', sTitle)
|
||||
sTitle = re.sub("[\xD6]", 'Oe', sTitle)
|
||||
sTitle = re.sub("[\x00-\x1F\x80-\xFF]", '', sTitle)
|
||||
return sTitle
|
||||
|
||||
|
||||
def _getQuality(sQuality):
|
||||
isMatch, aResult = cParser.parse(sQuality, '(HDCAM|HD|WEB|BLUERAY|BRRIP|DVD|TS|SD|CAM)', 1, True)
|
||||
if isMatch:
|
||||
return aResult[0]
|
||||
else:
|
||||
return sQuality
|
||||
|
||||
|
||||
def _showGenreMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
sType = params.getValue('sType')
|
||||
sMenu = params.getValue('sMenu')
|
||||
|
||||
if sLanguage == '2' or sLanguage == 'all':
|
||||
genres = {
|
||||
'Action': 'Action',
|
||||
'Abenteuer': 'Abenteuer',
|
||||
'Animation': 'Animation',
|
||||
'Biographie': 'Biographie',
|
||||
'Dokumentation': 'Dokumentation',
|
||||
'Drama': 'Drama',
|
||||
'Familie': 'Familie',
|
||||
'Fantasy': 'Fantasy',
|
||||
'Geschichte': 'Geschichte',
|
||||
'Horror': 'Horror',
|
||||
'Komödie': 'Komödie',
|
||||
'Krieg': 'Krieg',
|
||||
'Krimi': 'Krimi',
|
||||
'Musik': 'Musik',
|
||||
'Mystery': 'Mystery',
|
||||
'Romantik': 'Romantik',
|
||||
'Reality-TV': 'Reality-TV',
|
||||
'Sci-Fi': 'Sci-Fi',
|
||||
'Sports': 'Sport',
|
||||
'Thriller': 'Thriller',
|
||||
'Western': 'Western'
|
||||
}
|
||||
else:
|
||||
genres = {
|
||||
'Action': 'Action',
|
||||
'Adventure': 'Abenteuer',
|
||||
'Animation': 'Animation',
|
||||
'Biography': 'Biographie',
|
||||
'Comedy': 'Komödie',
|
||||
'Crime': 'Krimi',
|
||||
'Documentation': 'Dokumentation',
|
||||
'Drama': 'Drama',
|
||||
'Family': 'Familie',
|
||||
'Fantasy': 'Fantasy',
|
||||
'History': 'Geschichte',
|
||||
'Horror': 'Horror',
|
||||
'Music': 'Musik',
|
||||
'Mystery': 'Mystery',
|
||||
'Romance': 'Romantik',
|
||||
'Reality-TV': 'Reality-TV',
|
||||
'Sci-Fi': 'Sci-Fi',
|
||||
'Sports': 'Sport',
|
||||
'Thriller': 'Thriller',
|
||||
'War': 'Krieg',
|
||||
'Western': 'Western'
|
||||
}
|
||||
|
||||
for genre, searchGenre in genres.items():
|
||||
params.setParam('sUrl', URL_GENRE % (sLanguage, sType, sMenu, searchGenre, '1'))
|
||||
cGui().addFolder(cGuiElement(genre, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showMovieMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'Trending', '1')) ### Trending Filme trending1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30521), SITE_IDENTIFIER, 'showEntries'), params) ### Trending Filme trending1
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'new', '1')) ### neue filme neu1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30541), SITE_IDENTIFIER, 'showEntries'), params) ### neue filme neu1
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'views', '1')) ### Views filme views1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30552), SITE_IDENTIFIER, 'showEntries'), params) ### Views filme views1
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'rating', '1')) ### Rating Filme rating1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30510), SITE_IDENTIFIER, 'showEntries'), params) ### Rating Filme rating1
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'votes', '1')) ### votes filme votes 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30536), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'updates', '1')) ### updates filme updates 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30533), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'name', '1')) ### name filme
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'featured', '1')) ### featured filme features1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30530), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'requested', '1')) ### requested filme
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30534), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'movies', 'releases', '1')) ### releases filme
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30531), SITE_IDENTIFIER, 'showEntries'), params) ### Filme releases 1
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showGenreMMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
|
||||
params.setParam('sType', 'movies')
|
||||
params.setParam('sMenu', 'Trending')
|
||||
cGui().addFolder(cGuiElement('Genre trending', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Neu')
|
||||
cGui().addFolder(cGuiElement('Genre new', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Views')
|
||||
cGui().addFolder(cGuiElement('Genre viewed', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Votes')
|
||||
cGui().addFolder(cGuiElement('Genre voted', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Updates')
|
||||
cGui().addFolder(cGuiElement('Genre updated', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Rating')
|
||||
cGui().addFolder(cGuiElement('Genre rated', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Name')
|
||||
cGui().addFolder(cGuiElement('Genre named', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'requested')
|
||||
cGui().addFolder(cGuiElement('Genre requested', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'featured')
|
||||
cGui().addFolder(cGuiElement('Genre featured', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'releases')
|
||||
cGui().addFolder(cGuiElement('Genre released', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
# Serienmenue
|
||||
def showSeriesMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'neu', '1')) ### serien neu 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30514), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'views', '1')) ### serien views 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30537), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'votes', '1')) ### serien votes 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30519), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'updates', '1')) ### serien updates 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30533), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'name', '1')) ### serien name 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'featured', '1')) ###
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30530), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'requested', '1')) ### serien requested
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30534), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'releases', '1')) ### serien releases 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30531), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'rating', '1')) ### serien rating 1
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30535), SITE_IDENTIFIER, 'showEntries'), params) ###
|
||||
|
||||
#params.setParam('sUrl', URL_MAIN % (sLanguage, 'tvseries', 'Jahr', '1')) # ##
|
||||
#cGui().addFolder(cGuiElement('Jahr', SITE_IDENTIFIER, 'showEntries'), params) ##
|
||||
|
||||
#params.setParam('sCont', 'Jahr') #
|
||||
#cGui().addFolder(cGuiElement('Jahr', SITE_IDENTIFIER, 'showValue'), params), params) #
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
# show genre serien menue
|
||||
def showGenreSMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
|
||||
params.setParam('sType', 'tvseries')
|
||||
params.setParam('sMenu', 'Trending')
|
||||
cGui().addFolder(cGuiElement('Series genre trending', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Neu')
|
||||
cGui().addFolder(cGuiElement('Series genre new', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Views')
|
||||
cGui().addFolder(cGuiElement('Series genre viewed', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Votes')
|
||||
cGui().addFolder(cGuiElement('Series genre voted', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Updates')
|
||||
cGui().addFolder(cGuiElement('Series genre updated', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Rating')
|
||||
cGui().addFolder(cGuiElement('Series genre rated', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'Name')
|
||||
cGui().addFolder(cGuiElement('Series genre named', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'requested')
|
||||
cGui().addFolder(cGuiElement('Series genre requested', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'featured')
|
||||
cGui().addFolder(cGuiElement('Series genre featured', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
params.setParam('sMenu', 'releases')
|
||||
cGui().addFolder(cGuiElement('Series genre releases', SITE_IDENTIFIER, '_showGenreMenu'), params)
|
||||
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showYearsMenu():
|
||||
params = ParameterHandler()
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
|
||||
# Anfangs- und Endjahr für das menü eintragen
|
||||
start_jahr = 1931
|
||||
end_jahr = datetime.now().year
|
||||
|
||||
# show the current year first
|
||||
for jahr in range(end_jahr, start_jahr - 1, -1):
|
||||
params.setParam('sUrl', URL_YEAR % (sLanguage, 'movies', 'new', str(jahr), '1'))
|
||||
cGui().addFolder(cGuiElement(str(jahr), SITE_IDENTIFIER, 'showEntries'), params)
|
||||
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
isTvshow = False
|
||||
sThumbnail = ''
|
||||
sLanguage = params.getValue('sLanguage')
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
try:
|
||||
oRequest = cRequestHandler(entryUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # HTML Cache Zeit 6 Stunden
|
||||
oRequest.addHeaderEntry('Referer', REFERER)
|
||||
oRequest.addHeaderEntry('Origin', ORIGIN)
|
||||
sJson = oRequest.request()
|
||||
aJson = loads(sJson)
|
||||
except:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
if 'movies' not in aJson or not isinstance(aJson.get('movies'), list) or len(aJson['movies']) == 0:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = 0
|
||||
# ignore movies which does not contain any streams
|
||||
for movie in aJson['movies']:
|
||||
if '_id' in movie:
|
||||
total += 1
|
||||
for movie in aJson['movies']:
|
||||
if not '_id' in movie:
|
||||
continue
|
||||
sTitle = str(movie['title'])
|
||||
if sSearchText and not cParser.search(sSearchText, sTitle):
|
||||
continue
|
||||
if 'Staffel' in sTitle or 'Season' in sTitle:
|
||||
isTvshow = True
|
||||
oGuiElement = cGuiElement(sTitle, SITE_IDENTIFIER, 'showEpisodes' if isTvshow else 'showHosters')
|
||||
if 'poster_path_season' in movie and movie['poster_path_season']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['poster_path_season'])
|
||||
elif 'poster_path' in movie and movie['poster_path']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['poster_path'])
|
||||
elif 'backdrop_path' in movie and movie['backdrop_path']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['backdrop_path'])
|
||||
if sThumbnail:
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if 'storyline' in movie:
|
||||
oGuiElement.setDescription(str(movie['storyline']))
|
||||
elif 'overview' in movie:
|
||||
oGuiElement.setDescription(str(movie['overview']))
|
||||
if 'year' in movie and len(str(movie['year'])) == 4:
|
||||
oGuiElement.setYear(movie['year'])
|
||||
if 'quality' in movie:
|
||||
oGuiElement.setQuality(_getQuality(movie['quality']))
|
||||
if 'rating' in movie:
|
||||
oGuiElement.addItemValue('rating', movie['rating'])
|
||||
if 'lang' in movie:
|
||||
if (sLanguage != '1' and movie['lang'] == 2): # Deutsch
|
||||
oGuiElement.setLanguage('DE')
|
||||
if (sLanguage != '2' and movie['lang'] == 3): # Englisch
|
||||
oGuiElement.setLanguage('EN')
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
if 'runtime' in movie:
|
||||
isMatch, sRuntime = cParser.parseSingleResult(movie['runtime'], '\d+')
|
||||
if isMatch:
|
||||
oGuiElement.addItemValue('duration', sRuntime)
|
||||
params.setParam('entryUrl', URL_WATCH % str(movie['_id']))
|
||||
params.setParam('sName', sTitle)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
|
||||
if not sGui and not sSearchText:
|
||||
curPage = aJson['pager']['currentPage']
|
||||
if curPage < aJson['pager']['totalPages']:
|
||||
sNextUrl = entryUrl.replace('page=' + str(curPage), 'page=' + str(curPage + 1))
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
aEpisodes = []
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue("sThumbnail")
|
||||
try:
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 4 # HTML Cache Zeit 4 Stunden
|
||||
oRequest.addHeaderEntry('Referer', REFERER)
|
||||
oRequest.addHeaderEntry('Origin', ORIGIN)
|
||||
sJson = oRequest.request()
|
||||
aJson = loads(sJson)
|
||||
except:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
if 'streams' not in aJson or len(aJson['streams']) == 0:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for stream in aJson['streams']:
|
||||
if 'e' in stream:
|
||||
aEpisodes.append(int(stream['e']))
|
||||
if aEpisodes:
|
||||
aEpisodesSorted = set(aEpisodes)
|
||||
total = len(aEpisodesSorted)
|
||||
for sEpisode in aEpisodesSorted:
|
||||
oGuiElement = cGuiElement('Episode ' + str(sEpisode), SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if 's' in aJson:
|
||||
oGuiElement.setSeason(aJson['s'])
|
||||
oGuiElement.setTVShowTitle('Episode ' + str(sEpisode))
|
||||
oGuiElement.setEpisode(sEpisode)
|
||||
oGuiElement.setMediaType('episode')
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
cGui().setView('episodes')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sEpisode = params.getValue('episode')
|
||||
try:
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 8 # HTML Cache Zeit 8 Stunden
|
||||
oRequest.addHeaderEntry('Referer', REFERER)
|
||||
oRequest.addHeaderEntry('Origin', ORIGIN)
|
||||
sJson = oRequest.request()
|
||||
except:
|
||||
return hosters
|
||||
if sJson:
|
||||
aJson = loads(sJson)
|
||||
if 'streams' in aJson:
|
||||
i = 0
|
||||
for stream in aJson['streams']:
|
||||
if (('e' not in stream) or (str(sEpisode) == str(stream['e']))):
|
||||
sHoster = str(i) + ':'
|
||||
isMatch, aName = cParser.parse(stream['stream'], '//([^/]+)/')
|
||||
if isMatch:
|
||||
# sName = cParser.urlparse(sUrl) ### angezeigter hostername api
|
||||
|
||||
sName = aName[0][:aName[0].rindex('.')]
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
sHoster = sHoster + ' ' + sName
|
||||
if 'release' in stream and str(stream['release']) != '':
|
||||
sHoster = sHoster + ' [I][' + _getQuality(stream['release']) + '][/I]'
|
||||
hoster = {'link': stream['stream'], 'name': sHoster}
|
||||
hosters.append(hoster)
|
||||
i += 1
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearchActor():
|
||||
sName = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30280))
|
||||
if not sName: return
|
||||
_searchActor(False, sName)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _searchActor(oGui, sName):
|
||||
params = ParameterHandler()
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if sLanguage == '0': # prefLang Alle Sprachen
|
||||
sLang = 'all'
|
||||
if sLanguage == '1': # prefLang Deutsch
|
||||
sLang = '2'
|
||||
if sLanguage == '2': # prefLang Englisch
|
||||
sLang = '3'
|
||||
showEntries(URL_CAST % (sLanguage, 'movies', 'new', cParser.quotePlus(sName), '1'), oGui)
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
SSsearch(oGui, sSearchText)
|
||||
|
||||
|
||||
def SSsearch(sGui=False, sSearchText=False):
|
||||
global apiJson
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
|
||||
# Falls die Daten noch nicht geladen wurden oder neu geladen werden sollen
|
||||
if apiJson is None or 'movies' not in apiJson:
|
||||
loadMoviesData()
|
||||
|
||||
if 'movies' not in apiJson or not isinstance(apiJson.get('movies'), list) or len(apiJson['movies']) == 0:
|
||||
oGui.showInfo()
|
||||
return
|
||||
|
||||
sst = sSearchText.lower()
|
||||
|
||||
if not sGui:
|
||||
dialog = xbmcgui.DialogProgress()
|
||||
dialog.create(cConfig().getLocalizedString(30122), cConfig().getLocalizedString(30123))
|
||||
|
||||
total = len(apiJson['movies'])
|
||||
position = 0
|
||||
for movie in apiJson['movies']:
|
||||
position += 1
|
||||
if not '_id' in movie:
|
||||
continue
|
||||
if not sGui and position % 128 == 0: # Update progress every 128 items
|
||||
if dialog.iscanceled(): break
|
||||
dialog.update(position, str(position) + cConfig().getLocalizedString(30128) + str(total))
|
||||
sTitle = movie['title']
|
||||
if 'Staffel' in sTitle or 'Season' in sTitle:
|
||||
isTvshow = True
|
||||
sSearch = sTitle.rsplit('-', 1)[0].replace(' ', '').lower()
|
||||
else:
|
||||
isTvshow = False
|
||||
sSearch = sTitle.lower()
|
||||
if not sst in sSearch and not cUtil.isSimilarByToken(sst, sSearch):
|
||||
continue
|
||||
#logger.info('-> [DEBUG]: %s' % str(movie))
|
||||
oGuiElement = cGuiElement(sTitle, SITE_IDENTIFIER, 'showEpisodes' if isTvshow else 'showHosters')
|
||||
sThumbnail = ''
|
||||
if 'poster_path_season' in movie and movie['poster_path_season']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['poster_path_season'])
|
||||
elif 'poster_path' in movie and movie['poster_path']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['poster_path'])
|
||||
elif 'backdrop_path' in movie and movie['backdrop_path']:
|
||||
sThumbnail = URL_THUMBNAIL % str(movie['backdrop_path'])
|
||||
if sThumbnail:
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if 'storyline' in movie:
|
||||
oGuiElement.setDescription(str(movie['storyline']))
|
||||
elif 'overview' in movie:
|
||||
oGuiElement.setDescription(str(movie['overview']))
|
||||
if 'year' in movie and len(str(movie['year'])) == 4:
|
||||
oGuiElement.setYear(movie['year'])
|
||||
if 'quality' in movie:
|
||||
oGuiElement.setQuality(_getQuality(movie['quality']))
|
||||
if 'rating' in movie:
|
||||
oGuiElement.addItemValue('rating', movie['rating'])
|
||||
if 'lang' in movie:
|
||||
if (sLanguage != '1' and movie['lang'] == 2): # Deutsch
|
||||
oGuiElement.setLanguage('DE')
|
||||
if (sLanguage != '2' and movie['lang'] == 3): # Englisch
|
||||
oGuiElement.setLanguage('EN')
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
if 'runtime' in movie:
|
||||
isMatch, sRuntime = cParser.parseSingleResult(movie['runtime'], '\d+')
|
||||
if isMatch:
|
||||
oGuiElement.addItemValue('duration', sRuntime)
|
||||
params.setParam('entryUrl', URL_WATCH % str(movie['_id']))
|
||||
params.setParam('sName', sTitle)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
if not sGui:
|
||||
dialog.close()
|
||||
|
||||
def loadMoviesData():
|
||||
global apiJson
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if sLanguage == '0': # prefLang Alle Sprachen
|
||||
sLang = 'all'
|
||||
if sLanguage == '1': # prefLang Deutsch
|
||||
sLang = '2'
|
||||
if sLanguage == '2': # prefLang Englisch
|
||||
sLang = '3'
|
||||
|
||||
try:
|
||||
oRequest = cRequestHandler(URL_SEARCH % (sLang, 'new', '1'), caching=True)
|
||||
oRequest.addHeaderEntry('Referer', REFERER)
|
||||
oRequest.addHeaderEntry('Origin', ORIGIN)
|
||||
oRequest.cacheTime = 60 * 60 * 48 # HTML Cache Zeit 2 Tage
|
||||
sJson = oRequest.request()
|
||||
apiJson = loads(sJson)
|
||||
logger.info('API-Daten erfolgreich geladen')
|
||||
except:
|
||||
logger.error('Fehler beim Laden der API-Daten')
|
||||
apiJson = {'movies': []}
|
||||
|
||||
|
||||
# Daten werden lazy beim ersten Zugriff geladen (siehe SSsearch)
|
||||
# loadMoviesData() - entfernt: beschleunigt den Import/Start erheblich
|
||||
465
plugin.video.xstream/sites/burningseries.py
Normal file
@@ -0,0 +1,465 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
# Always pay attention to the translations in the menu!
|
||||
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showValue: 24 Stunden
|
||||
# showAllSeries: 24 Stunden
|
||||
# showEpisodes: 24 Stunden
|
||||
# SSsearch: 24 Stunden
|
||||
|
||||
|
||||
import json
|
||||
import locale
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser, cUtil
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib.captcha.captcha_helper import solve_recaptcha, extract_recaptcha_sitekey
|
||||
|
||||
|
||||
SITE_IDENTIFIER = 'burningseries'
|
||||
SITE_NAME = 'BurningSeries'
|
||||
SITE_ICON = 'burningseries.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
if cConfig().getSetting('2captcha.pass') == '':
|
||||
cConfig().setSetting('plugin_burningseries', 'false')
|
||||
cConfig().setSetting('global_search_burningseries', 'false')
|
||||
cConfig().setSetting('plugin_burningseries_checkDomain', 'false')
|
||||
logger.info('-> [SitePlugin]: 2Captcha API Key not set')
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'bs.to') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_LOGIN = ''
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
REFERER = 'https://' + DOMAIN
|
||||
URL_SERIES = URL_MAIN + '/andere-serien'
|
||||
URL_NEW_SERIES = URL_MAIN + '/'
|
||||
URL_NEW_EPISODES = URL_MAIN + '/'
|
||||
URL_POPULAR = URL_MAIN + '/vorgeschlagene-serien'
|
||||
URL_ALPHABET = URL_MAIN + '/serie-alphabet'
|
||||
URL_GENRES = URL_MAIN + '/serie-genre'
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
params.setParam('sUrl', URL_NEW_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30514), SITE_IDENTIFIER, 'showNewSeries'), params) # New Series
|
||||
params.setParam('sUrl', URL_NEW_EPISODES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30516), SITE_IDENTIFIER, 'showNewEpisodes'), params) # New Episodes
|
||||
# params.setParam('sUrl', URL_POPULAR)
|
||||
# cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30519), SITE_IDENTIFIER, 'showEntries'), params) # Popular Series
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30518), SITE_IDENTIFIER, 'showAllSeries'), params)# All Series
|
||||
params.setParam('sUrl', URL_ALPHABET)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showValue'), params) # From A-Z
|
||||
params.setParam('sUrl', URL_GENRES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showValue'), params) # Genre
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showValue():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
|
||||
pattern = r'<div class="genre">\s*<span><strong>([^<]+)</strong></span>'
|
||||
isMatch, aGenre = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sName in aGenre:
|
||||
params.setParam('sGenre', sName.strip())
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
sGenre = params.getValue('sGenre')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
|
||||
genre_div_pattern = rf'<div class="genre">\s*<span><strong>{sGenre}</strong></span>\s*<ul>(.*?)</ul>'
|
||||
isMatchGenre, aResultGenre = cParser.parseSingleResult(sHtmlContent, genre_div_pattern)
|
||||
if not isMatchGenre:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
pattern = r'<a[^>]+href="(serie/[^"]+)"[^>]+title="([^"]+)"'
|
||||
isMatch, aResult = cParser.parse(aResultGenre, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
total = len(aResult)
|
||||
sst = sSearchText.lower() if sSearchText else ''
|
||||
for sUrl, sName in aResult:
|
||||
sNameLow = sName.lower()
|
||||
if sSearchText and not sst in sNameLow and not cUtil.isSimilarByToken(sst, sNameLow):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + '/' + sUrl)
|
||||
params.setParam('TVShowTitle', sName)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showAllSeries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
|
||||
#logger.info('BurningSeries: showAllSeries: entryUrl request done: %s, sSearchText: %s' % (entryUrl, sSearchText))
|
||||
|
||||
# pattern = '<a[^>]*href="(serie\\/[^"]*)"\\stitle="(.*?)"[^>]*>.*</a>' # Original Pattern funktioniert
|
||||
# Optimiertes Pattern: weniger Backtracking, keine unnötigen Gruppen, kein .* am Ende
|
||||
# Ursprünglich: pattern = '<a[^>]*href="(serie\/[^"]*)"[^>]*title="([^"]*)"'
|
||||
# Optimiert:
|
||||
pattern = r'<a[^>]+href="(serie/[^"]+)"[^>]+title="([^"]+)"'
|
||||
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
# Sort entries alphabetically by name (second tuple element)
|
||||
# A locale aware sorting is used to get better results for e.g. german umlauts
|
||||
locale.setlocale(locale.LC_COLLATE, '') # use system locale settings
|
||||
aResult = sorted(aResult, key=lambda x: locale.strxfrm(x[1].lower()))
|
||||
|
||||
total = len(aResult)
|
||||
sst = sSearchText.lower() if sSearchText else ''
|
||||
for sUrl, sName in aResult:
|
||||
sNameLow = sName.lower()
|
||||
if sSearchText and not sst in sNameLow and not cUtil.isSimilarByToken(sst, sNameLow):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + '/' + sUrl)
|
||||
params.setParam('TVShowTitle', sName)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showNewEpisodes(entryUrl=False, sGui=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl:
|
||||
entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
sectionPattern = r'<section[^>]*id="newest_episodes"[^>]*>.*?<ul[^>]*>(.*?)</ul>.*?</section>'
|
||||
isMatch, aResult = cParser.parseSingleResult(sHtmlContent, sectionPattern)
|
||||
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
isEpisodesMatch, aEpisodes = cParser.parse(aResult, r'<li[^>]*>\s*<a href="([^"]+)"[^>]*class="title"[^>]*>([^<]+)</a>\s*<div class="info">([^<]+)<i[^>]*title="([^"]+)"[^>]*></i></div>\s*</li>')
|
||||
|
||||
if not isEpisodesMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
total = len(aEpisodes)
|
||||
for sUrl, sName, sInfo, sLang in aEpisodes:
|
||||
sMovieTitle = sName + ' ' + sInfo + ' (' + sLang + ')'
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
oGuiElement.setTitle(sMovieTitle)
|
||||
params.setParam('sUrl', URL_MAIN + '/' + sUrl)
|
||||
params.setParam('TVShowTitle', sMovieTitle)
|
||||
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showNewSeries(entryUrl=False, sGui=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl:
|
||||
entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
|
||||
pattern = r'<section[^>]*id="newest_series"[^>]*>.*?<ul[^>]*>(.*?)</ul>.*?</section>'
|
||||
isMatch, aResult = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
series_pattern = r'<li><a href="([^"]+)">([^<]+)</a></li>'
|
||||
isSeriesMatch, aSeriesResult = cParser.parse(aResult, series_pattern)
|
||||
|
||||
if not isSeriesMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aSeriesResult)
|
||||
for sUrl, sName in aSeriesResult:
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
params.setParam('sUrl', URL_MAIN + '/' + sUrl)
|
||||
params.setParam('TVShowTitle', sName)
|
||||
oGui.addFolder(oGuiElement, params, True, total)
|
||||
if not sGui:
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showSeasons():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
sTVShowTitle = params.getValue('TVShowTitle')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<li class="s(\d+)(?:\s+active)?"><a href="([^"]+)">([^<]+)</a></li>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, r'<div id="sp_left">.*?<p>(.*?)</p>')
|
||||
isThumbnail, sThumbnail = cParser.parseSingleResult(sHtmlContent, r'<div id="sp_right"[^>]*>.*?<img[^>]*src="([^"]+)"')
|
||||
if isThumbnail and sThumbnail.startswith('/'):
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
|
||||
total = len(aResult)
|
||||
for sNr, sUrl, sName in aResult:
|
||||
isMovie = sNr.startswith('0')
|
||||
oGuiElement = cGuiElement('Staffel ' + sName, SITE_IDENTIFIER, 'showEpisodes')
|
||||
oGuiElement.setMediaType('season')
|
||||
if isThumbnail:
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
if not isMovie:
|
||||
oGuiElement.setTVShowTitle(sTVShowTitle)
|
||||
oGuiElement.setSeason(sNr)
|
||||
params.setParam('sSeason', sNr)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('sUrl', URL_MAIN + '/' + sUrl)
|
||||
cGui().addFolder(oGuiElement, params, True, total)
|
||||
cGui().setView('seasons')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('sUrl')
|
||||
sTVShowTitle = params.getValue('TVShowTitle')
|
||||
sSeason = params.getValue('sSeason')
|
||||
sThumbnail = params.getValue('sThumbnail')
|
||||
|
||||
if not sSeason:
|
||||
sSeason = '1'
|
||||
isMovieList = sUrl.endswith('filme')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 24 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<tr[^>]*>\s*<td><a href="([^"]+)" title="([^"]+)">(\d+)</a></td>\s*<td>.*?<a href="([^"]+)" title="([^"]+)">.*?</td>\s*<td>(.*?)</td>\s*</tr>'
|
||||
isMatch, sEpisodes = cParser.parse(sHtmlContent, pattern)
|
||||
|
||||
if not isMatch:
|
||||
logger.error('BurningSeries: showEpisodes: No episodes found for URL: %s' % sUrl)
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, r'<div id="sp_left">.*?<p>(.*?)</p>')
|
||||
total = len(sEpisodes)
|
||||
for eLink, eTitle, sNumber, eLink2, eTitle2, eHosterContent in sEpisodes:
|
||||
sName = sNumber + ' - ' + eTitle
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setMediaType('episode')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
if not isMovieList:
|
||||
oGuiElement.setSeason(sSeason)
|
||||
oGuiElement.setEpisode(int(sNumber))
|
||||
oGuiElement.setTVShowTitle(sTVShowTitle)
|
||||
params.setParam('sUrl', URL_MAIN + '/' + eLink2)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
params.setParam('eHosterContent', eHosterContent)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
cGui().setView('episodes')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('sUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
|
||||
hosterTabspattern = r'<ul class="hoster-tabs[^"]*"[^>]*>(.*?)</ul>'
|
||||
hosterPattern = r'<a[^>]*href="([^"]+)"[^>]*>(?:.*?<i[^>]*></i>)?([^<]+)</a>';
|
||||
#languagesPattern = 'itemprop="keywords".content=".*?Season...([^"]+).S.*?' # HD Kennzeichen
|
||||
|
||||
# TODO: Sprachauswahl
|
||||
|
||||
# data-lang-key="1" Deutsch
|
||||
# data-lang-key="2" Englisch
|
||||
# data-lang-key="3" Englisch mit deutschen Untertitel
|
||||
|
||||
isMatchHosterTabs, rHosterTabs = cParser.parseSingleResult(sHtmlContent, hosterTabspattern)
|
||||
if not isMatchHosterTabs:
|
||||
cGui().showInfo()
|
||||
return
|
||||
# isMatchLang, aResult2 = cParser.parseSingleResult(sHtmlContent, languagesPattern)
|
||||
isMatch, aResult = cParser.parse(rHosterTabs, hosterPattern)
|
||||
sLang = '(DE)'
|
||||
sQuality = '720'
|
||||
if isMatch:
|
||||
for sUrl, sName in aResult:
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
# sLanguage = cConfig().getSetting('prefLanguage')
|
||||
sName = sName.strip()
|
||||
|
||||
hoster = {'link': [sUrl, sName], 'name': sName, 'displayedName': '%s [I]%s [%sp][/I]' % (sName, sLang, sQuality), 'quality': sQuality, 'languageCode': sLang} # Language Code für hoster.py Sprache Prio
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
if not hosters:
|
||||
cGui().showLanguage()
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(hUrl):
|
||||
if type(hUrl) == str: hUrl = eval(hUrl)
|
||||
|
||||
Request = cRequestHandler(URL_MAIN + '/' + hUrl[0], caching=False)
|
||||
Request.addHeaderEntry('Referer', ParameterHandler().getValue('entryUrl'))
|
||||
Request.addHeaderEntry('Upgrade-Insecure-Requests', '1')
|
||||
htmlContent = Request.request()
|
||||
sitekey = extract_recaptcha_sitekey(htmlContent)
|
||||
if not sitekey:
|
||||
logger.error('BurningSeries: getHosterUrl: No sitekey found in HTML content.')
|
||||
return [{'streamUrl': '', 'resolved': False}]
|
||||
|
||||
sUrl = Request.getRealUrl()
|
||||
|
||||
google_captcha_token = solve_recaptcha(sitekey, sUrl)
|
||||
if not google_captcha_token:
|
||||
logger.error('BurningSeries: getHosterUrl: Failed to solve captcha.')
|
||||
return [{'streamUrl': '', 'resolved': False}]
|
||||
|
||||
lIDMatch, lID = cParser.parseSingleResult(htmlContent, r'data-lid="([^"]+)"')
|
||||
securityTokenMatch, securityToken = cParser.parseSingleResult(htmlContent, r'security_token" content="([^"]+)"')
|
||||
|
||||
if not lIDMatch:
|
||||
logger.error('BurningSeries: getHosterUrl: No lID found in HTML content.')
|
||||
# return None?
|
||||
return [{'streamUrl': '', 'resolved': False}]
|
||||
|
||||
if not securityTokenMatch:
|
||||
logger.error('BurningSeries: getHosterUrl: No securityToken found in HTML content.')
|
||||
# return None?
|
||||
return [{'streamUrl': '', 'resolved': False}]
|
||||
|
||||
responseHeader = Request.getResponseHeader()
|
||||
if hasattr(responseHeader, 'get_all'):
|
||||
setCookieHeaders = responseHeader.get_all('Set-Cookie')
|
||||
elif hasattr(responseHeader, 'getheaders'):
|
||||
setCookieHeaders = responseHeader.getheaders('Set-Cookie')
|
||||
else:
|
||||
setCookieHeaders = []
|
||||
|
||||
cookie_string_parts = []
|
||||
|
||||
for header in setCookieHeaders:
|
||||
name_value = header.split(";", 1)[0].strip()
|
||||
if "=" in name_value:
|
||||
cookie_string_parts.append(name_value)
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'accept-language': 'de-DE,de;q=0.9',
|
||||
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'origin': URL_MAIN,
|
||||
'priority': 'u=1, i',
|
||||
'referer': sUrl,
|
||||
'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
}
|
||||
|
||||
cookies = {}
|
||||
for part in cookie_string_parts:
|
||||
if "=" in part:
|
||||
name, value = part.split("=", 1)
|
||||
cookies[name.strip()] = value.strip()
|
||||
|
||||
data = {
|
||||
'token': securityToken,
|
||||
'LID': lID,
|
||||
'ticket': google_captcha_token
|
||||
}
|
||||
|
||||
embedRequest = cRequestHandler(f'{URL_MAIN}/ajax/embed.php', caching=False, method='POST', data=data)
|
||||
for k, v in headers.items():
|
||||
embedRequest.addHeaderEntry(k, v)
|
||||
# Manually set cookies as header if needed
|
||||
if cookies:
|
||||
cookie_header = '; '.join([f"{k}={v}" for k, v in cookies.items()])
|
||||
embedRequest.addHeaderEntry('Cookie', cookie_header)
|
||||
response_text = embedRequest.request()
|
||||
|
||||
parsedJson = json.loads(response_text)
|
||||
if not parsedJson or 'link' not in parsedJson:
|
||||
logger.error('BurningSeries: getHosterUrl: No result from resolve request.')
|
||||
# return None?
|
||||
return [{'streamUrl': '', 'resolved': False}]
|
||||
|
||||
return [{'streamUrl': parsedJson['link'], 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showAllSeries(URL_SERIES, oGui, sSearchText)
|
||||
712
plugin.video.xstream/sites/dokus.py
Normal file
@@ -0,0 +1,712 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
# Multi Scraper für Dokumentationen
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib import youtube_fix
|
||||
|
||||
import sys, xbmcplugin, xbmc
|
||||
|
||||
#
|
||||
|
||||
SITE_IDENTIFIER = 'dokus'
|
||||
SITE_NAME = cConfig().getLocalizedString(30505)
|
||||
SITE_ICON = 'dokus.png'
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
cConfig().setSetting('global_search_' + SITE_IDENTIFIER, 'false')
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
#################### Hauptmenü ####################
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
# Abfrage ob Youtube installiert ist
|
||||
if cConfig().getSetting('plugin_' + SITE_IDENTIFIER) == 'true':
|
||||
if not xbmc.getCondVisibility('System.HasAddon(%s)' % 'plugin.video.youtube'):
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % 'plugin.video.youtube')
|
||||
# Menü für Dokus4.me
|
||||
logger.info('Load %s' % SITE_NAME_1)
|
||||
params.setParam('sUrl', URL_MAIN_1)
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_1 + ': [/B]' + cConfig().getLocalizedString(30505), SITE_IDENTIFIER, 'showEntries_1'), params) # Documentations
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_1 + ': [/B]' + cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showGenre_1'), params) # Genre
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_1 + ': [/B]' + cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch_1'), params) # Search
|
||||
# Menü für DokusStreams.de
|
||||
logger.info('Load %s' % SITE_NAME_2)
|
||||
params.setParam('sUrl', URL_MAIN_2)
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_2 + ': [/B]' + cConfig().getLocalizedString(30505), SITE_IDENTIFIER, 'showEntries_2'), params) # Documentations
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_2 + ': [/B]' + cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showGenre_2'), params) # Genre
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_2 + ': [/B]' + cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch_2'), params) # Search
|
||||
# Menü für Dokuh.de
|
||||
logger.info('Load %s' % SITE_NAME_3)
|
||||
params.setParam('sUrl', URL_MAIN_3)
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_3 + ': [/B]' + cConfig().getLocalizedString(30505), SITE_IDENTIFIER, 'showEntries_3'), params) # Documentations
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_3 + ': [/B]' + cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showGenre_3'), params) # Genre
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_3 + ': [/B]' + cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch_3'), params) # Search
|
||||
|
||||
# Menü für Videogold.de
|
||||
logger.info('Load %s' % SITE_NAME_6)
|
||||
params.setParam('sUrl', URL_MAIN_6)
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_6 + ': [/B]' + cConfig().getLocalizedString(30505), SITE_IDENTIFIER, 'showDoku_6'), params) # Doku
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_6 + ': [/B]' + cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showThemen_6'), params) # Themen
|
||||
cGui().addFolder(cGuiElement('[B]' + SITE_NAME_6 + ': [/B]' + cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch_6'), params) # Search
|
||||
cGui().addFolder(cGuiElement('[B]YouTube:[/B] Kanäle', SITE_IDENTIFIER, 'showYTChannels'), params)
|
||||
cGui().addFolder(cGuiElement('[B]YouTube:[/B] Genre', SITE_IDENTIFIER, 'showYTGenre'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
#################### Dokus4.me ####################
|
||||
|
||||
SITE_NAME_1 = 'Dokus4.me'
|
||||
SITE_ICON_1 = 'dokus4.png'
|
||||
URL_MAIN_1= 'http://www.dokus4.me/'
|
||||
URL_SEARCH_1 = URL_MAIN_1 + '?s=%s'
|
||||
|
||||
def showGenre_1():
|
||||
params = ParameterHandler()
|
||||
entryUrl = params.getValue('sUrl')
|
||||
sHtmlContent = cRequestHandler(entryUrl).request()
|
||||
pattern = 'cat-item.*?href="([^"]+)">([^<]+)</a>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
for sUrl, sName in aResult:
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_1'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showEntries_1(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
sHtmlContent = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False)).request()
|
||||
pattern = 'tbl_titel.*?title="([^"]+).*?href="([^"]+).*?src="([^"]+).*?vid_desc">([^<]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
total = len(aResult)
|
||||
for sName, sUrl, sThumbnail, sDesc in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters_1')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setDescription(sDesc)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
if not sGui:
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, 'rel="next" href="([^"]+)')
|
||||
if isMatchNextPage:
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries_1', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showHosters_1():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, 'src="([^"]+)" f')
|
||||
if isMatch:
|
||||
for sUrl in aResult:
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl_1')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl_1(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
def showSearch_1():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30289))
|
||||
if not sSearchText: return
|
||||
_search_1(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def _search_1(oGui, sSearchText):
|
||||
showEntries_1(URL_SEARCH_1 % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
#################### Dokustreams ####################
|
||||
|
||||
SITE_NAME_2 = 'Dokustreams.de'
|
||||
SITE_ICON_2 = 'Dokustreams.png'
|
||||
URL_MAIN_2= 'http://dokustreams.de/'
|
||||
URL_SEARCH_2 = URL_MAIN_2 + '?s=%s'
|
||||
|
||||
def showGenre_2():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN_2)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'Themen.*?<ul class="sub-menu">(.*?)</ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN_2 + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_2'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showEntries_2(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
isTvshow = True
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
iPage = int(params.getValue('page'))
|
||||
oRequest = cRequestHandler(entryUrl + 'page/' + str(iPage) if iPage > 0 else entryUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<article id="post-.*?href="([^"]+).*?<img.*?src="([^"]+).*?>([^<]+)</a></h2>.*?<p>([^<]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sThumbnail, sName, sDesc in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showEpisodes_2')
|
||||
oGuiElement.setMediaType('tvshow')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setDescription(sDesc)
|
||||
oGuiElement.setMediaType('season')
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries_2', params)
|
||||
oGui.setView('tvshows')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showEpisodes_2(sGui = False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
sUrl = params.getValue('entryUrl')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'yotu-videos.*?<ul>(.*?)</ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'data-videoid="([^"]+).*?data-title="([^"]+).*?src="([^"]+).*?')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
total = len(aResult)
|
||||
for sId, sName, sThumbnail in aResult:
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters_2')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setMediaType('episode')
|
||||
params.setParam('sId', sId)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
# Nächste Seite muss noch gebaut werden
|
||||
if not sGui:
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, 'rel="next" href="([^"]+)')
|
||||
if isMatchNextPage:
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEpisodes_2', params)
|
||||
cGui().setView('episodes')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showHosters_2():
|
||||
hosters = []
|
||||
sId = ParameterHandler().getValue('sId')
|
||||
sUrl = 'https://www.youtube.com/watch?v=' + sId
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl_2')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl_2(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
def showSearch_2():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30289))
|
||||
if not sSearchText: return
|
||||
_search_2(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def _search_2(oGui, sSearchText):
|
||||
showEntries_2(URL_SEARCH_2 % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
#################### Dokuh ####################
|
||||
|
||||
SITE_NAME_3 = 'Dokuh.de'
|
||||
SITE_ICON_3 = 'Dokuh.png'
|
||||
URL_MAIN_3= 'http://www.dokuh.de/'
|
||||
URL_SEARCH_3 = URL_MAIN_3 + '?s=%s'
|
||||
|
||||
def showGenre_3():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN_3)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '>Kategorien(.*?)</a></li></ul></li></ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN_3 + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_3'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showEntries_3(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
iPage = int(params.getValue('page'))
|
||||
oRequest = cRequestHandler(entryUrl + 'page/' + str(iPage) if iPage > 0 else entryUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'class="item-thumbnail">.*?href="([^"]+).*?title="([^"]+).*?src="([^"]+).*?'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sThumbnail in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters_3')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries_3', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showHosters_3():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, 'src="([^"]+)" f')
|
||||
if isMatch:
|
||||
for sUrl in aResult:
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl_3')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl_3(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
def showSearch_3():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30289))
|
||||
if not sSearchText: return
|
||||
_search_3(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def _search_3(oGui, sSearchText):
|
||||
showEntries_3(URL_SEARCH_3 % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
#################### VideoGold ####################
|
||||
|
||||
SITE_NAME_6 = 'VideoGold'
|
||||
SITE_ICON_6 = 'videogold.png'
|
||||
URL_MAIN_6= 'http://videogold.de/'
|
||||
URL_SEARCH_6 = URL_MAIN_6 + '?s=%s'
|
||||
|
||||
def showDoku_6():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN_6)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'Formate</a>(.*?)Themen'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN_6 + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_6'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showThemen_6():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN_6)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'Themen</a>(.*?)class="\swp-block-navigation-item\shas-child open-on-hover-click\swp-block-navigation-submenu">'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN_6 + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_6'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showEntries_6(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
iPage = int(params.getValue('page'))
|
||||
oRequest = cRequestHandler(entryUrl + 'seite/' + str(iPage) if iPage > 0 else entryUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<li\sclass="wp-block-post.*?href="([^"]+).*?alt="([^"]+).*?data-src="([^"]+).*?'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sThumbnail in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters_6')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setMediaType('movie')
|
||||
params.setParam('entryUrl', sUrl)
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries_6', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showHosters_6():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, r'<noscript><a\shref="([^"]+)')
|
||||
|
||||
if isMatch:
|
||||
for sUrl in aResult:
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl_6')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl_6(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
def showSearch_6():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30289))
|
||||
if not sSearchText: return
|
||||
_search_6(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def _search_6(oGui, sSearchText):
|
||||
showEntries_6(URL_SEARCH_6 % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
|
||||
#################### Youtube ####################
|
||||
|
||||
URL_MAIN = 'http://www.youtube.com'
|
||||
|
||||
def showYTChannels():
|
||||
youtube_fix.YT()
|
||||
try:
|
||||
params = ParameterHandler()
|
||||
apikey = cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
channelslists = {
|
||||
'Channel':[
|
||||
("ARTEde", "https://yt3.ggpht.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s240-c-k-c0x00ffffff-no-rj", "/channel/UCLLibJTCy3sXjHLVaDimnpQ"),
|
||||
("ARTE․tv Doku", "https://yt3.ggpht.com/FQMc44rW0CmIUKRCDjKcc0BiyyDyWobqb65gsaUbdG-jH3RYQakdYUMmBWusGnyFxlGvKEkK=s240-c-k-c0x00ffffff-no-rj", "/channel/UCr7BKJBRxT66pMGVpFQxzZw"),
|
||||
("BR24", "https://yt3.googleusercontent.com/3utKLXf5P7l9j_sTEuj6dqwSx3ff0pvflQNkuBiU9VHAT-XVz3OOw-SFcTctjFtSp5RSQo3aU7Q=s160-c-k-c0x00ffffff-no-rj", "/channel/UCcweJsCV2TUP_kzMX25U-oQ"),
|
||||
("De Doku Akte", "https://yt3.ggpht.com/ytc/AIdro_lpEa9xCRCqx5a5t8Bh1RB-Q0wzQ_jNgczmdQj3UgfNaw=s240-c-k-c0x00ffffff-no-rj", "/channel/UCaKgcPc5U_PiTSG-K3RKG3w"),
|
||||
("DOKU", "https://yt3.ggpht.com/8x5H67IovFv_P2CFKFSHbAzuAZv-tmtwpuV--29lC3tZMasxIYDUYDjvDt1SKVrrEBE7Xpg7SAY=s240-c-k-c0x00ffffff-no-rj", "/channel/UC4lYbU_FGJY95xSGaUF9PCA"),
|
||||
("Dokus und mehr", "https://yt3.ggpht.com/ytc/AIdro_mLaLdZfO84aZuqG1SppQ2eEnYmBysvKBu_IZFdGUo=s240-c-k-c0x00ffffff-no-rj", "/channel/UCue9a_27UYZUpx0azinesIw"),
|
||||
("Der Spiegel", "https://yt3.ggpht.com/ytc/AIdro_nQCxKfFs_hJhLzVDhXK_13EJjqFJNbSWM4JImydL02m68=s240-c-k-c0x00ffffff-no-rj", "/channel/UC1w6pNGiiLdZgyNpXUnA4Zw"),
|
||||
("DMAX", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj", "/channel/UCnytvQ-VexQ43pYx6KX-DPw"),
|
||||
("Galileo", "https://yt3.ggpht.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s240-c-k-c0x00ffffff-no-rj", "/channel/UC1XrG1M_hw8103zO2x-oivg"),
|
||||
("Mach dich schlau! - Mystery, Doku & Space", "https://yt3.ggpht.com/QybN6XyuPAOGMomZRpRKdfJR7Y_vumGuCHhzr3vodqmvvflO11pIaHd_qlEidRaYbRYjCRA5QQ=s240-c-k-c0x00ffffff-no-rj", "/channel/UCOidlT_g7LMXlLkiGK6aTdw"),
|
||||
("MDR DOK", "https://yt3.ggpht.com/ytc/AIdro_lYFPfGkjfEPgUxvMFD5ygFlRRoRX5X7_iS6JAHJgciIw=s240-c-k-c0x00ffffff-no-rj", "/channel/UCFxgCHRLXmaW3YUyDsikoSw"),
|
||||
("National Geographic Deutschland", "https://yt3.ggpht.com/ytc/AIdro_kU4mJluUbaOxrPEudLfQVR5jqy-Tg7jfEdXc1qLdnC78c=s240-c-k-c0x00ffffff-no-rj", "/channel/UCLZgflun26j9V9n73zA2T5g"),
|
||||
("NDR Doku", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj", "/channel/UCTPAHk1b-h-WGQn9cfGlw2Q"),
|
||||
("ntv Nachrichten", "https://yt3.googleusercontent.com/ytc/AIdro_mRTjZ8IflJpkRty2Q26wo7MA1gctT0jEz-iJqBRwD43bQ=s160-c-k-c0x00ffffff-no-rj", "/channel/UCSeil5V81-mEGB1-VNR7YEA"),
|
||||
("phoenix", "https://yt3.ggpht.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s240-c-k-c0x00ffffff-no-rj", "/channel/UCwyiPnNlT8UABRmGmU0T9jg"),
|
||||
("Real Wild Deutschland", "https://yt3.ggpht.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s240-c-k-c0x00ffffff-no-rj", "/channel/UCCf599vX34Bf8vT22RYJe0w"),
|
||||
("SRF Dok", "https://yt3.googleusercontent.com/EXf5J9hT7Nm_3hmF6jxfUMW0cFkmfvDxWb_-Sb-2O9_eG1lUsfrYtFBPWjTEmKRd3tfhM8U5Bw=s160-c-k-c0x00ffffff-no-rj", "/channel/UCdFkj0fA6VYJaty-v8_avvg"),
|
||||
("SWR Doku", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj", "/channel/UCK6jlnWA8t-XgUxwZJJHkQA"),
|
||||
("Terra X History", "https://yt3.ggpht.com/UrIj1GSSPUxtrlm2mbJC98ELpgOP0zeNjuYyB3ZAXCp4lScUes4-4PkqWESPSfAMC8eYC6mObA=s240-c-k-c0x00ffffff-no-rj", "/channel/UCA3mpqm67CpJ13YfA8qAnow"),
|
||||
("Welt der Wunder", "https://yt3.ggpht.com/ytc/AIdro_kvhxXUtVXav_qwl4bPJlsivUSCX0-oHX32rGUrs9B05Wo=s240-c-k-c0x00ffffff-no-rj", "/channel/UCBk6ZmyoyX1kLl-w17B0V1A"),
|
||||
("WDR Doku", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj", "/channel/UCUuab1dctZzN5ZmRmQnTzkg"),
|
||||
("XL Doku Deutschland", "https://yt3.googleusercontent.com/tCRuC1qvXA39Q7AeVQOK4x6n1dYtsIE_v8NVCkJqNcKkSJfuvsk2dQ0nLlILZT9Gz_x9Fqyw9A=s160-c-k-c0x00ffffff-no-rj", "/channel/UCYaNwUGngT8hljZRSZVTvUg"),
|
||||
("XL Geschichte", "https://yt3.googleusercontent.com/A2reg275ar2WKAHCosLxo-WZtUkoqwcr0BAXE5xB0SyU40_GsZYOPBefISvYky24uSJksyp4wPw=s160-c-k-c0x00ffffff-no-rj", "/channel/UCllLzS9TIgsD3E970AoSMnA"),
|
||||
("ZDFinfo Dokus & Reportagen", "https://yt3.ggpht.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s240-c-k-c0x00ffffff-no-rj", "/channel/UC7FeuS5wwfSR9IwOPkBV7SQ"),
|
||||
],}
|
||||
|
||||
for List in channelslists['Channel']:
|
||||
name = List[0]
|
||||
id = List[2]
|
||||
icon = List[1]
|
||||
if apikey == '' or apikey == None:
|
||||
sUrl="plugin://plugin.video.youtube" + id + "/?addon_id=plugin.video.xstream"
|
||||
else:
|
||||
sUrl="plugin://plugin.video.youtube" + id + "/"
|
||||
params.setParam('trumb', icon)
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(name,SITE_IDENTIFIER,''),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
except:return
|
||||
|
||||
def showYTGenre():
|
||||
youtube_fix.YT()
|
||||
params = ParameterHandler()
|
||||
channellist = [
|
||||
("Arbeit, Beruf & Leben", "Arbeit", "youtube.png"),
|
||||
("Autos, Technik & Wissen", "Autos", "youtube.png"),
|
||||
("Ernährung, Kochen & Essen", "Ernährung", "youtube.png"),
|
||||
("Geschichte, Kultur & Gesellschaft", "Geschichte", "youtube.png"),
|
||||
("Gesundheit, Medizin & Wissen", "Gesundheit", "youtube.png"),
|
||||
("Kriminalität, Sucht & Gewalt", "Kriminalität", "youtube.png"),
|
||||
("Musik, Klang & Rock", "Musik", "youtube.png"),
|
||||
("Natur, Tiere & die Erde", "Natur", "youtube.png"),
|
||||
("Wirtschaft, Finanzen & Politik", "Wirtschaft", "youtube.png"),
|
||||
("Wissenschaft, Universum & Ufos", "Wissenschaft", "youtube.png"),
|
||||
]
|
||||
|
||||
for name, id, icon in channellist:
|
||||
params.setParam('action', id)
|
||||
params.setParam('icon', icon)
|
||||
cGui().addFolder(cGuiElement(name, SITE_IDENTIFIER, 'showYTLists'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showYTLists():
|
||||
params = ParameterHandler()
|
||||
id = params.getValue('action')
|
||||
apikey = cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
|
||||
sublist = {
|
||||
'Arbeit': [
|
||||
("Architektur & Design | ARTE", "playlist/PLhGeNYH-50Kanjb7dddNt0ywYBX39NAm7", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("China | ARTE", "playlist/PLlQWnS27jXh94iIRrSqPzlfDOaWjdSfAU", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Helden der Baustelle | DMAX Motor", "playlist/PLu791Jb5lWoCOzceiS6SDeGW_u1s7x-0h", "https://yt3.googleusercontent.com/Nx3PWjIZtuLMfc5fye7PNIiZYEupPoEWxxjkxgzyPN-2-J-tbwQpr5ztqC9g-s-8435JDRvrUA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Kinder & Familie | WDR", "playlist/PLeVHoee00PXs9DuUuSohGq2GbnZZcnGJF", "https://yt3.googleusercontent.com/Nx3PWjIZtuLMfc5fye7PNIiZYEupPoEWxxjkxgzyPN-2-J-tbwQpr5ztqC9g-s-8435JDRvrUA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Anders Reisen & Arbeiten | WDR", "playlist/PLeVHoee00PXuKl5EIcyErW5ku-yyqhkNE", "https://yt3.googleusercontent.com/Nx3PWjIZtuLMfc5fye7PNIiZYEupPoEWxxjkxgzyPN-2-J-tbwQpr5ztqC9g-s-8435JDRvrUA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Außergewöhnlich leben | WDR", "playlist/PLeVHoee00PXulyH000ptJ21w-BoNEqFVL", "https://yt3.googleusercontent.com/Nx3PWjIZtuLMfc5fye7PNIiZYEupPoEWxxjkxgzyPN-2-J-tbwQpr5ztqC9g-s-8435JDRvrUA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Dokus und Reportagen | ZDFinfo", "playlist/PLo0xoJDmhYEacg7venaFlOYWsWlXu8_0_", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Deutschland 24/7 - Ohne uns läuft nichts! | DMAX", "playlist/PL-83LnmN43mZJ6ZmgzcvI1-HSmL6avP83", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("THW - Wahre Helden im Einsatz | NDR", "PLMJjvZqoYSrD4QZi6dDiuUj5AA4-aIJ6P", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Unternehmen | SWR", "playlist/PLF4F26D09B2C4FF18", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Galileo testet Berufe | ProSieben", "playlist/PLg_KHB2Fiu4eTR4SFQjDdhapk0yj4bD4I", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Schrecklich schöne Bausünden | ARTE", "playlist/PLhGeNYH-50KaS92AY2VQLKg81Q18DKaeM", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Autos': [
|
||||
("Auto Motor und Sport", "channel/UCLINPbYQ9sy6qc-TqtBeVnw", "https://yt3.googleusercontent.com/ytc/AIdro_k3FanN1l7rSijwXIXpP-hRkFk2CSYQi84W6xruiu23iCA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Auto Bild", "channel/UCJrXOOtvmGn4CF7aYaJwuHA", "https://yt3.googleusercontent.com/ytc/AIdro_nQEoz8_Bl0N9F1XX_Kp0ceWTJYzXhmkdYJdDO3HFYP7A=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Auto Zeitung", "channel/UCHwc6w57Q3S8CSHWergMJWg", "https://yt3.googleusercontent.com/ytc/AIdro_mqA44JCYCHj9Klaq8xwV4gwHufq5oT5Jr4r4hBy7Q8ODzJ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("JP Performance GmbH", "channel/UC1-VOKyTJrgLiBeiJqzeIUQ", "https://yt3.googleusercontent.com/VD-_UIQmAWviblV4ju2op5ksCiI405YMkhEqU3GoTYa3GRnE8aYLCz_N73DXDO8f2ochm7oJ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Limora Oldtimer", "channel/UCT-7FHrVJGFSniI8YhiCfIg", "https://yt3.googleusercontent.com/ytc/AIdro_kabpiOxSiXA2xNotYZP-FCYygBliD5NfqzodSaZ5kq_w=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Strassenklassiker", "channel/UCx7LHFyEJzIgQ96jhpPeV5g", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Max Carshop | DMAX", "playlist/PL-83LnmN43mbvMjyKvjqGqXiiRzl-llb5", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Speed Cops | DMAX", "playlist/PL-83LnmN43mZAGslLyLbkhtFPHAzom9Tg", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Steel Buddies | Staffel 8 | DMAX", "playlist/PL-83LnmN43mZCqnCtGp8RgOv3Iy2ZlPCd", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Steel Buddies | Staffel 11 | DMAX", "playlist/PL-83LnmN43mYQI7JHNCT3H0pI3MDUcWa3", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Cash für Chrom | Staffel 3 | DMAX", "playlist/PL-83LnmN43mYN4PyKYSQfwTJ03yVWWAnE", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Overhaulin - Aufgemotzt und Abgefahrn | DMAX", "playlist/PL-83LnmN43maV9n1M4VsjrbTuKOVyPnPj", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Fast N' Loud | DMAX", "playlist/PL-83LnmN43maCpqDqNfWQRp4-HkGOe0bG", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Ernährung': [
|
||||
("Kiki and Koko | @beagleskiko", "channel/UCMPw97JErllU6MQ8tnaMcLg", "https://yt3.googleusercontent.com/vvQyMcFuDLfn3sHdBh43u-YyvuU0421fXszR9ZvzYUyEafnWjcA7zEGtV7l28z9XLhRH5EIj=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("WELT Food", "channel/UCITYOgOaytxZWlBVK6q5apw", "https://yt3.googleusercontent.com/YjfuJ48M0oKjPkcc5jPxutzb_NI_Ty9qQMvBM0Q9E_599sDWQR73GFq1Iww2z49_en2l4IwJ3A=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Doku rund um's Essen, Kultur und Armut Weltweit | Best4ever", "playlist/PLrH_VmNS2hgXwe-o7F8amXWzoEOK6JM9U", "https://yt3.googleusercontent.com/ytc/AIdro_mKBLdCXVlFCdK9S52WXllejLSUPQsQfoE6ahZyvU4rJA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Brot & Stulle | NDR", "playlist/PLMJjvZqoYSrBYyf9kjKVQhyPO01-Gp92w", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Norddeutsche Imbiss-Kultur | NDR", "playlist/PLMJjvZqoYSrCZ1tbtR3GgX1cP-8vd-X7i", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Plätzchen-Werkstatt | SWR", "playlist/PLqcJ1tIeqh4jvH-9SpAkvXGnxZuROZhUr", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Koch ein! | SWR", "playlist/PLqcJ1tIeqh4h4qdELDWsuRIXeET6qSsJw", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Ernährung | SWR", "playlist/PLqcJ1tIeqh4jJqs305xUwKDDzeX13m7IB", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Lifestyle & Kochen | SWR", "playlist/PLqcJ1tIeqh4gldQL3sRGywM_1njJtUmMe", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Oma kocht am besten | SWR", "playlist/PLqcJ1tIeqh4hKc4oVoeky-N_-s7_O4lsk", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Galileo Burger | ProSieben", "playlist/PLg_KHB2Fiu4dreS90s4c4ZvGdJKDWTkdK", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Galileo FoodScan | ProSieben", "playlist/PLg_KHB2Fiu4ci4ewc2K9LYqhzsahTWIWo", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Galileo FoodScan | ProSieben", "playlist/PLg_KHB2Fiu4ci4ewc2K9LYqhzsahTWIWo", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Kulinarik | ARTE", "playlist/PLhGeNYH-50KbReiEQw8W-aBBRQMn5q0jG", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Fast and Good | ARTE", "playlist/PLhGeNYH-50KYvZj4C4RTX6Zb0SVGb38Ds", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Kochen und Rezepte | HR", "playlist/PLBoP5sAJK3ft5-1Gj9vPhx5Dr1MDspOxI", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Kochs anders | HR", "playlist/PLBoP5sAJK3fvTQ5ZIdZBQUxvD2bjlb6l6", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Einfach lecker | HR", "playlist/PLBoP5sAJK3fsaqG0C-6ksCio0jokHInpe", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Geschichte': [
|
||||
("Kunst | ARTE", "playlist/PLhGeNYH-50KYd7Rw7hw25cvk57gB5RT-S", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Die Mythen der Wikinger | ARTE", "playlist/PLlQWnS27jXh-A2EdHJ8nOpYq6_UdOpy-S", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Eine Geschichte des Antisemitismus | ARTE", "playlist/PLlQWnS27jXh8McGW6dctPIaZixmfa_-sP", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Der Amerikanische Bürgerkrieg | ARTE", "playlist/PLlQWnS27jXh9_Po60UOaxj0Cawlu4gbKY", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Skandale & Politik | WDR", "playlist/PLeVHoee00PXuSbteBjTrg9MTEXRP-Gq58", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Historisch und abgründig | ZDFinfo", "playlist/PLo0xoJDmhYEbGMoU6Y5Wy6pS9s7-3Ond3", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Andere Länder, andere Sitten | ZDFinfo", "playlist/PLo0xoJDmhYEaziLbtuOpgChskPjVIYpg-", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Umwelt und Nachhaltigkeit | ZDFinfo", "playlist/PLo0xoJDmhYEYzDcLGYFqK9gPpuMBWQHmi", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Geschichte und Europa | ZDFinfo", "playlist/PLo0xoJDmhYEbcQTzcQ3F9IikTpIHoTniR", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Jugoslawienkriege | ZDFinfo", "playlist/PLo0xoJDmhYEYXd48Cks4zyeaq48GVgiGP", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Unsere Geschichte | NDR", "playlist/PLMJjvZqoYSrBSUNUjlJcqjevsWB3zgCoa", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Griechenland: Von den Gipfeln bis ans Meer | WELT", "playlist/PLslDofkqdKI96RKNyrSlsa3s_xDhIoEWJ", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Gesellschaft | SWR", "playlist/PLqcJ1tIeqh4g_M3O2KSXOT91owaYyfb34", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Hitler privat | SPIEGEL TV", "playlist/PLuiYhcgFTmqCjQ7eYtpbUWf1oJtLujmHq", "https://yt3.googleusercontent.com/ytc/AIdro_nQCxKfFs_hJhLzVDhXK_13EJjqFJNbSWM4JImydL02m68=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Der Zweite Weltkrieg | SPIEGEL TV", "playlist/PLuiYhcgFTmqAi8SWk6p1iC4zESvcO0mDt", "https://yt3.googleusercontent.com/ytc/AIdro_nQCxKfFs_hJhLzVDhXK_13EJjqFJNbSWM4JImydL02m68=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("30 Jahre Mauerfall | SPIEGEL TV", "playlist/PLuiYhcgFTmqDdxOuhl4yAJ7KCiXK3jYij", "https://yt3.googleusercontent.com/ytc/AIdro_nQCxKfFs_hJhLzVDhXK_13EJjqFJNbSWM4JImydL02m68=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Gesundheit': [
|
||||
("Krankheit & Gesundheit | WDR", "playlist/PLeVHoee00PXvtH9M66B9qcVOprg9eqPPj", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Liebe & Sex | WDR", "PLeVHoee00PXuR8r-W2f2f8j60MixKDlCw", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Gesundheit | SWR", "playlist/PLqcJ1tIeqh4g4c-CBS5997U4uFa2aCsZN", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Wissen | SWR", "playlist/PLqcJ1tIeqh4j21kT9PTEtdwXr0axLdNEF", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Gesundheit | ARTE", "playlist/PLlQWnS27jXh_S3iOhmygd8hqRBNLyrBAW", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Unser Baby | HR", "playlist/PLBoP5sAJK3fthq_2OFmxID4G_cfQ111cr", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Notfall Krankenhaus | HR", "playlist/PLBoP5sAJK3fv5fIS8gEjIa_22IBBvTKzu", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Kinderarzt Berwald | HR", "playlist/PLBoP5sAJK3ftkNwmI8fOxTZ1jjGsQDTeE", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Die Gesundmacher | HR", "playlist/PLBoP5sAJK3fsO8MIQBxCwI8e0a19lRb1n", "https://yt3.googleusercontent.com/AESs0cOwQhi4Zlxmye5TGtsdlR-le5I-VgdvirAiX97--C8wwFOHG-osatCZLfWuybEXeJB0sg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Kriminalität': [
|
||||
("Süchtig nach Dopamin | ARTE", "playlist/PLhGeNYH-50KaGfPTo2_sl_yxVByNEfLTO", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Welt im Terror | ARTE", "playlist/PLlQWnS27jXh-SJ3lGY0kUb02WduOmw_TQ", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Alles zum Thema Drogen | ARTE", "playlist/PLlQWnS27jXh9McmOSmrMFzshYO4WOWcMG", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Nachtstreife | SWR", "playlist/PLqFRvy_OBSDpCgH03yvVVu9lPCBA3RN9V", "https://yt3.ggpht.com/k05Upm6oh65EuJnNPvYnBJpaxBrj-RWNNBS0BS70yw5Az0JoPw6_MpfA96_yNUSv1ObQn5hX=s240-c-k-c0x00ffffff-no-rj"),
|
||||
("Crime & Justiz | WDR", "playlist/PLeVHoee00PXuiVpqioBWVuGpnFjwmK9EU", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Hinter Gittern | ZDFinfo", "playlist/PLo0xoJDmhYEZ0TuKyDavuZXwCJf5cLyjz", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("True Crime | ZDFinfo", "playlist/PLo0xoJDmhYEZU3al7C0if6-wDVUrLSSdM", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Tatort Internet | ZDFinfo", "playlist/PLo0xoJDmhYEYCjvayqOEQ1faH2tm9dOgr", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Gangs und Clans | ZDFinfo", "playlist/PLo0xoJDmhYEZUCp5UF3iV5J81o99gg0oT", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Polizei im Einsatz | ZDFinfo", "playlist/PLo0xoJDmhYEbQ_-tMI1Jext3BXQUep6sU", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Verbrechen und Mord | ZDFinfo", "playlist/PLo0xoJDmhYEYKrB5-czkUjEiCP-oNlttC", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Border Control: Schwedens Grenzschützer | DMAX", "playlist/PL-83LnmN43maC8SPvHyz5heHhlYsCBi-2", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Verbrechen & Justiz | NDR", "playlist/PLMJjvZqoYSrBum1QtGLeDwwR1nVIqHS-g", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Natur': [
|
||||
("Natur & Umwelt | WDR", "playlist/PLeVHoee00PXsPbMnu_R_d2n1AbEbBCyKi", "https://yt3.googleusercontent.com/ytc/AIdro_lvqTnZ71kiSPuVGl0MwB94uktdGVECDzVrVNiUj5Yl0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Die Natur hinter den Mythen | ARTE", "playlist/PLlQWnS27jXh81J4EexYTn1AjPQh7cpgNa", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Mächtige Winde | ARTE", "playlist/PLlQWnS27jXh_A3o1EVe9WNmPz1saMcmDP", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Unser Wasser - Faszinierende Wunderwelten | ARTE", "playlist/PLlQWnS27jXh-nOtc4iaDn58AxlWdMcznl", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Home Rescue - Wohnen in der Wildnis | DMAX", "playlist/PL-83LnmN43mZ3_asZCua7i7g9ILnutj6e", "https://yt3.googleusercontent.com/iW1EB5wkDE76d08Jy6yRO5JX_06EokRm85rBLovQOQ-jTrhuoCbxD_t7T9DuGvGTnGNhMwJS=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Geschichten am Wasser | NDR", "playlist/PLMJjvZqoYSrD8HWA0zVEJvTfr0J1STo2u", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Abenteuer Kanu-Tour | NDR", "playlist/PLMJjvZqoYSrD8HWA0zVEJvTfr0J1STo2u", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("#wetterextrem | NDR", "playlist/PLMJjvZqoYSrAPvLvHEZwzEaunnaFGGo6B", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("NaturNah | NDR", "playlist/PLMJjvZqoYSrDDPHqBwAanhHtA1D9BWkgI", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Hanseblick | NDR", "playlist/PLMJjvZqoYSrDqbkkjJkGuWuUmiz2YlYnv", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Nordseereport | NDR", "playlist/PLMJjvZqoYSrCgXhI_S9JT2DD5v7rmyCv3", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Ostseereport | NDR", "playlist/PLMJjvZqoYSrAs3QxoP7kYZ8LPJO2oR2Lm", "https://yt3.googleusercontent.com/ytc/AIdro_lCawKvOSn5yZPta-q7z-OH0jiOphq0cjhyPMst4dDsAa8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("LOST PLACES - Doku Serie | WELT", "playlist/PLslDofkqdKI_520q4xOI31tK63E-KyobL", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Wetter & Klima | WELT", "playlist/PLslDofkqdKI-8ensvM5eAoBHoeRuEQbVu", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Die schönsten Reiseziele der Welt I Real Wild Deutschland", "playlist/PLSwXKep2Ci4FuCAEOMCf5skJ-o781_ybh", "https://yt3.googleusercontent.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Tierdokumentationen | Real Wild Deutschland", "playlist/PLSwXKep2Ci4FFqhP83ZBT-Ajlj8iL4XgJ", "https://yt3.googleusercontent.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Geniale Natur: clever und erstaunlich | Real Wild Deutschland", "playlist/PLSwXKep2Ci4EIjsXmudXsxaJqbpofGDm9", "https://yt3.googleusercontent.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Naturschutz | Real Wild Deutschland", "playlist/PLSwXKep2Ci4Fz6i7XTBPmeN0vse6kmkVK", "https://yt3.googleusercontent.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Faszinierende Natur | Real Wild Deutschland", "playlist/PLSwXKep2Ci4Fc18-vym377ywEN-Y-zW54", "https://yt3.googleusercontent.com/KsrwAPzVj9CzRTXg_6F2BwNUjzS8HakOCpAZGxeAP6N1gMfXN3f1n5VbnrahL5JUx9EEIR5bmA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Tierisch Galileo | ProSieben", "playlist/PLg_KHB2Fiu4cp-ofOvFcdkqkXj8UZhNkb", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Musik': [
|
||||
("DJPaoloMontiOfficial", "channel/UCHKb9guNj_pRq2OQ1QWgSAg", "https://yt3.googleusercontent.com/umHjglKoKH-POESb1cXGsCCY3kdrsOrpicNn8D5QoY52KI5Lk7EETCtED7Ii_asWvhyMaE_cXrA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Mix Akták Retro Party", "channel/UCfzTaGgL5NT7BblA7eiyFYg", "https://yt3.googleusercontent.com/1XYO899bDtvgdg27NehA2_1lXFaHmaysiF7wU5XtuzjFIu7kzX_5CGIQs5X0GRz70eGUqzN1eMY=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Mr.Stephen - Hit Maker ", "channel/UC9TUDA88MhhBmc0IS78DBoA", "https://yt3.googleusercontent.com/k_FJkpPO7gIWSqJupHwr1hTiXAgjap2QRrdaJMtuR4oAgRS5_RNWAAKRvDs9Aa1f2l4WG_91Pw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("HBz", "channel/UCj6ljqIWs4Sfk3TBIn1xP6Q", "https://yt3.googleusercontent.com/ytc/AIdro_nj9Ll2_o0VuyJpbI7K3tChWr_SUypT9eH5BuK1ZyrBBV4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Moreno J Remixes", "channel/UCJqyF-E8VW75fQz61ftchzg", "https://yt3.googleusercontent.com/zKjmrFXhlzaXsL29KBa06TazK2PJZr5180AaKcdk9eSAxM_gJrzU_SV8EdegguTqJ4QztOKbew=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Mashup Mixes", "channel/UC_MRLN4C-I0sApga0TrH7Sw", "https://yt3.googleusercontent.com/ytc/AIdro_kp3Jwpa8TLTMbDEyq4Fbppg3jr18Y3dZ5Wc4FNi0T93g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("N&T Party", "channel/UCC9rwt1T2i4klATksN6prdQ", "https://yt3.googleusercontent.com/ytc/AIdro_nLWadIzMDdiXcTIR0lHk78Tr7UGdQ3gSvL-QRDBQSnYg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Best Remixes Channel", "channel/UCmpqeOzl0kdzEBpGni1kh1w", "https://yt3.googleusercontent.com/ytc/AIdro_lFw-qBchdHY68UifRatZ2rLYyFnfnwTSgtQna3WZZRAw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("ZILITIK", "channel/UCEMmEHUY8JLErkizCPuUk_w", "https://yt3.googleusercontent.com/w8EYJ2IH_ZqzpLS9wqt47VcuT3ENK_Q6W7d406AlY4d3UYZI3oafzAuNPKr6FEjs8ZuiOXi1bw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("DJ Happy Vibes", "channel/UC5LrfTmOrn-4R6tvltTOIRg", "https://yt3.googleusercontent.com/ytc/AIdro_mXiNbV6MyHIOOUeXbz4aIuR_2NTbUVcqvoB9JNU6UJIAg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Top Hits 80s - Ohrwürmer der 80er Jahre", "playlist/PLd5xnond3B5Q76AHn0yU7iuCU5j7b4DLi", "https://yt3.googleusercontent.com/AyjYEnhHw62pOnBagaJwDqEjHTSXMqYno6m1bOxnk7t60KaJybbzq8QV8IpCNtKOYNgaP2FHiCg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("90er Party Hits", "playlist/PLILCJF6YwPP-Uk6R539eCrqIUKaJOCyZ-", "https://yt3.googleusercontent.com/ytc/AIdro_m_q1gNRBvL1PCtss-fL81oeFCgx7m4q9iprHw52EhOdqg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Martin Davis", "channel/UCqcBhsHlUuwQaI-il7xlcYQ", "https://yt3.googleusercontent.com/ytc/AIdro_m_q1gNRBvL1PCtss-fL81oeFCgx7m4q9iprHw52EhOdqg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("2000er Hits Playlist", "playlist/PLJNXa5V1YTQT-Um4F8dVit4Uuu2Ag8NqG", "https://yt3.googleusercontent.com/U0EyubzYWFpfTGMwatogyyO-pOnjMs2AIsWIG7FTF4GlxZx4Q2Pjgcxwb97CMHkmk_easYC5JA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Trampsta", "channel/UCOlxGyNCuzG8xm5ogYM6OOQ", "https://yt3.googleusercontent.com/ytc/AIdro_kqguRLm3O5_6sPkiqLVWev2L6GLTnnXd6GZeCvyr2a9Rw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Die Prinzen", "channel/UCvkPFtR30EM6wXu5Dy07p-g", "https://yt3.googleusercontent.com/F7c2A2Vo_9IxQrCKYvbRQVS-e9gr00wcE1qU4SXet7owfI8NNtLZhUJlCs8ZU5FgiYRVVJgLgis=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("MaxRiven", "channel/UCb7G5XdA9I8sNxC-3E3maFQ", "https://yt3.googleusercontent.com/ytc/AIdro_nQIbgFExwRCDB0NnZl1IX-APNWkz4ugpdLg2C0LTACui8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Musik | ARTE", "playlist/PLhGeNYH-50KYyCOpar7RnUF3ycN0jIi7F", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Wirtschaft': [
|
||||
("Industrie und Wirtschaft – Dokus und Reportagen kompakt | ZDFinfo", "playlist/PLo0xoJDmhYEZYhZe1a0LcS_BMOoQZEZcB", "https://yt3.googleusercontent.com/ytc/AIdro_nPZhGyR2qcxE745O7Xa0O1jzqTtIxuOyLFQLnZ7R9Dx6c=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Der Tag: News, Politik, Panorama, Wirtschaft und Sport | WELT", "playlist/PLslDofkqdKI9iy5ns0XnD8f7lvoSnnTot", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("LTW 2024 Brandenburg | phoenix", "playlist/PLoeytWjTuSuqRGjrqNduz1WuLgCBe7PUg", "https://yt3.googleusercontent.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Bundestag 2024 | phoenix", "playlist/PLoeytWjTuSur7r-5DIpm4RE3clHBokEU5", "https://yt3.googleusercontent.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Europawahl 2024 | Rechtsruck in der EU? | phoenix", "playlist/PLoeytWjTuSurgOmUfemNWL2pI4O6npoK7", "https://yt3.googleusercontent.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("#Gamescom2022 | phoenix", "playlist/PLoeytWjTuSuowLmvkg4azapIw3r4M2icx", "https://yt3.googleusercontent.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("ntv Faktenzeichen | N-TV", "playlist/PLwneNHYIBCIQO-vQ_N_xdb9daLVGc5aBh", "https://yt3.googleusercontent.com/ytc/AIdro_mRTjZ8IflJpkRty2Q26wo7MA1gctT0jEz-iJqBRwD43bQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Weltwirtschaftsforum WEF | N-TV", "playlist/PLwneNHYIBCISLENJlZUKMfE4gMQnGO95Q", "https://yt3.googleusercontent.com/ytc/AIdro_mRTjZ8IflJpkRty2Q26wo7MA1gctT0jEz-iJqBRwD43bQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("ntv Wirtschaft | N-TV", "playlist/PLwneNHYIBCIQDX9Tkvy5-xeBOqOpiwR1h", "https://yt3.googleusercontent.com/ytc/AIdro_mRTjZ8IflJpkRty2Q26wo7MA1gctT0jEz-iJqBRwD43bQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("ntv Nachrichten | N-TV", "playlist/PLwneNHYIBCISaAk_rizKfxde9OjT40Xz1", "https://yt3.googleusercontent.com/ytc/AIdro_mRTjZ8IflJpkRty2Q26wo7MA1gctT0jEz-iJqBRwD43bQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Universum | phoenix", "playlist/PL2TzD1hFT-xlhOsrV865Tie5-mbMJ9h1P", "https://yt3.googleusercontent.com/Z8hNO57BolkhiNu-nWUuQ6h_WCwH8k11LBVEfBbjKtIabNMogzbFQ8Jjr0YS3Kr0B-7g6kk-Dw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
|
||||
'Wissenschaft': [
|
||||
("Das Universum entdecken | ARTE", "playlist/PLlQWnS27jXh9zChHrrOkFrpYBYIKLitu8", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("42 - Die Antwort auf fast alles | ARTE", "playlist/PLlQWnS27jXh_ofqIJZH7vdOkQ-p_w9gKw", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Wissenschaftsdokus | ARTE", "playlist/PLlQWnS27jXh9_eNjMwbuhNhlwEmjQRr-q", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Couchwissen | ARTE", "playlist/PLhGeNYH-50Ka-CHu6q-dytZJT4uRLKxY7", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Der Mensch, die Natur, das Abenteuer | ARTE", "playlist/PLlQWnS27jXh_WXZtlemAN3V0li9Gi57Cn", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Europa und das Weltall | ARTE", "playlist/PLlQWnS27jXh9o5zB_sBlecHpKkejRI8SI", "https://yt3.googleusercontent.com/ytc/AIdro_lK_Yng-tiFUXYY2ukKBmdtzqSazAH7iPHda9YgHX3JVZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("DOKUS: Raumfahrt | WELT", "playlist/PLslDofkqdKI_KNyed3YcuDhKcE9RxtfMt", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Expedition Sternenhimmel | WELT", "playlist/PLslDofkqdKI_VYfR6q5au7iLpdce84asE", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Mysterien und Paranormales | WELT", "playlist/PLslDofkqdKI87XYQ68rgbCOMSCWJPGgvs", "https://yt3.googleusercontent.com/warJ1-zqcnR1n0LMK6ONepLoYwFcQS9u-noc8bl0-Uk6Lfbd8vIuXPEgDk6bjs34vp8FZpM5yw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Aliens, Ufo, Geister, Monster, Horror und vieles mehr | Mach dich schlau!", "playlist/PL2TzD1hFT-xnsecdrlXpNyM3EkvAgUEcs", "https://yt3.googleusercontent.com/QybN6XyuPAOGMomZRpRKdfJR7Y_vumGuCHhzr3vodqmvvflO11pIaHd_qlEidRaYbRYjCRA5QQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Out of Place Artefakte | Mach dich schlau!", "playlist/PL2TzD1hFT-xnkM_8_KC7yrelq1C9_3aKk", "https://yt3.googleusercontent.com/QybN6XyuPAOGMomZRpRKdfJR7Y_vumGuCHhzr3vodqmvvflO11pIaHd_qlEidRaYbRYjCRA5QQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Universum | Mach dich schlau!", "playlist/PL2TzD1hFT-xlhOsrV865Tie5-mbMJ9h1P", "https://yt3.googleusercontent.com/QybN6XyuPAOGMomZRpRKdfJR7Y_vumGuCHhzr3vodqmvvflO11pIaHd_qlEidRaYbRYjCRA5QQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Unterhaltung | SPIEGEL TV", "playlist/PL084070CE355CB92A", "https://yt3.googleusercontent.com/ytc/AIdro_nQCxKfFs_hJhLzVDhXK_13EJjqFJNbSWM4JImydL02m68=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Science & Galileo | ProSieben", "playlist/PLg_KHB2Fiu4fc88rrCum8XMaB69WcRNUi", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("Galileo Wissenscountdown | ProSieben", "playlist/PLg_KHB2Fiu4eQGBwmccma9Tua-XVK_wEf", "https://yt3.googleusercontent.com/g_I4TuM3r7n7ukBZH-R4Tp4eKabz1OA_F6bOfuacGWDZky-MC8jbyywxLWbQlb3exNaBjE-ICL8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
],
|
||||
}
|
||||
for List in sublist[id]:
|
||||
name = List[0]
|
||||
id = List[1]
|
||||
icon = List[2]
|
||||
if apikey == '' or apikey == None:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/?addon_id=plugin.video.xstream"
|
||||
else:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/"
|
||||
params.setParam('trumb', icon)
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(name,SITE_IDENTIFIER,''),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
|
||||
250
plugin.video.xstream/sites/einschalten.py
Normal file
@@ -0,0 +1,250 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
|
||||
# Always pay attention to the translations in the menu!
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showEntries: 6 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
SITE_IDENTIFIER = 'einschalten'
|
||||
SITE_NAME = 'Einschalten'
|
||||
SITE_ICON = 'einschalten.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'einschalten.in') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
URL_NEW_MOVIES = URL_MAIN + '/movies/new'
|
||||
URL_LAST_MOVIES = URL_MAIN + '/movies/recently-added'
|
||||
URL_COLLECTIONS = URL_MAIN + '/collections'
|
||||
URL_SEARCH = URL_MAIN + '/search?query=%s'
|
||||
URL_THUMBNAIL = URL_MAIN + '/api/image/poster'
|
||||
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
params.setParam('sUrl', URL_NEW_MOVIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30541), SITE_IDENTIFIER, 'showEntries'), params) # New Movies
|
||||
params.setParam('sUrl', URL_LAST_MOVIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30549), SITE_IDENTIFIER, 'showEntriesLast'), params) # Recently added movies
|
||||
params.setParam('sUrl', URL_COLLECTIONS)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30543), SITE_IDENTIFIER, 'showCollections'), params) # Collections
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
iPage = int(params.getValue('page'))
|
||||
|
||||
# Check if URL already has parameters to decide between ? and &
|
||||
separator = '&' if '?' in entryUrl else '?'
|
||||
sUrl = entryUrl + separator + 'page=' + str(iPage) if iPage > 0 else entryUrl
|
||||
|
||||
oRequest = cRequestHandler(sUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '{"id":([^,"]+).*?title":"([^"]+).*?Date":"([^-]+).*?"posterPath":"([^"]+).*?collectionId":([^}]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sYear, sThumbnail, sDummy in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setYear(sYear)
|
||||
oGuiElement.setThumbnail(URL_THUMBNAIL + sThumbnail)
|
||||
oGuiElement.setMediaType('movie')
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showCollections(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
iPage = int(params.getValue('page'))
|
||||
|
||||
separator = '&' if '?' in entryUrl else '?'
|
||||
sUrl = entryUrl + separator + 'page=' + str(iPage) if iPage > 0 else entryUrl
|
||||
|
||||
oRequest = cRequestHandler(sUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '{"id":([^,"]+).*?name":"([^"]+).*?"posterPath":"([^"]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sThumbnail in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER,'showCollectionEntries')
|
||||
oGuiElement.setThumbnail(URL_THUMBNAIL + sThumbnail)
|
||||
oGuiElement.setMediaType('movie')
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, total)
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showCollections', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showCollectionEntries(sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
entryUrl = URL_COLLECTIONS + '/' + params.getValue('entryUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '{"id":([^,"]+).*?title":"([^"]+).*?Date":"([^-]+).*?"posterPath":"([^"]+).*?collectionId":([^}]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sYear, sThumbnail, sDummy in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setYear(sYear)
|
||||
oGuiElement.setThumbnail(URL_THUMBNAIL + sThumbnail)
|
||||
oGuiElement.setMediaType('movie')
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
if not sGui and not sSearchText:
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntriesLast(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
iPage = int(params.getValue('page'))
|
||||
|
||||
separator = '&' if '?' in entryUrl else '?'
|
||||
sUrl = entryUrl + separator + 'page=' + str(iPage) if iPage > 0 else entryUrl
|
||||
|
||||
oRequest = cRequestHandler(sUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '{"id":([^,"]+).*?title":"([^"]+).*?Date":"([^-]+).*?"posterPath":"([^"]+).*?collectionId":([^}]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sYear, sThumbnail, sDummy in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setYear(sYear)
|
||||
oGuiElement.setThumbnail(URL_THUMBNAIL + sThumbnail)
|
||||
oGuiElement.setMediaType('movie')
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntriesLast', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
params = ParameterHandler()
|
||||
entryUrl = params.getValue('entryUrl')
|
||||
hosters = []
|
||||
sUrl = URL_MAIN + '/api/movies/' + entryUrl + '/watch'
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
pattern = 'streamUrl":"([^"]+)'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch: return
|
||||
sQuality = '720'
|
||||
for sUrl in aResult:
|
||||
sName = cParser.urlparse(sUrl)
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I][%sp][/I]' % (sName, sQuality), 'quality': sQuality}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30287))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showEntries(URL_SEARCH % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
299
plugin.video.xstream/sites/filmpalast.py
Normal file
@@ -0,0 +1,299 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
|
||||
# Always pay attention to the translations in the menu!
|
||||
# Sprachauswahl für Filme
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showValue: 24 Stunden
|
||||
# showEntries: 6 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
|
||||
import re
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
|
||||
SITE_IDENTIFIER = 'filmpalast'
|
||||
SITE_NAME = 'FilmPalast'
|
||||
SITE_ICON = 'filmpalast.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'filmpalast.to') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
# URL_MAIN = 'https://filmpalast.to'
|
||||
URL_MOVIES = URL_MAIN + '/movies/%s'
|
||||
URL_ENGLISH = URL_MAIN + '/search/genre/Englisch'
|
||||
URL_SEARCH = URL_MAIN + '/search/title/%s'
|
||||
URL_SERIES = URL_MAIN + '/serien/view'
|
||||
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
|
||||
# Logic moved from showMovieMenu to main load function
|
||||
if sLanguage == '0' or '1': # Alle Sprachen oder Deutsch
|
||||
params.setParam('sUrl', URL_MOVIES % 'new')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30502), SITE_IDENTIFIER, 'showEntries'), params) # Filme
|
||||
params.setParam('sUrl', URL_MOVIES % 'top')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30509), SITE_IDENTIFIER, 'showEntries'), params) # Top movies
|
||||
params.setParam('sUrl', URL_MOVIES % 'imdb')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30510), SITE_IDENTIFIER, 'showEntries'), params) # IMDB rating
|
||||
if sLanguage == '0': # Nur bei Alle Sprachen
|
||||
params.setParam('sUrl', URL_ENGLISH)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30104), SITE_IDENTIFIER, 'showEntries'), params) # English
|
||||
params.setParam('sUrl', URL_MOVIES % 'new')
|
||||
params.setParam('value', 'genre')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showValue'), params) # Genre
|
||||
params.setParam('value', 'movietitle')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showValue'), params) # From A-Z
|
||||
|
||||
if sLanguage == '2': # English
|
||||
params.setParam('sUrl', URL_ENGLISH)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30104), SITE_IDENTIFIER, 'showEntries'), params) # English
|
||||
|
||||
elif sLanguage == '3': # Japanisch
|
||||
cGui().showLanguage()
|
||||
|
||||
# Add Serien entry above search
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30511), SITE_IDENTIFIER, 'showSeriesMenu')) # Serien
|
||||
|
||||
# Search added at the bottom
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showValue():
|
||||
params = ParameterHandler()
|
||||
value = params.getValue("value")
|
||||
oRequest = cRequestHandler(params.getValue('sUrl'), bypass_dns=True)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 24 # HTML Cache Zeit 1 Tag
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<section[^>]id="%s">(.*?)</section>' % value # Suche in der Section Einträge
|
||||
isMatch, sContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sContainer, 'href="([^"]+)">([^<]+)')
|
||||
aResult = sorted(aResult, key=lambda x: x[1].lower()) # Sort alphabetically by name (case-insensitive)
|
||||
for sUrl, sName in aResult:
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
isTvshow = False
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False), bypass_dns=True)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<article[^>]*>\s*<a href="([^"]+)" title="([^"]+)">\s*<img src=["\']([^"\']+)["\'][^>]*>(.*?)</article>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
pattern = r'<a[^>]*href="([^"]*)"[^>]*title="([^"]*)"[^>]*>[^<]*<img[^>]*src=["\']([^"\']*)["\'][^>]*>\s*</a>(\s*)</article>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
seen_tv_shows = set()
|
||||
|
||||
# SORTING CHANGE: Removed the check for '/movies/new'
|
||||
# Now everything is sorted alphabetically
|
||||
aResult = sorted(aResult, key=lambda x: x[1].lower()) # Sort alphabetically by name (case-insensitive)
|
||||
|
||||
for sUrl, sName, sThumbnail, sDummy in aResult:
|
||||
isTvshow, _ = cParser.parse(sName, r'S\d\dE\d\d')
|
||||
# seriesname should not be crippled here!
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
|
||||
if isTvshow:
|
||||
# Clean the name (e.g. "ABCDE S01E02" -> "ABCDE")
|
||||
cleanNameMatch = re.search(r'(.*?)\s*S\d+E\d+', sName, re.IGNORECASE)
|
||||
if cleanNameMatch:
|
||||
cleanName = cleanNameMatch.group(1).strip()
|
||||
# If we have already seen this show in this loop, skip it
|
||||
if cleanName in seen_tv_shows:
|
||||
continue
|
||||
# Mark as seen and update the display name to the clean title
|
||||
seen_tv_shows.add(cleanName)
|
||||
sName = cleanName
|
||||
|
||||
if sThumbnail.startswith('/'):
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
### ÄNDERUNG ANFANG ###
|
||||
isYear, sYear = cParser.parseSingleResult(sDummy, r'Jahr:[^>]([\d]+)')
|
||||
isDuration, sDuration = cParser.parseSingleResult(sDummy, r'(?:Laufzeit|Spielzeit):[^>]([\d]+)')
|
||||
isRating, sRating = cParser.parseSingleResult(sDummy, 'Imdb:[^>]([^/]+)')
|
||||
### ÄNDERUNG ENDE ###
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons' if isTvshow else 'showHosters')
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isYear:
|
||||
oGuiElement.setYear(sYear)
|
||||
if isDuration:
|
||||
oGuiElement.addItemValue('duration', sDuration)
|
||||
if isRating:
|
||||
oGuiElement.addItemValue('rating', sRating.replace(',', '.'))
|
||||
# Parameter übergeben
|
||||
if sUrl.startswith('//'):
|
||||
params.setParam('entryUrl', 'https:' + sUrl)
|
||||
else:
|
||||
params.setParam('entryUrl', sUrl)
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
if not sGui and not sSearchText:
|
||||
pattern = r'<a class="pageing[^"]*"\s*href=([^>]+)>[^\+]+\+</a>\s*</div>'
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatchNextPage:
|
||||
sNextUrl = sNextUrl.replace("'", "").replace('"', '')
|
||||
if sNextUrl.startswith('/'):
|
||||
sNextUrl = URL_MAIN + sNextUrl
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showSeasons():
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue("sThumbnail")
|
||||
sName = params.getValue('sName')
|
||||
oRequest = cRequestHandler(sUrl, bypass_dns=True)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # HTML Cache Zeit 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<a[^>]*class="staffTab"[^>]*data-sid="(\d+)"[^>]*>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, '"description">([^<]+)')
|
||||
total = len(aResult)
|
||||
for sSeason in aResult:
|
||||
oGuiElement = cGuiElement('Staffel ' + str(sSeason), SITE_IDENTIFIER, 'showEpisodes')
|
||||
oGuiElement.setTVShowTitle(sName)
|
||||
oGuiElement.setSeason(sSeason)
|
||||
oGuiElement.setMediaType('season')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
cGui().addFolder(oGuiElement, params, True, total)
|
||||
cGui().setView('seasons')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue("sThumbnail")
|
||||
sSeason = params.getValue('season')
|
||||
sShowName = params.getValue('TVShowTitle')
|
||||
oRequest = cRequestHandler(sUrl, bypass_dns=True)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 4 # HTML Cache Zeit 4 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = r'<div[^>]*class="staffelWrapperLoop[^"]*"[^>]*data-sid="%s">(.*?)</ul></div>' % sSeason
|
||||
isMatch, sContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
pattern = 'href="([^"]+)'
|
||||
isMatch, aResult = cParser.parse(sContainer, pattern)
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, '"description">([^<]+)')
|
||||
total = len(aResult)
|
||||
for sUrl in aResult:
|
||||
isMatch, sName = cParser.parseSingleResult(sUrl, r'e(\d+)')
|
||||
oGuiElement = cGuiElement('Episode ' + str(sName), SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setTVShowTitle(sShowName)
|
||||
oGuiElement.setSeason(sSeason)
|
||||
oGuiElement.setEpisode(sName)
|
||||
oGuiElement.setMediaType('episode')
|
||||
if sUrl.startswith('//'):
|
||||
params.setParam('entryUrl', 'https:' + sUrl)
|
||||
else:
|
||||
params.setParam('entryUrl', sUrl)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
cGui().setView('episodes')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('entryUrl')
|
||||
if '-english' in sUrl: sLang = '(EN)'
|
||||
else: sLang = ''
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False, bypass_dns=True).request()
|
||||
pattern = 'hostName">([^<]+).*?(http[^"]+)' # Hoster Link
|
||||
releaseQuality = r'class="rb">.*?(\d\d\d+)p\.' # Release Qualität
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
isQuality, sQuality = cParser.parseSingleResult(sHtmlContent, releaseQuality) # sReleaseQuality auslesen z.B. 1080
|
||||
if not isQuality: sQuality = '720'
|
||||
hosters = []
|
||||
if isMatch:
|
||||
for sName, sUrl in aResult:
|
||||
sName = sName.split(' HD')[0].strip()
|
||||
if 'Filemoon' in sName or 'Swiftload' in sName or 'Vidhide' in sName:
|
||||
sUrl = sUrl + '$$https://filmpalast.to/' # Referer hinzugefügt
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I]%s [%sp][/I]' % (sName, sLang, sQuality), 'languageCode': sLang, 'quality': sQuality} # Qualität Anzeige aus Release Eintrag
|
||||
hosters.append(hoster)
|
||||
else:
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I]%s [%sp][/I]' % (sName, sLang, sQuality), 'languageCode': sLang, 'quality': sQuality} # Qualität Anzeige aus Release Eintrag
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showEntries(URL_SEARCH % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
|
||||
def showSeriesMenu(): # Menu structure of series menu
|
||||
params = ParameterHandler()
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement('Alle ' + cConfig().getLocalizedString(30511), SITE_IDENTIFIER, 'showEntries'), params) # Alle Serien
|
||||
params.setParam('value', 'movietitle')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30517), SITE_IDENTIFIER, 'showValue'), params) # Von A bis Z
|
||||
cGui().setEndOfDirectory()
|
||||
253
plugin.video.xstream/sites/hdfilme.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showGenre: 48 Stunden
|
||||
# showYears: 48 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
SITE_IDENTIFIER = 'hdfilme'
|
||||
SITE_NAME = 'HD Filme'
|
||||
SITE_ICON = 'hdfilme.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'hdfilme.garden') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN + '/'
|
||||
# URL_MAIN = 'https://hdfilme.im/'
|
||||
URL_NEW = URL_MAIN + 'kinofilme-online/'
|
||||
URL_KINO = URL_MAIN + 'aktuelle-kinofilme-im-kino/'
|
||||
URL_MOVIES = URL_MAIN + 'kinofilme-online'
|
||||
URL_SERIES = URL_MAIN + 'serienstream-deutsch/'
|
||||
URL_SEARCH = URL_MAIN + 'index.php?do=search&subaction=search&story=%s'
|
||||
#
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
params.setParam('sUrl', URL_NEW)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30500), SITE_IDENTIFIER, 'showEntries'), params) # New
|
||||
params.setParam('sUrl', URL_KINO)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30501), SITE_IDENTIFIER, 'showEntries'), params) # Current films in the cinema
|
||||
params.setParam('sUrl', URL_MOVIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30502), SITE_IDENTIFIER, 'showEntries'), params) # Movies
|
||||
cGui().addFolder(cGuiElement('Jahr', SITE_IDENTIFIER, 'showYearSearch')) # New Year entry
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30511), SITE_IDENTIFIER, 'showEntries'), params) # Series
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params)# Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showGenre(entryUrl=False):
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 48 # 48 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'Genre">Genre.*?</ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showCountry(entryUrl=False):
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 48 # 48 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'Land?">Land.*?</ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False, sSearchPageText = False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
isTvshow = False
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '<div class="box-product(.*?)<h3.*?href="([^"]+).*?">([^<]+).*?(.*?)</li>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sInfo, sUrl, sName, sDummy in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
# Abfrage der voreingestellten Sprache
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
if (sLanguage == '1' and 'English*' in sName): # Deutsch
|
||||
continue
|
||||
if (sLanguage == '2' and not 'English*' in sName): # English
|
||||
continue
|
||||
elif sLanguage == '3': # Japanisch
|
||||
cGui().showLanguage()
|
||||
continue
|
||||
isThumbnail, sThumbnail = cParser.parseSingleResult(sInfo, 'data-src="([^"]+)') # Thumbnail
|
||||
isYear, sYear = cParser.parseSingleResult(sDummy, r'([\d]+)\s</p>') # Release Jahr
|
||||
isQuality, sQuality = cParser.parseSingleResult(sDummy, 'quality-product">([^<]+)') # Qualität
|
||||
isTvshow = True if 'taffel' in sName else False
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showEpisodes' if isTvshow else 'showHosters')
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
if isThumbnail:
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isYear:
|
||||
oGuiElement.setYear(sYear)
|
||||
if isQuality:
|
||||
oGuiElement.setQuality(sQuality)
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
params.setParam('entryUrl', sUrl)
|
||||
params.setParam('sName', sName)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
|
||||
if not sGui and not sSearchText and not sSearchPageText:
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, 'href="([^"]+)">›</a></div>')
|
||||
# Start Page Function
|
||||
isMatchSiteSearch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, 'class="pagination(.*?)</div></div>')
|
||||
if isMatchSiteSearch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, r'<span>([\d]+)</span>.*?nav_ext">.*?">([\d]+)</a>.*?href="([^"]+)')
|
||||
for sPageActive, sPageLast, sNextPage in aResult:
|
||||
#sPageName = '[I]Seitensuche starten >>> [/I] Seite ' + str(sPageActive) + ' von ' + str(sPageLast) + ' Seiten [I]<<<[/I]'
|
||||
sPageName = cConfig().getLocalizedString(30284) + str(sPageActive) + cConfig().getLocalizedString(30285) + str(sPageLast) + cConfig().getLocalizedString(30286)
|
||||
params.setParam('sNextPage', sNextPage)
|
||||
params.setParam('sPageLast', sPageLast)
|
||||
oGui.searchNextPage(sPageName, SITE_IDENTIFIER, 'showSearchPage', params)
|
||||
# End Page Function
|
||||
if isMatchNextPage:
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
params = ParameterHandler()
|
||||
entryUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue('sThumbnail')
|
||||
oRequest = cRequestHandler(entryUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, '"><a href="#">([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
isDesc, sDesc = cParser.parseSingleResult(sHtmlContent, '"description"[^>]content="([^"]+)')
|
||||
total = len(aResult)
|
||||
for sName in aResult:
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
if isDesc:
|
||||
oGuiElement.setDescription(sDesc)
|
||||
oGuiElement.setMediaType('episode')
|
||||
params.setParam('entryUrl', entryUrl)
|
||||
params.setParam('episode', sName)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
cGui().setView('episodes')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
sHtmlContent = cRequestHandler(ParameterHandler().getValue('entryUrl'), caching=False).request()
|
||||
if ParameterHandler().getValue('episode'):
|
||||
pass
|
||||
pattern = '%s<.*?</ul>' % ParameterHandler().getValue('episode')
|
||||
isMatch, sHtmlContent = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, 'link="([^"]+)')
|
||||
if isMatch:
|
||||
sQuality = '720'
|
||||
for sUrl in aResult:
|
||||
if 'youtube' in sUrl or 'dropload' in sUrl:
|
||||
continue
|
||||
elif sUrl.startswith('//'):
|
||||
sUrl = 'https:' + sUrl
|
||||
sName = cParser.urlparse(sUrl).split('.')[0].strip()
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I][%sp][/I]' % (sName, sQuality), 'quality': sQuality}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showEntries(URL_SEARCH % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
|
||||
def showSearchPage(): # Suche für die Page Funktion
|
||||
params = ParameterHandler()
|
||||
sNextPage = params.getValue('sNextPage') # URL mit nächster Seite
|
||||
sPageLast = params.getValue('sPageLast') # Anzahl gefundener Seiten
|
||||
#sHeading = 'Bitte eine Zahl zwischen 1 und ' + str(sPageLast) + ' wählen.'
|
||||
sHeading = cConfig().getLocalizedString(30282) + str(sPageLast)
|
||||
sSearchPageText = cGui().showKeyBoard(sHeading=sHeading)
|
||||
if not sSearchPageText: return
|
||||
sNextSearchPage = sNextPage.split('page/')[0].strip() + 'page/' + sSearchPageText + '/'
|
||||
showEntries(sNextSearchPage)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showYearSearch():
|
||||
sYear = cGui().showKeyBoard(sHeading="Jahr eintragen (z.B., 2017)")
|
||||
if not sYear: return
|
||||
searchUrl = URL_MAIN + '/xfsearch/' + sYear
|
||||
showEntries(searchUrl)
|
||||
cGui().setEndOfDirectory()
|
||||
303
plugin.video.xstream/sites/hdfilme_1.py
Normal file
@@ -0,0 +1,303 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showValue: 48 Stunden
|
||||
# showEntries: 6 Stunden
|
||||
# showEpisodes: 4 Stunden
|
||||
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
SITE_IDENTIFIER = 'hdfilme_1'
|
||||
SITE_NAME = 'FHD Filme'
|
||||
SITE_ICON = 'hdfilme_1.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'hdfilme.legal') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
# URL_MAIN = 'https://hdfilme.my'
|
||||
|
||||
URL_NEW = URL_MAIN + '/filme1/'
|
||||
URL_KINO = URL_MAIN + '/kinofilme/'
|
||||
URL_MOVIES = URL_MAIN
|
||||
URL_SERIES = URL_MAIN + '/serien/'
|
||||
URL_SEARCH = URL_MAIN + '/?story=%s&do=search&subaction=search'
|
||||
|
||||
#
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
params.setParam('sUrl', URL_NEW)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30500), SITE_IDENTIFIER, 'showEntries'), params) # New
|
||||
params.setParam('sUrl', URL_KINO)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30501), SITE_IDENTIFIER, 'showEntries'), params) # Current films in the cinema
|
||||
params.setParam('sUrl', URL_MOVIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30502), SITE_IDENTIFIER, 'showEntries'), params) # Movies
|
||||
params.setParam('sUrl', URL_SERIES)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30511), SITE_IDENTIFIER, 'showEntries'), params) # Series
|
||||
cGui().addFolder(cGuiElement('Jahr', SITE_IDENTIFIER, 'showYearSearch')) # New Year entry
|
||||
params.setParam('Value', 'Genre')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showValue'), params) # Genre
|
||||
params.setParam('Value', 'Land')
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30538), SITE_IDENTIFIER, 'showValue'), params) # Country
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch'), params) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showValue():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 48 # 48 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '>{0}</(.*?)</a[^<]*</div>'.format(params.getValue('Value'))
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries'), params)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False, sSearchPageText = False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
isTvshow = False
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl, ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'class="item relative mt-3">.*?href="([^"]+).*?title="([^"]+).*?data-src="([^"]+)(.*?)</div></div>'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sThumbnail, sDummy in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
isYear, sYear = cParser.parseSingleResult(sDummy, r'mt-1">[^<]*<span>([\d]+)</span>') # Release Jahr
|
||||
isDuration, sDuration = cParser.parseSingleResult(sDummy, r'<span>([\d]+)\smin</span>') # Laufzeit
|
||||
if int(sDuration) <= int('70'): # Wenn Laufzeit kleiner oder gleich 70min, dann ist es eine Serie.
|
||||
isTvshow = True
|
||||
else:
|
||||
from resources.lib.tmdb import cTMDB
|
||||
oMetaget = cTMDB()
|
||||
if not oMetaget:
|
||||
isTvshow = False
|
||||
else:
|
||||
if isYear:
|
||||
meta = oMetaget.search_movie_name(sName, year=sYear)
|
||||
else:
|
||||
meta = oMetaget.search_movie_name(sName)
|
||||
if meta and 'id' in meta:
|
||||
isTvshow = False
|
||||
else:
|
||||
isTvshow = True
|
||||
if 'South Park: The End Of Obesity' in sName:
|
||||
isTvshow = False
|
||||
isQuality, sQuality = cParser.parseSingleResult(sDummy, '">([^<]+)</span>') # Qualität
|
||||
sThumbnail = URL_MAIN + sThumbnail
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons' if isTvshow else 'showHosters')
|
||||
if isYear:
|
||||
oGuiElement.setYear(sYear)
|
||||
if isDuration:
|
||||
oGuiElement.addItemValue('duration', sDuration)
|
||||
if isQuality:
|
||||
oGuiElement.setQuality(sQuality)
|
||||
oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
params.setParam('sThumbnail', sThumbnail)
|
||||
oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
if not sGui and not sSearchText and not sSearchPageText:
|
||||
isMatchNextPage, sNextUrl = cParser.parseSingleResult(sHtmlContent, 'nav_ext">.*?next">.*?href="([^"]+)')
|
||||
# Start Page Function
|
||||
isMatchSiteSearch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, 'class="pages">(.*?)<svg')
|
||||
if isMatchSiteSearch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, r'<span>([\d]+)</span>.*?">([\d]+)</a></div>.*?ref="([^"]+)')
|
||||
for sPageActive, sPageLast, sNextPage in aResult:
|
||||
#sPageName = '[I]Seitensuche starten >>> [/I] Seite ' + str(sPageActive) + ' von ' + str(sPageLast) + ' Seiten [I]<<<[/I]'
|
||||
sPageName = cConfig().getLocalizedString(30284) + str(sPageActive) + cConfig().getLocalizedString(30285) + str(sPageLast) + cConfig().getLocalizedString(30286)
|
||||
params.setParam('sNextPage', sNextPage)
|
||||
params.setParam('sPageLast', sPageLast)
|
||||
oGui.searchNextPage(sPageName, SITE_IDENTIFIER, 'showSearchPage', params)
|
||||
# End Page Function
|
||||
if isMatchNextPage:
|
||||
params.setParam('sUrl', sNextUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries', params)
|
||||
oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showSeasons():
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue('sThumbnail')
|
||||
oRequest = cRequestHandler(sUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # HTML Cache Zeit 6 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'class="su-accordion collapse show"(.*?)<br>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, r'#se-ac-(\d+)')
|
||||
total = len(aResult)
|
||||
for sSeason in aResult:
|
||||
oGuiElement = cGuiElement('Staffel ' + str(sSeason), SITE_IDENTIFIER, 'showEpisodes')
|
||||
oGuiElement.setSeason(sSeason)
|
||||
oGuiElement.setMediaType('season')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
cGui().addFolder(oGuiElement, params, True, total)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
cGui().setView('seasons')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodes():
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
entryUrl = params.getValue('entryUrl')
|
||||
sThumbnail = params.getValue('sThumbnail')
|
||||
sSeason = params.getValue('season')
|
||||
oRequest = cRequestHandler(entryUrl)
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 4 # HTML Cache Zeit 4 Stunden
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = '#se-ac-%s(.*?)</div></div>' % sSeason
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, r'Episode\s(\d+)')
|
||||
total = len(aResult)
|
||||
for sEpisode in aResult:
|
||||
oGuiElement = cGuiElement('Episode ' + str(sEpisode), SITE_IDENTIFIER, 'showEpisodeHosters')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
oGuiElement.setMediaType('episode')
|
||||
params.setParam('entryUrl', entryUrl)
|
||||
params.setParam('season', sSeason)
|
||||
params.setParam('episode', sEpisode)
|
||||
cGui().addFolder(oGuiElement, params, False, total)
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
cGui().setView('episodes')
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showEpisodeHosters():
|
||||
hosters = []
|
||||
params = ParameterHandler()
|
||||
# Parameter laden
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sSeason = params.getValue('season')
|
||||
sEpisode = params.getValue('episode')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
pattern = '#se-ac-%s(.*?)</div></div>' % sSeason
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
pattern = r'x%s\sEpisode(.*?)<br' % sEpisode
|
||||
isMatch, sHtmlLink = cParser.parseSingleResult(sHtmlContainer, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlLink, 'href="([^"]+)')
|
||||
if isMatch:
|
||||
sQuality = '720'
|
||||
for sUrl in aResult:
|
||||
if 'youtube' in sUrl:
|
||||
continue
|
||||
elif sUrl.startswith('//'):
|
||||
sUrl = 'https:' + sUrl
|
||||
sName = cParser.urlparse(sUrl).split('.')[0].strip()
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I][%sp][/I]' % (sName, sQuality), 'quality': sQuality}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
pattern = r'<iframe\sw.*?src="([^"]+)'
|
||||
isMatch, hUrl = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
sHtmlContainer = cRequestHandler(hUrl).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'data-link="([^"]+)')
|
||||
if isMatch:
|
||||
sQuality= '720'
|
||||
for sUrl in aResult:
|
||||
if 'youtube' in sUrl:
|
||||
continue
|
||||
elif sUrl.startswith('//'):
|
||||
sUrl = 'https:' + sUrl
|
||||
sName = cParser.urlparse(sUrl).split('.')[0].strip()
|
||||
if cConfig().isBlockedHoster(sName)[0]: continue # Hoster aus settings.xml oder deaktivierten Resolver ausschließen
|
||||
hoster = {'link': sUrl, 'name': sName, 'displayedName': '%s [I][%sp][/I]' % (sName, sQuality), 'quality': sQuality}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showEntries(URL_SEARCH % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
|
||||
def showSearchPage(): # Suche für die Page Funktion
|
||||
params = ParameterHandler()
|
||||
sNextPage = params.getValue('sNextPage') # URL mit nächster Seite
|
||||
sPageLast = params.getValue('sPageLast') # Anzahl gefundener Seiten
|
||||
#sHeading = 'Bitte eine Zahl zwischen 1 und ' + str(sPageLast) + ' wählen.'
|
||||
sHeading = cConfig().getLocalizedString(30282) + str(sPageLast)
|
||||
sSearchPageText = cGui().showKeyBoard(sHeading=sHeading)
|
||||
if not sSearchPageText: return
|
||||
sNextSearchPage = sNextPage.split('page/')[0].strip() + 'page/' + sSearchPageText + '/'
|
||||
showEntries(sNextSearchPage)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def showYearSearch():
|
||||
sYear = cGui().showKeyBoard(sHeading="Jahr eintragen (z.B., 2017)")
|
||||
if not sYear: return
|
||||
searchUrl = URL_MAIN + '/xfsearch/' + sYear
|
||||
showEntries(searchUrl)
|
||||
cGui().setEndOfDirectory()
|
||||
243
plugin.video.xstream/sites/internetarchive.py
Normal file
@@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
# HTML LangzeitCache hinzugefügt
|
||||
# showGenre: 48 Stunden
|
||||
# showEntries: 6 Stunden
|
||||
|
||||
|
||||
import json
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
|
||||
SITE_IDENTIFIER = 'internetarchive'
|
||||
SITE_NAME = 'Internet Archive'
|
||||
SITE_ICON = 'internetarchive.png'
|
||||
|
||||
# Global search function is thus deactivated!
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'false':
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
# Domain Abfrage
|
||||
DOMAIN = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '.domain', 'archive.org') # Domain Auswahl über die xStream Einstellungen möglich
|
||||
STATUS = cConfig().getSetting('plugin_' + SITE_IDENTIFIER + '_status') # Status Code Abfrage der Domain
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
URL_MAIN = 'https://' + DOMAIN
|
||||
URL_MOVIE = URL_MAIN + '/details/'
|
||||
URL_SEARCH = URL_MAIN + '/advancedsearch.php?q=%s'
|
||||
URL_COLL = URL_MAIN + ('/advancedsearch.php?q=collection%3A%22')
|
||||
URL_COLL1 = ('%22&fl%5B%5D=collection&fl%5B%5D=description&fl%5B%5D=genre&fl%5B%5D=identifier&fl%5B%5D=language&fl%5B%5D=title&fl%5B%5D=year&rows=80000&page=1&output=json')
|
||||
URL_COLLECTIONS_LIST = {'Film Noir': 'Film_Noir', 'Feature Films': 'feature_films', 'Movie Trailers': 'movie_trailers', 'Short Format Films': 'short_films',
|
||||
'SciFi / Horror': 'SciFi_Horror', 'Cinemocracy': 'cinemocracy'}
|
||||
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30543), SITE_IDENTIFIER, 'menuCollections')) # Kollektionen
|
||||
cGui().addFolder(cGuiElement(cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch')) # Search
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def menuCollections():
|
||||
params = ParameterHandler()
|
||||
for key in sorted(URL_COLLECTIONS_LIST):
|
||||
params.setParam('sUrl', (URL_COLL + URL_COLLECTIONS_LIST[key] + URL_COLL1))
|
||||
cGui().addFolder(cGuiElement(key, SITE_IDENTIFIER, 'showCollections'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showCollections(entryUrl=False, sGui=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler((entryUrl), ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
jSearch = json.loads(oRequest.request()) # Lade JSON aus dem Request der URL
|
||||
if not jSearch: return # # Wenn Suche erfolglos - Abbruch
|
||||
aResults = jSearch['response']['docs']
|
||||
total = len(aResults)
|
||||
if len(aResults) == 0:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
# Filter nach eingestellter Sprache in xstream laden
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
|
||||
for i in aResults:
|
||||
if 'identifier' in i and i['identifier'] != '' and 'title' in i and i['title'] != '':
|
||||
sId = str(i['identifier']) # ID des Films / Serie für die weitere URL
|
||||
sName = str(i['title']) # Name des Films / Serie
|
||||
#sName = sName.replace('"', '-')
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
# Resultate aus JSON nach voreingestellter Sprache filtern (Deutsch, Englisch, alle Sprachen)
|
||||
if 'language' in i and i['language'] != '':
|
||||
sLang = i['language']
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
if not sLang in ['ger', 'german', 'Ger', 'German']:
|
||||
continue
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
if not sLang in ['eng', 'english', 'English']:
|
||||
continue
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if not sLang in ['ger', 'eng', 'english', 'English', 'Ger', 'German']:
|
||||
continue
|
||||
else:
|
||||
i['language'] = 'und'
|
||||
sLang = 'und'
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
continue
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
continue
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if not sLang in ['ger', 'eng', 'english', 'und', 'Ger', 'German', 'English']:
|
||||
continue
|
||||
oGuiElement.setLanguage(i['language'])
|
||||
|
||||
#if 'is_series' in i: isTvshow = i['is_series'] # Wenn True dann Serie ToDo Prüfen wie sich Serien verhalten
|
||||
#oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons' if isTvshow else 'showHosters')
|
||||
#oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
if 'year' in i and len(str(i['year'])) == 4: # Suche bei year nach 4 stelliger Zahl
|
||||
oGuiElement.setYear(i['year'])
|
||||
if 'description' in i and i['description'] != '':
|
||||
sDesc = str(i['description'])
|
||||
sDesc = sDesc.replace("'", "")
|
||||
oGuiElement.setDescription(sDesc) # Suche nach Desc wenn nicht leer dann setze GuiElement
|
||||
|
||||
#oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
oGuiElement.setMediaType('movie')
|
||||
# Parameter übergeben
|
||||
params.setParam('entryUrl', URL_MOVIE + sId)
|
||||
params.setParam('sName', sName)
|
||||
#oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
#oGui.addFolder(oGuiElement, params, total)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
|
||||
if not sGui:
|
||||
#oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
|
||||
|
||||
def showEntries(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
oRequest = cRequestHandler(entryUrl + '%20AND%20mediatype%3Amovies&fl%5B%5D=description&fl%5B%5D=genre&fl%5B%5D=identifier&fl%5B%5D=language&fl%5B%5D=title&fl%5B%5D=year&rows=500&output=json', ignoreErrors=(sGui is not False))
|
||||
if cConfig().getSetting('global_search_' + SITE_IDENTIFIER) == 'true':
|
||||
oRequest.cacheTime = 60 * 60 * 6 # 6 Stunden
|
||||
jSearch = json.loads(oRequest.request()) # Lade JSON aus dem Request der URL
|
||||
if not jSearch: return # # Wenn Suche erfolglos - Abbruch
|
||||
aResults = jSearch['response']['docs']
|
||||
total = len(aResults)
|
||||
if len(aResults) == 0:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
# Filter nach eingestellter Sprache in xstream laden
|
||||
sLanguage = cConfig().getSetting('prefLanguage')
|
||||
|
||||
for i in aResults:
|
||||
if 'identifier' in i and i['identifier'] != '' and 'title' in i and i['title'] != '':
|
||||
sId = str(i['identifier']) # ID des Films / Serie für die weitere URL
|
||||
sName = str(i['title']) # Name des Films / Serie
|
||||
sName = sName.replace(':', '-')
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
# Resultate aus JSON nach voreingestellter Sprache filtern (Deutsch, Englisch, alle Sprachen)
|
||||
if 'language' in i and i['language'] != '':
|
||||
sLang = i['language']
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
if not sLang in ['ger', 'german']:
|
||||
continue
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
if not sLang in ['eng', 'english']:
|
||||
continue
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if not sLang in ['ger', 'eng', 'english']:
|
||||
continue
|
||||
else:
|
||||
i['language'] = 'UND'
|
||||
sLang = 'UND'
|
||||
if sLanguage == '1': # Voreingestellte Sprache Deutsch in settings.xml
|
||||
continue
|
||||
if sLanguage == '2': # Voreingestellte Sprache Englisch in settings.xml
|
||||
continue
|
||||
if sLanguage == '0': # Alle Sprachen
|
||||
if not sLang in ['ger', 'eng', 'english', 'UND']:
|
||||
continue
|
||||
oGuiElement.setLanguage(i['language'])
|
||||
|
||||
#if 'is_series' in i: isTvshow = i['is_series'] # Wenn True dann Serie ToDo Prüfen wie sich Serien verhalten
|
||||
#oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showSeasons' if isTvshow else 'showHosters')
|
||||
#oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters')
|
||||
if 'year' in i and len(str(i['year'])) == 4: # Suche bei year nach 4 stelliger Zahl
|
||||
oGuiElement.setYear(i['year'])
|
||||
if 'description' in i and i['description'] != '':
|
||||
sDesc = str(i['description'])
|
||||
sDesc = sDesc.replace("'", "")
|
||||
oGuiElement.setDescription(sDesc) # Suche nach Desc wenn nicht leer dann setze GuiElement
|
||||
|
||||
#oGuiElement.setMediaType('tvshow' if isTvshow else 'movie')
|
||||
oGuiElement.setMediaType('movie')
|
||||
# Parameter übergeben
|
||||
params.setParam('entryUrl', URL_MOVIE + sId)
|
||||
params.setParam('sName', sName)
|
||||
#oGui.addFolder(oGuiElement, params, isTvshow, total)
|
||||
#oGui.addFolder(oGuiElement, params, total)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
|
||||
if not sGui:
|
||||
#oGui.setView('tvshows' if isTvshow else 'movies')
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
|
||||
def showHosters():
|
||||
hosters = []
|
||||
params = ParameterHandler()
|
||||
sUrl = params.getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, 'itemprop="embedUrl".*?href="([^"]+)')
|
||||
if isMatch:
|
||||
for sUrl in aResult:
|
||||
if sUrl.startswith('//'):
|
||||
sUrl = 'https:' + sUrl
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl')
|
||||
return hosters
|
||||
|
||||
|
||||
def getHosterUrl(sUrl=False):
|
||||
if 'youtube' in sUrl:
|
||||
import xbmc
|
||||
if not xbmc.getCondVisibility('System.HasAddon(%s)' % 'plugin.video.youtube'):
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % 'plugin.video.youtube')
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
|
||||
def showSearch():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30287))
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showSearchColl(sName):
|
||||
# sSearchText = cGui().showKeyBoard()
|
||||
sSearchText = sName
|
||||
if not sSearchText: return
|
||||
_search(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
def _search(oGui, sSearchText):
|
||||
showEntries(URL_SEARCH % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
463
plugin.video.xstream/sites/kids_tube.py
Normal file
@@ -0,0 +1,463 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Python 3
|
||||
# Always pay attention to the translations in the menu!
|
||||
# Multi Scraper für Kinder Videos
|
||||
|
||||
|
||||
import xbmc
|
||||
import xbmcgui, sys
|
||||
import requests
|
||||
import random
|
||||
|
||||
from resources.lib.handler.ParameterHandler import ParameterHandler
|
||||
from resources.lib.handler.requestHandler import cRequestHandler
|
||||
from resources.lib.tools import logger, cParser
|
||||
from resources.lib.gui.guiElement import cGuiElement
|
||||
from resources.lib.config import cConfig
|
||||
from resources.lib.gui.gui import cGui
|
||||
from resources.lib import youtube_fix
|
||||
|
||||
#
|
||||
|
||||
SITE_IDENTIFIER = 'kids_tube'
|
||||
SITE_NAME = 'Kids Tube'
|
||||
SITE_ICON = 'kids_tube.png'
|
||||
|
||||
SITE_GLOBAL_SEARCH = False
|
||||
cConfig().setSetting('global_search_' + SITE_IDENTIFIER, 'false')
|
||||
logger.info('-> [SitePlugin]: globalSearch for %s is deactivated.' % SITE_NAME)
|
||||
|
||||
ACTIVE = cConfig().getSetting('plugin_' + SITE_IDENTIFIER) # Ob Plugin aktiviert ist oder nicht
|
||||
|
||||
#################### Hauptmenü ####################
|
||||
|
||||
def load(): # Menu structure of the site plugin
|
||||
logger.info('Load %s' % SITE_NAME)
|
||||
params = ParameterHandler()
|
||||
# Abfrage ob Youtube installiert ist
|
||||
if cConfig().getSetting('plugin_' + SITE_IDENTIFIER) == 'true':
|
||||
if not xbmc.getCondVisibility('System.HasAddon(%s)' % 'plugin.video.youtube'):
|
||||
xbmc.executebuiltin('InstallAddon(%s)' % 'plugin.video.youtube')
|
||||
# Menü für Kinderserien
|
||||
logger.info('Load %s' % SITE_NAME_1)
|
||||
if params.getValue("action") is False:
|
||||
font = '[B]%s : [/B]' % SITE_NAME_1
|
||||
|
||||
params.setParam('sUrl', URL_MAIN_1)
|
||||
cGui().addFolder(cGuiElement(font + cConfig().getLocalizedString(30500), SITE_IDENTIFIER, 'showEntries_1'), params) # Neues
|
||||
params.setParam('sUrl', URL_MOVIES_1)
|
||||
cGui().addFolder(cGuiElement(font + cConfig().getLocalizedString(30502), SITE_IDENTIFIER, 'showEntries_1'), params) # Movies
|
||||
cGui().addFolder(cGuiElement(font + cConfig().getLocalizedString(30506), SITE_IDENTIFIER, 'showGenre_1'), params) # Genre
|
||||
cGui().addFolder(cGuiElement(font + cConfig().getLocalizedString(30520), SITE_IDENTIFIER, 'showSearch_1'), params) # Search
|
||||
main_list()
|
||||
else:
|
||||
action = params.getValue("action")
|
||||
if '#' in str(params.getValue("action")):
|
||||
action = action.split('#')[1]
|
||||
sub_listw(action)
|
||||
elif '*' in str(params.getValue("action")):
|
||||
action = action.split('*')[1]
|
||||
search(action)
|
||||
else:
|
||||
sub_list(action)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def loads():
|
||||
params = ParameterHandler()
|
||||
action = params.getValue("action")
|
||||
action2 = params.getValue("action1")
|
||||
if '#' in str(params.getValue("action1")):
|
||||
action = action2.split('#')[1]
|
||||
sub_listw(action)
|
||||
elif '*' in str(params.getValue("action1")):
|
||||
action = action2.split('*')[1]
|
||||
search(action)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
|
||||
#################### Kinderserien.tv ####################
|
||||
|
||||
SITE_NAME_1 = 'Kinderserien.tv'
|
||||
SITE_ICON_1 = 'kids_tube.png'
|
||||
URL_MAIN_1 = 'https://kinderserien.tv/'
|
||||
URL_MOVIES_1 = URL_MAIN_1 + 'serien/kinderfilme/'
|
||||
URL_SEARCH_1 = URL_MAIN_1 + '?s=%s'
|
||||
|
||||
def showGenre_1():
|
||||
params = ParameterHandler()
|
||||
oRequest = cRequestHandler(URL_MAIN_1)
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'Serien</h2>(.*?)</ul>'
|
||||
isMatch, sHtmlContainer = cParser.parseSingleResult(sHtmlContent, pattern)
|
||||
if isMatch:
|
||||
isMatch, aResult = cParser.parse(sHtmlContainer, 'href="([^"]+).*?>([^<]+)')
|
||||
if not isMatch:
|
||||
cGui().showInfo()
|
||||
return
|
||||
|
||||
for sUrl, sName in aResult:
|
||||
if sUrl.startswith('/'):
|
||||
sUrl = URL_MAIN_1 + sUrl
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(sName, SITE_IDENTIFIER, 'showEntries_1'), params)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def showEntries_1(entryUrl=False, sGui=False, sSearchText=False):
|
||||
oGui = sGui if sGui else cGui()
|
||||
params = ParameterHandler()
|
||||
if not entryUrl: entryUrl = params.getValue('sUrl')
|
||||
iPage = int(params.getValue('page'))
|
||||
oRequest = cRequestHandler(entryUrl + 'page/' + str(iPage) if iPage > 0 else entryUrl, ignoreErrors=(sGui is not False))
|
||||
sHtmlContent = oRequest.request()
|
||||
pattern = 'class="item-thumbnail">.*?href="([^"]+).*?title="([^"]+).*?src="([^"]+).*?'
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, pattern)
|
||||
if not isMatch:
|
||||
if not sGui: oGui.showInfo()
|
||||
return
|
||||
|
||||
total = len(aResult)
|
||||
for sUrl, sName, sThumbnail in aResult:
|
||||
if sSearchText and not cParser.search(sSearchText, sName):
|
||||
continue
|
||||
oGuiElement = cGuiElement(sName, SITE_IDENTIFIER, 'showHosters_1')
|
||||
oGuiElement.setThumbnail(sThumbnail)
|
||||
params.setParam('entryUrl', sUrl)
|
||||
oGui.addFolder(oGuiElement, params, False, total)
|
||||
|
||||
if not sGui and not sSearchText:
|
||||
sPageNr = int(params.getValue('page'))
|
||||
if sPageNr == 0:
|
||||
sPageNr = 2
|
||||
else:
|
||||
sPageNr += 1
|
||||
params.setParam('page', int(sPageNr))
|
||||
params.setParam('sUrl', entryUrl)
|
||||
oGui.addNextPage(SITE_IDENTIFIER, 'showEntries_1', params)
|
||||
oGui.setView('movies')
|
||||
oGui.setEndOfDirectory()
|
||||
|
||||
def showHosters_1():
|
||||
hosters = []
|
||||
sUrl = ParameterHandler().getValue('entryUrl')
|
||||
sHtmlContent = cRequestHandler(sUrl, caching=False).request()
|
||||
isMatch, aResult = cParser.parse(sHtmlContent, 'src="([^"]+)" f')
|
||||
if isMatch:
|
||||
for sUrl in aResult:
|
||||
sUrl = sUrl.split('?')[0].strip()
|
||||
hoster = {'link': sUrl, 'name': cParser.urlparse(sUrl)}
|
||||
hosters.append(hoster)
|
||||
if hosters:
|
||||
hosters.append('getHosterUrl_1')
|
||||
return hosters
|
||||
|
||||
def getHosterUrl_1(sUrl=False):
|
||||
return [{'streamUrl': sUrl, 'resolved': False}]
|
||||
|
||||
def showSearch_1():
|
||||
sSearchText = cGui().showKeyBoard(sHeading=cConfig().getLocalizedString(30281))
|
||||
if not sSearchText: return
|
||||
_search_1(False, sSearchText)
|
||||
cGui().setEndOfDirectory()
|
||||
|
||||
def _search_1(oGui, sSearchText):
|
||||
showEntries_1(URL_SEARCH_1 % cParser.quotePlus(sSearchText), oGui, sSearchText)
|
||||
|
||||
#################### Youtube Kanäle ####################
|
||||
|
||||
URL_MAIN = 'http://www.youtube.com'
|
||||
|
||||
channellist = [
|
||||
("[B]YouTube:[/B] Kanäle", "Kanäle", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Filme", "Kinder Filme", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Serien", "Kinder Serien", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Klassiker", "Kinder Klassiker", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Märchen", "Kinder Geschichten", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Hörbücher", "Kinder Buch", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Wissen", "Kinder Wissen", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B]YouTube:[/B] Musik", "Kinder Musik", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
]
|
||||
|
||||
sublists = {
|
||||
'Kanäle': [
|
||||
("[B]KIKA[/B] von ARD & ZDF", "user/meinKiKA", "https://yt3.googleusercontent.com/eVEM7kLayi8-pFKQ2jMVMqWMMf-Sj-LFtPD5oD5d4vctMxwa_MxvYkYQOihpO8YxHO3Fo8qHVA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]KiKANiNCHEN[/B] von ARD & ZDF", "channel/UCv4Pvhg1LY8U9E-XF8hn6WA", "https://yt3.googleusercontent.com/PHGbrL1fSJgdN-1S69zDJ7GUVuw9ypSiq8skG1GAzxcESnwCgRYwv0yhe7sTR_5VS-NwjjIxyA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]ZDFtivi[/B] von ZDF", "user/ZDFtiviKinder", "https://yt3.googleusercontent.com/IgitEsBoWmeD_xSHFMiu8nwOuGvlCSCtOzUQxSm_91VXPa1nzEOnb3lcFfrfZnpe_DWkoWEq6A=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Löwenzahn TV[/B] von ZDF", "channel/UCOPJAVJeBhqWL8k7K9Wx6SQ", "https://yt3.googleusercontent.com/ytc/AIdro_lTKpWSboFuBYXfl3EhkZow8Yot6_lnmZRk3hEnvz32nQ0=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Sesamstrasse[/B] von NDR", "user/SesamstrasseNDR", "https://yt3.googleusercontent.com/TsFEPe9IC45mP5kMx6fLabeF0vO5IGebWSp8E4AokyT3dyZwpy-3FDi8NIWFgLmBwYQkjiYLBg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kindernetz[/B] von SWR", "user/Kindernetz", "https://yt3.googleusercontent.com/vKDz1wZkfM8HNFaYN7QQQciIJq65bmVAmOt6iKDdKhmtTzW5YJ7A-HpnAZhxMC6xfQoEn_5zXw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die Sendung mit der Maus[/B] von WDR", "channel/UCRWSxXBnz9IRS4SgRhG2wpQ", "https://yt3.ggpht.com/AB02vMc3urTNtF1fs8xC_beAkkB__S8J3cLiJpZ4vdMXLf2GOBlkd6RWkNGVqOdFjNkHmTrLaA=s176-c-k-c0x00ffffff-no-rj-mo"),
|
||||
("[B]Toggolino[/B] von SuperRTL", "user/TOGGOLINOde", "https://yt3.googleusercontent.com/cL_dcEztZBDvVCR5K3JNE3o5K5580xSNaETJonhzE2mK1fN1UdMYsWx5rr1essWmHBbL6a_2OA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Disney Channel[/B] Deutschland", "user/DisneyChannelGermany", "https://yt3.googleusercontent.com/c3_JbPq0s5lc_pfXglSsnNTMv4T-oEK6zvmQDBFqxmAmgmKn1yDm5141NzYqudS2qhzt50fDJw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Disney Junior[/B] Deutschland", "user/DisneyJuniorGermany", "https://yt3.googleusercontent.com/lP2bAzpPZyCY_mEBPCwpCBRwHV6wc6nePDCjCuK2UoXx6uvnUpWeQ5iGxLmOpADOpFYR9jSbSJ4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Nickelodeon[/B] Deutschland", "user/nickelodeonoffiziell", "https://yt3.googleusercontent.com/mr1Sx4O0oKwfhM3kuRWGpKHU5OGIKgalPIx6HL47L_76MZUbPrCmXGxc4VEB-m_CN1X3-ZEy=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Cartoonito[/B] Deutschland", "channel/UChcDNaLm0F8GZY4fRhZEAeQ", "https://yt3.googleusercontent.com/hvStglMjagBU8hzH2Ke4PYTs05Ock_2ryEK7pwZXRNXnok9hy8UaqsqF3JAgSph9SoI7UOFcYA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Cartoon Network[/B] Deutschland", "user/cartoonnetworkde", "https://yt3.googleusercontent.com/ytc/AIdro_nXPNGVJ3K15vK1QeDT_k0_3bGTyN7dhAxNm6o0StPVtCU=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Nanoki[/B] Kinderfilme, Lehrfilme, Lernserien", "channel/UCF2IFFQyO5gbhvOCObh1WTQ", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Karussell[/B] KinderTV", "channel/UCdT-eMkUGKqvVebDkn1KqlQ", "https://yt3.googleusercontent.com/95cCyBJIjuANCB5IDAyHvang2jGlPBt_jbY__nNHOz6gFH5ErboDnmqb879peVqKnWrRX4gJxA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kinderlieder[/B] zum Mitsingen und Bewegen", "channel/UCctbi1Jw2jiVhj2ogdwiFdA", "https://yt3.googleusercontent.com/BGoTUkK-TRjC6fC1fFP700dgrV5cjuHw-O8OcgGnqsHi3RoeU7b0hKm-0eS4dDOg-PWs5lz9Ew=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kinderlieder[/B] von Volker Rosin", "channel/UC7HM-Pm3mLzZBvhJKFnL6oA", "https://yt3.googleusercontent.com/JJrATrwxWPSfcl96xTQqdKCsPoRu-szzn_NTvazvFG9Vx8aAvTHZBT_WuznsbOuiClwdTf0RXw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]KIDFLIX[/B]", "channel/UCBZZcF8BC0kvZL_-zM7yuFg", "https://yt3.googleusercontent.com/ZHa7HKjK0Dj9-bg2AlM9Udts_3bItaKWEe7aEkvmvDAf1kDFpqfcTe9a0e8-GhbkiFQd8dm1IHI=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Ric TV[/B]", "user/RICTVChannel", "https://yt3.googleusercontent.com/ytc/AIdro_llzhDC24z43QbBNVmCWEuNP_1cVflbImhBIw2_7np6Bw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Filme': [
|
||||
("[B]Kinderfilme[/B] von Netzkino", "playlist/PLfEblejE-l3k5xxDsBiKVhdNqWAuCIYRr", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Zeichentrickfilme[/B] von Netzkino", "playlist/PLfEblejE-l3l8AwrdcuMp-p31uwBaJJXl", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Pferdefilme[/B] von Netzkino", "playlist/PLfEblejE-l3k3qM4Q1GlnGdGeRjzlZvRn", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Hundefilme[/B] von Netzkino", "playlist/PLfEblejE-l3nIBgJeAv7KO1DPvC2yN8-5", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Tennie Komödien[/B] von Netzkino", "playlist/PLfEblejE-l3kyX48AXCaOnKFLxxkasxOM", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Familienkino[/B] von Netzkino", "playlist/PLfEblejE-l3nta_dpVmIwGomgE_a3sm6g", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Weihnachtskino[/B] von Netzkino", "playlist/PLfEblejE-l3n9NKla09pwoYVLtnrL2kv3", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Kinderkino[/B] von Netzkino", "channel/UCmZUsl5MLqXIhuSTVP6x-EA", "special://home/addons/plugin.video.xstream/resources/art/sites/netzkino.png"),
|
||||
("[B]Kinderfilme[/B] von Nanoki", "playlist/PLAroxwS0jZuS521YByzaxA6ZO5VO-ppXJ", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Zeichentrickfilme[/B] von Nanoki", "playlist/PLAroxwS0jZuQSy2pjwruhzz44-kQ0YHEh", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tierfilme[/B] von Nanoki", "playlist/PLAroxwS0jZuQQlgJJ3_-imn1abL4A4Zlc", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Hundefilme[/B] von Nanoki", "playlist/PLAroxwS0jZuQC6yoU8LJF3USzgQBlrzx5", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Familienfilme[/B] von Nanoki", "playlist/PLAroxwS0jZuQUBhbxcAUlfh9qeJGV_tom", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Weihnachtsfilme[/B] von Nanoki", "playlist/PLAroxwS0jZuTLDgN-YeW2Yy0trylROSs4", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kinderfilme Klassiker[/B] von Nanoki", "playlist/PLAroxwS0jZuQUke_UTBPvbyCB27G0NtwA", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Serien': [
|
||||
("[B]Feuerwehrman Sam[/B]", "playlist/PLK-pDTpRfGThLNOPhnUn_442Sr3X94ehe", "https://yt3.googleusercontent.com/ofigmXJBP1ws5e5PqlNPpNTHoEjf0CuYa8Wjg4EExWxqsyB_NppwXqSIi34EQGXAiROvzry_=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Bob der Baumeister[/B]", "channel/UC1gSKVovUYLmXoDnsTY039Q", "https://yt3.googleusercontent.com/ytc/AIdro_libsJ_qNx01lsU8cqy4G3bQQJGqPQvtqZwQwWPFF0oeA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Bob der Baumeister DVD[/B]", "playlist/PLiK4BrMh3Fy8z0X2Q1nh9Ts5Uq_XVXoB1", "https://yt3.googleusercontent.com/ytc/AIdro_libsJ_qNx01lsU8cqy4G3bQQJGqPQvtqZwQwWPFF0oeA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Peppa Pig[/B]", "playlist/PLIHhW2rjlyqazX9VsDH3OB_BvXu3y2arK", "https://yt3.googleusercontent.com/ytc/AIdro_lXig6Qp5KuxzRLn4K_hmZOEzB3o9eI9bz-ALdaOP9wAg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die Fixies[/B]", "playlist/PLMu5a51zMv6zUI7ydYUdLkwHLyAq9mlw5", "https://yt3.googleusercontent.com/bsnnz9N70ntIDrDuRaXY4KNU_WKUu94V-MuaXCdqGgsvz17Ekju_1VZRNLzLLvHwSVYi8ZZBaQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Caillou[/B]", "playlist/PLYbELRCXraUV5CxUx0VFqALe5B2NOnDM-", "https://yt3.googleusercontent.com/ytc/AIdro_l30kpdJm0Ir6rselzA8yAhAidkjxmm89coucfOyagQBw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tom der Abschleppwagen[/B]", "playlist/PLfrJ8rAzdNSWKpsTGcZGNLyjsVDiwyj90", "https://yt3.googleusercontent.com/vMiHUQlNXiHl1eJmx1-tErx29DBt4beiSPJ50e0V4MjA1O0VFPUbvIOKW2HbqiG7RKC30rATHg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Bali[/B]", "playlist/PL73AN-YruC-Ah9yKL8v09fB0nI-Ygt5I4", "https://yt3.googleusercontent.com/ytc/AIdro_kliL7apFXyhzAB7eDHAYELARqUYf0Q1NSIuU1QHJpUvQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Der kleine Nick[/B]", "channel/UCzNZ46g2SGaC66WEjxIMYag", "https://yt3.googleusercontent.com/ytc/AIdro_kMkfp1N13Gecb_TeoNizjvZq_m3qn5GEAtsOsJf3sTZQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Peter Pan[/B] Spannende Abenteuer", "playlist/PLEsSrAkc-N6rXexCXeRYvk3m69rYU6LmT", "https://yt3.googleusercontent.com/ytc/AIdro_kElXO0Ln2cwmXqZECYT3wHnAUun28MduBMkPaFni_cOQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Barbie[/B]", "channel/UCSTlH3dDtCBT94sjO2MTyww", "https://yt3.googleusercontent.com/J5l0pIYLHcZByl5o70xs_fTVx0gqxN7FSl20UKtyne_EKszZlgsYqNrmnt9oAFUhUra70on8RA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Wendy[/B] Pferde sind ihr Leben", "playlist/PLAqP3cngI26r65PV1PZbYoSc5sjMHmt2_", "https://yt3.googleusercontent.com/ytc/AIdro_kk9NKO2oocRNI-ehYC1KtIBaT06bOGBy_0lwxQY-SUVg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Justice League[/B] DC Kids", "playlist/PLWH6DXF9upwP53YyfSKaLD_XbMwwgphuB", "https://yt3.googleusercontent.com/ytc/AIdro_ny-OEqibjYRcfyTNpP0Gr7zBhthcHZRMYNrc116J4a9Q=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Benjamin Blümchen[/B] BenjaminBlümchen.tv", "playlist/PL1SAyTUFBb74hUX32r6aqasGENGL4YShb", "https://yt3.googleusercontent.com/ytc/AIdro_nxryECurSuDeK6loDsbG2inLuGas2vBWi7zMCEYh-rlmo=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Bibi Blocksberg[/B] BibiBlocksberg.tv", "playlist/PLOZg6nrLYB7-EgluZK6CuMBzJtKhvFIAU", "https://yt3.googleusercontent.com/ytc/AIdro_kaXwWIBwqekbuxe6Dhcrgw25Aywv4Ujp824KcTfD0DhxI=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Mister Bean[/B]", "playlist/PLiDbV9ObbZLV_R9ofcSdJSPP4PSrobk3g", "https://yt3.googleusercontent.com/ytc/AIdro_kU_LbBWM7nAscqjTm9cu4cxYE8U0u2nvccYpc2iIrHbg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Pink Panther[/B] 1. Klassik Serie", "playlist/PL546904B9DC923B31", "https://yt3.googleusercontent.com/ytc/AIdro_lEeZP6Urk3JhCp_BnVamf57KSdAv-ZJb2nJqHmW-8ZIuM=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Pink Panther[/B] 2. Die neue Serie", "playlist/PL2MVdpCy9PxFMwC6UaXwNOTZMPUxiU6KB", "https://yt3.googleusercontent.com/ytc/AIdro_lEeZP6Urk3JhCp_BnVamf57KSdAv-ZJb2nJqHmW-8ZIuM=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tom & Jerry[/B] 1. Klassik Serie", "playlist/PLUCHDQsTRtWb1AlJQV0_ojhg5hpLjpdC_", "https://yt3.googleusercontent.com/l3sgu7fdZPWCiwrMak7lttvETa3QwED1ezhicGLc1kI3Q2ZyKe5bb3gP8OTwKx0aMNAIMy4YXw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tom & Jerry[/B] 2. Die neue Serie", "playlist/PL2MVdpCy9PxFf_BZJfjTtNuXTBdyu1ig9", "https://yt3.googleusercontent.com/l3sgu7fdZPWCiwrMak7lttvETa3QwED1ezhicGLc1kI3Q2ZyKe5bb3gP8OTwKx0aMNAIMy4YXw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Wickie und die starken Männer[/B] 1. Klassik Serie", "playlist/PLGP11O3gIZb-CAn5df2AE687pqGLxv2hr", "https://yt3.googleusercontent.com/ytc/AIdro_kx_tPHk_DdII3m8ZTy6o9nCdp6vuGS6lC9zvZBcXrciCI=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Wickie und die starken Männer[/B] 2. Die neue Serie", "playlist/PLGP11O3gIZb8jsxU_InVjehKgJy1Z1Eyj", "https://yt3.googleusercontent.com/ytc/AIdro_kx_tPHk_DdII3m8ZTy6o9nCdp6vuGS6lC9zvZBcXrciCI=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Biene Maja[/B] 1. Klassik Serie", "playlist/PLdHRcaRTf6chVA47IIGwDRJbvgkgBS6Xz", "https://yt3.googleusercontent.com/ytc/AIdro_k_scJq9HnRuToNP--3iIsRfqAAFa27DZG6X9yRS9C8Ctw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Biene Maja[/B] 2. Die neue Serie", "playlist/PLdHRcaRTf6cie56jn0JUsHVGMMSU4AqY-", "https://yt3.googleusercontent.com/ytc/AIdro_k_scJq9HnRuToNP--3iIsRfqAAFa27DZG6X9yRS9C8Ctw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Heidi[/B] 1. Klassik Serie", "playlist/-J_Clayv2_c&list=PLwz9HrBqSQF-WEycaVv_2m5o_4rmxICyn", "https://yt3.googleusercontent.com/ytc/AIdro_k2eKhwRc8WZGXcGEg0LQvZATEqa9IMYrQ039nfznwc15M=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Heidi[/B] 2. Die neue Serie", "playlist/PLwz9HrBqSQF9Y9W6hf-AwwBxTunNYrFvA", "https://yt3.googleusercontent.com/ytc/AIdro_k2eKhwRc8WZGXcGEg0LQvZATEqa9IMYrQ039nfznwc15M=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Das Dschungelbuch[/B] 1. Klassik Serie", "playlist/PLYekapbFEdMmzDN29jkQwATMx3BgznsdM", "https://yt3.googleusercontent.com/ytc/AIdro_mvfRl7am0OpQD3Ctwcsl1ks-Y75vNtISY_z0IOJRhHJQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Das Dschungelbuch[/B] 2. Die neue Serie", "playlist/PL_YOcJJS3cSLJ6-4pXn9SURAkw8Z98FdV", "https://yt3.googleusercontent.com/ytc/AIdro_mvfRl7am0OpQD3Ctwcsl1ks-Y75vNtISY_z0IOJRhHJQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Robin Hood[/B] 1. Klassik Serie", "playlist/PLYekapbFEdMlSWa8pmtr1EEQGI3lEofb3", "https://yt3.googleusercontent.com/qcHBiOPL7Tr5VPT17va19m1756JeJ3ekce-UAno-HervduE-uXffo0KBxpdFKKjp2vM-pkCs=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Robin Hood[/B] 2. Die neue Serie", "playlist/PLM5esjyCBU_qStUbQ_YqpE--k700AWoG5", "https://yt3.googleusercontent.com/qcHBiOPL7Tr5VPT17va19m1756JeJ3ekce-UAno-HervduE-uXffo0KBxpdFKKjp2vM-pkCs=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die Schlümpfe[/B] 1. Klassik Serie", "playlist/PLe9POcs8knUCprw4_XXxTm8hkGBVw6NHs", "https://yt3.googleusercontent.com/ytc/AIdro_lz1JwV2UcYvO3sU4glwAbVK7jXobyldkMW4I8LCyX_hEk=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Simba der Löwenkönig[/B] 1. Klassik Serie", "playlist/PLYekapbFEdMlAfVPEpc9EQxHP4nCIXd3T", "https://yt3.googleusercontent.com/ytc/AIdro_nT99re9EdPau__EwlwulmRIW4GHKioxyPBsNlh88FBow=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Christoph Columbus[/B] 1. Klassik Serie", "playlist/PLYekapbFEdMnpBAXde80pONK86-k_TPFl", "https://yt3.googleusercontent.com/ytc/AIdro_kwdQSy_A8r3HQ3nlzl8DZUTSPElLpishRBD2oxszbYig=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Meister Eder & sein Pumuckl[/B] 1. Klassik Serie", "playlist/PLpalD7hdQxE3nzQxRxxfmFk9D33EuPPtO", "https://yt3.googleusercontent.com/ytc/AIdro_nXpiXiPR937WSN2duuEAg366vba-YUkAahxAOimfDi6w=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Lucky Luke[/B]", "playlist/PLbfm0-ETt5G6yq5oFYSlAkaNLQXbOSttk", "https://yt3.googleusercontent.com/ytc/AIdro_mcGqztk0W5DB34AR7JL0kcv2jmoJk49Re_SG8ZlH8bZA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die kleinen Strolche[/B]", "playlist/PLueQL19LT3Kbf6zvCwESHFOS7uPvUeTdr", "https://yt3.googleusercontent.com/ytc/AIdro_kLWdfVshplzUekAptJLhCnd4vZR9tGVptbs1Jeo3X80w=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Grisu der kleine Drache[/B] 1. Klassik Serie", "playlist/PLfbRhPoVa4gNvf3OaRPHcvGpiTv_AJf9U", "https://yt3.googleusercontent.com/IgitEsBoWmeD_xSHFMiu8nwOuGvlCSCtOzUQxSm_91VXPa1nzEOnb3lcFfrfZnpe_DWkoWEq6A=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Alfred J Kwak[/B] 1. Klassik Serie", "playlist/PL58016D15BD79A97F", "https://yt3.googleusercontent.com/ytc/AIdro_kTe98feEy_Gbop5bwCS2BEOU2FyBqpvWL5hDNagEGysg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Scooby Doo[/B] 1. Klassik Serie", "playlist/PLJYf0JdTApCqEYfW77tMTtqN-gA65_LPW", "https://yt3.googleusercontent.com/R297M7jAVBV8_1NIsybn59l5XIvTVygHhrgu8F7iZCblqS-kHRGDWwDlOkR3Lgt13fHpYVvb=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kleo, das fliegende Einhorn[/B] Staffel 1", "playlist/PLx7XyMBfhgopl8zhHyagkK5MZP-CzXZCl", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Jim Knopf[/B] (Zeichentrickserie) Staffel 1-2", "playlist/PL3kvPcxZMm3asH5sX_M4kSvdkHCDwZn3h", "https://yt3.googleusercontent.com/ytc/AIdro_kEiju73Tovm8K3dMsI2Wzhz6j8ZPyHZ6fb20wJDSbejg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Zoes Zauberschrank[/B]", "PLPHcQYsAZvMA4PIV8JYR4ZZpJjZ8i4Jjs", "https://yt3.googleusercontent.com/PHGbrL1fSJgdN-1S69zDJ7GUVuw9ypSiq8skG1GAzxcESnwCgRYwv0yhe7sTR_5VS-NwjjIxyA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Trotro[/B]", "channel/UCAML9huAKtSutHftRbbNmTA", "https://yt3.googleusercontent.com/tHrYXausgAx1JUVDSaQkxoyJ3YA7TXMNSUIZESsKGDJcrcTpHcpke_uLriqZqh3D9IRP2cs4pQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tim & Struppi[/B]", "playlist/PLY25BVSAuho3YVOUhdzCvpE11y2GGz2Nf", "https://yt3.googleusercontent.com/ytc/AIdro_m8aufXYahT6M_Ls2moneQrvHYYgZX-65jKnau6_euSbg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Klassiker': [
|
||||
("[B]Flipper[/B] Staffel 1", "playlist/PLHEDxAYF32QJmHISru7c-MZn_sYt9MpCL", "https://yt3.googleusercontent.com/ytc/AIdro_mHgXhrgT5R7YBXCB6jozpG7ASarqCaiT5qKVi5s2fIWA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Flipper[/B] Staffel 2", "playlist/PLHEDxAYF32QIXWcvfBkxxzmfW6xI-i4Gl", "https://yt3.googleusercontent.com/ytc/AIdro_mHgXhrgT5R7YBXCB6jozpG7ASarqCaiT5qKVi5s2fIWA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Flipper[/B] Staffel 3", "playlist/PLHEDxAYF32QJPtxGimOndyo8VunXLk62Q", "https://yt3.googleusercontent.com/ytc/AIdro_mHgXhrgT5R7YBXCB6jozpG7ASarqCaiT5qKVi5s2fIWA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Pan Tau[/B] Staffel 1-3", "playlist/PLx7XyMBfhgoo-6tYzBcnVJv4BCZC41q_W", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die rote Zora und ihre Bande[/B] Alle Folgen", "playlist/PLx7XyMBfhgookKfFmo-hE7YfMCRA9YJIw", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Die Strandclique[/B] Staffel 1", "playlist/PLx7XyMBfhgoq2FJuMdl7RNh1TJGzZN7Kj", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Gegen den Wind[/B] Staffel 1", "playlist/PLx7XyMBfhgoqH3sckwgLRpmSI06VTEWUX", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Trickfilm-Klassiker[/B] DEFA", "playlist/PLx7XyMBfhgoq5obBLDvi58-ljZfZb0qzp", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Spielfilmklassiker zu Weihnachten[/B]", "playlist/PLx7XyMBfhgoqJHGE11Dr3oEhWMCDvRFLo", "https://yt3.googleusercontent.com/m9EMpzxqRblZQ9CuD6_b-KZEbXeGs_fYern_BKeANOTlQTZ-YynVvcUTKpoo3I0gVQ3X_0A1qK4=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kinderfilm Klassiker[/B]", "playlist/PLAroxwS0jZuTN6tscgrR_ubgHM4UaFBPv", "https://yt3.googleusercontent.com/OfyylHIKU_TcdZXA8gWLAv2Z7S4BhZtpDhRCDewnzvfgQkTfeSQGkrDeJSPr8CR_8XjlTLSB=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Geschichten': [
|
||||
("[B]Unser Sandmännchen[/B] von RBB", "user/sandmannshop", "https://yt3.googleusercontent.com/4cfX7kHTycQlmmphdCoFqqMcxBSbFmLZ05QdpjCANri0f0YJInrQwNj-MCH_IZl1onT_Wphvfg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]German Fairy Tales[/B]", "playlist/PLYJXC9hVK9ZdUXMrhOTC-kpfIEwJQ2c0u", "https://yt3.googleusercontent.com/ytc/AIdro_m7h0lhnTJCALtmVQpnOTCXLO5ON9OpPIZ3X45mLlmftA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Märchen für Kinder[/B] Gutenachtgeschichten", "playlist/PLRSUQa10y6VFV1kYPPc1hH0kSksmCvGU1", "https://yt3.googleusercontent.com/ytc/AIdro_nLeRvtckO3nepGlumWyHZl8IE2KkUin8Mp3A3YYIHEMQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Gute Nacht Geschichten[/B] DE.BedtimeStory.TV", "playlist/PLSeYZc0WTfTc-eqLP1bZLj0fJ13ZVfzBv", "https://yt3.googleusercontent.com/ytc/AIdro_ljVg3c7DXHAGU3leumLAX6txqgon5D9Re4uGQYzuleFw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Deine Märchenwelt[/B] Märchen, Geschichten, Sagen", "playlist/PLvsVeezf83quto2DL-5J4ZPmm2cYgwaFU", "https://yt3.googleusercontent.com/ytc/AIdro_nQkYXLLccuK_7aLuPMjZHAN4TVoiZkmqbleYIoaQHjxQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Geschichten für Kinder[/B]", "playlist/PLT8zuqWPJkYAJI2jiNa67Q3YaRMOVR-uL", "https://yt3.googleusercontent.com/ytc/AIdro_l_cpO1x5DrRvlYSdq_LaO1q_Epc06e8S5Ofme2kFSdKw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]SimsalaGrimm[/B]", "playlist/PLN5h7nQDQsiNAygibR61TxqtquFrvydt6", "https://yt3.googleusercontent.com/TPSvYLHHLDCNQea0b1viNt6mGEGs1We9vvnZMOeUcoILoektm_BDeVIlpcHNx5S1Gm7LFppZYg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Grimms Märchen[/B] Filme", "playlist/PL9A89EE24241DACF2", "https://yt3.googleusercontent.com/AgKnoiN2B95xJcDsF3wbrIvDaMWNwJ1l4d8VMEOlnMHWLRi2tQfBlLNyPxSjryi6rMhW8NCYug=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Buch': [
|
||||
("[B]Kinder- & Jugend Hörspiele[/B] HoerTalk", "playlist/PL6IcxBEYItDV7PWjFYdnHF9IsvAAGPaRx", "https://yt3.googleusercontent.com/ytc/AIdro_lUNtb8xK82q6O4iYTaNYFxj2syawEI_o-iLsngDA7j0g=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Hörspiel[/B] Guru", "channel/UCCT5k5hwaSIRGoJ5vALZuMg", "https://yt3.googleusercontent.com/65wFWDo8cizVOOLeW5hDP7VLDmka6zrpXObnlobNOo8vlPg40skZwYISCtWj5Scg7_qFsDnXmw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Jugend Hörspiele[/B] Spooks", "playlist/PLBCqvaIr4yUkjmEq5okDt5UAcNhqPgbq6", "https://yt3.googleusercontent.com/3jjA5W1C714Oej9uSA9VcisLgT63t1Kpvssf4cTrflyesf4Du2ZxwjhURMt5QRkbrWf332vE=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Pumuckl Hörspiele 1960-1975[/B] Hörspiel Fabrik", "playlist/PLo-9MW9vCL30fO8QcGDyzusOFW3XLRVXB", "https://yt3.googleusercontent.com/ytc/AIdro_nXpiXiPR937WSN2duuEAg366vba-YUkAahxAOimfDi6w=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tim & Struppi Hörspiele[/B]", "playlist/PLkLkiOLu9xekbDC9aYJDftPBqPs8oCa8D", "https://yt3.googleusercontent.com/65wFWDo8cizVOOLeW5hDP7VLDmka6zrpXObnlobNOo8vlPg40skZwYISCtWj5Scg7_qFsDnXmw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Benjamin Blümchen Hörspiele[/B] BenjaminBlümchen.tv", "playlist/PL1SAyTUFBb762GpJFH19_NvETovF_-NXu", "https://yt3.googleusercontent.com/ytc/AIdro_nxryECurSuDeK6loDsbG2inLuGas2vBWi7zMCEYh-rlmo=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Bibi Blocksberg[/B] BibiBlocksberg.tv", "playlist/PLOZg6nrLYB79IpjmXsBPoq2tuW1DjEb1J", "https://yt3.googleusercontent.com/ytc/AIdro_kaXwWIBwqekbuxe6Dhcrgw25Aywv4Ujp824KcTfD0DhxI=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Barbie Hörspiele[/B] ", "playlist/PLnH8v4sKlmyDprODopPXEZQ3B28MYCkQC", "https://yt3.googleusercontent.com/J5l0pIYLHcZByl5o70xs_fTVx0gqxN7FSl20UKtyne_EKszZlgsYqNrmnt9oAFUhUra70on8RA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Grimm´s Märchen Hörspiele[/B] Märchenwelt", "playlist/PL_7pajp36h-R_VBtOQMaFg7SAmlT02fX8", "https://yt3.googleusercontent.com/lARZK6WQ3oS0BX9U7ExdgAdcHSbazCDTHUvLoFF9FpFhY9tIPkBukcEVF2Ng0zknSHTvnj4q=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Märchen Hörspiele[/B] Hörspiel Fuchs", "playlist/PL49N08rhGF4dp7S5egoLmLT50_MqtTtVD", "https://yt3.googleusercontent.com/ytc/AIdro_mZpPJSHYcsJQ9rVpJ9-qXbsAvQF5rroosKij8JbP08_A=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Klassische Hörspiele[/B] Märchenwelt", "playlist/PL_7pajp36h-QXzz1ncxNJFtg2GZzHV1dL", "https://yt3.googleusercontent.com/lARZK6WQ3oS0BX9U7ExdgAdcHSbazCDTHUvLoFF9FpFhY9tIPkBukcEVF2Ng0zknSHTvnj4q=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Christliche Hörspiele[/B] die Bibel", "channel/UCJSF-0y7Pz7VUNH3cCdqwLw", "https://yt3.googleusercontent.com/ytc/AIdro_m0D75VUeq8rjNhGPl80HH8WMqMhGBHk0p8t61jPzUDWw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Urmel[/B] Hörspiel Serie", "playlist/PLa3cPVZ_c5hhE6mgOvbDAfLzxxiPDWkId", "https://yt3.googleusercontent.com/65wFWDo8cizVOOLeW5hDP7VLDmka6zrpXObnlobNOo8vlPg40skZwYISCtWj5Scg7_qFsDnXmw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Tim und Struppi[/B] Hörspiel", "playlist/PLa3cPVZ_c5hisrwl_5GopM5x57xDPauS7", "https://yt3.googleusercontent.com/65wFWDo8cizVOOLeW5hDP7VLDmka6zrpXObnlobNOo8vlPg40skZwYISCtWj5Scg7_qFsDnXmw=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Wissen': [
|
||||
("[B]KiWi[/B] Schlaue Fragen, schlaue Antworten!", "playlist/PLVWZ8fAnW6IbmOFpTtp6ImFFW-K4T83YN", "https://yt3.googleusercontent.com/ytc/AIdro_nRrCb-aovIu_su17Fed1V8J70QdFKP_f6H_6mc7TOBoJo=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]KiWi[/B] Professor Schlaufuchs", "playlist/PLVWZ8fAnW6IbqKL4j7uaR4RtPsqB6rKc0", "https://yt3.googleusercontent.com/ytc/AIdro_nRrCb-aovIu_su17Fed1V8J70QdFKP_f6H_6mc7TOBoJo=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Planet Schule[/B] von ARD", "playlist/PL93F091E59FDFDDBF", "https://yt3.googleusercontent.com/ytc/AIdro_nkghDj-XHzlJ0CCE1q4BXzL01ufINgm9KUiqfhaWTBjUnZ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Checker Welt[/B] Experimente", "playlist/PLXHkZNhCrU2ZGzKXPeq_8NY8ZF3coQyR9", "https://yt3.googleusercontent.com/lOR8wZvBdSO56a4CXiQm45EPeUoQEUN8ctVFJW75MDgrL_sFJ8SPe5KrW0owlTKQJUcjz1pnQQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]DIY Inspiration Kids Club[/B] Experimente", "playlist/PLjXEwjXTkbzqewdd_3DTgb0GZ_0LhECPJ", "https://yt3.googleusercontent.com/ytc/AIdro_mXiHnjWsyR6OwXG1zncEa08PxvWKnmVdc5Xo8XohfWLFs=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]PAXI[/B] European Space Agency", "playlist/PLbyvawxScNbvwcIVrGQV4p6g6cp9pH0To", "https://yt3.googleusercontent.com/gnGJqh7iQPl66irKn3xdT9BDv2K7LOPMFghqL0MHQKk5XjmK-nD9r7CrdYcIEWFmSC0rlr_a=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
|
||||
'Kinder Musik': [
|
||||
("[B]Sing mit Mir[/B] Kinderlieder", "playlist/PLu791Jb5lWoCOzceiS6SDeGW_u1s7x-0h", "https://yt3.googleusercontent.com/OZ_jeNxvsxe4NG-aefWNxG8NrKJTXoElYRQZ45ISKmXCVhu_RNSboaIMOKRfOWBveo61R9EeCg=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Hurra[/B] Kinderlieder", "playlist/PLz8hTTrU37YTw06tseX2sHpXMbDK9x_Ds", "https://yt3.googleusercontent.com/ytc/AIdro_kcnVO0eY6JzCRVjB2AUx7sndCRWLpdPnITOq2t06NaQQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kinderlieder[/B] zum Mitsingen und Tanzen", "playlist/PLM9BsUcYb5Mn8xN72IX5LUHj25_ssTN5_", "https://yt3.googleusercontent.com/BGoTUkK-TRjC6fC1fFP700dgrV5cjuHw-O8OcgGnqsHi3RoeU7b0hKm-0eS4dDOg-PWs5lz9Ew=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Karaoke[/B] Kinderlieder mit Biene Maja, Wickie und Co.", "playlist/PLCywMP0BLGOk_cTbmLNENN711N3Yw0hRF", "https://yt3.googleusercontent.com/ytc/AIdro_kQAr8CtbgD9MB9WP-U_AQsSDfAOVsQ4KsQliL2Ztc-T-E=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Disney[/B] Titelmusik", "playlist/PL4BrNFx1j7E6a6IKg8N0IgnkoamHlCHWa", "https://yt3.ggpht.com/c3_JbPq0s5lc_pfXglSsnNTMv4T-oEK6zvmQDBFqxmAmgmKn1yDm5141NzYqudS2qhzt50fDJw=s176-c-k-c0x00ffffff-no-rj-mo"),
|
||||
("[B]KinderliederTV.de[/B]", "playlist/PLmMaywx47bx5w5YJ3uLJz73X81srE4wlQ", "https://yt3.googleusercontent.com/puwtJUTPtemz8BDpE2ep4NL3RVqqNrwhQ2jBQ-Utkc32r6XJAbVHSXIJZLMWRMsAZhNHRj6fWR8=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]GiraffenaffenTV[/B]", "channel/UCUWTq9Jq97CNE9j28OarHbQ", "https://yt3.googleusercontent.com/5f81fzOw1sMs0u9zlz8hUqXWrDJ5XWbdsTM3z2VMgoAsPX_cENGCip8_YI8Yx9xsp7BfDjmZyQ=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Liederkiste[/B] Kinderlieder", "channel/UCAOmPP7Xt9YmPYtjettV3oA", "https://yt3.googleusercontent.com/uDGJirtwpri8ClvehxG1GMHIL5AXPDnHquIKQRmituyTdH46HK443mcSzBuUl3UDMNN6vkXfJA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B]Kika TanzAlarm[/B] | Mehr auf KiKA.de", "playlist/PLIFhkWbVDf6wcorvcRTbQvSYvYemSvJoa", "https://yt3.googleusercontent.com/eVEM7kLayi8-pFKQ2jMVMqWMMf-Sj-LFtPD5oD5d4vctMxwa_MxvYkYQOihpO8YxHO3Fo8qHVA=s160-c-k-c0x00ffffff-no-rj"),
|
||||
("[B][I]Weitere[/I][/B]", "Weitere", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
("[B][I]Suche[/I][/B]", "Suche", "special://home/addons/plugin.video.xstream/resources/art/sites/kids_tube.png"),
|
||||
],
|
||||
}
|
||||
|
||||
def search_playlists(query, max_results=5):
|
||||
search_url = "https://www.googleapis.com/youtube/v3/search"
|
||||
my_keys = ['AIzaSyBQ68nE4JxFSlyogirJUo8b4TYF2iGMJms', 'AIzaSyAyvS7LLZsBF6mNWiAmISYvdJWtu_MSvf4']
|
||||
key = random.choice(my_keys)
|
||||
params = {
|
||||
'part': 'snippet',
|
||||
'q': query,
|
||||
'type': 'playlist',
|
||||
'maxResults': max_results,
|
||||
'key': key}
|
||||
response = requests.get(search_url, params=params)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
playlists = data.get('items', [])
|
||||
sublists = []
|
||||
|
||||
if not playlists:
|
||||
xbmcgui.Dialog().notification('Kids_Tube', 'Not found')
|
||||
else:
|
||||
for i, item in enumerate(playlists, start=1):
|
||||
playlist_title = item['snippet']['title']
|
||||
playlist_id = 'playlist/' + item['id']['playlistId']
|
||||
playlist_icon = item['snippet']['thumbnails']['default']['url']
|
||||
sublists.append({'title': playlist_title, 'id': playlist_id, 'icon': playlist_icon})
|
||||
return sublists
|
||||
else:
|
||||
xbmcgui.Dialog().notification('Kids_Tube', 'Not found')
|
||||
|
||||
|
||||
def sub_list(action):
|
||||
youtube_fix.YT()
|
||||
params = ParameterHandler()
|
||||
action1 = '#' + str(action) + ' deutsch für kinder'
|
||||
action2 = '*' + str(action)
|
||||
apikey = cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
for List in sublists[str(action)]:
|
||||
name = List[0]
|
||||
id = List[1]
|
||||
icon = List[2]
|
||||
if apikey == '' or apikey == None:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/?addon_id=plugin.video.xstream"
|
||||
else:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/"
|
||||
if 'Weitere' in str(id):
|
||||
params.setParam('action', action1)
|
||||
params.setParam('sUrl', '')
|
||||
params.setParam('trumb', icon)
|
||||
cGui().addFolder(cGuiElement(name, SITE_IDENTIFIER, 'load'),params,bIsFolder=True)
|
||||
elif 'Suche' in str(id):
|
||||
params.setParam('action', action2)
|
||||
params.setParam('sUrl', '')
|
||||
params.setParam('trumb', icon)
|
||||
cGui().addFolder(cGuiElement(name, SITE_IDENTIFIER, 'load'),params,bIsFolder=True)
|
||||
else:
|
||||
params.setParam('trumb', icon)
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(name,SITE_IDENTIFIER,''),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
|
||||
|
||||
def sub_listw(action):
|
||||
youtube_fix.YT()
|
||||
params = ParameterHandler()
|
||||
sublist2 = search_playlists(action, max_results=50)
|
||||
apikey = cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
for List in sublist2:
|
||||
name = "[B]%s[/B]" % List['title']
|
||||
id = List['id']
|
||||
icon = List['icon']
|
||||
if apikey == '' or apikey == None:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/?addon_id=plugin.video.xstream"
|
||||
else:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/"
|
||||
params.setParam('trumb', icon)
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(name,SITE_IDENTIFIER,''),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
|
||||
|
||||
def keyb():
|
||||
heading = cConfig().getLocalizedString(30281)
|
||||
keyboard = xbmc.Keyboard('default', 'heading', True)
|
||||
keyboard.setDefault()
|
||||
keyboard.setHeading(heading)
|
||||
keyboard.setHiddenInput(False)
|
||||
keyboard.doModal()
|
||||
if keyboard.isConfirmed() and not keyboard.getText() == '':
|
||||
return keyboard.getText()
|
||||
else:sys.exit()
|
||||
|
||||
def search(action):
|
||||
youtube_fix.YT()
|
||||
params = ParameterHandler()
|
||||
apikey = cConfig('plugin.video.youtube').getSetting('youtube.api.key')
|
||||
query= keyb()
|
||||
sublist2 = search_playlists(query + ' ' + action + ' deutsch', max_results=50)
|
||||
for List in sublist2:
|
||||
name = "[B]%s[/B]" % List['title']
|
||||
id = List['id']
|
||||
icon = List['icon']
|
||||
if apikey == '' or apikey == None:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/?addon_id=plugin.video.xstream"
|
||||
else:
|
||||
sUrl="plugin://plugin.video.youtube/" + id + "/"
|
||||
params.setParam('trumb', icon)
|
||||
params.setParam('sUrl', sUrl)
|
||||
cGui().addFolder(cGuiElement(name,SITE_IDENTIFIER,''),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
|
||||
|
||||
def main_list():
|
||||
params = ParameterHandler()
|
||||
for name, id, icon in channellist:
|
||||
params.setParam('action', id)
|
||||
params.setParam('trumb', icon)
|
||||
cGui().addFolder(cGuiElement(name, SITE_IDENTIFIER, 'load'),params,bIsFolder=True)
|
||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True)
|
||||
|
||||