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/
This commit is contained in:
Paperclip
2026-08-17 21:24:11 +02:00
parent 47ae3285bf
commit 7b8f40d9da
138 changed files with 24994 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.paperclip/

View File

@@ -1,5 +1,10 @@
# Xstream-Standalone # 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.

View 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)

View 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>

View 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()

View File

@@ -0,0 +1 @@

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@

View 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

View 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

View 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

View 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')

View File

@@ -0,0 +1 @@

View 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

View 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')))

View 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

View 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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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')

View 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', '')

View 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

View 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('&nbsp;', '')
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)

File diff suppressed because one or more lines are too long

View 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))

View 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

View 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

View 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

View 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 (('\\/', '/'), ('&amp;', '&'), ('\\u00c4', 'Ä'), ('\\u00e4', 'ä'),
('\\u00d6', 'Ö'), ('\\u00f6', 'ö'), ('\\u00dc', 'Ü'), ('\\u00fc', 'ü'),
('\\u00df', 'ß'), ('\\u2013', '-'), ('\\u00b2', '²'), ('\\u00b3', '³'),
('\\u00e9', 'é'), ('\\u2018', ''), ('\\u201e', ''), ('\\u201c', ''),
('\\u00c9', 'É'), ('\\u2026', '...'), ('\\u202f', 'h'), ('\\u2019', ''),
('\\u0308', '̈'), ('\\u00e8', 'è'), ('#038;', ''), ('\\u00f8', 'ø'),
('', '/'), ('\\u00e1', 'á'), ('&#8211;', '-'), ('&#8220;', ''), ('&#8222;', ''),
('&#8217;', ''), ('&#8230;', ''), ('\\u00bc', '¼'), ('\\u00bd', '½'), ('\\u00be', '¾'),
('\\u2153', ''), ('\\u002A', '*')):
s = s.replace(*t)
# Umlaute HTML konvertieren
for h in (('\\/', '/'), ('&#x26;', '&'), ('&#039;', "'"), ("&#39;", "'"),
('&#xC4;', 'Ä'), ('&#xE4;', 'ä'), ('&#xD6;', 'Ö'), ('&#xF6;', 'ö'),
('&#xDC;', 'Ü'), ('&#xFC;', 'ü'), ('&#xDF;', 'ß') , ('&#xB2;', '²'),
('&#xDC;', '³'), ('&#xBC;', '¼'), ('&#xBD;', '½'), ('&#xBE;', '¾'),
('&#8531;', ''), ('&#8727;', '*')):
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()

View 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)

View 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

View 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

View 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]

File diff suppressed because it is too large Load Diff

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

View 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()

View 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="([^"]+)">&gt;</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

View 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

View 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)

View 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"),
("ARTEtv 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)

View 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)

View 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()

View 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()

View 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()

View 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)

View 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)

Some files were not shown because too many files have changed in this diff Show More