# -*- coding: utf-8 -*- # -------------------------------------------------------------------------------- # Server management # -------------------------------------------------------------------------------- import os import re import filetools import urlparse from core import httptools from core import jsontools from core.item import Item from platformcode import config, logger from platformcode import platformtools # from servers.decrypters import zcrypt from lib import unshortenit dict_servers_parameters = {} def find_video_items(item=None, data=None): """ Función genérica para buscar vídeos en una página, devolviendo un itemlist con los items listos para usar. - Si se pasa un Item como argumento, a los items resultantes mantienen los parametros del item pasado - Si no se pasa un Item, se crea uno nuevo, pero no contendra ningun parametro mas que los propios del servidor. @param item: Item al cual se quieren buscar vídeos, este debe contener la url válida @type item: Item @param data: Cadena con el contendio de la página ya descargado (si no se pasa item) @type data: str @return: devuelve el itemlist con los resultados @rtype: list """ logger.info() itemlist = [] # Descarga la página if data is None: data = httptools.downloadpage(item.url).data data = unshortenit.findlinks(data) # Crea un item si no hay item if item is None: item = Item() # Pasa los campos thumbnail y title a contentThumbnail y contentTitle else: if not item.contentThumbnail: item.contentThumbnail = item.thumbnail if not item.contentTitle: item.contentTitle = item.title # Busca los enlaces a los videos for label, url, server, thumbnail in findvideos(data): title = config.get_localized_string(70206) % label itemlist.append( item.clone(title=title, action="play", url=url, thumbnail=thumbnail, server=server, folder=False)) return itemlist def get_servers_itemlist(itemlist, fnc=None, sort=False): """ Obtiene el servidor para cada uno de los items, en funcion de su url. - Asigna el servidor, la url modificada, el thumbnail (si el item no contiene contentThumbnail se asigna el del thumbnail) - Si se pasa una funcion por el argumento fnc, esta se ejecuta pasando el item como argumento, el resultado de esa funcion se asigna al titulo del item - En esta funcion podemos modificar cualquier cosa del item - Esta funcion siempre tiene que devolver el item.title como resultado - Si no se encuentra servidor para una url, se asigna "directo" @param itemlist: listado de items @type itemlist: list @param fnc: función para ejecutar con cada item (para asignar el titulo) @type fnc: function @param sort: indica si el listado resultante se ha de ordenar en funcion de la lista de servidores favoritos @type sort: bool """ # Recorre los servidores for serverid in get_servers_list().keys(): server_parameters = get_server_parameters(serverid) # Recorre los patrones for pattern in server_parameters.get("find_videos", {}).get("patterns", []): logger.info(pattern["pattern"]) # Recorre los resultados for match in re.compile(pattern["pattern"], re.DOTALL).finditer( "\n".join([item.url.split('|')[0] for item in itemlist if not item.server])): url = pattern["url"] for x in range(len(match.groups())): url = url.replace("\\%s" % (x + 1), match.groups()[x]) for item in itemlist: if match.group() in item.url: if not item.contentThumbnail: item.contentThumbnail = item.thumbnail item.thumbnail = server_parameters.get("thumbnail", "") item.server = serverid if '|' in item.url: item.url = url + '|' + item.url.split('|')[1] else: item.url = url # Eliminamos los servidores desactivados itemlist = filter(lambda i: not i.server or is_server_enabled(i.server), itemlist) for item in itemlist: # Asignamos "directo" en caso de que el server no se encuentre en pelisalcarta if not item.server and item.url: item.server = 'directo' if fnc: item.title = fnc(item) # Filtrar si es necesario itemlist = filter_servers(itemlist) # Ordenar segun favoriteslist si es necesario if sort: itemlist = sort_servers(itemlist) return itemlist def findvideos(data, skip=False): """ Recorre la lista de servidores disponibles y ejecuta la funcion findvideosbyserver para cada uno de ellos :param data: Texto donde buscar los enlaces :param skip: Indica un limite para dejar de recorrer la lista de servidores. Puede ser un booleano en cuyo caso seria False para recorrer toda la lista (valor por defecto) o True para detenerse tras el primer servidor que retorne algun enlace. Tambien puede ser un entero mayor de 1, que representaria el numero maximo de enlaces a buscar. :return: """ logger.info() devuelve = [] skip = int(skip) servers_list = get_servers_list().keys() # Ordenar segun favoriteslist si es necesario servers_list = sort_servers(servers_list) is_filter_servers = False # Ejecuta el findvideos en cada servidor activo for serverid in servers_list: if not is_server_enabled(serverid): continue if config.get_setting("filter_servers") == True and config.get_setting("black_list", server=serverid): is_filter_servers = True continue devuelve.extend(findvideosbyserver(data, serverid)) if skip and len(devuelve) >= skip: devuelve = devuelve[:skip] break if config.get_setting("filter_servers") == False: is_filter_servers = False if not devuelve and is_filter_servers: platformtools.dialog_ok(config.get_localized_string(60000), config.get_localized_string(60001)) return devuelve def findvideosbyserver(data, serverid): serverid = get_server_name(serverid) if not serverid: return [] server_parameters = get_server_parameters(serverid) devuelve = [] if "find_videos" in server_parameters: # Recorre los patrones for pattern in server_parameters["find_videos"].get("patterns", []): msg = "%s\npattern: %s" % (serverid, pattern["pattern"]) # Recorre los resultados for match in re.compile(pattern["pattern"], re.DOTALL).finditer(data): url = pattern["url"] # Crea la url con los datos for x in range(len(match.groups())): url = url.replace("\\%s" % (x + 1), match.groups()[x]) msg += "\nurl encontrada: %s" % url value = server_parameters["name"], url, serverid, server_parameters.get("thumbnail", "") if value not in devuelve and url not in server_parameters["find_videos"].get("ignore_urls", []): devuelve.append(value) logger.info(msg) return devuelve def guess_server_thumbnail(serverid): server = get_server_name(serverid) server_parameters = get_server_parameters(server) return server_parameters.get('thumbnail', "") def get_server_from_url(url): encontrado = findvideos(url, True) if len(encontrado) > 0: devuelve = encontrado[0][2] else: devuelve = "directo" return devuelve def resolve_video_urls_for_playing(server, url, video_password="", muestra_dialogo=False): """ Función para obtener la url real del vídeo @param server: Servidor donde está alojado el vídeo @type server: str @param url: url del vídeo @type url: str @param video_password: Password para el vídeo @type video_password: str @param muestra_dialogo: Muestra el diálogo de progreso @type muestra_dialogo: bool @return: devuelve la url del video @rtype: list """ logger.info("Server: %s, Url: %s" % (server, url)) server = server.lower() video_urls = [] video_exists = True error_messages = [] opciones = [] # Si el vídeo es "directo" o "local", no hay que buscar más if server == "directo" or server == "local": logger.info("Server: %s, la url es la buena" % server) video_urls.append(["%s [%s]" % (urlparse.urlparse(url)[2][-4:], server), url]) # Averigua la URL del vídeo else: if server: server_parameters = get_server_parameters(server) else: server_parameters = {} if server_parameters: # Muestra un diágo de progreso if muestra_dialogo: progreso = platformtools.dialog_progress(config.get_localized_string(20000), config.get_localized_string(70180) % server_parameters["name"]) # Cuenta las opciones disponibles, para calcular el porcentaje orden = [ ["free"] + [server] + [premium for premium in server_parameters["premium"] if not premium == server], [server] + [premium for premium in server_parameters["premium"] if not premium == server] + ["free"], [premium for premium in server_parameters["premium"] if not premium == server] + [server] + ["free"] ] if server_parameters["free"] == True: opciones.append("free") opciones.extend( [premium for premium in server_parameters["premium"] if config.get_setting("premium", server=premium)]) priority = int(config.get_setting("resolve_priority")) opciones = sorted(opciones, key=lambda x: orden[priority].index(x)) logger.info("Opciones disponibles: %s | %s" % (len(opciones), opciones)) else: logger.error("No existe conector para el servidor %s" % server) error_messages.append(config.get_localized_string(60004) % server) muestra_dialogo = False # Importa el server try: server_module = __import__('servers.%s' % server, None, None, ["servers.%s" % server]) logger.info("Servidor importado: %s" % server_module) except: server_module = None logger.error("No se ha podido importar el servidor: %s" % server) import traceback logger.error(traceback.format_exc()) # Si tiene una función para ver si el vídeo existe, lo comprueba ahora if hasattr(server_module, 'test_video_exists'): logger.info("Invocando a %s.test_video_exists" % server) try: video_exists, message = server_module.test_video_exists(page_url=url) if not video_exists: error_messages.append(message) logger.info("test_video_exists dice que el video no existe") else: logger.info("test_video_exists dice que el video SI existe") except: logger.error("No se ha podido comprobar si el video existe") import traceback logger.error(traceback.format_exc()) # Si el video existe y el modo free está disponible, obtenemos la url if video_exists: for opcion in opciones: # Opcion free y premium propio usa el mismo server if opcion == "free" or opcion == server: serverid = server_module server_name = server_parameters["name"] # Resto de opciones premium usa un debrider else: serverid = __import__('servers.debriders.%s' % opcion, None, None, ["servers.debriders.%s" % opcion]) server_name = get_server_parameters(opcion)["name"] # Muestra el progreso if muestra_dialogo: progreso.update((100 / len(opciones)) * opciones.index(opcion), config.get_localized_string(70180) % server_name) # Modo free if opcion == "free": try: logger.info("Invocando a %s.get_video_url" % server) response = serverid.get_video_url(page_url=url, video_password=video_password) video_urls.extend(response) except: logger.error("Error al obtener la url en modo free") error_messages.append(config.get_localized_string(60006) % server_name) import traceback logger.error(traceback.format_exc()) # Modo premium else: try: logger.info("Invocando a %s.get_video_url" % opcion) response = serverid.get_video_url(page_url=url, premium=True, user=config.get_setting("user", server=opcion), password=config.get_setting("password", server=opcion), video_password=video_password) if response and response[0][1]: video_urls.extend(response) elif response and response[0][0]: error_messages.append(response[0][0]) else: error_messages.append(config.get_localized_string(60006) % server_name) except: logger.error("Error en el servidor: %s" % opcion) error_messages.append(config.get_localized_string(60006) % server_name) import traceback logger.error(traceback.format_exc()) # Si ya tenemos URLS, dejamos de buscar if video_urls and config.get_setting("resolve_stop") == True: break # Cerramos el progreso if muestra_dialogo: progreso.update(100, config.get_localized_string(60008)) progreso.close() # Si no hay opciones disponibles mostramos el aviso de las cuentas premium if video_exists and not opciones and server_parameters.get("premium"): listapremium = [get_server_parameters(premium)["name"] for premium in server_parameters["premium"]] error_messages.append( config.get_localized_string(60009) % (server, " o ".join(listapremium))) # Si no tenemos urls ni mensaje de error, ponemos uno generico elif not video_urls and not error_messages: error_messages.append(config.get_localized_string(60006) % get_server_parameters(server)["name"]) return video_urls, len(video_urls) > 0, "
".join(error_messages) def get_server_name(serverid): """ Función obtener el nombre del servidor real a partir de una cadena. @param serverid: Cadena donde mirar @type serverid: str @return: Nombre del servidor @rtype: str """ serverid = serverid.lower().split(".")[0] # Obtenemos el listado de servers server_list = get_servers_list().keys() # Si el nombre está en la lista if serverid in server_list: return serverid # Recorre todos los servers buscando el nombre for server in server_list: params = get_server_parameters(server) # Si la nombre esta en el listado de ids if serverid in params["id"]: return server # Si el nombre es mas de una palabra, comprueba si algun id esta dentro del nombre: elif len(serverid.split()) > 1: for id in params["id"]: if id in serverid: return server # Si no se encuentra nada se devuelve una cadena vacia return "" def is_server_enabled(server): """ Función comprobar si un servidor está segun la configuración establecida @param server: Nombre del servidor @type server: str @return: resultado de la comprobación @rtype: bool """ server = get_server_name(server) # El server no existe if not server: return False server_parameters = get_server_parameters(server) if server_parameters["active"] == True: if not config.get_setting("hidepremium"): return True elif server_parameters["free"] == True: return True elif [premium for premium in server_parameters["premium"] if config.get_setting("premium", server=premium)]: return True return False def get_server_parameters(server): """ Obtiene los datos del servidor @param server: Nombre del servidor @type server: str @return: datos del servidor @rtype: dict """ # logger.info("server %s" % server) global dict_servers_parameters server = server.split('.')[0] if not server: return {} if server not in dict_servers_parameters: try: # Servers if os.path.isfile(os.path.join(config.get_runtime_path(), "servers", server + ".json")): path = os.path.join(config.get_runtime_path(), "servers", server + ".json") # Debriders elif os.path.isfile(os.path.join(config.get_runtime_path(), "servers", "debriders", server + ".json")): path = os.path.join(config.get_runtime_path(), "servers", "debriders", server + ".json") # #Cuando no está bien definido el server en el canal (no existe conector), muestra error por no haber "path" y se tiene que revisar el canal # data = filetools.read(path) dict_server = jsontools.load(data) # Imagenes: se admiten url y archivos locales dentro de "resources/images" if dict_server.get("thumbnail") and "://" not in dict_server["thumbnail"]: dict_server["thumbnail"] = os.path.join("https://raw.githubusercontent.com/kodiondemand/media/master/resources/servers", dict_server["thumbnail"]) for k in ['premium', 'id']: dict_server[k] = dict_server.get(k, list()) if type(dict_server[k]) == str: dict_server[k] = [dict_server[k]] if "find_videos" in dict_server: dict_server['find_videos']["patterns"] = dict_server['find_videos'].get("patterns", list()) dict_server['find_videos']["ignore_urls"] = dict_server['find_videos'].get("ignore_urls", list()) if "settings" in dict_server: dict_server['has_settings'] = True else: dict_server['has_settings'] = False dict_servers_parameters[server] = dict_server except: mensaje = config.get_localized_string(59986) % server import traceback logger.error(mensaje + traceback.format_exc()) return {} return dict_servers_parameters[server] def get_server_json(server_name): # logger.info("server_name=" + server_name) try: server_path = filetools.join(config.get_runtime_path(), "servers", server_name + ".json") if not filetools.exists(server_path): server_path = filetools.join(config.get_runtime_path(), "servers", "debriders", server_name + ".json") # logger.info("server_path=" + server_path) server_json = jsontools.load(filetools.read(server_path)) # logger.info("server_json= %s" % server_json) except Exception, ex: template = "An exception of type %s occured. Arguments:\n%r" message = template % (type(ex).__name__, ex.args) logger.error(" %s" % message) server_json = None return server_json def get_server_host(server_name): from core import scrapertools return [scrapertools.get_domain_from_url(pattern['url']) for pattern in get_server_json(server_name)['find_videos']['patterns']] def get_server_controls_settings(server_name): dict_settings = {} list_controls = get_server_json(server_name).get('settings', []) import copy list_controls = copy.deepcopy(list_controls) # Conversion de str a bool, etc... for c in list_controls: if 'id' not in c or 'type' not in c or 'default' not in c: # Si algun control de la lista no tiene id, type o default lo ignoramos continue # new dict with key(id) and value(default) from settings dict_settings[c['id']] = c['default'] return list_controls, dict_settings def get_server_setting(name, server, default=None): """ Retorna el valor de configuracion del parametro solicitado. Devuelve el valor del parametro 'name' en la configuracion propia del servidor 'server'. Busca en la ruta \addon_data\plugin.video.addon\settings_servers el archivo server_data.json y lee el valor del parametro 'name'. Si el archivo server_data.json no existe busca en la carpeta servers el archivo server.json y crea un archivo server_data.json antes de retornar el valor solicitado. Si el parametro 'name' tampoco existe en el el archivo server.json se devuelve el parametro default. @param name: nombre del parametro @type name: str @param server: nombre del servidor @type server: str @param default: valor devuelto en caso de que no exista el parametro name @type default: any @return: El valor del parametro 'name' @rtype: any """ # Creamos la carpeta si no existe if not os.path.exists(os.path.join(config.get_data_path(), "settings_servers")): os.mkdir(os.path.join(config.get_data_path(), "settings_servers")) file_settings = os.path.join(config.get_data_path(), "settings_servers", server + "_data.json") dict_settings = {} dict_file = {} if os.path.exists(file_settings): # Obtenemos configuracion guardada de ../settings/channel_data.json try: dict_file = jsontools.load(open(file_settings, "rb").read()) if isinstance(dict_file, dict) and 'settings' in dict_file: dict_settings = dict_file['settings'] except EnvironmentError: logger.info("ERROR al leer el archivo: %s" % file_settings) if not dict_settings or name not in dict_settings: # Obtenemos controles del archivo ../servers/server.json try: list_controls, default_settings = get_server_controls_settings(server) except: default_settings = {} if name in default_settings: # Si el parametro existe en el server.json creamos el server_data.json default_settings.update(dict_settings) dict_settings = default_settings dict_file['settings'] = dict_settings # Creamos el archivo ../settings/channel_data.json json_data = jsontools.dump(dict_file) try: open(file_settings, "wb").write(json_data) except EnvironmentError: logger.info("ERROR al salvar el archivo: %s" % file_settings) # Devolvemos el valor del parametro local 'name' si existe, si no se devuelve default return dict_settings.get(name, default) def set_server_setting(name, value, server): # Creamos la carpeta si no existe if not os.path.exists(os.path.join(config.get_data_path(), "settings_servers")): os.mkdir(os.path.join(config.get_data_path(), "settings_servers")) file_settings = os.path.join(config.get_data_path(), "settings_servers", server + "_data.json") dict_settings = {} dict_file = None if os.path.exists(file_settings): # Obtenemos configuracion guardada de ../settings/channel_data.json try: dict_file = jsontools.load(open(file_settings, "r").read()) dict_settings = dict_file.get('settings', {}) except EnvironmentError: logger.info("ERROR al leer el archivo: %s" % file_settings) dict_settings[name] = value # comprobamos si existe dict_file y es un diccionario, sino lo creamos if dict_file is None or not dict_file: dict_file = {} dict_file['settings'] = dict_settings # Creamos el archivo ../settings/channel_data.json try: json_data = jsontools.dump(dict_file) open(file_settings, "w").write(json_data) except EnvironmentError: logger.info("ERROR al salvar el archivo: %s" % file_settings) return None return value def get_servers_list(): """ Obtiene un diccionario con todos los servidores disponibles @return: Diccionario cuyas claves son los nombre de los servidores (nombre del json) y como valor un diccionario con los parametros del servidor. @rtype: dict """ server_list = {} for server in os.listdir(os.path.join(config.get_runtime_path(), "servers")): if server.endswith(".json") and not server == "version.json": server_parameters = get_server_parameters(server) if server_parameters["active"] == True: server_list[server.split(".")[0]] = server_parameters return server_list def get_debriders_list(): """ Obtiene un diccionario con todos los debriders disponibles @return: Diccionario cuyas claves son los nombre de los debriders (nombre del json) y como valor un diccionario con los parametros del servidor. @rtype: dict """ server_list = {} for server in os.listdir(os.path.join(config.get_runtime_path(), "servers", "debriders")): if server.endswith(".json"): server_parameters = get_server_parameters(server) if server_parameters["active"] == True: logger.info(server_parameters) server_list[server.split(".")[0]] = server_parameters return server_list def sort_servers(servers_list): """ Si esta activada la opcion "Ordenar servidores" en la configuracion de servidores y existe un listado de servidores favoritos en la configuracion lo utiliza para ordenar la lista servers_list :param servers_list: Listado de servidores para ordenar. Los elementos de la lista servers_list pueden ser strings u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str. :return: Lista del mismo tipo de objetos que servers_list ordenada en funcion de los servidores favoritos. """ if servers_list and config.get_setting('favorites_servers'): if isinstance(servers_list[0], Item): servers_list = sorted(servers_list, key=lambda x: config.get_setting("favorites_servers_list", server=x.server) or 100) else: servers_list = sorted(servers_list, key=lambda x: config.get_setting("favorites_servers_list", server=x) or 100) return servers_list def filter_servers(servers_list): """ Si esta activada la opcion "Filtrar por servidores" en la configuracion de servidores, elimina de la lista de entrada los servidores incluidos en la Lista Negra. :param servers_list: Listado de servidores para filtrar. Los elementos de la lista servers_list pueden ser strings u objetos Item. En cuyo caso es necesario q tengan un atributo item.server del tipo str. :return: Lista del mismo tipo de objetos que servers_list filtrada en funcion de la Lista Negra. """ if servers_list and config.get_setting('filter_servers'): if isinstance(servers_list[0], Item): servers_list_filter = filter(lambda x: not config.get_setting("black_list", server=x.server), servers_list) else: servers_list_filter = filter(lambda x: not config.get_setting("black_list", server=x), servers_list) # Si no hay enlaces despues de filtrarlos if servers_list_filter or not platformtools.dialog_yesno(config.get_localized_string(60000), config.get_localized_string(60010), config.get_localized_string(70281)): servers_list = servers_list_filter return servers_list # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Comprobación de enlaces # ----------------------- def check_list_links(itemlist, numero='', timeout=3): """ Comprueba una lista de enlaces a videos y la devuelve modificando el titulo con la verificacion. El parámetro numero indica cuantos enlaces hay que verificar (0:5, 1:10, 2:15, 3:20) El parámetro timeout indica un tope de espera para descargar la página """ numero = ((int(numero) + 1) * 5) if numero != '' else 10 for it in itemlist: if numero > 0 and it.server != '' and it.url != '': verificacion = check_video_link(it.url, it.server, timeout) it.title = verificacion + ' ' + it.title.strip() logger.info('VERIFICATION= '+ verificacion) it.alive = verificacion numero -= 1 return itemlist def check_video_link(url, server, timeout=3): """ Comprueba si el enlace a un video es valido y devuelve un string de 2 posiciones con la verificacion. :param url, server: Link y servidor :return: str(2) '??':No se ha podido comprobar. 'Ok':Parece que el link funciona. 'NO':Parece que no funciona. """ NK = "[COLOR 0xFFF9B613][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]" OK = "[COLOR 0xFF00C289][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]" KO = "[COLOR 0xFFC20000][B]" + u"\u2022".encode('utf-8') + "[/B][/COLOR]" # NK = "[COLOR 0xFFF9B613][B]♥[/B][/COLOR]" # OK = "[COLOR 0xFF00C289][B]♥[/B][/COLOR]" # KO = "[COLOR 0xFFC20000][B]♥[/B][/COLOR]" try: server_module = __import__('servers.%s' % server, None, None, ["servers.%s" % server]) except: server_module = None logger.info("[check_video_link] No se puede importar el servidor! %s" % server) return NK if hasattr(server_module, 'test_video_exists'): ant_timeout = httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = timeout # Limitar tiempo de descarga try: video_exists, message = server_module.test_video_exists(page_url=url) if not video_exists: logger.info("[check_video_link] No existe! %s %s %s" % (message, server, url)) resultado = KO else: logger.info("[check_video_link] comprobacion OK %s %s" % (server, url)) resultado = OK except: logger.info("[check_video_link] No se puede comprobar ahora! %s %s" % (server, url)) resultado = NK finally: httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = ant_timeout # Restaurar tiempo de descarga return resultado logger.info("[check_video_link] No hay test_video_exists para servidor: %s" % server) return NK