Created
February 11, 2026 10:50
-
-
Save CarstenG2/45e4b6ee18798c978313cdbb71f601ac to your computer and use it in GitHub Desktop.
xShip: download manager support (JD1, JD2, MyJD, PyLoad) - all new + modified files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 2023-05-10 | |
| # edit 2025-06-12 | |
| import sys, json | |
| from resources.lib import control | |
| params = dict(control.parse_qsl(control.urlsplit(sys.argv[2]).query)) | |
| action = params.get('action') | |
| name = params.get('name') | |
| table = params.get('table') | |
| title = params.get('title') | |
| source = params.get('source') | |
| # ------ navigator -------------- | |
| if action == None or action == 'root': | |
| from resources.lib.indexers import navigator | |
| navigator.navigator().root() | |
| elif action == 'pluginInfo': | |
| from resources.lib import supportinfo | |
| supportinfo.pluginInfo() | |
| elif action == 'movieNavigator': | |
| from resources.lib.indexers import navigator | |
| navigator.navigator().movies() | |
| elif action == 'tvNavigator': | |
| from resources.lib.indexers import navigator | |
| navigator.navigator().tvshows() | |
| elif action == 'toolNavigator': | |
| from resources.lib.indexers import navigator | |
| navigator.navigator().tools() | |
| elif action == 'downloadNavigator': | |
| from resources.lib.indexers import navigator | |
| navigator.navigator().downloads() | |
| # ------------------------------------------- | |
| elif action == 'download': | |
| image = params.get('image') | |
| from resources.lib import downloader | |
| from resources.lib import sources | |
| try: downloader.download(name, image, sources.sources().sourcesResolve(json.loads(source)[0], True)) | |
| except: pass | |
| elif action == 'sendToJD': | |
| from resources.lib import sources | |
| from resources.lib.handler.jdownloaderHandler import cJDownloaderHandler | |
| url = sources.sources().sourcesResolve(json.loads(source)[0], True) | |
| if url: | |
| if '|' in url: url = url.split('|')[0] | |
| cJDownloaderHandler().sendToJDownloader(url) | |
| elif action == 'sendToJD2': | |
| from resources.lib import sources | |
| from resources.lib.handler.jdownloader2Handler import cJDownloader2Handler | |
| url = sources.sources().sourcesResolve(json.loads(source)[0], True) | |
| if url: | |
| if '|' in url: url = url.split('|')[0] | |
| cJDownloader2Handler().sendToJDownloader2(url) | |
| elif action == 'sendToMyJD': | |
| from resources.lib import sources | |
| from resources.lib.handler.myjdownloaderHandler import cMyJDownloaderHandler | |
| url = sources.sources().sourcesResolve(json.loads(source)[0], True) | |
| if url: | |
| if '|' in url: url = url.split('|')[0] | |
| cMyJDownloaderHandler().sendToMyJDownloader(url, name) | |
| elif action == 'sendToPyLoad': | |
| from resources.lib import sources | |
| from resources.lib.handler.pyLoadHandler import cPyLoadHandler | |
| url = sources.sources().sourcesResolve(json.loads(source)[0], True) | |
| if url: | |
| if '|' in url: url = url.split('|')[0] | |
| cPyLoadHandler().sendToPyLoad(name, url) | |
| elif action == 'playExtern': | |
| import json | |
| if not control.visible(): control.busy() | |
| try: | |
| sysmeta = {} | |
| for key, value in params.items(): | |
| if key == 'action': continue | |
| elif key == 'year' or key == 'season' or key == 'episode': value = int(value) | |
| if value == 0: continue | |
| sysmeta.update({key : value}) | |
| if int(params.get('season')) == 0: | |
| mediatype = 'movie' | |
| else: | |
| mediatype = 'tvshow' | |
| sysmeta.update({'mediatype': mediatype}) | |
| # if control.getSetting('hosts.mode') == '2': | |
| # sysmeta.update({'select': '2'}) | |
| # else: | |
| # sysmeta.update({'select': '1'}) | |
| sysmeta.update({'select': control.getSetting('hosts.mode')}) | |
| sysmeta = json.dumps(sysmeta) | |
| params.update({'sysmeta': sysmeta}) | |
| from resources.lib import sources | |
| sources.sources().play(params) | |
| except: | |
| pass | |
| elif action == 'playURL': | |
| try: | |
| import resolveurl | |
| import xbmcgui, xbmc | |
| #url = 'https://streamvid.net/embed-uhgo683xes41' | |
| #url = 'https://moflix-stream.click/v/gcd0aueegeia' | |
| url = xbmcgui.Dialog().input("URL Input") | |
| hmf = resolveurl.HostedMediaFile(url=url, include_disabled=True, include_universal=False) | |
| try: | |
| if hmf.valid_url(): url = hmf.resolve() | |
| except: | |
| pass | |
| item = xbmcgui.ListItem('URL-direkt') | |
| kodiver = int(xbmc.getInfoLabel("System.BuildVersion").split(".")[0]) | |
| if ".m3u8" in url or '.mpd' in url: | |
| item.setProperty("inputstream", "inputstream.adaptive") | |
| if '.mpd' in url: | |
| if kodiver < 21: item.setProperty('inputstream.adaptive.manifest_type', 'mpd') | |
| item.setMimeType('application/dash+xml') | |
| else: | |
| if kodiver < 21: item.setProperty('inputstream.adaptive.manifest_type', 'hls') | |
| item.setMimeType("application/vnd.apple.mpegurl") | |
| item.setContentLookup(False) | |
| if '|' in url: | |
| stream_url, strhdr = url.split('|') | |
| item.setProperty('inputstream.adaptive.stream_headers', strhdr) | |
| if kodiver > 19: item.setProperty('inputstream.adaptive.manifest_headers', strhdr) | |
| # item.setPath(stream_url) | |
| url = stream_url | |
| item.setPath(url) | |
| xbmc.Player().play(url, item) | |
| except: | |
| #print('Kein Video Link gefunden') | |
| control.infoDialog("Keinen Video Link gefunden", sound=True, icon='WARNING', time=1000) | |
| elif action == 'UpdatePlayCount': | |
| from resources.lib import playcountDB | |
| playcountDB.UpdatePlaycount(params) | |
| control.execute('Container.Refresh') | |
| # listings ------------------------------- | |
| elif action == 'listings': | |
| from resources.lib.indexers import listings | |
| listings.listings().get(params) | |
| elif action == 'movieYears': | |
| from resources.lib.indexers import listings | |
| listings.listings().movieYears() | |
| elif action == 'movieGenres': | |
| from resources.lib.indexers import listings | |
| listings.listings().movieGenres() | |
| elif action == 'tvGenres': | |
| from resources.lib.indexers import listings | |
| listings.listings().tvGenres() | |
| # search ---------------------- | |
| elif action == 'searchNew': | |
| from resources.lib import searchDB | |
| searchDB.search_new(table) | |
| elif action == 'searchClear': | |
| from resources.lib import searchDB | |
| searchDB.remove_all_query(table) | |
| # if len(searchDB.getSearchTerms()) == 0: | |
| # control.execute('Action(ParentDir)') | |
| elif action == 'searchDelTerm': | |
| from resources.lib import searchDB | |
| searchDB.remove_query(name, table) | |
| # if len(searchDB.getSearchTerms()) == 0: | |
| # control.execute('Action(ParentDir)') | |
| # person ---------------------- | |
| elif action == 'person': | |
| from resources.lib.indexers import person | |
| person.person().get(params) | |
| elif action == 'personSearch': | |
| from resources.lib.indexers import person | |
| person.person().search() | |
| elif action == 'personCredits': | |
| from resources.lib.indexers import person | |
| person.person().getCredits(params) | |
| elif action == 'playfromPerson': | |
| if not control.visible(): control.busy() | |
| sysmeta = json.loads(params['sysmeta']) | |
| if sysmeta['mediatype'] == 'movie': | |
| from resources.lib.indexers import movies | |
| sysmeta = movies.movies().super_meta(sysmeta['tmdb_id']) | |
| sysmeta = json.dumps(sysmeta) | |
| else: | |
| from resources.lib.indexers import tvshows | |
| sysmeta = tvshows.tvshows().super_meta(sysmeta['tmdb_id']) | |
| sysmeta = control.quote_plus(json.dumps(sysmeta)) | |
| params.update({'sysmeta': sysmeta}) | |
| from resources.lib import sources | |
| sources.sources().play(params) | |
| # movies ---------------------- | |
| elif action == 'movies': | |
| from resources.lib.indexers import movies | |
| movies.movies().get(params) | |
| elif action == 'moviesSearch': | |
| from resources.lib.indexers import movies | |
| movies.movies().search() | |
| # tvshows --------------------------------- | |
| elif action == 'tvshows': # 'tvshowPage' | |
| from resources.lib.indexers import tvshows | |
| tvshows.tvshows().get(params) | |
| elif action == 'tvshowsSearch': | |
| from resources.lib.indexers import tvshows | |
| tvshows.tvshows().search() | |
| # seasons --------------------------------- | |
| elif action == 'seasons': | |
| from resources.lib.indexers import seasons | |
| seasons.seasons().get(params) # params | |
| # episodes --------------------------------- | |
| elif action == 'episodes': | |
| from resources.lib.indexers import episodes | |
| episodes.episodes().get(params) | |
| # sources --------------------------------- | |
| elif action == 'play': | |
| if not control.visible(): control.busy() | |
| from resources.lib import sources | |
| sources.sources().play(params) | |
| elif action == 'addItem': | |
| from resources.lib import sources | |
| sources.sources().addItem(title) | |
| elif action == 'playItem': | |
| if not control.visible(): control.busy() | |
| from resources.lib import sources | |
| sources.sources().playItem(title, source) | |
| # Settings ------------------------------ | |
| elif action == "settings": # alle Quellen aktivieren / deaktivieren | |
| from resources import settings | |
| settings.run(params) | |
| elif action == 'addonSettings': | |
| # query = None | |
| query = params.get('query') | |
| control.openSettings(query) | |
| elif action == 'resetSettings': | |
| status = control.resetSettings() | |
| if status: | |
| control.reload_profile() | |
| control.sleep(500) | |
| control.execute('RunAddon("%s")' % control.addonId) | |
| elif action == 'resolverSettings': | |
| import resolveurl as resolver | |
| resolver.display_settings() | |
| # try: | |
| # import pydevd | |
| # if pydevd.connected: pydevd.kill_all_pydev_threads() | |
| # except: | |
| # pass | |
| # finally: | |
| # exit() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| # Python 3 | |
| import re | |
| from resources.lib import control | |
| 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: | |
| control.infoDialog('Einstellungen nicht konfiguriert', heading='JDownloader 2', icon='ERROR') | |
| return False | |
| if self.__checkConnection() is False: | |
| control.infoDialog('Verbindung fehlgeschlagen', heading='JDownloader 2', icon='ERROR') | |
| return False | |
| if self.__download(sUrl) is True: | |
| control.infoDialog('Link gesendet', heading='JDownloader 2', icon='INFO') | |
| 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('xShip -> [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('xShip -> [jdownloader2Handler]: check JD2 Addon settings', LOGNOTICE) | |
| bEnabled = control.getSetting('jd2_enabled') | |
| if bEnabled == 'true': | |
| return True | |
| return False | |
| def __getHost(self): | |
| return control.getSetting('jd2_host') | |
| def __getPort(self): | |
| return control.getSetting('jd2_port') | |
| def __checkConnection(self): | |
| log('xShip -> [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| # Python 3 | |
| from xbmc import LOGINFO as LOGNOTICE, log | |
| from resources.lib import control | |
| from urllib.request import Request, urlopen | |
| class cJDownloaderHandler: | |
| def sendToJDownloader(self, sUrl): | |
| if self.__checkConfig() == False: | |
| control.infoDialog('Einstellungen nicht konfiguriert', heading='JDownloader', icon='ERROR') | |
| return False | |
| if self.__checkConnection() == False: | |
| control.infoDialog('Verbindung fehlgeschlagen', heading='JDownloader', icon='ERROR') | |
| return False | |
| bDownload = self.__download(sUrl) | |
| if bDownload == True: | |
| control.infoDialog('Link gesendet', heading='JDownloader', icon='INFO') | |
| def __checkConfig(self): | |
| log('xShip -> [jdownloaderHandler]: check JD Addon settings', LOGNOTICE) | |
| bEnabled = control.getSetting('jd_enabled') | |
| if bEnabled == 'true': | |
| return True | |
| return False | |
| def __getHost(self): | |
| return control.getSetting('jd_host') | |
| def __getPort(self): | |
| return control.getSetting('jd_port') | |
| def __getAutomaticStart(self): | |
| bAutomaticStart = control.getSetting('jd_automatic_start') | |
| if bAutomaticStart == 'true': | |
| return True | |
| return False | |
| def __getLinkGrabber(self): | |
| bGrabber = control.getSetting('jd_grabber') | |
| if bGrabber == '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('xShip -> [jdownloaderHandler]: JD Link: ' + str(sLinkForJd), LOGNOTICE) | |
| request = Request(sLinkForJd) | |
| urlopen(request).read() | |
| return True | |
| def __createJDUrl(self, sFileUrl, sHost, sPort, bAutomaticDownload, bLinkGrabber): | |
| sGrabber = '1' if bLinkGrabber else '0' | |
| sAutomaticStart = '1' if bAutomaticDownload else '0' | |
| sUrl = 'http://' + str(sHost) + ':' + str(sPort) + '/action/add/links/grabber' + sGrabber + '/start' + sAutomaticStart + '/' + sFileUrl | |
| return sUrl | |
| def __checkConnection(self): | |
| log('xShip -> [jdownloaderHandler]: check JD Connection', LOGNOTICE) | |
| sHost = self.__getHost() | |
| sPort = self.__getPort() | |
| sLinkForJd = 'http://' + str(sHost) + ':' + str(sPort) | |
| try: | |
| request = Request(sLinkForJd) | |
| urlopen(request).read() | |
| return True | |
| except Exception: | |
| return False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| # Python 3 | |
| from resources.lib.handler import myjdapi | |
| from resources.lib import control | |
| from xbmc import LOGINFO as LOGNOTICE, log | |
| class cMyJDownloaderHandler: | |
| def sendToMyJDownloader(self, sUrl, sMovieTitle): | |
| if self.__checkConfig() == False: | |
| control.infoDialog('Einstellungen nicht konfiguriert', heading='My.JDownloader', icon='ERROR') | |
| return False | |
| jd = myjdapi.Myjdapi() | |
| try: | |
| jd.connect(self.__getUser(), self.__getPass()) | |
| except Exception: | |
| control.infoDialog('Verbindung fehlgeschlagen', heading='My.JDownloader', icon='ERROR') | |
| return False | |
| try: | |
| jd.update_devices() | |
| except Exception: | |
| control.infoDialog('Keine Geraete gefunden', heading='My.JDownloader', icon='ERROR') | |
| return False | |
| try: | |
| device = jd.get_device(self.__getDevice()) | |
| if device.linkgrabber.add_links([{"autostart": False, "links": sUrl, "packageName": sMovieTitle}])['id'] > 0: | |
| control.infoDialog('Link gesendet', heading='My.JDownloader', icon='INFO') | |
| return True | |
| except Exception: | |
| control.infoDialog('Fehler beim Senden', heading='My.JDownloader', icon='ERROR') | |
| return False | |
| def __checkConfig(self): | |
| log('xShip -> [myjdownloaderHandler]: check MYJD Addon settings', LOGNOTICE) | |
| if control.getSetting('myjd_enabled') == 'true': | |
| return True | |
| return False | |
| def __getDevice(self): | |
| return control.getSetting('myjd_device') | |
| def __getUser(self): | |
| return control.getSetting('myjd_user') | |
| def __getPass(self): | |
| return control.getSetting('myjd_pass') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| # Python 3 | |
| import sys | |
| from resources.lib import control | |
| 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 sendToPyLoad(self, sPackage, sUrl): | |
| log('xShip -> [pyLoadHandler]: PyLoad package: ' + str(sPackage) + ', ' + str(sUrl), LOGNOTICE) | |
| if self.__sendLinkToCore(sPackage, sUrl): | |
| control.infoDialog('Link gesendet', heading='PyLoad', icon='INFO') | |
| else: | |
| control.infoDialog('Senden fehlgeschlagen', heading='PyLoad', icon='ERROR') | |
| def __sendLinkToCore(self, sPackage, sUrl): | |
| log('xShip -> [pyLoadHandler]: Sending link...', LOGNOTICE) | |
| try: | |
| py_host = control.getSetting('pyload_host') | |
| py_port = control.getSetting('pyload_port') | |
| py_user = control.getSetting('pyload_user') | |
| py_passwd = control.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('xShip -> [pyLoadHandler]: Attempting 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('xShip -> [pyLoadHandler]: PyLoad API call: ' + py_url, LOGNOTICE) | |
| sock = opener.open(py_url).read() | |
| sock.close() | |
| return True | |
| except HTTPError as e: | |
| log('xShip -> [pyLoadHandler]: unable to send link: Error= ' + str(sys.exc_info()[0]), LOGNOTICE) | |
| log(str(e.code), LOGNOTICE) | |
| try: | |
| sock.close() | |
| except Exception: | |
| log('xShip -> [pyLoadHandler]: unable to close socket...', LOGNOTICE) | |
| return False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8" standalone="yes"?> | |
| <settings> | |
| <category label="Allgemein"> | |
| <setting id="hosts.mode" type="enum" label="Standard-Aktion" values="Dialog|Verzeichnis|Autoplay" default="1" /> | |
| <setting id="scrapers.timeout" type="slider" label="Zeitlimit für Indexseiten" default="35" range="10,60" option="int" /> | |
| <setting id="fanart" type="bool" label="Fanart verwenden" default="true" /> | |
| <setting id="search.doku" type="bool" label="Auch nach Dokumentationen suchen" default="false" /> | |
| <setting type="lsep" label="Einstellungen RequestHandler" /> | |
| <setting default="10" id="requestTimeout" label="Timeout für HTTP-Anfragen in Sekunden" option="int" range="1,1,60" type="slider" /> | |
| <setting default="600" id="cacheTime" label="Cachezeit für Webseiten (HtmlCache) in Sekunden" option="int" range="0,60,1800" type="slider" /> | |
| <setting default="true" id="volatileHtmlCache" type="bool" label= "HTML Inhalte werden nur im RAM zwischengespeichert" visible="true"/> | |
| <setting default="2" id="cacheDeltaDay" label="Html Cache nach Tage löschen" option="int" range="1,1,7" type="slider" /> | |
| <setting id="delHtmlCache" label="[B][COLOR red]Html Cache einmalig bei Neustart löschen[/COLOR][/B]" type="bool" default="false" /> | |
| <setting type="lsep" label="Status - gesehen / ungesehen" /> | |
| <setting type="text" label="[COLOR blue]Auf leistungsschwachen Geräten benötigt diese [CR]Komfortfunktion etwas mehr Zeit![/COLOR]" /> | |
| <setting id="status.refresh" type="text" label="Status aktualisieren (refresh)" default="" visible="false"/> | |
| <setting id="status.refresh.movies" type="bool" label="Filme - Status aktualisieren (refresh)" default="true" /> | |
| <setting id="status.refresh.episodes" type="bool" label="Episoden - Status aktualisieren (refresh)" default="true" /> | |
| <setting id="status.position" type="bool" label="Status - Bei Serien die aktuelle Position setzen" default="true" /> | |
| <setting id="status.debug" type="bool" label="Debug" default="false" /> | |
| </category> | |
| <category label="Indexseiten (DE)"> | |
| <setting type="text" label="[COLOR blue]ACHTUNG: Die folgenden Optionen werden sofort[/COLOR]" /> | |
| <setting type="text" label="[COLOR blue]und für [B]alle[/B] Indexseiten ausgeführt![/COLOR]" /> | |
| <setting type="sep"/> | |
| <!--<setting label="Einstellung auf Defaults setzen" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=defaultsGerman)"/> --> | |
| <setting label="Alle Quellen aktivieren" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=toggleGerman&setting=true)" option=""/> | |
| <setting label="Alle Quellen deaktivieren" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=toggleGerman&setting=false)" option=""/> | |
| <!-- <setting type="lsep" label="Scrapers" /> --> | |
| <setting type="sep"/> | |
| <setting id="provider.aniworld" type="bool" label="ANIWORLD | [B][COLOR green]Serien[/COLOR][/B]" default="false"/> | |
| <setting id="provider.aniworld.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.aniworld.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.einschalten" type="bool" label="EINSCHALTEN | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.einschalten.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.einschalten.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.filmpalast" type="bool" label="FILMPALAST | [B][COLOR blue] Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.filmpalast.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.filmpalast.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.filmpro" type="bool" label="FILMPRO | [B][COLOR blue] Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.filmpro.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.filmpro.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <!--<setting id="provider.flimmerstube" type="bool" label="FLIMMERSTUBE | [B][COLOR blue] Filme & Serien[/COLOR][/B]" default="false" /> | |
| <setting id="provider.flimmerstube.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.flimmerstube.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/>--> | |
| <setting id="provider.hdfilme" type="bool" label="HDFILME | [B][COLOR blue] Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.hdfilme.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.hdfilme.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <!--<setting id="provider.huhu" type="bool" label="HUHU | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.huhu.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.huhu.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/>--> | |
| <setting id="provider.kinoger" type="bool" label="KINOGER | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.kinoger.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.kinoger.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.kinokiste" type="bool" label="KINOKISTE | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.kinokiste.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.kinokiste.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.kinox" type="bool" label="KINOX | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.kinox.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.kinox.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.kkiste" type="bool" label="KKISTE | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.kkiste.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.kkiste.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting id="provider.kkiste.checkHoster" type="bool" label="Streams auf Verfügbarkeit überprüfen (langsamer)" default="true" visible="eq(-2,true)"/> | |
| </category> | |
| <category label="Indexseiten 2 (DE)"> | |
| <setting type="text" label="[COLOR blue]ACHTUNG: Die folgenden Optionen werden sofort[/COLOR]" /> | |
| <setting type="text" label="[COLOR blue]und für [B]alle[/B] Indexseiten ausgeführt![/COLOR]" /> | |
| <setting type="sep"/> | |
| <!--<setting label="Einstellung auf Defaults setzen" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=defaultsGerman)"/> --> | |
| <setting label="Alle Quellen aktivieren" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=toggleGerman&setting=true)" option=""/> | |
| <setting label="Alle Quellen deaktivieren" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=toggleGerman&setting=false)" option=""/> | |
| <!-- <setting type="lsep" label="Scrapers" /> --> | |
| <setting type="sep"/> | |
| <setting id="provider.megakino" type="bool" label="MEGAKINO | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.megakino.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.megakino.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.moflix" type="bool" label="MOFLIX-STREAM | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.moflix.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.moflix.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.movie2k" type="bool" label="MOVIE2k | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true"/> | |
| <setting id="provider.movie2k.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.movie2k.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.movie4k" type="bool" label="MOVIE4k | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true"/> | |
| <setting id="provider.movie4k.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.movie4k.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting id="provider.movie4k.checkHoster" type="bool" label="Streams auf Verfügbarkeit überprüfen" default="true" visible="eq(-2,true)"/> | |
| <setting type="sep"/> | |
| <setting id="provider.netzkino" type="bool" label="NETZKINO | [B][COLOR red]Filme[/COLOR][/B]" default="false" /> | |
| <setting id="provider.netzkino.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.netzkino.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.serienstream" type="bool" label="SERIENSTREAM | [B][COLOR green]Serien[/COLOR][/B]" default="true"/> | |
| <setting id="provider.serienstream.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.serienstream.check" label="Check" type="bool" default="true" visible="false"/> | |
| <!--<setting type="sep"/> | |
| <setting id="provider.serien-stream" type="bool" label="Serien-Stream | [B][COLOR green]Serien[/COLOR][/B]" default="true"/> | |
| <setting id="provider.serien-stream.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.serien-stream.check" label="Check" type="bool" default="true" visible="false"/>--> | |
| <setting type="sep"/> | |
| <setting id="provider.streamcloud" type="bool" label="STREAMCLOUD | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting id="provider.streamcloud.domain" label="[B][COLOR red]PluginError oder Domain nicht erreichbar[/COLOR][/B]" type="text" default="" visible="eq(1,false)"/> | |
| <setting id="provider.streamcloud.check" label="Check" type="bool" default="true" visible="false"/> | |
| <setting type="sep"/> | |
| <setting id="provider.vavoo" type="bool" label="VAVOO | [B][COLOR blue]Filme & Serien[/COLOR][/B]" default="true" /> | |
| <setting type="sep"/> | |
| </category> | |
| <category label="Wiedergabe"> | |
| <setting id="progress.dialog" type="enum" label="Fortschrittsdialog" values="Vordergrund|Hintergrund" default="1" /> | |
| <!--<setting id="prem.identify" type="enum" label="Textfarbe für Premium-Links" values="Blau|Rot|Gelb|Altrosa|Cyan|Grasgrün|Gold|Magenta|Gelbgrün|Ohne Farbe" default="0" /> --> | |
| <setting type="lsep" label="Dateinanbieter-Filter" /> | |
| <setting id="hosts.quality" type="enum" label="Höchste Qualität" values="4K|1440p|1080p|720p|480p" default="0" /> | |
| <setting id="hosts.sort.priority" type="bool" label="Indexseiten bei Serien nach Priorität sortieren" default="true" /> | |
| <setting id="hosts.sort.provider" type="bool" label="Indexseiten nach Anbieter sortieren" default="true" /> | |
| <setting id="hosts.refresh" type="text" label="Status aktualisieren (refresh)" default="" visible="true"/> | |
| <setting id="hosts.limit" type="bool" label="Limit Hosteranzahl" default="False" /> | |
| <setting id="hosts.limit.num" type="slider" label="Liste begrenzen auf:" default="15" range="5,50" option="int" visible="eq(-1,true)"/> | |
| <setting id="hosts.filter" type="text" label="Hoster ausschließen (Liste)" default=""/> | |
| <setting type="lsep" label="Bei Wiedergabe-Start..." /> | |
| <setting id="bookmarks" type="bool" label="Fortsetzen" default="true" /> | |
| <setting id="bookmarks.auto" type="bool" label="Automatisch Fortsetzen" default="true" enable="!eq(-1,false)" /> | |
| </category> | |
| <category label="Konten"> | |
| <setting type="lsep" label="Konto [B]SerienStream.to[/B]"/> | |
| <setting id="serienstream.user" type="text" label="Email" default=""/> | |
| <setting id="serienstream.pass" type="text" option="hidden" label="Password" default=""/> | |
| <setting type="lsep" label="Konto [B]FlimmerStube.com[/B]"/> | |
| <setting id="flimmerstube.user" type="text" label="Email" default=""/> | |
| <setting id="flimmerstube.pass" type="text" option="hidden" label="Password" default=""/> | |
| <setting type="lsep" label="Konto [B]Aniworld.io[/B]"/> | |
| <setting id="aniworld.user" type="text" label="Email" default=""/> | |
| <setting id="aniworld.pass" type="text" option="hidden" label="Password" default=""/> | |
| <setting type="lsep" label="TMDb" /> | |
| <setting id="api.tmdb" type="text" option="hidden" label="API-Key" default="0b529d296545c2545a68db5cb903cd94" /> | |
| <setting type="lsep" label="TRAKT" /> | |
| <setting id="api.trakt" type="text" option="hidden" label="API-Key" default="85d3b28a69fd4d8162b3ea78689e1c8b15ff2ad9c1430ef6fc5a950662aee3ec" /> | |
| <setting type="lsep" label="FANART.TV" /> | |
| <setting id="api.fanart.tv" type="text" label="API-Key" option="hidden" default="e376e8591e1580089d93ab4135fcc082" /> | |
| <setting type="lsep" label="Konto [B]opensubtitles.org[/B]"/> | |
| <!-- <setting id="subtitles.os_user" type="text" label="UserName" default=""/> | |
| <setting id="subtitles.os_pass" type="text" option="hidden" label="Password" default=""/> --> | |
| </category> | |
| <category label="Downloads / Untertitel"> | |
| <setting id="downloads" type="bool" label="Herunterladen aktivieren" default="false" /> | |
| <!-- <setting type="sep" /> --> | |
| <setting type="lsep" label="Bitte Ordnerpfade eingeben" /> | |
| <!--<<setting type="text" label="oder [COLOR blue]smb://ServerIP/path_to_files/ | nfs://ServerIP/path_to_files/[/COLOR]" /> --> | |
| <setting label="[COLOR blue]Hier Hilfe zum Syntax für den Ordnerpfad[/COLOR]" type="action" action="RunPlugin(plugin://plugin.video.xship/?action=settings&subaction=downloadInfo)" option=""/> | |
| <setting id="download.movie.path" type="text" label="Filme" enable="!eq(-3,false)" default="" /> | |
| <!--<setting id="download.tv.path" type="folder" label="TV-Serien" enable="!eq(-4,false)" default="" /> --> | |
| <setting id="download.tv.path" type="text" label="TV-Serien" enable="!eq(-4,false)" default="" /> | |
| <setting type="lsep" label="[CR]" />--> | |
| <!--<setting type="text" label="Herunterladen ist standardmäßig deaktiviert." />--> | |
| <setting type="sep" /> | |
| <setting id="subtitles" type="bool" label="Untertitel aktivieren" default="false" /> | |
| <setting type="text" label="[COLOR blue]Konto auf [B]opensubtitles.org[/B] einrichten und[CR]die Zugandsdaten unter Einstellungen / Konten eintragen![CR][/COLOR]" visible="!eq(-1,false)"/> | |
| <setting type="lsep" label="Konto [B]opensubtitles.org[/B]"/> | |
| <setting id="subtitles.os_user" type="text" label="UserName" default=""/> | |
| <setting id="subtitles.os_pass" type="text" option="hidden" label="Password" default=""/> | |
| <setting type="sep" /> | |
| <setting id="subtitles.lang.1" type="select" label="Hauptsprache" values="Afrikaans|Albanian|Arabic|Armenian|Basque|Bengali|Bosnian|Breton|Bulgarian|Burmese|Catalan|Chinese|Croatian|Czech|Danish|Dutch|English|Esperanto|Estonian|Finnish|French|Galician|Georgian|German|Greek|Hebrew|Hindi|Hungarian|Icelandic|Indonesian|Italian|Japanese|Kazakh|Khmer|Korean|Latvian|Lithuanian|Luxembourgish|Macedonian|Malay|Malayalam|Manipuri|Mongolian|Montenegrin|Norwegian|Occitan|Persian|Polish|Portuguese|Portuguese(Brazil)|Romanian|Russian|Serbian|Sinhalese|Slovak|Slovenian|Spanish|Swahili|Swedish|Syriac|Tagalog|Tamil|Telugu|Thai|Turkish|Ukrainian|Urdu" enable="!eq(-3,false)" default="German" /> | |
| <setting id="subtitles.lang.2" type="select" label="Zweitsprache" values="Afrikaans|Albanian|Arabic|Armenian|Basque|Bengali|Bosnian|Breton|Bulgarian|Burmese|Catalan|Chinese|Croatian|Czech|Danish|Dutch|English|Esperanto|Estonian|Finnish|French|Galician|Georgian|German|Greek|Hebrew|Hindi|Hungarian|Icelandic|Indonesian|Italian|Japanese|Kazakh|Khmer|Korean|Latvian|Lithuanian|Luxembourgish|Macedonian|Malay|Malayalam|Manipuri|Mongolian|Montenegrin|Norwegian|Occitan|Persian|Polish|Portuguese|Portuguese(Brazil)|Romanian|Russian|Serbian|Sinhalese|Slovak|Slovenian|Spanish|Swahili|Swedish|Syriac|Tagalog|Tamil|Telugu|Thai|Turkish|Ukrainian|Urdu" enable="!eq(-4,false)" default="English" /> | |
| <setting id="subtitles.utf" type="bool" label="Untertitel nach UTF-8 umwandeln" enable="!eq(-5,false)" default="false" /> | |
| <setting type="lsep" label="Download-Manager" /> | |
| <setting id="jd_enabled" type="bool" label="JDownloader aktivieren" default="false" /> | |
| <setting id="jd_host" type="text" label="Host" default="127.0.0.1" enable="!eq(-1,false)" /> | |
| <setting id="jd_port" type="number" label="Port" default="10025" enable="!eq(-2,false)" /> | |
| <setting id="jd_automatic_start" type="bool" label="Automatischer Start" default="false" enable="!eq(-3,false)" /> | |
| <setting id="jd_grabber" type="bool" label="LinkGrabber verwenden" default="true" enable="!eq(-4,false)" /> | |
| <setting type="sep" /> | |
| <setting id="jd2_enabled" type="bool" label="JDownloader 2 aktivieren" default="false" /> | |
| <setting id="jd2_host" type="text" label="Host" default="127.0.0.1" enable="!eq(-1,false)" /> | |
| <setting id="jd2_port" type="number" label="Port" default="9666" enable="!eq(-2,false)" /> | |
| <setting type="sep" /> | |
| <setting id="myjd_enabled" type="bool" label="My.JDownloader aktivieren" default="false" /> | |
| <setting id="myjd_device" type="text" label="Gerätename" default="JDownloader@Device" enable="!eq(-1,false)" /> | |
| <setting id="myjd_user" type="text" label="E-Mail" default="" enable="!eq(-2,false)" /> | |
| <setting id="myjd_pass" type="text" label="Passwort" option="hidden" default="" enable="!eq(-3,false)" /> | |
| <setting type="sep" /> | |
| <setting id="pyload_enabled" type="bool" label="PyLoad aktivieren" default="false" /> | |
| <setting id="pyload_host" type="text" label="Host" default="127.0.0.1" enable="!eq(-1,false)" /> | |
| <setting id="pyload_port" type="number" label="Port" default="8000" enable="!eq(-2,false)" /> | |
| <setting id="pyload_user" type="text" label="Benutzername" default="" enable="!eq(-3,false)" /> | |
| <setting id="pyload_passwd" type="text" label="Passwort" option="hidden" default="" enable="!eq(-4,false)" /> | |
| </category> | |
| </settings> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # edit 2025-06-12 | |
| import sys | |
| import re, json, random, time | |
| from concurrent.futures import ThreadPoolExecutor | |
| from resources.lib import log_utils, utils, control | |
| from resources.lib.control import py2_decode, py2_encode, quote_plus, parse_qsl | |
| import resolveurl as resolver | |
| # from functools import reduce | |
| from resources.lib.control import getKodiVersion | |
| if int(getKodiVersion()) >= 20: from infotagger.listitem import ListItemInfoTag | |
| # für self.sysmeta - zur späteren verwendung als meta | |
| _params = dict(parse_qsl(sys.argv[2].replace('?',''))) if len(sys.argv) > 1 else dict() | |
| class sources: | |
| def __init__(self): | |
| self.getConstants() | |
| self.sources = [] | |
| self.current = int(time.time()) | |
| if 'sysmeta' in _params: self.sysmeta = _params['sysmeta'] # string zur späteren verwendung als meta | |
| self.watcher = False | |
| self.executor = ThreadPoolExecutor(max_workers=20) | |
| self.url = None | |
| def get(self, params): | |
| data = json.loads(params['sysmeta']) | |
| self.mediatype = data.get('mediatype') | |
| self.aliases = data.get('aliases') if 'aliases' in data else [] | |
| title = py2_encode(data.get('title')) | |
| originaltitle = py2_encode(data.get('originaltitle')) if 'originaltitle' in data else title | |
| year = data.get('year') if 'year' in data else None | |
| imdb = data.get('imdb_id') if 'imdb_id' in data else data.get('imdbnumber') if 'imdbnumber' in data else None | |
| if not imdb and 'imdb' in data: imdb = data.get('imdb') | |
| tmdb = data.get('tmdb_id') if 'tmdb_id' in data else None | |
| #if tmdb and not imdb: print 'hallo' #TODO | |
| season = data.get('season') if 'season' in data else 0 | |
| episode = data.get('episode') if 'episode' in data else 0 | |
| premiered = data.get('premiered') if 'premiered' in data else None | |
| meta = params['sysmeta'] | |
| select = data.get('select') if 'select' in data else None | |
| return title, year, imdb, season, episode, originaltitle, premiered, meta, select | |
| def play(self, params): | |
| title, year, imdb, season, episode, originaltitle, premiered, meta, select = self.get(params) | |
| try: | |
| url = None | |
| #Liste der gefundenen Streams | |
| items = self.getSources(title, year, imdb, season, episode, originaltitle, premiered) | |
| select = control.getSetting('hosts.mode') if select == None else select | |
| ## unnötig | |
| #select = '1' if control.getSetting('downloads') == 'true' and not (control.getSetting('download.movie.path') == '' or control.getSetting('download.tv.path') == '') else select | |
| # # TODO überprüfen wofür mal gedacht | |
| # if control.window.getProperty('PseudoTVRunning') == 'True': | |
| # return control.resolveUrl(int(sys.argv[1]), True, control.item(path=str(self.sourcesDirect(items)))) | |
| if len(items) > 0: | |
| # Auswahl Verzeichnis | |
| if select == '1' and 'plugin' in control.infoLabel('Container.PluginName'): | |
| control.window.clearProperty(self.itemsProperty) | |
| control.window.setProperty(self.itemsProperty, json.dumps(items)) | |
| control.window.clearProperty(self.metaProperty) | |
| control.window.setProperty(self.metaProperty, meta) | |
| control.sleep(2) | |
| return control.execute('Container.Update(%s?action=addItem&title=%s)' % (sys.argv[0], quote_plus(title))) | |
| # Auswahl Dialog | |
| elif select == '0' or select == '1': | |
| url = self.sourcesDialog(items) | |
| if url == 'close://': return | |
| # Autoplay | |
| else: | |
| url = self.sourcesDirect(items) | |
| if url == None: return self.errorForSources() | |
| try: meta = json.loads(meta) | |
| except: pass | |
| from resources.lib.player import player | |
| player().run(title, url, meta) | |
| except Exception as e: | |
| log_utils.log('Error %s' % str(e), log_utils.LOGERROR) | |
| # Liste gefundene Streams Indexseite|Hoster | |
| def addItem(self, title): | |
| control.playlist.clear() | |
| items = control.window.getProperty(self.itemsProperty) | |
| items = json.loads(items) | |
| if items == None or len(items) == 0: control.idle() ; sys.exit() | |
| sysaddon = sys.argv[0] | |
| syshandle = int(sys.argv[1]) | |
| systitle = sysname = quote_plus(title) | |
| meta = control.window.getProperty(self.metaProperty) | |
| meta = json.loads(meta) | |
| #TODO | |
| if meta['mediatype'] == 'movie': | |
| # downloads = True if control.getSetting('downloads') == 'true' and control.exists(control.translatePath(control.getSetting('download.movie.path'))) else False | |
| downloads = True if control.getSetting('downloads') == 'true' and control.getSetting('download.movie.path') else False | |
| else: | |
| # downloads = True if control.getSetting('downloads') == 'true' and control.exists(control.translatePath(control.getSetting('download.tv.path'))) else False | |
| downloads = True if control.getSetting('downloads') == 'true' and control.getSetting('download.tv.path') else False | |
| addonPoster, addonBanner = control.addonPoster(), control.addonBanner() | |
| addonFanart, settingFanart = control.addonFanart(), control.getSetting('fanart') | |
| if 'backdrop_url' in meta and 'http' in meta['backdrop_url']: fanart = meta['backdrop_url'] | |
| elif 'fanart' in meta and 'http' in meta['fanart']: fanart = meta['fanart'] | |
| else: fanart = addonFanart | |
| if 'cover_url' in meta and 'http' in meta['cover_url']: poster = meta['cover_url'] | |
| elif 'poster' in meta and 'http' in meta['poster']: poster = meta['poster'] | |
| else: poster = addonPoster | |
| sysimage = poster | |
| if 'season' in meta and 'episode' in meta: | |
| sysname += quote_plus(' S%02dE%02d' % (int(meta['season']), int(meta['episode']))) | |
| elif 'year' in meta: | |
| sysname += quote_plus(' (%s)' % meta['year']) | |
| for i in range(len(items)): | |
| try: | |
| label = items[i]['label'] | |
| syssource = quote_plus(json.dumps([items[i]])) | |
| item = control.item(label=label, offscreen=True) | |
| item.setProperty('IsPlayable', 'true') | |
| item.setArt({'poster': poster, 'banner': addonBanner}) | |
| if settingFanart == 'true': item.setProperty('Fanart_Image', fanart) | |
| cm = [] | |
| if downloads: | |
| cm.append(("Download", 'RunPlugin(%s?action=download&name=%s&image=%s&source=%s)' % (sysaddon, sysname, sysimage, syssource))) | |
| if control.getSetting('jd_enabled') == 'true': | |
| cm.append(("Sende zum JDownloader", 'RunPlugin(%s?action=sendToJD&name=%s&source=%s)' % (sysaddon, sysname, syssource))) | |
| if control.getSetting('jd2_enabled') == 'true': | |
| cm.append(("Sende zum JDownloader2", 'RunPlugin(%s?action=sendToJD2&name=%s&source=%s)' % (sysaddon, sysname, syssource))) | |
| if control.getSetting('myjd_enabled') == 'true': | |
| cm.append(("Sende zu My.JDownloader", 'RunPlugin(%s?action=sendToMyJD&name=%s&source=%s)' % (sysaddon, sysname, syssource))) | |
| if control.getSetting('pyload_enabled') == 'true': | |
| cm.append(("Sende zu PyLoad", 'RunPlugin(%s?action=sendToPyLoad&name=%s&source=%s)' % (sysaddon, sysname, syssource))) | |
| cm.append(('Einstellungen', 'RunPlugin(%s?action=addonSettings)' % sysaddon)) | |
| item.addContextMenuItems(cm) | |
| url = "%s?action=playItem&title=%s&source=%s" % (sysaddon, systitle, syssource) | |
| # ## Notwendig für Library Exporte ## | |
| # ## Amazon Scraper Details ## | |
| # if "amazon" in label.lower(): | |
| # aid = re.search(r'asin%3D(.*?)%22%2C', url) | |
| # url = "plugin://plugin.video.amazon-test/?mode=PlayVideo&asin=" + aid.group(1) | |
| ##https: // codedocs.xyz / AlwinEsch / kodi / group__python__xbmcgui__listitem.html # ga0b71166869bda87ad744942888fb5f14 | |
| name = '%s%sStaffel: %s Episode: %s' % (title, "\n", meta['season'], meta['episode']) if 'season' in meta else title | |
| plot = meta['plot'] if 'plot' in meta and len(meta['plot'].strip()) >= 1 else '' | |
| plot = '[COLOR blue]%s[/COLOR]%s%s' % (name, "\n\n", py2_encode(plot)) | |
| if 'duration' in meta: | |
| infolable = {'plot': plot,'duration': meta['duration']} | |
| else: | |
| infolable = {'plot': plot} | |
| # TODO | |
| # if 'cast' in meta and meta['cast']: item.setCast(meta['cast']) | |
| # # # remove unsupported InfoLabels | |
| meta.pop('cast', None) # ersetzt durch item.setCast(i['cast']) | |
| meta.pop('number_of_seasons', None) | |
| meta.pop('imdb_id', None) | |
| meta.pop('tvdb_id', None) | |
| meta.pop('tmdb_id', None) | |
| ## Quality Video Stream from source.append quality - items[i]['quality'] | |
| video_streaminfo ={} | |
| if "4k" in items[i]['quality'].lower(): | |
| video_streaminfo.update({'width': 3840, 'height': 2160}) | |
| elif "1080p" in items[i]['quality'].lower(): | |
| video_streaminfo.update({'width': 1920, 'height': 1080}) | |
| elif "hd" in items[i]['quality'].lower() or "720p" in items[i]['quality'].lower(): | |
| video_streaminfo.update({'width': 1280,'height': 720}) | |
| else: | |
| # video_streaminfo.update({"width": 720, "height": 576}) | |
| video_streaminfo.update({}) | |
| ## Codec for Video Stream from extra info - items[i]['info'] | |
| if 'hevc' in items[i]['label'].lower(): | |
| video_streaminfo.update({'codec': 'hevc'}) | |
| elif '265' in items[i]['label'].lower(): | |
| video_streaminfo.update({'codec': 'h265'}) | |
| elif 'mkv' in items[i]['label'].lower(): | |
| video_streaminfo.update({'codec': 'mkv'}) | |
| elif 'mp4' in items[i]['label'].lower(): | |
| video_streaminfo.update({'codec': 'mp4'}) | |
| else: | |
| # video_streaminfo.update({'codec': 'h264'}) | |
| video_streaminfo.update({'codec': ''}) | |
| ## Quality & Channels Audio Stream from extra info - items[i]['info'] | |
| audio_streaminfo = {} | |
| if 'dts' in items[i]['label'].lower(): | |
| audio_streaminfo.update({'codec': 'dts'}) | |
| elif 'plus' in items[i]['label'].lower() or 'e-ac3' in items[i]['label'].lower(): | |
| audio_streaminfo.update({'codec': 'eac3'}) | |
| elif 'dolby' in items[i]['label'].lower() or 'ac3' in items[i]['label'].lower(): | |
| audio_streaminfo.update({'codec': 'ac3'}) | |
| else: | |
| # audio_streaminfo.update({'codec': 'aac'}) | |
| audio_streaminfo.update({'codec': ''}) | |
| ## Channel update ## | |
| if '7.1' in items[i].get('info','').lower(): | |
| audio_streaminfo.update({'channels': 8}) | |
| elif '5.1' in items[i].get('info','').lower(): | |
| audio_streaminfo.update({'channels': 6}) | |
| else: | |
| # audio_streaminfo.update({'channels': 2}) | |
| audio_streaminfo.update({'channels': ''}) | |
| if int(getKodiVersion()) <= 19: | |
| item.setInfo(type='Video', infoLabels=infolable) | |
| item.addStreamInfo('video', video_streaminfo) | |
| item.addStreamInfo('audio', audio_streaminfo) | |
| else: | |
| info_tag = ListItemInfoTag(item, 'video') | |
| info_tag.set_info(infolable) | |
| stream_details = { | |
| 'video': [video_streaminfo], | |
| 'audio': [audio_streaminfo]} | |
| info_tag.set_stream_details(stream_details) | |
| # info_tag.set_cast(aActors) | |
| control.addItem(handle=syshandle, url=url, listitem=item, isFolder=False) | |
| except: | |
| pass | |
| control.content(syshandle, 'videos') | |
| control.plugincategory(syshandle, control.addonVersion) | |
| control.endofdirectory(syshandle, cacheToDisc=True) | |
| def playItem(self, title, source): | |
| isDebug = False | |
| if isDebug: log_utils.log('start playItem', log_utils.LOGWARNING) | |
| try: | |
| meta = control.window.getProperty(self.metaProperty) | |
| meta = json.loads(meta) | |
| header = control.addonInfo('name') | |
| # control.idle() #ok | |
| progressDialog = control.progressDialog if control.getSetting('progress.dialog') == '0' else control.progressDialogBG | |
| progressDialog.create(header, '') | |
| progressDialog.update(0) | |
| item = json.loads(source)[0] | |
| #if isDebug: log_utils.log('playItem 237', log_utils.LOGWARNING) | |
| if item['source'] == None: raise Exception() | |
| future = self.executor.submit(self.sourcesResolve, item) | |
| waiting_time = 30 | |
| while waiting_time > 0: | |
| try: | |
| if control.abortRequested: return sys.exit() | |
| if progressDialog.iscanceled(): return progressDialog.close() | |
| except: | |
| pass | |
| if future.done(): break | |
| control.sleep(1) | |
| waiting_time = waiting_time - 1 | |
| progressDialog.update(int(100 - 100. / 30 * waiting_time), str(item['label'])) | |
| #if isDebug: log_utils.log('playItem 252', log_utils.LOGWARNING) | |
| if control.condVisibility('Window.IsActive(virtualkeyboard)') or \ | |
| control.condVisibility('Window.IsActive(yesnoDialog)'): | |
| # or control.condVisibility('Window.IsActive(PopupRecapInfoWindow)'): | |
| waiting_time = waiting_time + 1 # dont count down while dialog is presented | |
| if future.done(): break | |
| try: progressDialog.close() | |
| except: pass | |
| if isDebug: log_utils.log('playItem 261', log_utils.LOGWARNING) | |
| control.execute('Dialog.Close(virtualkeyboard)') | |
| control.execute('Dialog.Close(yesnoDialog)') | |
| if isDebug: log_utils.log('playItem url: %s' % self.url, log_utils.LOGWARNING) | |
| if self.url == None: | |
| #self.errorForSources() | |
| return | |
| from resources.lib.player import player | |
| player().run(title, self.url, meta) | |
| return self.url | |
| except Exception as e: | |
| log_utils.log('Error %s' % str(e), log_utils.LOGERROR) | |
| def getSources(self, title, year, imdb, season, episode, originaltitle, premiered, quality='HD', timeout=30): | |
| #TODO | |
| # self._getHostDict() | |
| control.idle() #ok | |
| progressDialog = control.progressDialog if control.getSetting('progress.dialog') == '0' else control.progressDialogBG | |
| progressDialog.create(control.addonInfo('name'), '') | |
| progressDialog.update(0) | |
| progressDialog.update(0, "Quellen werden vorbereitet") | |
| sourceDict = self.sourceDict | |
| sourceDict = [(i[0], i[1], i[1].priority) for i in sourceDict] | |
| random.shuffle(sourceDict) | |
| sourceDict = sorted(sourceDict, key=lambda i: i[2]) | |
| content = 'movies' if season == 0 or season == '' or season == None else 'shows' | |
| aliases, localtitle = utils.getAliases(imdb, content) | |
| if localtitle and title != localtitle and originaltitle != localtitle: | |
| if not title in aliases: aliases.append(title) | |
| title = localtitle | |
| for i in self.aliases: | |
| if not i in aliases: | |
| aliases.append(i) | |
| titles = utils.get_titles_for_search(title, originaltitle, aliases) | |
| futures = {self.executor.submit(self._getSource, titles, year, season, episode, imdb, provider[0], provider[1]): provider[0] for provider in sourceDict} | |
| provider_names = {provider[0].upper() for provider in sourceDict} | |
| string4 = "Total" | |
| try: timeout = int(control.getSetting('scrapers.timeout')) | |
| except: pass | |
| quality = control.getSetting('hosts.quality') | |
| if quality == '': quality = '0' | |
| source_4k = 0 | |
| source_1080 = 0 | |
| source_720 = 0 | |
| source_sd = 0 | |
| total = d_total = 0 | |
| total_format = '[COLOR %s][B]%s[/B][/COLOR]' | |
| pdiag_format = ' 4K: %s | 1080p: %s | 720p: %s | SD: %s | %s: %s '.split('|') | |
| for i in range(0, 4 * timeout): | |
| try: | |
| if control.abortRequested: return sys.exit() | |
| try: | |
| if progressDialog.iscanceled(): break | |
| except: | |
| pass | |
| if len(self.sources) > 0: | |
| if quality in ['0']: | |
| source_4k = len([e for e in self.sources if e['quality'] == '4K']) | |
| source_1080 = len([e for e in self.sources if e['quality'] in ['1440p','1080p']]) | |
| source_720 = len([e for e in self.sources if e['quality'] in ['720p','HD']]) | |
| source_sd = len([e for e in self.sources if e['quality'] not in ['4K','1440p','1080p','720p','HD']]) | |
| elif quality in ['1']: | |
| source_1080 = len([e for e in self.sources if e['quality'] in ['1440p','1080p']]) | |
| source_720 = len([e for e in self.sources if e['quality'] in ['720p','HD']]) | |
| source_sd = len([e for e in self.sources if e['quality'] not in ['4K','1440p','1080p','720p','HD']]) | |
| elif quality in ['2']: | |
| source_1080 = len([e for e in self.sources if e['quality'] in ['1080p']]) | |
| source_720 = len([e for e in self.sources if e['quality'] in ['720p','HD']]) | |
| source_sd = len([e for e in self.sources if e['quality'] not in ['4K','1440p','1080p','720p','HD']]) | |
| elif quality in ['3']: | |
| source_720 = len([e for e in self.sources if e['quality'] in ['720p','HD']]) | |
| source_sd = len([e for e in self.sources if e['quality'] not in ['4K','1440p','1080p','720p','HD']]) | |
| else: | |
| source_sd = len([e for e in self.sources if e['quality'] not in ['4K','1440p','1080p','720p','HD']]) | |
| total = source_4k + source_1080 + source_720 + source_sd | |
| source_4k_label = total_format % ('red', source_4k) if source_4k == 0 else total_format % ('lime', source_4k) | |
| source_1080_label = total_format % ('red', source_1080) if source_1080 == 0 else total_format % ('lime', source_1080) | |
| source_720_label = total_format % ('red', source_720) if source_720 == 0 else total_format % ('lime', source_720) | |
| source_sd_label = total_format % ('red', source_sd) if source_sd == 0 else total_format % ('lime', source_sd) | |
| source_total_label = total_format % ('red', total) if total == 0 else total_format % ('lime', total) | |
| try: | |
| info = [name.upper() for future, name in futures.items() if not future.done()] | |
| percent = int(100 * float(i) / (2 * timeout) + 1) | |
| if quality in ['0']: | |
| line1 = '|'.join(pdiag_format) % (source_4k_label, source_1080_label, source_720_label, source_sd_label, str(string4), source_total_label) | |
| elif quality in ['1']: | |
| line1 = '|'.join(pdiag_format[1:]) % (source_1080_label, source_720_label, source_sd_label, str(string4), source_total_label) | |
| elif quality in ['2']: | |
| line1 = '|'.join(pdiag_format[1:]) % (source_1080_label, source_720_label, source_sd_label, str(string4), source_total_label) | |
| elif quality in ['3']: | |
| line1 = '|'.join(pdiag_format[2:]) % (source_720_label, source_sd_label, str(string4), source_total_label) | |
| else: | |
| line1 = '|'.join(pdiag_format[3:]) % (source_sd_label, str(string4), source_total_label) | |
| if (i / 2) < timeout: | |
| string = "Verbleibende Indexseiten: %s" | |
| else: | |
| string = 'Waiting for: %s' | |
| if len(info) > 6: line = line1 + string % (str(len(info))) | |
| elif len(info) > 1: line = line1 + string % (', '.join(info)) | |
| elif len(info) == 1: line = line1 + string % (''.join(info)) | |
| else: line = line1 + 'Suche beendet!' | |
| progressDialog.update(max(1, percent), line) | |
| if len(info) == 0: break | |
| except Exception as e: | |
| log_utils.log('Exception Raised: %s' % str(e), log_utils.LOGERROR) | |
| control.sleep(1) | |
| except: | |
| pass | |
| time.sleep(1) | |
| try: progressDialog.close() | |
| except: pass | |
| self.sourcesFilter() | |
| return self.sources | |
| def _getSource(self, titles, year, season, episode, imdb, source, call): | |
| try: | |
| sources = call.run(titles, year, season, episode, imdb) # kasi self.hostDict | |
| if sources == None or sources == []: raise Exception() | |
| sources = [json.loads(t) for t in set(json.dumps(d, sort_keys=True) for d in sources)] | |
| for i in sources: | |
| i.update({'provider': source}) | |
| if not 'priority' in i: i.update({'priority': 100}) | |
| if not 'prioHoster' in i: i.update({'prioHoster': 100}) | |
| self.sources.extend(sources) | |
| except: | |
| pass | |
| def sourcesFilter(self): | |
| # hostblockDict = utils.getHostDict() | |
| # self.sources = [i for i in self.sources if i['source'].split('.')[0] not in str(hostblockDict)] # Hoster ausschließen (Liste) | |
| quality = control.getSetting('hosts.quality') | |
| if quality == '': quality = '0' | |
| random.shuffle(self.sources) | |
| self.sources = sorted(self.sources, key=lambda k: k['prioHoster'], reverse=False) | |
| for i in range(len(self.sources)): | |
| q = self.sources[i]['quality'] | |
| if q.lower() == 'hd': self.sources[i].update({'quality': '720p'}) | |
| filter = [] | |
| if quality in ['0']: filter += [i for i in self.sources if i['quality'] == '4K'] | |
| if quality in ['0', '1']: filter += [i for i in self.sources if i['quality'] == '1440p'] | |
| if quality in ['0', '1', '2']: filter += [i for i in self.sources if i['quality'] == '1080p'] | |
| if quality in ['0', '1', '2', '3']: filter += [i for i in self.sources if i['quality'] == '720p'] | |
| #filter += [i for i in self.sources if i['quality'] in ['SD', 'SCR', 'CAM']] | |
| filter += [i for i in self.sources if i['quality'] not in ['4k', '1440p', '1080p', '720p']] | |
| self.sources = filter | |
| if control.getSetting('hosts.sort.provider') == 'true': | |
| self.sources = sorted(self.sources, key=lambda k: k['provider']) | |
| if control.getSetting('hosts.sort.priority') == 'true' and self.mediatype == 'tvshow': self.sources = sorted(self.sources, key=lambda k: k['priority'], reverse=False) | |
| if str(control.getSetting('hosts.limit')) == 'true': | |
| self.sources = self.sources[:int(control.getSetting('hosts.limit.num'))] | |
| else: | |
| self.sources = self.sources[:100] | |
| for i in range(len(self.sources)): | |
| p = self.sources[i]['provider'] | |
| q = self.sources[i]['quality'] | |
| s = self.sources[i]['source'] | |
| ## s = s.rsplit('.', 1)[0] | |
| l = self.sources[i]['language'] | |
| try: f = (' | '.join(['[I]%s [/I]' % info.strip() for info in self.sources[i]['info'].split('|')])) | |
| except: f = '' | |
| label = '%02d | [B]%s[/B] | ' % (int(i + 1), p) | |
| if q in ['4K', '1440p', '1080p', '720p']: label += '%s | [B][I]%s [/I][/B] | %s' % (s, q, f) | |
| elif q == 'SD': label += '%s | %s' % (s, f) | |
| else: label += '%s | %s | [I]%s [/I]' % (s, f, q) | |
| label = label.replace('| 0 |', '|').replace(' | [I]0 [/I]', '') | |
| label = re.sub(r'\[I\]\s+\[/I\]', ' ', label) | |
| label = re.sub(r'\|\s+\|', '|', label) | |
| label = re.sub(r'\|(?:\s+|)$', '', label) | |
| self.sources[i]['label'] = label.upper() | |
| # ## EMBY shown as premium link ## | |
| # if self.sources[i]['provider']=="emby" or self.sources[i]['provider']=="amazon" or self.sources[i]['provider']=="netflix" or self.sources[i]['provider']=="maxdome": | |
| # prem_identify = 'blue' | |
| # self.sources[i]['label'] = ('[COLOR %s]' % (prem_identify)) + label.upper() + '[/COLOR]' | |
| self.sources = [i for i in self.sources if 'label' in i] | |
| return self.sources | |
| def sourcesResolve(self, item, info=False): | |
| try: | |
| self.url = None | |
| url = item['url'] | |
| direct = item['direct'] | |
| local = item.get('local', False) | |
| provider = item['provider'] | |
| call = [i[1] for i in self.sourceDict if i[0] == provider][0] | |
| url = call.resolve(url) | |
| if not direct == True: | |
| try: | |
| hmf = resolver.HostedMediaFile(url=url, include_disabled=True, include_universal=False) | |
| if hmf.valid_url(): | |
| url = hmf.resolve() | |
| if url == False or url == None or url == '': url = None # raise Exception() | |
| except: | |
| url = None | |
| if url == None or (not '://' in str(url) and not local): | |
| log_utils.log('Kein Video Link gefunden: Provider %s / %s / %s ' % (item['provider'], item['source'] , str(item['source'])), log_utils.LOGERROR) | |
| raise Exception() | |
| # if not utils.test_stream(url): | |
| # log_utils.log('URL Test Error: %s' % url, log_utils.LOGERROR) | |
| # raise Exception() | |
| # url = utils.m3u8_check(url) | |
| if url: | |
| self.url = url | |
| return url | |
| else: | |
| raise Exception() | |
| except: | |
| if info: self.errorForSources() | |
| return | |
| def sourcesDialog(self, items): | |
| labels = [i['label'] for i in items] | |
| select = control.selectDialog(labels) | |
| if select == -1: return 'close://' | |
| next = [y for x,y in enumerate(items) if x >= select] | |
| prev = [y for x,y in enumerate(items) if x < select][::-1] | |
| items = [items[select]] | |
| items = [i for i in items+next+prev][:40] | |
| header = control.addonInfo('name') | |
| header2 = header.upper() | |
| progressDialog = control.progressDialog if control.getSetting('progress.dialog') == '0' else control.progressDialogBG | |
| progressDialog.create(header, '') | |
| progressDialog.update(0) | |
| block = None | |
| try: | |
| for i in range(len(items)): | |
| try: | |
| if items[i]['source'] == block: raise Exception() | |
| future = self.executor.submit(self.sourcesResolve, items[i]) | |
| try: | |
| if progressDialog.iscanceled(): break | |
| progressDialog.update(int((100 / float(len(items))) * i), str(items[i]['label'])) | |
| except: | |
| progressDialog.update(int((100 / float(len(items))) * i), str(header2) + str(items[i]['label'])) | |
| waiting_time = 30 | |
| while waiting_time > 0: | |
| try: | |
| if control.abortRequested: return sys.exit() #xbmc.Monitor().abortRequested() | |
| if progressDialog.iscanceled(): return progressDialog.close() | |
| except: | |
| pass | |
| if future.done(): break | |
| control.sleep(1) | |
| waiting_time = waiting_time - 1 | |
| if control.condVisibility('Window.IsActive(virtualkeyboard)') or \ | |
| control.condVisibility('Window.IsActive(yesnoDialog)') or \ | |
| control.condVisibility('Window.IsActive(ProgressDialog)'): | |
| waiting_time = waiting_time + 1 #dont count down while dialog is presented ## control.condVisibility('Window.IsActive(PopupRecapInfoWindow)') or \ | |
| if not future.done(): block = items[i]['source'] | |
| if self.url == None: raise Exception() | |
| self.selectedSource = items[i]['label'] | |
| try: progressDialog.close() | |
| except: pass | |
| control.execute('Dialog.Close(virtualkeyboard)') | |
| control.execute('Dialog.Close(yesnoDialog)') | |
| return self.url | |
| except: | |
| pass | |
| try: progressDialog.close() | |
| except: pass | |
| except Exception as e: | |
| try: progressDialog.close() | |
| except: pass | |
| log_utils.log('Error %s' % str(e), log_utils.LOGINFO) | |
| def sourcesDirect(self, items): | |
| # TODO - OK | |
| # filter = [i for i in items if i['source'].lower() in self.hostcapDict and i['debrid'] == ''] | |
| # items = [i for i in items if not i in filter] | |
| # items = [i for i in items if ('autoplay' in i and i['autoplay'] == True) or not 'autoplay' in i] | |
| u = None | |
| header = control.addonInfo('name') | |
| header2 = header.upper() | |
| try: | |
| control.sleep(1) | |
| progressDialog = control.progressDialog if control.getSetting('progress.dialog') == '0' else control.progressDialogBG | |
| progressDialog.create(header, '') | |
| progressDialog.update(0) | |
| except: | |
| pass | |
| for i in range(len(items)): | |
| try: | |
| if progressDialog.iscanceled(): break | |
| progressDialog.update(int((100 / float(len(items))) * i), str(items[i]['label'])) | |
| except: | |
| progressDialog.update(int((100 / float(len(items))) * i), str(header2) + str(items[i]['label'])) | |
| try: | |
| if control.abortRequested: return sys.exit() | |
| url = self.sourcesResolve(items[i]) | |
| if u == None: u = url | |
| if not url == None: break | |
| except: | |
| pass | |
| try: progressDialog.close() | |
| except: pass | |
| return u | |
| def errorForSources(self): | |
| control.infoDialog("Keine Streams verfügbar oder ausgewählt", sound=False, icon='INFO') | |
| def getTitle(self, title): | |
| title = utils.normalize(title) | |
| return title | |
| def getConstants(self): | |
| self.itemsProperty = '%s.container.items' % control.Addon.getAddonInfo('id') | |
| self.metaProperty = '%s.container.meta' % control.Addon.getAddonInfo('id') | |
| from scrapers import sources | |
| self.sourceDict = sources() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment