# -*- coding: utf-8 -*- # ------------------------------------------------------------ # Controlador para HTML # ------------------------------------------------------------ import json import os import re import threading import time import channelselector from controller import Controller from controller import Platformtools from platformcode import config from core.item import Item from core.tmdb import Tmdb from platformcode import launcher, logger ## Obtiene la versión del addon version = config.get_addon_version() class html(Controller): pattern = re.compile("##") name = "HTML" def __init__(self, handler=None, ID=None): super(html, self).__init__(handler, ID) self.platformtools = platform(self) self.data = {} if self.handler: if hasattr(handler, "client"): self.client_ip = handler.client.getpeername()[0] else: self.client_ip = handler.client_address[0] self.send_message({"action": "connect", "data": {"version": "Alfa %s" % version, "date": "--/--/----"}}) t = threading.Thread(target=launcher.start, name=ID) t.setDaemon(True) t.start() def run(self, path): if path: item = Item().fromurl(path) else: item = Item(channel="channelselector", action="mainlist", viewmode="banner") launcher.run(item) def get_data(self, id): while not "id" in self.data or not self.data["id"] == id: time.sleep(0.1) data = self.data["result"] self.data = {} return data def send_message(self, data): import random ID = "%032x" % (random.getrandbits(128)) data["id"] = ID self.handler.sendMessage(json.dumps(data)) return ID class platform(Platformtools): def __init__(self, controller): self.controller = controller self.handler = controller.handler self.get_data = controller.get_data self.send_message = controller.send_message def render_items(self, itemlist, parent_item): """ Función encargada de mostrar el itemlist, se pasa como parametros el itemlist y el item del que procede @type itemlist: list @param itemlist: lista de elementos a mostrar @type parent_item: item @param parent_item: elemento padre """ # Si el itemlist no es un list salimos if not type(itemlist) == list: JsonData = {} JsonData["action"] = "HideLoading" JsonData["data"] = {} self.send_message(JsonData) return # Si no hay ningun item, mostramos un aviso if not len(itemlist): itemlist.append(Item(title="No hay elementos que mostrar")) if parent_item.channel == "channelselector" and not parent_item.action == "filterchannels": parent_item.viewmode = "banner" elif parent_item.channel == "channelselector" and parent_item.action == "filterchannels": parent_item.viewmode = "channel" if not parent_item.viewmode: parent_item.viewmode = "list" # Item Atrás if not (parent_item.channel == "channelselector" and parent_item.action == "mainlist") and not \ itemlist[0].action == "go_back": if parent_item.viewmode in ["banner", "channel"]: itemlist.insert(0, Item(title="Atrás", action="go_back", thumbnail=channelselector.get_thumb("back.png", "banner_"))) else: itemlist.insert(0, Item(title="Atrás", action="go_back", thumbnail=channelselector.get_thumb("back.png", "banner_"))) JsonData = {} JsonData["action"] = "EndItems" JsonData["data"] = {} JsonData["data"]["itemlist"] = [] JsonData["data"]["viewmode"] = parent_item.viewmode JsonData["data"]["category"] = parent_item.category.capitalize() JsonData["data"]["host"] = self.controller.host if parent_item.url: JsonData["data"]["url"] = parent_item.url # Recorremos el itemlist for item in itemlist: if not item.thumbnail and item.action == "search": item.thumbnail = channelselector.get_thumb("search.png", "banner_") #if not item.thumbnail and item.folder == True: item.thumbnail = channelselector.get_thumb("folder.png", "banner_") if not item.thumbnail and item.folder == False: item.thumbnail = channelselector.get_thumb("nofolder.png", "banner_") # Estas imagenes no estan en banner, asi que si queremos banner, para que no se vean mal las quitamos elif parent_item.viewmode in ["banner", "channel"] and item.thumbnail.startswith( "http://media.xxxxx/thumb_"): item.thumbnail = "" # Si el item no contiene categoria,le ponemos la del item padre if item.category == "": item.category = parent_item.category # Si el item no contiene fanart,le ponemos la del item padre if item.fanart == "": item.fanart = parent_item.fanart title = item.title.replace(item.title.lstrip(), "").replace(" ", " ") + item.title.lstrip() # Formatear titulo if item.text_color: title = '[COLOR %s]%s[/COLOR]' % (item.text_color, title) if item.text_bold: title = '[B]%s[/B]' % title if item.text_italic: title = '[I]%s[/I]' % title title = self.kodi_labels_to_html(title) # Añade headers a las imagenes si estan en un servidor con cloudflare from core import httptools item.thumbnail = httptools.get_url_headers(item.thumbnail) item.fanart = httptools.get_url_headers(item.fanart) JsonItem = {} JsonItem["title"] = title JsonItem["thumbnail"] = item.thumbnail JsonItem["fanart"] = item.fanart JsonItem["plot"] = item.plot JsonItem["action"] = item.action JsonItem["url"] = item.tourl() JsonItem["context"] = [] if not item.action == "go_back": for Comando in self.set_context_commands(item, parent_item): JsonItem["context"].append({"title": Comando[0], "url": Comando[1]}) JsonData["data"]["itemlist"].append(JsonItem) ID = self.send_message(JsonData) self.get_data(ID) def set_context_commands(self, item, parent_item): """ Función para generar los menus contextuales. 1. Partiendo de los datos de item.context a. Metodo antiguo item.context tipo str separando las opciones por "|" (ejemplo: item.context = "1|2|3") (solo predefinidos) b. Metodo list: item.context es un list con las diferentes opciones del menu: - Predefinidos: Se cargara una opcion predefinida con un nombre. item.context = ["1","2","3"] - dict(): Se cargara el item actual modificando los campos que se incluyan en el dict() en caso de modificar los campos channel y action estos serán guardados en from_channel y from_action. item.context = [{"title":"Nombre del menu", "action": "action del menu", "channel", "channel del menu"}, {...}] 2. Añadiendo opciones segun criterios Se pueden añadir opciones al menu contextual a items que cumplan ciertas condiciones 3. Añadiendo opciones a todos los items Se pueden añadir opciones al menu contextual para todos los items @param item: elemento que contiene los menu contextuales @type item: item @param parent_item: @type parent_item: item """ context_commands = [] # Creamos un list con las diferentes opciones incluidas en item.context if type(item.context) == str: context = item.context.split("|") elif type(item.context) == list: context = item.context else: context = [] # Opciones segun item.context for command in context: # Predefinidos if type(command) == str: if command == "buscar_trailer": context_commands.append(("Buscar Trailer", item.clone(channel="trailertools", action="buscartrailer", contextual=True).tourl())) # Formato dict if type(command) == dict: # Los parametros del dict, se sobreescriben al nuevo context_item en caso de sobreescribir "action" y # "channel", los datos originales se guardan en "from_action" y "from_channel" if "action" in command: command["from_action"] = item.action if "channel" in command: command["from_channel"] = item.channel context_commands.append( (command["title"], item.clone(**command).tourl())) # Opciones segun criterios # Ir al Menu Principal (channel.mainlist) if parent_item.channel not in ["news", "channelselector"] and item.action != "mainlist" and parent_item.action != "mainlist": context_commands.append(("Ir al Menu Principal", Item(channel=item.channel, action="mainlist").tourl())) # Añadir a Favoritos if item.channel not in ["favorites", "videolibrary", "help", "setting", ""] and not parent_item.channel == "favorites": context_commands.append((config.get_localized_string(30155), item.clone(channel="favorites", action="addFavourite", from_channel=item.channel, from_action=item.action).tourl())) # Añadimos opción contextual para Añadir la serie completa a la videoteca if item.channel != "videolibrary" and item.action in ["episodios", "get_episodios"] \ and (item.contentSerieName or item.show): context_commands.append(("Añadir Serie a Videoteca", item.clone(action="add_serie_to_library", from_action=item.action).tourl())) # Añadir Pelicula a videoteca if item.channel != "videolibrary" and item.action in ["detail", "findvideos"] \ and item.contentType == 'movie': context_commands.append(("Añadir Pelicula a Videoteca", item.clone(action="add_pelicula_to_library", from_action=item.action).tourl())) # Descargar pelicula if item.contentType == "movie" and not item.channel == "downloads": context_commands.append(("Descargar Pelicula", item.clone(channel="downloads", action="save_download", from_channel=item.channel, from_action=item.action).tourl())) # Descargar serie if item.contentType == "tvshow" and not item.channel == "downloads": context_commands.append(("Descargar Serie", item.clone(channel="downloads", action="save_download", from_channel=item.channel, from_action=item.action).tourl())) # Descargar episodio if item.contentType == "episode" and not item.channel == "downloads": context_commands.append(("Descargar Episodio", item.clone(channel="downloads", action="save_download", from_channel=item.channel, from_action=item.action).tourl())) # Descargar temporada if item.contentType == "season" and not item.channel == "downloads": context_commands.append(("Descargar Temporada", item.clone(channel="downloads", action="save_download", from_channel=item.channel, from_action=item.action).tourl())) # Abrir configuración if parent_item.channel not in ["setting", "news", "search"]: context_commands.append(("Abrir Configuración", Item(channel="setting", action="mainlist").tourl())) return sorted(context_commands, key=lambda comand: comand[0]) def dialog_ok(self, heading, line1, line2="", line3=""): text = line1 if line2: text += "\n" + line2 if line3: text += "\n" + line3 text = self.kodi_labels_to_html(text) JsonData = {} JsonData["action"] = "Alert" JsonData["data"] = {} JsonData["data"]["title"] = heading JsonData["data"]["text"] = unicode(text, "utf8", "ignore").encode("utf8") ID = self.send_message(JsonData) self.get_data(ID) def dialog_notification(self, heading, message, icon=0, time=5000, sound=True): JsonData = {} JsonData["action"] = "notification" JsonData["data"] = {} JsonData["data"]["title"] = self.kodi_labels_to_html(heading) JsonData["data"]["text"] = self.kodi_labels_to_html(message) JsonData["data"]["icon"] = icon JsonData["data"]["sound"] = sound JsonData["data"]["time"] = time self.send_message(JsonData) return def dialog_yesno(self, heading, line1, line2="", line3="", nolabel="No", yeslabel="Si", autoclose=""): text = line1 if line2: text += "\n" + line2 if line3: text += "\n" + line3 text = self.kodi_labels_to_html(text) heading = self.kodi_labels_to_html(heading) JsonData = {} JsonData["action"] = "AlertYesNo" JsonData["data"] = {} JsonData["data"]["title"] = heading JsonData["data"]["text"] = text ID = self.send_message(JsonData) response = self.get_data(ID) return response def dialog_select(self, heading, list): JsonData = {} heading = self.kodi_labels_to_html(heading) JsonData["action"] = "List" JsonData["data"] = {} JsonData["data"]["title"] = heading JsonData["data"]["list"] = [] for Elemento in list: JsonData["data"]["list"].append(self.kodi_labels_to_html(Elemento)) ID = self.send_message(JsonData) response = self.get_data(ID) return response def dialog_progress(self, heading, line1, line2="", line3=""): class Dialog(object): def __init__(self, heading, line1, line2, line3, platformtools): self.platformtools = platformtools self.closed = False self.heading = self.platformtools.kodi_labels_to_html(heading) text = line1 if line2: text += "\n" + line2 if line3: text += "\n" + line3 text = self.platformtools.kodi_labels_to_html(text) JsonData = {} JsonData["action"] = "Progress" JsonData["data"] = {} JsonData["data"]["title"] = heading JsonData["data"]["text"] = text JsonData["data"]["percent"] = 0 ID = self.platformtools.send_message(JsonData) self.platformtools.get_data(ID) def iscanceled(self): JsonData = {} JsonData["action"] = "ProgressIsCanceled" JsonData["data"] = {} ID = self.platformtools.send_message(JsonData) response = self.platformtools.get_data(ID) return response def update(self, percent, line1, line2="", line3=""): text = line1 if line2: text += "\n" + line2 if line3: text += "\n" + line3 text = self.platformtools.kodi_labels_to_html(text) JsonData = {} JsonData["action"] = "ProgressUpdate" JsonData["data"] = {} JsonData["data"]["title"] = self.heading JsonData["data"]["text"] = text JsonData["data"]["percent"] = percent self.platformtools.send_message(JsonData) def close(self): JsonData = {} JsonData["action"] = "ProgressClose" JsonData["data"] = {} ID = self.platformtools.send_message(JsonData) self.platformtools.get_data(ID) self.closed = True return Dialog(heading, line1, line2, line3, self) def dialog_progress_bg(self, heading, message=""): class Dialog(object): def __init__(self, heading, message, platformtools): self.platformtools = platformtools self.closed = False self.heading = self.platformtools.kodi_labels_to_html(heading) message = self.platformtools.kodi_labels_to_html(message) JsonData = {} JsonData["action"] = "ProgressBG" JsonData["data"] = {} JsonData["data"]["title"] = heading JsonData["data"]["text"] = message JsonData["data"]["percent"] = 0 ID = self.platformtools.send_message(JsonData) self.platformtools.get_data(ID) def isFinished(self): return not self.closed def update(self, percent=0, heading="", message=""): JsonData = {} JsonData["action"] = "ProgressBGUpdate" JsonData["data"] = {} JsonData["data"]["title"] = self.platformtools.kodi_labels_to_html(heading) JsonData["data"]["text"] = self.platformtools.kodi_labels_to_html(message) JsonData["data"]["percent"] = percent self.platformtools.send_message(JsonData) def close(self): JsonData = {} JsonData["action"] = "ProgressBGClose" JsonData["data"] = {} ID = self.platformtools.send_message(JsonData) self.platformtools.get_data(ID) self.closed = True return Dialog(heading, message, self) def dialog_input(self, default="", heading="", hidden=False): JsonData = {} JsonData["action"] = "Keyboard" JsonData["data"] = {} JsonData["data"]["title"] = self.kodi_labels_to_html(heading) JsonData["data"]["text"] = default JsonData["data"]["password"] = hidden ID = self.send_message(JsonData) response = self.get_data(ID) return response def dialog_numeric(self, type, heading, default=""): return self.dialog_input("", heading, False) def itemlist_refresh(self): JsonData = {} JsonData["action"] = "Refresh" JsonData["data"] = {} ID = self.send_message(JsonData) self.get_data(ID) def itemlist_update(self, item): JsonData = {} JsonData["action"] = "Update" JsonData["data"] = {} JsonData["data"]["url"] = item.tourl() ID = self.send_message(JsonData) self.get_data(ID) def is_playing(self): JsonData = {} JsonData["action"] = "isPlaying" JsonData["data"] = {} ID = self.send_message(JsonData) response = self.get_data(ID) return response def play_video(self, item): if item.contentTitle: title = item.contentTitle elif item.fulltitle: title = item.fulltitle else: title = item.title if item.contentPlot: plot = item.contentPlot else: plot = item.plot if item.server == "torrent": self.play_torrent(item) else: JsonData = {} JsonData["action"] = "Play" JsonData["data"] = {} JsonData["data"]["title"] = title JsonData["data"]["plot"] = plot JsonData["data"]["video_url"] = item.video_url JsonData["data"]["url"] = item.url JsonData["data"]["host"] = self.controller.host ID = self.send_message(JsonData) self.get_data(ID) def play_torrent(self, item): import time import os played = False # Importamos el cliente from btserver import Client # Iniciamos el cliente: c = Client(url=item.url, is_playing_fnc=self.is_playing, wait_time=None, timeout=5, temp_path=os.path.join(config.get_data_path(), "torrent")) # Mostramos el progreso progreso = self.dialog_progress("Alfa - Torrent", "Iniciando...") # Mientras el progreso no sea cancelado ni el cliente cerrado while not progreso.iscanceled() and not c.closed: try: # Obtenemos el estado del torrent s = c.status # Montamos las tres lineas con la info del torrent txt = '%.2f%% de %.1fMB %s | %.1f kB/s' % \ (s.progress_file, s.file_size, s.str_state, s._download_rate) txt2 = 'S: %d(%d) P: %d(%d) | DHT:%s (%d) | Trakers: %d' % \ ( s.num_seeds, s.num_complete, s.num_peers, s.num_incomplete, s.dht_state, s.dht_nodes, s.trackers) txt3 = 'Origen Peers TRK: %d DHT: %d PEX: %d LSD %d ' % \ (s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers) progreso.update(s.buffer, txt, txt2, txt3) time.sleep(1) # Si el buffer se ha llenado y la reproduccion no ha sido iniciada, se inicia if s.buffer == 100 and not played: # Cerramos el progreso progreso.close() # Obtenemos el playlist del torrent item.video_url = c.get_play_list() item.server = "directo" self.play_video(item) # Marcamos como reproducido para que no se vuelva a iniciar played = True # Y esperamos a que el reproductor se cierre while self.is_playing(): time.sleep(1) # Cuando este cerrado, Volvemos a mostrar el dialogo progreso = self.dialog_progress("Alfa - Torrent", "Iniciando...") except: import traceback logger.info(traceback.format_exc()) break progreso.update(100, "Terminando y eliminando datos", " ", " ") # Detenemos el cliente if not c.closed: c.stop() # Y cerramos el progreso progreso.close() return def open_settings(self, items): from platformcode import config JsonData = {} JsonData["action"] = "OpenConfig" JsonData["data"] = {} JsonData["data"]["title"] = "Opciones" JsonData["data"]["items"] = [] for item in items: if item.get('option') == 'hidden': item['hidden'] = True for key in item: if key in ["lvalues", "label", "category"]: try: ops = item[key].split("|") for x, op in enumerate(ops): ops[x] = config.get_localized_string(int(ops[x])) item[key] = "|".join(ops) except: pass JsonData["data"]["items"].append(item) ID = self.send_message(JsonData) response = self.get_data(ID) if response: from platformcode import config config.set_settings(response) JsonData = {} JsonData["action"] = "HideLoading" JsonData["data"] = {} self.send_message(JsonData) def show_channel_settings(self, list_controls=None, dict_values=None, caption="", callback=None, item=None, custom_button=None, channelpath=None): from platformcode import config from core import channeltools from core import servertools import inspect if not os.path.isdir(os.path.join(config.get_data_path(), "settings_channels")): os.mkdir(os.path.join(config.get_data_path(), "settings_channels")) title = caption if type(custom_button) == dict: custom_button = {"label": custom_button.get("label", ""), "function": custom_button.get("function", ""), "visible": bool(custom_button.get("visible", True)), "close": bool(custom_button.get("close", False))} else: custom_button = None # Obtenemos el canal desde donde se ha echo la llamada y cargamos los settings disponibles para ese canal if not channelpath: channelpath = inspect.currentframe().f_back.f_back.f_code.co_filename channelname = os.path.basename(channelpath).replace(".py", "") ch_type = os.path.basename(os.path.dirname(channelpath)) # Si no tenemos list_controls, hay que sacarlos del json del canal if not list_controls: # Si la ruta del canal esta en la carpeta "channels", obtenemos los controles y valores mediante chaneltools if os.path.join(config.get_runtime_path(), "channels") in channelpath: # La llamada se hace desde un canal list_controls, default_values = channeltools.get_channel_controls_settings(channelname) kwargs = {"channel": channelname} # Si la ruta del canal esta en la carpeta "servers", obtenemos los controles y valores mediante servertools elif os.path.join(config.get_runtime_path(), "servers") in channelpath: # La llamada se hace desde un server list_controls, default_values = servertools.get_server_controls_settings(channelname) kwargs = {"server": channelname} # En caso contrario salimos else: return None # Si no se pasan dict_values, creamos un dict en blanco if dict_values == None: dict_values = {} # Ponemos el titulo if caption == "": caption = str(config.get_localized_string(30100)) + " -- " + channelname.capitalize() elif caption.startswith('@') and unicode(caption[1:]).isnumeric(): caption = config.get_localized_string(int(caption[1:])) JsonData = {} JsonData["action"] = "OpenConfig" JsonData["data"] = {} JsonData["data"]["title"] = self.kodi_labels_to_html(caption) JsonData["data"]["custom_button"] = custom_button JsonData["data"]["items"] = [] # Añadir controles for c in list_controls: if not "default" in c: c["default"] = "" if not "color" in c: c["color"] = "auto" if not "label" in c: continue # Obtenemos el valor if "id" in c: if not c["id"] in dict_values: if not callback: c["value"] = config.get_setting(c["id"], **kwargs) else: c["value"] = c["default"] dict_values[c["id"]] = c["value"] else: c["value"] = dict_values[c["id"]] # Translation if c['label'].startswith('@') and unicode(c['label'][1:]).isnumeric(): c['label'] = str(config.get_localized_string(c['label'][1:])) if c["label"].endswith(":"): c["label"] = c["label"][:-1] if c['type'] == 'list': lvalues = [] for li in c['lvalues']: if li.startswith('@') and unicode(li[1:]).isnumeric(): lvalues.append(str(config.get_localized_string(li[1:]))) else: lvalues.append(li) c['lvalues'] = lvalues c["label"] = self.kodi_labels_to_html(c["label"]) JsonData["data"]["items"].append(c) ID = self.send_message(JsonData) close = False while True: data = self.get_data(ID) if type(data) == dict: JsonData["action"] = "HideLoading" JsonData["data"] = {} self.send_message(JsonData) for v in data: if data[v] == "true": data[v] = True if data[v] == "false": data[v] = False if unicode(data[v]).isnumeric(): data[v] = int(data[v]) if callback and '.' in callback: package, callback = callback.rsplit('.', 1) else: package = '%s.%s' % (ch_type, channelname) cb_channel = None try: cb_channel = __import__(package, None, None, [package]) except ImportError: logger.error('Imposible importar %s' % package) if callback: # Si existe una funcion callback la invocamos ... return getattr(cb_channel, callback)(item, data) else: # si no, probamos si en el canal existe una funcion 'cb_validate_config' ... try: return getattr(cb_channel, 'cb_validate_config')(item, data) except AttributeError: # ... si tampoco existe 'cb_validate_config'... for v in data: config.set_setting(v, data[v], **kwargs) elif data == "custom_button": if '.' in callback: package, callback = callback.rsplit('.', 1) else: package = '%s.%s' % (ch_type, channelname) try: cb_channel = __import__(package, None, None, [package]) except ImportError: logger.error('Imposible importar %s' % package) else: return_value = getattr(cb_channel, custom_button['function'])(item, dict_values) if custom_button["close"] == True: return return_value else: JsonData["action"] = "custom_button" JsonData["data"] = {} JsonData["data"]["values"] = dict_values JsonData["data"]["return_value"] = return_value ID = self.send_message(JsonData) elif data == False: return None def show_video_info(self, data, caption="", item=None, scraper=Tmdb): from platformcode import html_info_window return html_info_window.InfoWindow().start(self, data, caption, item, scraper) def show_recaptcha(self, key, url): from platformcode import html_recaptcha return html_recaptcha.recaptcha().start(self, key, url) def kodi_labels_to_html(self, text): text = re.sub(r"(?:\[I\])(.*?)(?:\[/I\])", r"\1", text) text = re.sub(r"(?:\[B\])(.*?)(?:\[/B\])", r"\1", text) text = re.sub(r"(?:\[COLOR (?:0x)?([0-f]{2})([0-f]{2})([0-f]{2})([0-f]{2})\])(.*?)(?:\[/COLOR\])", lambda m: "%s" % ( int(m.group(2), 16), int(m.group(3), 16), int(m.group(4), 16), int(m.group(1), 16) / 255.0, m.group(5)), text) text = re.sub(r"(?:\[COLOR (?:0x)?([0-f]{2})([0-f]{2})([0-f]{2})\])(.*?)(?:\[/COLOR\])", r"\4", text) text = re.sub(r"(?:\[COLOR (?:0x)?([a-z|A-Z]+)\])(.*?)(?:\[/COLOR\])", r"\2", text) return text