Merge pull request #487 from pipcat/master
Múltiples listas de alfavoritos
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# Alfa favoritos (Mis enlaces)
|
||||
# ============================
|
||||
# Alfa favoritos
|
||||
# ==============
|
||||
# - Lista de enlaces guardados como favoritos, solamente en Alfa, no Kodi.
|
||||
# - Los enlaces se organizan en carpetas que puede definir el usuario.
|
||||
# - Se utiliza un sólo fichero para guardar todas las carpetas y enlaces: user_favorites.json
|
||||
# - Se puede copiar user_favorites.json a otros dispositivos ya que la única dependencia local es el thumbnail asociado a los enlaces,
|
||||
# - Los enlaces se organizan en carpetas (virtuales) que puede definir el usuario.
|
||||
# - Se utiliza un sólo fichero para guardar todas las carpetas y enlaces: alfavorites-default.json
|
||||
# - Se puede copiar alfavorites-default.json a otros dispositivos ya que la única dependencia local es el thumbnail asociado a los enlaces,
|
||||
# pero se detecta por código y se ajusta al dispositivo actual.
|
||||
# - Se pueden tener distintos ficheros de alfavoritos y alternar entre ellos, pero solamente uno de ellos es la "lista activa".
|
||||
# - Los ficheros deben estar en config.get_data_path() y empezar por alfavorites- y terminar en .json
|
||||
|
||||
# Requerimientos en otros módulos para ejecutar este canal:
|
||||
# - Añadir un enlace a este canal en channelselector.py
|
||||
@@ -14,35 +16,106 @@
|
||||
# ------------------------------------------------------------
|
||||
|
||||
import os, re
|
||||
from datetime import datetime
|
||||
|
||||
from core import filetools
|
||||
from core import jsontools
|
||||
from core.item import Item
|
||||
from platformcode import config, logger
|
||||
from platformcode import platformtools
|
||||
from platformcode import config, logger, platformtools
|
||||
|
||||
from core import filetools, jsontools
|
||||
|
||||
|
||||
def fechahora_actual():
|
||||
return datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
# Helpers para listas
|
||||
# -------------------
|
||||
|
||||
PREFIJO_LISTA = 'alfavorites-'
|
||||
|
||||
# Devuelve el nombre de la lista activa (Ej: alfavorites-default.json)
|
||||
def get_lista_activa():
|
||||
return config.get_setting('lista_activa', default = PREFIJO_LISTA + 'default.json')
|
||||
|
||||
# Extrae nombre de la lista del fichero, quitando prefijo y sufijo (Ej: alfavorites-Prueba.json => Prueba)
|
||||
def get_name_from_filename(filename):
|
||||
return filename.replace(PREFIJO_LISTA, '').replace('.json', '')
|
||||
|
||||
# Componer el fichero de lista a partir de un nombre, añadiendo prefijo y sufijo (Ej: Prueba => alfavorites-Prueba.json)
|
||||
def get_filename_from_name(name):
|
||||
return PREFIJO_LISTA + name + '.json'
|
||||
|
||||
# Apuntar en un fichero de log los códigos de los ficheros que se hayan compartido
|
||||
def save_log_lista_shared(msg):
|
||||
msg = fechahora_actual() + ': ' + msg + os.linesep
|
||||
fullfilename = os.path.join(config.get_data_path(), 'alfavorites_shared.log')
|
||||
with open(fullfilename, 'a') as f: f.write(msg); f.close()
|
||||
|
||||
# Limpiar texto para usar como nombre de fichero
|
||||
def text_clean(txt, disallowed_chars = '[^a-zA-Z0-9\-_()\[\]. ]+', blank_char = ' '):
|
||||
import unicodedata
|
||||
try:
|
||||
txt = unicode(txt, 'utf-8')
|
||||
except NameError: # unicode is a default on python 3
|
||||
pass
|
||||
txt = unicodedata.normalize('NFKD', txt).encode('ascii', 'ignore')
|
||||
txt = txt.decode('utf-8').strip()
|
||||
if blank_char != ' ': txt = txt.replace(' ', blank_char)
|
||||
txt = re.sub(disallowed_chars, '', txt)
|
||||
return str(txt)
|
||||
|
||||
|
||||
|
||||
# Clase para cargar y guardar en el fichero de Alfavoritos
|
||||
# --------------------------------------------------------
|
||||
class AlfavoritesData:
|
||||
|
||||
def __init__(self):
|
||||
self.user_favorites_file = os.path.join(config.get_data_path(), 'user_favorites.json')
|
||||
def __init__(self, filename = None):
|
||||
|
||||
# Si no se especifica ningún fichero se usa la lista_activa (si no la hay se crea)
|
||||
if filename == None:
|
||||
filename = get_lista_activa()
|
||||
|
||||
self.user_favorites_file = os.path.join(config.get_data_path(), filename)
|
||||
|
||||
if not os.path.exists(self.user_favorites_file):
|
||||
self.user_favorites = []
|
||||
else:
|
||||
try:
|
||||
self.user_favorites = jsontools.load(filetools.read(self.user_favorites_file))
|
||||
except:
|
||||
fichero_anterior = os.path.join(config.get_data_path(), 'user_favorites.json')
|
||||
if os.path.exists(fichero_anterior): # formato anterior, convertir (a eliminar después de algunas versiones)
|
||||
jsondata = jsontools.load(filetools.read(fichero_anterior))
|
||||
self.user_favorites = jsondata
|
||||
self.info_lista = {}
|
||||
self.save()
|
||||
filetools.remove(fichero_anterior)
|
||||
else:
|
||||
self.user_favorites = []
|
||||
else:
|
||||
jsondata = jsontools.load(filetools.read(self.user_favorites_file))
|
||||
if not 'user_favorites' in jsondata or not 'info_lista' in jsondata: # formato incorrecto
|
||||
self.user_favorites = []
|
||||
else:
|
||||
self.user_favorites = jsondata['user_favorites']
|
||||
self.info_lista = jsondata['info_lista']
|
||||
|
||||
|
||||
if len(self.user_favorites) == 0:
|
||||
self.user_favorites.append({ 'title': 'Carpeta por defecto', 'items': [] })
|
||||
self.info_lista = {}
|
||||
|
||||
# Crear algunas carpetas por defecto
|
||||
self.user_favorites.append({ 'title': 'Películas', 'items': [] })
|
||||
self.user_favorites.append({ 'title': 'Series', 'items': [] })
|
||||
self.user_favorites.append({ 'title': 'Otros', 'items': [] })
|
||||
|
||||
self.save()
|
||||
|
||||
def save(self):
|
||||
filetools.write(self.user_favorites_file, jsontools.dump(self.user_favorites))
|
||||
if 'created' not in self.info_lista:
|
||||
self.info_lista['created'] = fechahora_actual()
|
||||
self.info_lista['updated'] = fechahora_actual()
|
||||
|
||||
jsondata = {}
|
||||
jsondata['user_favorites'] = self.user_favorites
|
||||
jsondata['info_lista'] = self.info_lista
|
||||
if not filetools.write(self.user_favorites_file, jsontools.dump(jsondata)):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se puede grabar la lista!', os.path.basename(self.user_favorites_file))
|
||||
|
||||
|
||||
# ============================
|
||||
@@ -55,21 +128,21 @@ def addFavourite(item):
|
||||
|
||||
# Si se llega aquí mediante el menú contextual, hay que recuperar los parámetros action y channel
|
||||
if item.from_action:
|
||||
item.__dict__["action"] = item.__dict__.pop("from_action")
|
||||
item.__dict__['action'] = item.__dict__.pop('from_action')
|
||||
if item.from_channel:
|
||||
item.__dict__["channel"] = item.__dict__.pop("from_channel")
|
||||
item.__dict__['channel'] = item.__dict__.pop('from_channel')
|
||||
|
||||
# Limpiar título y quitar color
|
||||
# Limpiar título
|
||||
item.title = re.sub(r'\[COLOR [^\]]*\]', '', item.title.replace('[/COLOR]', '')).strip()
|
||||
if item.text_color:
|
||||
item.__dict__.pop("text_color")
|
||||
item.__dict__.pop('text_color')
|
||||
|
||||
# Diálogo para escoger/crear carpeta
|
||||
i_perfil = _selecciona_perfil(alfav, 'Guardar enlace en:')
|
||||
if i_perfil == -1: return False
|
||||
|
||||
# Detectar que el mismo enlace no exista ya en la carpeta
|
||||
campos = ['channel','action','url','extra'] # si todos estos campos coinciden se considera que el enlace ya existe
|
||||
campos = ['channel','action','url','extra','list_type'] # si todos estos campos coinciden se considera que el enlace ya existe
|
||||
for enlace in alfav.user_favorites[i_perfil]['items']:
|
||||
it = Item().fromurl(enlace)
|
||||
repe = True
|
||||
@@ -81,11 +154,14 @@ def addFavourite(item):
|
||||
platformtools.dialog_notification('Enlace repetido', 'Ya tienes este enlace en la carpeta')
|
||||
return False
|
||||
|
||||
# Si es una película, completar información de tmdb si no se tiene activado tmdb_plus_info
|
||||
if item.contentType == 'movie' and not config.get_setting('tmdb_plus_info', default=False):
|
||||
# Si es una película/serie, completar información de tmdb si no se tiene activado tmdb_plus_info (para season/episodio no hace falta pq ya se habrá hecho la "segunda pasada")
|
||||
if (item.contentType == 'movie' or item.contentType == 'tvshow') and not config.get_setting('tmdb_plus_info', default=False):
|
||||
from core import tmdb
|
||||
tmdb.set_infoLabels(item, True) # obtener más datos en "segunda pasada" (actores, duración, ...)
|
||||
|
||||
# Añadir fecha en que se guarda
|
||||
item.date_added = fechahora_actual()
|
||||
|
||||
# Guardar
|
||||
alfav.user_favorites[i_perfil]['items'].append(item.tourl())
|
||||
alfav.save()
|
||||
@@ -102,6 +178,7 @@ def addFavourite(item):
|
||||
def mainlist(item):
|
||||
logger.info()
|
||||
alfav = AlfavoritesData()
|
||||
item.category = get_name_from_filename(os.path.basename(alfav.user_favorites_file))
|
||||
|
||||
itemlist = []
|
||||
last_i = len(alfav.user_favorites) - 1
|
||||
@@ -128,11 +205,10 @@ def mainlist(item):
|
||||
plot = '%d enlaces en la carpeta' % len(perfil['items'])
|
||||
itemlist.append(Item(channel=item.channel, action='mostrar_perfil', title=perfil['title'], plot=plot, i_perfil=i_perfil, context=context))
|
||||
|
||||
plot = '* Crea diferentes carpetas para guardar tus enlaces favoritos dentro de Alfa.[CR]'
|
||||
plot += '* Para añadir enlaces a las carpetas accede al menú contextual desde cualquier punto de Alfa.[CR]'
|
||||
plot += '* Los enlaces pueden ser canales, secciones dentro de los canales, búsquedas, e incluso películas y series aunque para esto último es preferible utilizar la videoteca.'
|
||||
itemlist.append(item.clone(action='crear_perfil', title='Crear nueva carpeta ...', plot=plot, folder=False))
|
||||
itemlist.append(item.clone(action='crear_perfil', title='Crear nueva carpeta ...', folder=False))
|
||||
|
||||
itemlist.append(item.clone(action='mainlist_listas', title='Gestionar listas de enlaces'))
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
@@ -149,38 +225,15 @@ def mostrar_perfil(item):
|
||||
ruta_runtime = config.get_runtime_path()
|
||||
|
||||
for i_enlace, enlace in enumerate(alfav.user_favorites[i_perfil]['items']):
|
||||
context = []
|
||||
|
||||
if i_enlace > 0:
|
||||
context.append({'title': 'Mover arriba del todo', 'channel': item.channel, 'action': 'mover_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil, 'direccion': 'top'})
|
||||
context.append({'title': 'Mover hacia arriba', 'channel': item.channel, 'action': 'mover_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil, 'direccion': 'arriba'})
|
||||
if i_enlace < last_i:
|
||||
context.append({'title': 'Mover hacia abajo', 'channel': item.channel, 'action': 'mover_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil, 'direccion': 'abajo'})
|
||||
context.append({'title': 'Mover abajo del todo', 'channel': item.channel, 'action': 'mover_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil, 'direccion': 'bottom'})
|
||||
|
||||
if len(alfav.user_favorites) > 1: # si se tiene más de una carpeta permitir mover entre ellas
|
||||
context.append({'title': 'Mover a otra carpeta', 'channel': item.channel, 'action': 'editar_enlace_carpeta',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil})
|
||||
|
||||
context.append({'title': 'Cambiar título', 'channel': item.channel, 'action': 'editar_enlace_titulo',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil})
|
||||
|
||||
context.append({'title': 'Cambiar color', 'channel': item.channel, 'action': 'editar_enlace_color',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil})
|
||||
|
||||
context.append({'title': 'Cambiar thumbnail', 'channel': item.channel, 'action': 'editar_enlace_thumbnail',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil})
|
||||
|
||||
context.append({'title': 'Eliminar enlace', 'channel': item.channel, 'action': 'eliminar_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil})
|
||||
|
||||
it = Item().fromurl(enlace)
|
||||
it.context = context
|
||||
it.plot = '[COLOR blue]Canal: ' + it.channel + '[/COLOR][CR]' + it.plot
|
||||
it.context = [ {'title': '[COLOR blue]Modificar enlace[/COLOR]', 'channel': item.channel, 'action': 'acciones_enlace',
|
||||
'i_enlace': i_enlace, 'i_perfil': i_perfil} ]
|
||||
|
||||
it.plot += '[CR][CR][COLOR blue]Canal:[/COLOR] ' + it.channel + ' [COLOR blue]Action:[/COLOR] ' + it.action
|
||||
if it.extra != '': it.plot += ' [COLOR blue]Extra:[/COLOR] ' + it.extra
|
||||
it.plot += '[CR][COLOR blue]Url:[/COLOR] ' + it.url if isinstance(it.url, str) else '...'
|
||||
if it.date_added != '': it.plot += '[CR][COLOR blue]Added:[/COLOR] ' + it.date_added
|
||||
|
||||
# Si no es una url, ni tiene la ruta del sistema, convertir el path ya que se habrá copiado de otro dispositivo.
|
||||
# Sería más óptimo que la conversión se hiciera con un menú de importar, pero de momento se controla en run-time.
|
||||
@@ -277,6 +330,37 @@ def eliminar_perfil(item):
|
||||
return True
|
||||
|
||||
|
||||
def acciones_enlace(item):
|
||||
logger.info()
|
||||
|
||||
acciones = ['Cambiar título', 'Cambiar color', 'Cambiar thumbnail', 'Mover a otra carpeta', 'Mover a otra lista', 'Eliminar enlace',
|
||||
'Mover arriba del todo', 'Mover hacia arriba', 'Mover hacia abajo', 'Mover abajo del todo']
|
||||
|
||||
ret = platformtools.dialog_select('Acción a ejecutar', acciones)
|
||||
if ret == -1:
|
||||
return False # pedido cancel
|
||||
elif ret == 0:
|
||||
return editar_enlace_titulo(item)
|
||||
elif ret == 1:
|
||||
return editar_enlace_color(item)
|
||||
elif ret == 2:
|
||||
return editar_enlace_thumbnail(item)
|
||||
elif ret == 3:
|
||||
return editar_enlace_carpeta(item)
|
||||
elif ret == 4:
|
||||
return editar_enlace_lista(item)
|
||||
elif ret == 5:
|
||||
return eliminar_enlace(item)
|
||||
elif ret == 6:
|
||||
return mover_enlace(item.clone(direccion='top'))
|
||||
elif ret == 7:
|
||||
return mover_enlace(item.clone(direccion='arriba'))
|
||||
elif ret == 8:
|
||||
return mover_enlace(item.clone(direccion='abajo'))
|
||||
elif ret == 9:
|
||||
return mover_enlace(item.clone(direccion='bottom'))
|
||||
|
||||
|
||||
def editar_enlace_titulo(item):
|
||||
logger.info()
|
||||
alfav = AlfavoritesData()
|
||||
@@ -402,6 +486,44 @@ def editar_enlace_carpeta(item):
|
||||
return True
|
||||
|
||||
|
||||
def editar_enlace_lista(item):
|
||||
logger.info()
|
||||
alfav = AlfavoritesData()
|
||||
|
||||
if not alfav.user_favorites[item.i_perfil]: return False
|
||||
if not alfav.user_favorites[item.i_perfil]['items'][item.i_enlace]: return False
|
||||
|
||||
# Diálogo para escoger lista
|
||||
opciones = []
|
||||
itemlist_listas = mainlist_listas(item)
|
||||
for it in itemlist_listas:
|
||||
if it.lista != '' and '[lista activa]' not in it.title: # descarta item crear y lista activa
|
||||
opciones.append(it.lista)
|
||||
|
||||
if len(opciones) == 0:
|
||||
platformtools.dialog_ok('Alfa', 'No hay otras listas dónde mover el enlace.', 'Puedes crearlas desde el menú Gestionar listas de enlaces')
|
||||
return False
|
||||
|
||||
ret = platformtools.dialog_select('Seleccionar lista destino', opciones)
|
||||
|
||||
if ret == -1:
|
||||
return False # pedido cancel
|
||||
|
||||
alfav_destino = AlfavoritesData(opciones[ret])
|
||||
|
||||
# Diálogo para escoger/crear carpeta en la lista de destino
|
||||
i_perfil = _selecciona_perfil(alfav_destino, 'Seleccionar carpeta destino', -1)
|
||||
if i_perfil == -1: return False
|
||||
|
||||
alfav_destino.user_favorites[i_perfil]['items'].append(alfav.user_favorites[item.i_perfil]['items'][item.i_enlace])
|
||||
del alfav.user_favorites[item.i_perfil]['items'][item.i_enlace]
|
||||
alfav_destino.save()
|
||||
alfav.save()
|
||||
|
||||
platformtools.itemlist_refresh()
|
||||
return True
|
||||
|
||||
|
||||
def eliminar_enlace(item):
|
||||
logger.info()
|
||||
alfav = AlfavoritesData()
|
||||
@@ -466,3 +588,339 @@ def _mover_item(lista, i_selected, direccion):
|
||||
lista.insert(last_i, lista.pop(i_selected))
|
||||
|
||||
return lista
|
||||
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
# Gestionar diferentes listas de alfavoritos
|
||||
# ------------------------------------------
|
||||
|
||||
def mainlist_listas(item):
|
||||
logger.info()
|
||||
itemlist = []
|
||||
item.category = 'Listas'
|
||||
|
||||
lista_activa = get_lista_activa()
|
||||
|
||||
import glob
|
||||
|
||||
path = os.path.join(config.get_data_path(), PREFIJO_LISTA+'*.json')
|
||||
for fichero in glob.glob(path):
|
||||
lista = os.path.basename(fichero)
|
||||
nombre = get_name_from_filename(lista)
|
||||
titulo = nombre if lista != lista_activa else '[COLOR gold]%s[/COLOR] [lista activa]' % nombre
|
||||
|
||||
itemlist.append(item.clone(action='acciones_lista', lista=lista, title=titulo, folder=False))
|
||||
|
||||
itemlist.append(item.clone(action='acciones_nueva_lista', title='Crear/descargar lista / Info ...', folder=False))
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
def acciones_lista(item):
|
||||
logger.info()
|
||||
|
||||
acciones = ['Establecer como lista activa', 'Cambiar nombre de la lista',
|
||||
'Compartir en tinyupload', 'Eliminar lista', 'Información de la lista']
|
||||
|
||||
ret = platformtools.dialog_select(item.lista, acciones)
|
||||
|
||||
if ret == -1:
|
||||
return False # pedido cancel
|
||||
elif ret == 0:
|
||||
return activar_lista(item)
|
||||
elif ret == 1:
|
||||
return renombrar_lista(item)
|
||||
elif ret == 2:
|
||||
return compartir_lista(item)
|
||||
elif ret == 3:
|
||||
return eliminar_lista(item)
|
||||
elif ret == 4:
|
||||
return informacion_lista(item)
|
||||
|
||||
|
||||
def activar_lista(item):
|
||||
logger.info()
|
||||
|
||||
fullfilename = os.path.join(config.get_data_path(), item.lista)
|
||||
if not os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se encuentra la lista!', item.lista)
|
||||
return False
|
||||
|
||||
config.set_setting('lista_activa', item.lista)
|
||||
|
||||
from channelselector import get_thumb
|
||||
item_inicio = Item(title=config.get_localized_string(70527), channel="alfavorites", action="mainlist",
|
||||
thumbnail=get_thumb("mylink.png"),
|
||||
category=config.get_localized_string(70527), viewmode="thumbnails")
|
||||
platformtools.itemlist_update(item_inicio, replace=True)
|
||||
return True
|
||||
|
||||
|
||||
def renombrar_lista(item):
|
||||
logger.info()
|
||||
|
||||
fullfilename_current = os.path.join(config.get_data_path(), item.lista)
|
||||
if not os.path.exists(fullfilename_current):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se encuentra la lista!', fullfilename_current)
|
||||
return False
|
||||
|
||||
nombre = get_name_from_filename(item.lista)
|
||||
titulo = platformtools.dialog_input(default=nombre, heading='Nombre de la lista')
|
||||
if titulo is None or titulo == '' or titulo == nombre:
|
||||
return False
|
||||
titulo = text_clean(titulo, blank_char='_')
|
||||
|
||||
filename = get_filename_from_name(titulo)
|
||||
fullfilename = os.path.join(config.get_data_path(), filename)
|
||||
|
||||
# Comprobar que el nuevo nombre no exista
|
||||
if os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, ya existe una lista con este nombre!', fullfilename)
|
||||
return False
|
||||
|
||||
# Rename del fichero
|
||||
if not filetools.rename(fullfilename_current, filename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se ha podido renombrar la lista!', fullfilename)
|
||||
return False
|
||||
|
||||
# Update settings si es la lista activa
|
||||
if item.lista == get_lista_activa():
|
||||
config.set_setting('lista_activa', filename)
|
||||
|
||||
|
||||
platformtools.itemlist_refresh()
|
||||
return True
|
||||
|
||||
|
||||
def eliminar_lista(item):
|
||||
logger.info()
|
||||
|
||||
fullfilename = os.path.join(config.get_data_path(), item.lista)
|
||||
if not os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se encuentra la lista!', item.lista)
|
||||
return False
|
||||
|
||||
if item.lista == get_lista_activa():
|
||||
platformtools.dialog_ok('Alfa', 'La lista activa no se puede eliminar', item.lista)
|
||||
return False
|
||||
|
||||
if not platformtools.dialog_yesno('Eliminar lista', '¿Estás seguro de querer borrar la lista %s ?' % item.lista): return False
|
||||
filetools.remove(fullfilename)
|
||||
|
||||
platformtools.itemlist_refresh()
|
||||
return True
|
||||
|
||||
|
||||
def informacion_lista(item):
|
||||
logger.info()
|
||||
|
||||
fullfilename = os.path.join(config.get_data_path(), item.lista)
|
||||
if not os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se encuentra la lista!', item.lista)
|
||||
return False
|
||||
|
||||
alfav = AlfavoritesData(item.lista)
|
||||
|
||||
txt = 'Lista: [COLOR gold]%s[/COLOR]' % item.lista
|
||||
txt += '[CR]Creada el %s y modificada el %s' % (alfav.info_lista['created'], alfav.info_lista['updated'])
|
||||
|
||||
if 'downloaded_date' in alfav.info_lista:
|
||||
txt += '[CR]Descargada el %s desde [COLOR blue]%s[/COLOR]' % (alfav.info_lista['downloaded_date'], alfav.info_lista['downloaded_from'])
|
||||
|
||||
if 'tinyupload_date' in alfav.info_lista:
|
||||
txt += '[CR]Compartida en tinyupload el %s con el código [COLOR blue]%s[/COLOR]' % (alfav.info_lista['tinyupload_date'], alfav.info_lista['tinyupload_code'])
|
||||
|
||||
txt += '[CR]Número de carpetas: %d' % len(alfav.user_favorites)
|
||||
for perfil in alfav.user_favorites:
|
||||
txt += '[CR]- %s (%d enlaces)' % (perfil['title'], len(perfil['items']))
|
||||
|
||||
platformtools.dialog_textviewer('Información de la lista', txt)
|
||||
return True
|
||||
|
||||
|
||||
def compartir_lista(item):
|
||||
logger.info()
|
||||
|
||||
fullfilename = os.path.join(config.get_data_path(), item.lista)
|
||||
if not os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se encuentra la lista!', fullfilename)
|
||||
return False
|
||||
|
||||
try:
|
||||
progreso = platformtools.dialog_progress_bg('Compartir lista', 'Conectando con tinyupload ...')
|
||||
|
||||
# Acceso a la página principal de tinyupload para obtener datos necesarios
|
||||
from core import httptools, scrapertools
|
||||
data = httptools.downloadpage('http://s000.tinyupload.com/index.php').data
|
||||
upload_url = scrapertools.find_single_match(data, 'form action="([^"]+)')
|
||||
sessionid = scrapertools.find_single_match(upload_url, 'sid=(.+)')
|
||||
|
||||
progreso.update(10, 'Subiendo fichero', 'Espera unos segundos a que acabe de subirse tu fichero de lista a tinyupload')
|
||||
|
||||
# Envío del fichero a tinyupload mediante multipart/form-data
|
||||
from lib import MultipartPostHandler
|
||||
import urllib2
|
||||
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
|
||||
params = { 'MAX_FILE_SIZE' : '52428800', 'file_description' : '', 'sessionid' : sessionid, 'uploaded_file' : open(fullfilename, 'rb') }
|
||||
handle = opener.open(upload_url, params)
|
||||
data = handle.read()
|
||||
|
||||
progreso.close()
|
||||
|
||||
if not 'File was uploaded successfuly' in data:
|
||||
logger.debug(data)
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se ha podido subir el fichero a tinyupload.com!')
|
||||
return False
|
||||
|
||||
codigo = scrapertools.find_single_match(data, 'href="index\.php\?file_id=([^"]+)')
|
||||
|
||||
except:
|
||||
platformtools.dialog_ok('Alfa', 'Error, al intentar subir el fichero a tinyupload.com!', item.lista)
|
||||
return False
|
||||
|
||||
# Apuntar código en fichero de log y dentro de la lista
|
||||
save_log_lista_shared('Subido fichero %s a tinyupload.com. El código para descargarlo es: %s' % (item.lista, codigo))
|
||||
|
||||
alfav = AlfavoritesData(item.lista)
|
||||
alfav.info_lista['tinyupload_date'] = fechahora_actual()
|
||||
alfav.info_lista['tinyupload_code'] = codigo
|
||||
alfav.save()
|
||||
|
||||
platformtools.dialog_ok('Alfa', 'Subida lista a tinyupload. Si quieres compartirla con alguien, pásale este código:', codigo)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def acciones_nueva_lista(item):
|
||||
logger.info()
|
||||
|
||||
acciones = ['Crear una nueva lista',
|
||||
'Descargar lista con código de tinyupload',
|
||||
'Descargar lista de una url directa',
|
||||
'Información sobre las listas']
|
||||
|
||||
ret = platformtools.dialog_select('Listas de enlaces', acciones)
|
||||
|
||||
if ret == -1:
|
||||
return False # pedido cancel
|
||||
|
||||
elif ret == 0:
|
||||
return crear_lista(item)
|
||||
|
||||
elif ret == 1:
|
||||
codigo = platformtools.dialog_input(default='', heading='Código de descarga de tinyupload') # 05370382084539519168
|
||||
if codigo is None or codigo == '':
|
||||
return False
|
||||
return descargar_lista(item, 'http://s000.tinyupload.com/?file_id=' + codigo)
|
||||
|
||||
elif ret == 2:
|
||||
url = platformtools.dialog_input(default='https://', heading='URL de dónde descargar la lista')
|
||||
if url is None or url == '':
|
||||
return False
|
||||
return descargar_lista(item, url)
|
||||
|
||||
elif ret == 3:
|
||||
txt = '- Puedes tener diferentes listas, pero solamente una de ellas está activa. La lista activa es la que se muestra en "Mis enlaces" y dónde se guardan los enlaces que se vayan añadiendo.'
|
||||
txt += '[CR]- Puedes ir cambiando la lista activa y alternar entre las que tengas.'
|
||||
txt += '[CR]- Puedes compartir una lista a través de tinyupload y luego pasarle el código resultante a tus amistades para que se la puedan bajar.'
|
||||
txt += '[CR]- Puedes descargar una lista si te pasan un código de tinyupload o una url dónde esté alojada.'
|
||||
txt += '[CR]- Si lo quieres hacer manualmente, puedes copiar una lista alfavorites-*.json que te hayan pasado a la carpeta userdata del addon. Y puedes subir estos json a algún servidor y pasar sus urls a tus amigos para compartirlas.'
|
||||
txt += '[CR]- Para compartir listas desde el addon se utiliza el servicio de tinyupload.com por ser gratuíto, privado y relativamente rápido. Los ficheros se guardan mientras no pasen 100 días sin que nadie lo descargue, son privados porque requieren un código para acceder a ellos, y la limitación de 50MB es suficiente para las listas.'
|
||||
|
||||
platformtools.dialog_textviewer('Información sobre las listas', txt)
|
||||
return False
|
||||
|
||||
|
||||
def crear_lista(item):
|
||||
logger.info()
|
||||
|
||||
titulo = platformtools.dialog_input(default='', heading='Nombre de la lista')
|
||||
if titulo is None or titulo == '':
|
||||
return False
|
||||
titulo = text_clean(titulo, blank_char='_')
|
||||
|
||||
filename = get_filename_from_name(titulo)
|
||||
fullfilename = os.path.join(config.get_data_path(), filename)
|
||||
|
||||
# Comprobar que el fichero no exista ya
|
||||
if os.path.exists(fullfilename):
|
||||
platformtools.dialog_ok('Alfa', 'Error, ya existe una lista con este nombre!', fullfilename)
|
||||
return False
|
||||
|
||||
# Provocar que se guarde con las carpetas vacías por defecto
|
||||
alfav = AlfavoritesData(filename)
|
||||
|
||||
platformtools.itemlist_refresh()
|
||||
return True
|
||||
|
||||
|
||||
def descargar_lista(item, url):
|
||||
logger.info()
|
||||
from core import httptools, scrapertools
|
||||
|
||||
if 'tinyupload.com/' in url:
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
data = httptools.downloadpage(url).data
|
||||
logger.debug(data)
|
||||
down_url, url_name = scrapertools.find_single_match(data, ' href="(download\.php[^"]*)"><b>([^<]*)')
|
||||
url_json = '{uri.scheme}://{uri.netloc}/'.format(uri=urlparse(url)) + down_url
|
||||
except:
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se puede descargar la lista!', url)
|
||||
return False
|
||||
|
||||
elif 'zippyshare.com/' in url:
|
||||
from core import servertools
|
||||
video_urls, puedes, motivo = servertools.resolve_video_urls_for_playing('zippyshare', url)
|
||||
|
||||
if not puedes:
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se puede descargar la lista!', motivo)
|
||||
return False
|
||||
url_json = video_urls[0][1] # https://www58.zippyshare.com/d/qPzzQ0UM/25460/alfavorites-testeanding.json
|
||||
url_name = url_json[url_json.rfind('/')+1:]
|
||||
|
||||
elif 'friendpaste.com/' in url:
|
||||
url_json = url if url.endswith('/raw') else url + '/raw'
|
||||
url_name = 'friendpaste'
|
||||
|
||||
else:
|
||||
url_json = url
|
||||
url_name = url[url.rfind('/')+1:]
|
||||
|
||||
|
||||
# Download json
|
||||
data = httptools.downloadpage(url_json).data
|
||||
|
||||
# Verificar formato json de alfavorites y añadir info de la descarga
|
||||
jsondata = jsontools.load(data)
|
||||
if 'user_favorites' not in jsondata or 'info_lista' not in jsondata:
|
||||
logger.debug(data)
|
||||
platformtools.dialog_ok('Alfa', 'Error, el fichero descargado no tiene el formato esperado!')
|
||||
return False
|
||||
|
||||
jsondata['info_lista']['downloaded_date'] = fechahora_actual()
|
||||
jsondata['info_lista']['downloaded_from'] = url
|
||||
data = jsontools.dump(jsondata)
|
||||
|
||||
# Pedir nombre para la lista descargada
|
||||
nombre = get_name_from_filename(url_name)
|
||||
titulo = platformtools.dialog_input(default=nombre, heading='Nombre para guardar la lista')
|
||||
if titulo is None or titulo == '':
|
||||
return False
|
||||
titulo = text_clean(titulo, blank_char='_')
|
||||
|
||||
filename = get_filename_from_name(titulo)
|
||||
fullfilename = os.path.join(config.get_data_path(), filename)
|
||||
|
||||
# Si el nuevo nombre ya existe pedir confirmación para sobrescribir
|
||||
if os.path.exists(fullfilename):
|
||||
if not platformtools.dialog_yesno('Alfa', 'Ya existe una lista con este nombre.', '¿ Sobrescribir el fichero ?', filename):
|
||||
return False
|
||||
|
||||
if not filetools.write(fullfilename, data):
|
||||
platformtools.dialog_ok('Alfa', 'Error, no se puede grabar la lista!', filename)
|
||||
|
||||
platformtools.dialog_ok('Alfa', 'Ok, lista descargada correctamente', filename)
|
||||
platformtools.itemlist_refresh()
|
||||
return True
|
||||
|
||||
127
plugin.video.alfa/lib/MultipartPostHandler.py
Normal file
127
plugin.video.alfa/lib/MultipartPostHandler.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
#
|
||||
####
|
||||
# 2006/02 Will Holcomb <wholcomb@gmail.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# 2007/07/26 Slightly modified by Brian Schneider
|
||||
#
|
||||
# in order to support unicode files ( multipart_encode function )
|
||||
# From http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work-for.html
|
||||
#
|
||||
# 2013/07 Ken Olum <kdo@cosmos.phy.tufts.edu>
|
||||
#
|
||||
# Removed one of \r\n and send Content-Length
|
||||
#
|
||||
# 2014/05 Applied Fedora rpm patch
|
||||
#
|
||||
# https://bugzilla.redhat.com/show_bug.cgi?id=920778
|
||||
# http://pkgs.fedoraproject.org/cgit/python-MultipartPostHandler2.git/diff/python-MultipartPostHandler2-cut-out-main.patch?id=c1638bb3e45596232b4d02f1e69901db0c28cfdb
|
||||
#
|
||||
# 2014/05/09 Sérgio Basto <sergio@serjux.com>
|
||||
#
|
||||
# Better deal with None values, don't throw an exception and just send an empty string.
|
||||
# Simplified text example
|
||||
#
|
||||
"""
|
||||
Usage:
|
||||
Enables the use of multipart/form-data for posting forms
|
||||
|
||||
Inspirations:
|
||||
Upload files in python:
|
||||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
|
||||
urllib2_file:
|
||||
Fabien Seisen: <fabien@seisen.org>
|
||||
|
||||
Example:
|
||||
import MultipartPostHandler, urllib2
|
||||
|
||||
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
|
||||
params = { "username" : "bob", "password" : "riviera",
|
||||
"file" : open("filename", "rb") }
|
||||
opener.open("http://wwww.bobsite.com/upload/", params)
|
||||
"""
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import mimetools, mimetypes
|
||||
import os, stat
|
||||
from cStringIO import StringIO
|
||||
|
||||
class Callable:
|
||||
def __init__(self, anycallable):
|
||||
self.__call__ = anycallable
|
||||
|
||||
# Controls how sequences are uncoded. If true, elements may be given multiple values by
|
||||
# assigning a sequence.
|
||||
doseq = 1
|
||||
|
||||
class MultipartPostHandler(urllib2.BaseHandler):
|
||||
handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
|
||||
|
||||
def http_request(self, request):
|
||||
data = request.get_data()
|
||||
if data is not None and type(data) != str:
|
||||
v_files = []
|
||||
v_vars = []
|
||||
try:
|
||||
for(key, value) in data.items():
|
||||
if type(value) == file:
|
||||
v_files.append((key, value))
|
||||
else:
|
||||
v_vars.append((key, value))
|
||||
except TypeError:
|
||||
systype, value, traceback = sys.exc_info()
|
||||
raise TypeError, "not a valid non-string sequence or mapping object", traceback
|
||||
|
||||
if len(v_files) == 0:
|
||||
data = urllib.urlencode(v_vars, doseq)
|
||||
else:
|
||||
boundary, data = self.multipart_encode(v_vars, v_files)
|
||||
contenttype = 'multipart/form-data; boundary=%s' % boundary
|
||||
# ~ if(request.has_header('Content-Type')
|
||||
# ~ and request.get_header('Content-Type').find('multipart/form-data') != 0):
|
||||
# ~ print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data')
|
||||
request.add_unredirected_header('Content-Type', contenttype)
|
||||
|
||||
request.add_data(data)
|
||||
return request
|
||||
|
||||
def multipart_encode(vars, files, boundary = None, buffer = None):
|
||||
if boundary is None:
|
||||
boundary = mimetools.choose_boundary()
|
||||
if buffer is None:
|
||||
buffer = StringIO()
|
||||
for(key, value) in vars:
|
||||
buffer.write('--%s\r\n' % boundary)
|
||||
buffer.write('Content-Disposition: form-data; name="%s"' % key)
|
||||
if value is None:
|
||||
value = ""
|
||||
# if type(value) is not str, we need str(value) to not error with cannot concatenate 'str'
|
||||
# and 'dict' or 'tuple' or somethingelse objects
|
||||
buffer.write('\r\n\r\n' + str(value) + '\r\n')
|
||||
for(key, fd) in files:
|
||||
file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
|
||||
filename = fd.name.split('/')[-1]
|
||||
contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
buffer.write('--%s\r\n' % boundary)
|
||||
buffer.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename))
|
||||
buffer.write('Content-Type: %s\r\n' % contenttype)
|
||||
buffer.write('Content-Length: %s\r\n' % file_size)
|
||||
fd.seek(0)
|
||||
buffer.write('\r\n' + fd.read() + '\r\n')
|
||||
buffer.write('--' + boundary + '--\r\n')
|
||||
buffer = buffer.getvalue()
|
||||
return boundary, buffer
|
||||
multipart_encode = Callable(multipart_encode)
|
||||
|
||||
https_request = http_request
|
||||
@@ -93,12 +93,20 @@ def dialog_numeric(_type, heading, default=""):
|
||||
return d
|
||||
|
||||
|
||||
def dialog_textviewer(heading, text): # disponible a partir de kodi 16
|
||||
return xbmcgui.Dialog().textviewer(heading, text)
|
||||
|
||||
|
||||
|
||||
def itemlist_refresh():
|
||||
xbmc.executebuiltin("Container.Refresh")
|
||||
|
||||
|
||||
def itemlist_update(item):
|
||||
xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")")
|
||||
def itemlist_update(item, replace=False):
|
||||
if replace: # reset the path history
|
||||
xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ", replace)")
|
||||
else:
|
||||
xbmc.executebuiltin("Container.Update(" + sys.argv[0] + "?" + item.tourl() + ")")
|
||||
|
||||
|
||||
def render_items(itemlist, parent_item):
|
||||
|
||||
@@ -125,6 +125,8 @@
|
||||
<setting label="Gestión de actualizaciones urgentes de módulos de Alfa (Quick Fixes):" type="lsep"/>
|
||||
<setting id="addon_update_timer" type="labelenum" values="0|6|12|24" label="Intervalo entre actualizaciones automáticas (horas)" default="12"/>
|
||||
<setting id="addon_update_message" type="bool" label="Quiere ver mensajes de las actualizaciones" default="false"/>
|
||||
|
||||
<setting label="Lista activa" type="text" id="lista_activa" default="alfavorites-default.json" visible="false"/>
|
||||
</category>
|
||||
|
||||
</settings>
|
||||
|
||||
Reference in New Issue
Block a user