Merge pull request #499 from lopezvg/master
SMB client: versión 1.1.25 de pysmb y nuevos canales con Enlaces de Emergencia
This commit is contained in:
@@ -577,7 +577,8 @@ def findvideos(item):
|
|||||||
return item #... y nos vamos
|
return item #... y nos vamos
|
||||||
|
|
||||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
if not item.videolibray_emergency_urls:
|
||||||
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||||
|
|
||||||
#Ahora tratamos los enlaces .torrent
|
#Ahora tratamos los enlaces .torrent
|
||||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
"id": "intervenidos_channels_list",
|
"id": "intervenidos_channels_list",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Lista de canales y clones de NewPct1 intervenidos y orden de sustitución de URLs",
|
"label": "Lista de canales y clones de NewPct1 intervenidos y orden de sustitución de URLs",
|
||||||
"default": "('0', 'canal_org', 'canal_des', 'url_org', 'url_des', 'patron1', 'patron2', 'patron3', 'patron4', 'patron5', 'content_inc', 'content_exc', 'ow_force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)([^0-9]+-)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-\\d+-(Temporada-).html', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-(\\d+)-', '', 'tvshow, season', '', 'force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-([^.]+).html', '', '', '', 'movie', '', 'force'), ('0', 'mejortorrent', 'mejortorrent', 'http://www.mejortorrent.com/', 'http://www.mejortorrent.org/', '', '', '', '', '', '*', '', 'force'), ('0', 'plusdede', 'megadede', 'https://www.plusdede.com', 'https://www.megadede.com', '', '', '', '', '', '*', '', 'auto')",
|
"default": "('0', 'canal_org', 'canal_des', 'url_org', 'url_des', 'patron1', 'patron2', 'patron3', 'patron4', 'patron5', 'content_inc', 'content_exc', 'ow_force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)([^0-9]+-)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-\\d+-(Temporada-).html', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-(?:[^-]+-)[^0-9]+-(\\d+)-', '', 'tvshow, season', '', 'force'), ('0', 'mejortorrent', 'mejortorrent1', 'http://www.mejortorrent.com/', 'https://mejortorrent1.com/', '(http.?:\/\/.*?\/)', 'http.?:\/\/.*?\/.*?-torrent.?-[^-]+-([^.]+).html', '', '', '', 'movie', '', 'force'), ('0', 'mejortorrent', 'mejortorrent', 'http://www.mejortorrent.com/', 'http://www.mejortorrent.org/', '', '', '', '', '', '*', '', 'force'), ('1', 'plusdede', 'megadede', 'https://www.plusdede.com', 'https://www.megadede.com', '', '', '', '', '', '*', '', 'auto'), ('1', 'newpct1', 'descargas2020', 'http://www.newpct1.com', 'http://descargas2020.com', '', '', '', '', '', '*', '', 'force')",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"visible": false
|
"visible": false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,6 +45,28 @@
|
|||||||
"VOSE"
|
"VOSE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls",
|
||||||
|
"type": "list",
|
||||||
|
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||||
|
"default": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true,
|
||||||
|
"lvalues": [
|
||||||
|
"No",
|
||||||
|
"Guardar",
|
||||||
|
"Borrar",
|
||||||
|
"Actualizar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls_torrents",
|
||||||
|
"type": "bool",
|
||||||
|
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||||
|
"default": true,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": "!eq(-1,'No')"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "include_in_newest_torrent",
|
"id": "include_in_newest_torrent",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ def listado(item):
|
|||||||
title = re.sub(r'[\(|\[]\s+[\)|\]]', '', title)
|
title = re.sub(r'[\(|\[]\s+[\)|\]]', '', title)
|
||||||
title = title.replace('()', '').replace('[]', '').strip().lower().title()
|
title = title.replace('()', '').replace('[]', '').strip().lower().title()
|
||||||
|
|
||||||
item_local.from_title = title.strip().lower().title() #Guardamos esta etiqueta para posible desambiguación de título
|
item_local.from_title = title.strip().lower().title() #Guardamos esta etiqueta para posible desambiguación de título
|
||||||
|
|
||||||
#Salvamos el título según el tipo de contenido
|
#Salvamos el título según el tipo de contenido
|
||||||
if item_local.contentType == "movie":
|
if item_local.contentType == "movie":
|
||||||
@@ -387,8 +387,8 @@ def listado(item):
|
|||||||
|
|
||||||
title = '%s' % curr_page
|
title = '%s' % curr_page
|
||||||
|
|
||||||
if cnt_matches + 1 >= last_title: #Si hemos pintado ya todo lo de esta página...
|
if cnt_matches + 1 >= last_title: #Si hemos pintado ya todo lo de esta página...
|
||||||
cnt_matches = 0 #... la próxima pasada leeremos otra página
|
cnt_matches = 0 #... la próxima pasada leeremos otra página
|
||||||
next_page_url = re.sub(r'page=(\d+)', r'page=' + str(int(re.search('\d+', next_page_url).group()) + 1), next_page_url)
|
next_page_url = re.sub(r'page=(\d+)', r'page=' + str(int(re.search('\d+', next_page_url).group()) + 1), next_page_url)
|
||||||
|
|
||||||
itemlist.append(Item(channel=item.channel, action="listado", title=">> Página siguiente " + title, url=next_page_url, extra=item.extra, extra2=item.extra2, last_page=str(last_page), curr_page=str(curr_page + 1), cnt_matches=str(cnt_matches)))
|
itemlist.append(Item(channel=item.channel, action="listado", title=">> Página siguiente " + title, url=next_page_url, extra=item.extra, extra2=item.extra2, last_page=str(last_page), curr_page=str(curr_page + 1), cnt_matches=str(cnt_matches)))
|
||||||
@@ -399,10 +399,10 @@ def listado(item):
|
|||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
logger.info()
|
logger.info()
|
||||||
itemlist = []
|
itemlist = []
|
||||||
itemlist_t = [] #Itemlist total de enlaces
|
itemlist_t = [] #Itemlist total de enlaces
|
||||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||||
if not item.language:
|
if not item.language:
|
||||||
item.language = ['CAST'] #Castellano por defecto
|
item.language = ['CAST'] #Castellano por defecto
|
||||||
matches = []
|
matches = []
|
||||||
item.category = categoria
|
item.category = categoria
|
||||||
|
|
||||||
@@ -412,22 +412,53 @@ def findvideos(item):
|
|||||||
#logger.debug(item)
|
#logger.debug(item)
|
||||||
|
|
||||||
matches = item.url
|
matches = item.url
|
||||||
if not matches: #error
|
if not matches: #error
|
||||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web: " + item)
|
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web: " + str(item))
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
#logger.debug(matches)
|
#logger.debug(matches)
|
||||||
|
|
||||||
|
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||||
|
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||||
|
item.emergency_urls.append(matches) #Salvamnos matches...
|
||||||
|
|
||||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
if not item.videolibray_emergency_urls:
|
||||||
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||||
|
|
||||||
#Ahora tratamos los enlaces .torrent
|
#Ahora tratamos los enlaces .torrent
|
||||||
for scrapedurl, quality in matches: #leemos los magnets con la diferentes calidades
|
for scrapedurl, quality in matches: #leemos los magnets con la diferentes calidades
|
||||||
#Generamos una copia de Item para trabajar sobre ella
|
#Generamos una copia de Item para trabajar sobre ella
|
||||||
item_local = item.clone()
|
item_local = item.clone()
|
||||||
|
|
||||||
item_local.url = scrapedurl
|
item_local.url = scrapedurl
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls[0].append(scrapedurl) #guardamos la url y pasamos a la siguiente
|
||||||
|
continue
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||||
|
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||||
|
if item.armagedon:
|
||||||
|
item_local.url = item.emergency_urls[0][0] #... ponemos la emergencia como primaria
|
||||||
|
del item.emergency_urls[0][0] #Una vez tratado lo limpiamos
|
||||||
|
|
||||||
|
size = ''
|
||||||
|
if not item.armagedon:
|
||||||
|
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||||
|
if size:
|
||||||
|
quality += ' [%s]' % size
|
||||||
|
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||||
|
quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % quality
|
||||||
|
|
||||||
#Añadimos la calidad y copiamos la duración
|
#Añadimos la calidad y copiamos la duración
|
||||||
item_local.quality = quality
|
item_local.quality = quality
|
||||||
@@ -445,9 +476,9 @@ def findvideos(item):
|
|||||||
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
||||||
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
||||||
|
|
||||||
item_local.alive = "??" #Calidad del link sin verificar
|
item_local.alive = "??" #Calidad del link sin verificar
|
||||||
item_local.action = "play" #Visualizar vídeo
|
item_local.action = "play" #Visualizar vídeo
|
||||||
item_local.server = "torrent" #Servidor Torrent
|
item_local.server = "torrent" #Servidor Torrent
|
||||||
|
|
||||||
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
||||||
|
|
||||||
@@ -459,6 +490,9 @@ def findvideos(item):
|
|||||||
|
|
||||||
#logger.debug(item_local)
|
#logger.debug(item_local)
|
||||||
|
|
||||||
|
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||||
|
return item #... nos vamos
|
||||||
|
|
||||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -44,6 +44,28 @@
|
|||||||
"VOSE"
|
"VOSE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls",
|
||||||
|
"type": "list",
|
||||||
|
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||||
|
"default": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true,
|
||||||
|
"lvalues": [
|
||||||
|
"No",
|
||||||
|
"Guardar",
|
||||||
|
"Borrar",
|
||||||
|
"Actualizar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls_torrents",
|
||||||
|
"type": "bool",
|
||||||
|
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||||
|
"default": true,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": "!eq(-1,'No')"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "timeout_downloadpage",
|
"id": "timeout_downloadpage",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
|
|||||||
@@ -372,6 +372,7 @@ def findvideos(item):
|
|||||||
if not item.language:
|
if not item.language:
|
||||||
item.language = ['CAST'] #Castellano por defecto
|
item.language = ['CAST'] #Castellano por defecto
|
||||||
matches = []
|
matches = []
|
||||||
|
subtitles = []
|
||||||
item.category = categoria
|
item.category = categoria
|
||||||
|
|
||||||
#logger.debug(item)
|
#logger.debug(item)
|
||||||
@@ -389,51 +390,74 @@ def findvideos(item):
|
|||||||
if not data:
|
if not data:
|
||||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
|
||||||
|
|
||||||
#Extraemos el thumb
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
if not item.thumbnail:
|
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||||
item.thumbnail = scrapertools.find_single_match(data, patron) #guardamos thumb si no existe
|
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
#Extraemos quality, audio, year, country, size, scrapedlanguage
|
if not item.armagedon:
|
||||||
patron = '<\/script><\/div><ul>(?:<li><label>Fecha de estreno <\/label>[^<]+<\/li>)?(?:<li><label>Genero <\/label>[^<]+<\/li>)?(?:<li><label>Calidad <\/label>([^<]+)<\/li>)?(?:<li><label>Audio <\/label>([^<]+)<\/li>)?(?:<li><label>Fecha <\/label>.*?(\d+)<\/li>)?(?:<li><label>Pais de Origen <\/label>([^<]+)<\/li>)?(?:<li><label>Tamaño <\/label>([^<]+)<\/li>)?(<li> Idioma[^<]+<img src=.*?<br \/><\/li>)?'
|
#Extraemos el thumb
|
||||||
try:
|
if not item.thumbnail:
|
||||||
quality, audio, year, country, size, scrapedlanguage = scrapertools.find_single_match(data, patron)
|
item.thumbnail = scrapertools.find_single_match(data, patron) #guardamos thumb si no existe
|
||||||
except:
|
|
||||||
quality = ''
|
|
||||||
audio = ''
|
|
||||||
year = ''
|
|
||||||
country = ''
|
|
||||||
size = ''
|
|
||||||
scrapedlanguage = ''
|
|
||||||
if quality: item.quality = quality
|
|
||||||
if audio: item.quality += ' %s' % audio.strip()
|
|
||||||
if not item.infoLabels['year'] and year: item.infoLabels['year'] = year
|
|
||||||
if size: item.quality += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
|
||||||
if size: item.title += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
|
||||||
language = []
|
|
||||||
matches = re.compile('(\d+.png)', re.DOTALL).findall(scrapedlanguage)
|
|
||||||
for lang in matches:
|
|
||||||
if "1.png" in lang and not 'CAST' in language: language += ['CAST']
|
|
||||||
if "512.png" in lang and not 'LAT' in language: language += ['LAT']
|
|
||||||
if ("1.png" not in lang and "512.png" not in lang) and not 'VOSE' in language: language += ['VOSE']
|
|
||||||
if language: item.language = language
|
|
||||||
|
|
||||||
#Extraemos los enlaces .torrent
|
#Extraemos quality, audio, year, country, size, scrapedlanguage
|
||||||
##Modalidad de varios archivos
|
patron = '<\/script><\/div><ul>(?:<li><label>Fecha de estreno <\/label>[^<]+<\/li>)?(?:<li><label>Genero <\/label>[^<]+<\/li>)?(?:<li><label>Calidad <\/label>([^<]+)<\/li>)?(?:<li><label>Audio <\/label>([^<]+)<\/li>)?(?:<li><label>Fecha <\/label>.*?(\d+)<\/li>)?(?:<li><label>Pais de Origen <\/label>([^<]+)<\/li>)?(?:<li><label>Tamaño <\/label>([^<]+)<\/li>)?(<li> Idioma[^<]+<img src=.*?<br \/><\/li>)?'
|
||||||
patron = '<div class="fichadescargat"><\/div><div class="table-responsive"[^>]+>.*?<\/thead><tbody>(.*?)<\/tbody><\/table><\/div>'
|
try:
|
||||||
if scrapertools.find_single_match(data, patron):
|
quality = ''
|
||||||
data_torrents = scrapertools.find_single_match(data, patron)
|
audio = ''
|
||||||
patron = '<tr><td>.*?<\/td><td><a href="([^"]+)"[^>]+><[^>]+><\/a><\/td><\/tr>'
|
year = ''
|
||||||
#Modalidad de un archivo
|
country = ''
|
||||||
else:
|
size = ''
|
||||||
data_torrents = data
|
scrapedlanguage = ''
|
||||||
patron = '<div class="fichasubtitulos">.*?<\/div><\/li><\/ul>.*?<a href="([^"]+)"'
|
quality, audio, year, country, size, scrapedlanguage = scrapertools.find_single_match(data, patron)
|
||||||
matches = re.compile(patron, re.DOTALL).findall(data_torrents)
|
except:
|
||||||
if not matches: #error
|
pass
|
||||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
if quality: item.quality = quality
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
if audio: item.quality += ' %s' % audio.strip()
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
if not item.infoLabels['year'] and year: item.infoLabels['year'] = year
|
||||||
|
if size: item.quality += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||||
|
if size:
|
||||||
|
item.title = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item.title) #Quitamos size de título, si lo traía
|
||||||
|
item.title += ' [%s]' % size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b').replace('.', ',').strip()
|
||||||
|
|
||||||
|
language = []
|
||||||
|
matches_lang = re.compile('(\d+.png)', re.DOTALL).findall(scrapedlanguage)
|
||||||
|
for lang in matches_lang:
|
||||||
|
if "1.png" in lang and not 'CAST' in language: language += ['CAST']
|
||||||
|
if "512.png" in lang and not 'LAT' in language: language += ['LAT']
|
||||||
|
if ("1.png" not in lang and "512.png" not in lang) and not 'VOSE' in language: language += ['VOSE']
|
||||||
|
if language: item.language = language
|
||||||
|
|
||||||
|
#Extraemos los enlaces .torrent
|
||||||
|
#Modalidad de varios archivos
|
||||||
|
patron = '<div class="fichadescargat"><\/div><div class="table-responsive"[^>]+>.*?<\/thead><tbody>(.*?)<\/tbody><\/table><\/div>'
|
||||||
|
if scrapertools.find_single_match(data, patron):
|
||||||
|
data_torrents = scrapertools.find_single_match(data, patron)
|
||||||
|
patron = '<tr><td>.*?<\/td><td><a href="([^"]+)"[^>]+><[^>]+><\/a><\/td><\/tr>'
|
||||||
|
#Modalidad de un archivo
|
||||||
|
else:
|
||||||
|
data_torrents = data
|
||||||
|
patron = '<div class="fichasubtitulos">.*?<\/div><\/li><\/ul>.*?<a href="([^"]+)"'
|
||||||
|
matches = re.compile(patron, re.DOTALL).findall(data_torrents)
|
||||||
|
if not matches: #error
|
||||||
|
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||||
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||||
|
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||||
|
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
else: #SERIES: ya viene con las urls
|
else: #SERIES: ya viene con las urls
|
||||||
data = item.url #inicio data por compatibilidad
|
data = item.url #inicio data por compatibilidad
|
||||||
@@ -447,11 +471,22 @@ def findvideos(item):
|
|||||||
del item.subtitle
|
del item.subtitle
|
||||||
else:
|
else:
|
||||||
subtitle = scrapertools.find_single_match(data, patron).replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
subtitle = scrapertools.find_single_match(data, patron).replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
||||||
data_subtitle = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)", "", httptools.downloadpage(subtitle, timeout=timeout).data)
|
|
||||||
patron = '<tbody>(<tr class="fichserietabla_b">.*?<\/tr>)<\/tbody>' #salvamos el bloque
|
try:
|
||||||
data_subtitle = scrapertools.find_single_match(data_subtitle, patron)
|
data_subtitle = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)", "", httptools.downloadpage(subtitle, timeout=timeout).data)
|
||||||
patron = '<tr class="fichserietabla_b">.*?<a href="([^"]+)"'
|
except:
|
||||||
subtitles = re.compile(patron, re.DOTALL).findall(data_subtitle) #Creamos una lista con todos los sub-títulos
|
pass
|
||||||
|
|
||||||
|
if not data_subtitle:
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches de vídeos
|
||||||
|
subtitles = item.emergency_urls[2] #Restauramos matches de subtítulos
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
patron = '<tbody>(<tr class="fichserietabla_b">.*?<\/tr>)<\/tbody>' #salvamos el bloque
|
||||||
|
data_subtitle = scrapertools.find_single_match(data_subtitle, patron)
|
||||||
|
patron = '<tr class="fichserietabla_b">.*?<a href="([^"]+)"'
|
||||||
|
subtitles = re.compile(patron, re.DOTALL).findall(data_subtitle) #Creamos una lista con todos los sub-títulos
|
||||||
if subtitles:
|
if subtitles:
|
||||||
item.subtitle = []
|
item.subtitle = []
|
||||||
for subtitle in subtitles:
|
for subtitle in subtitles:
|
||||||
@@ -460,29 +495,49 @@ def findvideos(item):
|
|||||||
|
|
||||||
#logger.debug("PATRON: " + patron)
|
#logger.debug("PATRON: " + patron)
|
||||||
#logger.debug(matches)
|
#logger.debug(matches)
|
||||||
|
#logger.debug(subtitles)
|
||||||
#logger.debug(data)
|
#logger.debug(data)
|
||||||
|
|
||||||
|
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||||
|
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||||
|
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||||
|
item.emergency_urls.append(subtitles) #Salvamnos matches de los subtítulos
|
||||||
|
|
||||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
if not item.videolibray_emergency_urls:
|
||||||
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||||
|
|
||||||
#Ahora tratamos los enlaces .torrent
|
#Ahora tratamos los enlaces .torrent
|
||||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||||
#Generamos una copia de Item para trabajar sobre ella
|
#Generamos una copia de Item para trabajar sobre ella
|
||||||
item_local = item.clone()
|
item_local = item.clone()
|
||||||
|
|
||||||
|
item_local.url = scrapedurl.replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls[0].append(scrapedurl) #guardamos la url y pasamos a la siguiente
|
||||||
|
continue
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||||
|
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||||
|
if item.armagedon:
|
||||||
|
item_local.url = item.emergency_urls[0][0] #... ponemos la emergencia como primaria
|
||||||
|
del item.emergency_urls[0][0] #Una vez tratado lo limpiamos
|
||||||
|
|
||||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
size = scrapertools.find_single_match(item_local.quality, '\s*\[(\d+,?\d*?\s\w\s*[b|B])\]')
|
||||||
if not size:
|
if not size and not item.armagedon:
|
||||||
size = generictools.get_torrent_size(scrapedurl) #Buscamos el tamaño en el .torrent
|
size = generictools.get_torrent_size(scrapedurl) #Buscamos el tamaño en el .torrent
|
||||||
if size:
|
if size:
|
||||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
|
||||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
|
||||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
item_local.title = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||||
|
item_local.quality = re.sub(r'\s*\[\d+,?\d*?\s\w\s*[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||||
|
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||||
|
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||||
|
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||||
|
|
||||||
#Ahora pintamos el link del Torrent
|
#Ahora pintamos el link del Torrent
|
||||||
item_local.url = scrapedurl.replace('&', '&').replace('.io/', sufix).replace('.com/', sufix)
|
|
||||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||||
|
|
||||||
#Preparamos título y calidad, quitamos etiquetas vacías
|
#Preparamos título y calidad, quitamos etiquetas vacías
|
||||||
@@ -506,6 +561,9 @@ def findvideos(item):
|
|||||||
#logger.debug("TORRENT: " + scrapedurl + " / title gen/torr: " + item.title + " / " + item_local.title + " / calidad: " + item_local.quality + " / content: " + item_local.contentTitle + " / " + item_local.contentSerieName)
|
#logger.debug("TORRENT: " + scrapedurl + " / title gen/torr: " + item.title + " / " + item_local.title + " / calidad: " + item_local.quality + " / content: " + item_local.contentTitle + " / " + item_local.contentSerieName)
|
||||||
#logger.debug(item_local)
|
#logger.debug(item_local)
|
||||||
|
|
||||||
|
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||||
|
return item #... nos vamos
|
||||||
|
|
||||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -45,6 +45,28 @@
|
|||||||
"VOSE"
|
"VOSE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls",
|
||||||
|
"type": "list",
|
||||||
|
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||||
|
"default": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true,
|
||||||
|
"lvalues": [
|
||||||
|
"No",
|
||||||
|
"Guardar",
|
||||||
|
"Borrar",
|
||||||
|
"Actualizar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls_torrents",
|
||||||
|
"type": "bool",
|
||||||
|
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||||
|
"default": true,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": "!eq(-1,'No')"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "include_in_newest_peliculas",
|
"id": "include_in_newest_peliculas",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
|
|||||||
@@ -345,56 +345,100 @@ def findvideos(item):
|
|||||||
if not data:
|
if not data:
|
||||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
|
||||||
|
|
||||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
|
if not item.armagedon:
|
||||||
|
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||||
if not matches: #error
|
if not matches: #error
|
||||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
item = generictools.web_intervenida(item, data) #Verificamos que no haya sido clausurada
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
if item.intervencion: #Sí ha sido clausurada judicialmente
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist) #Llamamos al método para el pintado del error
|
||||||
|
else:
|
||||||
|
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||||
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||||
|
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
#logger.debug("PATRON: " + patron)
|
#logger.debug("PATRON: " + patron)
|
||||||
#logger.debug(matches)
|
#logger.debug(matches)
|
||||||
#logger.debug(data)
|
#logger.debug(data)
|
||||||
|
|
||||||
|
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||||
|
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||||
|
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||||
|
|
||||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
if not item.videolibray_emergency_urls:
|
||||||
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||||
|
|
||||||
#Ahora tratamos los enlaces .torrent
|
#Ahora tratamos los enlaces .torrent
|
||||||
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
for scrapedurl in matches: #leemos los torrents con la diferentes calidades
|
||||||
if 'javascript' in scrapedurl: #evitamos la basura
|
if 'javascript' in scrapedurl: #evitamos la basura
|
||||||
continue
|
continue
|
||||||
|
url = ''
|
||||||
|
if not item.armagedon:
|
||||||
|
url = urlparse.urljoin(host, scrapedurl)
|
||||||
|
#Leemos la siguiente página, que es de verdad donde está el magnet/torrent
|
||||||
|
try:
|
||||||
|
data = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)| ", "", httptools.downloadpage(url, timeout=timeout).data)
|
||||||
|
data = unicode(data, "utf-8", errors="replace").encode("utf-8")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
url = urlparse.urljoin(host, scrapedurl)
|
patron = "window.open\('([^']+)'"
|
||||||
#Leemos la siguiente página, que es de verdad donde está el magnet/torrent
|
url = scrapertools.find_single_match(data, patron)
|
||||||
try:
|
if not url: #error
|
||||||
data = re.sub(r"\n|\r|\t|\s{2}|(<!--.*?-->)| ", "", httptools.downloadpage(url, timeout=timeout).data)
|
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||||
data = unicode(data, "utf-8", errors="replace").encode("utf-8")
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
patron = "window.open\('([^']+)'"
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
url = scrapertools.find_single_match(data, patron)
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
if not url: #error
|
else:
|
||||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
continue #si no hay más datos, algo no funciona, pasamos al siguiente
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
|
||||||
continue #si no hay más datos, algo no funciona, pasamos al siguiente
|
|
||||||
|
|
||||||
#Generamos una copia de Item para trabajar sobre ella
|
#Generamos una copia de Item para trabajar sobre ella
|
||||||
item_local = item.clone()
|
item_local = item.clone()
|
||||||
|
|
||||||
item_local.url = urlparse.urljoin(host, url)
|
item_local.url = urlparse.urljoin(host, url)
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls[0].append(item_local.url) #guardamos la url y pasamos a la siguiente
|
||||||
|
continue
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||||
|
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||||
|
if item.armagedon:
|
||||||
|
item_local.url = item.emergency_urls[0][0] #Restauramos la url
|
||||||
|
if len(item.emergency_urls[0]) > 1:
|
||||||
|
del item.emergency_urls[0][0]
|
||||||
|
|
||||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
size = scrapertools.find_single_match(item_local.quality, '\s?\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||||
if not size:
|
if not size and not item.armagedon:
|
||||||
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||||
if size:
|
if size:
|
||||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
item_local.title = re.sub(r'\s?\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
item_local.quality = re.sub(r'\s?\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||||
|
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||||
|
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||||
|
|
||||||
#Ahora pintamos el link del Torrent
|
#Ahora pintamos el link del Torrent
|
||||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||||
@@ -421,6 +465,9 @@ def findvideos(item):
|
|||||||
|
|
||||||
#logger.debug(item_local)
|
#logger.debug(item_local)
|
||||||
|
|
||||||
|
if item.videolibray_emergency_urls: #Si ya hemos guardado todas las urls...
|
||||||
|
return item #... nos vamos
|
||||||
|
|
||||||
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
if len(itemlist_f) > 0: #Si hay entradas filtradas...
|
||||||
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
itemlist.extend(itemlist_f) #Pintamos pantalla filtrada
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ def list_movies(item, silent=False):
|
|||||||
|
|
||||||
head_nfo, new_item = videolibrarytools.read_nfo(nfo_path)
|
head_nfo, new_item = videolibrarytools.read_nfo(nfo_path)
|
||||||
|
|
||||||
|
if not new_item: #Si no ha leído bien el .nfo, pasamos a la siguiente
|
||||||
|
continue
|
||||||
|
|
||||||
if len(new_item.library_urls) > 1:
|
if len(new_item.library_urls) > 1:
|
||||||
multicanal = True
|
multicanal = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -47,6 +47,28 @@
|
|||||||
"VOSE"
|
"VOSE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls",
|
||||||
|
"type": "list",
|
||||||
|
"label": "Se quieren guardar Enlaces de Emergencia por si se cae la Web?",
|
||||||
|
"default": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true,
|
||||||
|
"lvalues": [
|
||||||
|
"No",
|
||||||
|
"Guardar",
|
||||||
|
"Borrar",
|
||||||
|
"Actualizar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emergency_urls_torrents",
|
||||||
|
"type": "bool",
|
||||||
|
"label": "Se quieren guardar Torrents de Emergencia por si se cae la Web?",
|
||||||
|
"default": true,
|
||||||
|
"enabled": true,
|
||||||
|
"visible": "!eq(-1,'No')"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "timeout_downloadpage",
|
"id": "timeout_downloadpage",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
|
|||||||
@@ -488,6 +488,8 @@ def findvideos(item):
|
|||||||
itemlist = []
|
itemlist = []
|
||||||
itemlist_t = [] #Itemlist total de enlaces
|
itemlist_t = [] #Itemlist total de enlaces
|
||||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||||
|
titles = [] #Títulos de servidores Directos
|
||||||
|
urls = [] #Urls de servidores Directos
|
||||||
if not item.language:
|
if not item.language:
|
||||||
item.language = ['CAST'] #Castellano por defecto
|
item.language = ['CAST'] #Castellano por defecto
|
||||||
matches = []
|
matches = []
|
||||||
@@ -512,33 +514,75 @@ def findvideos(item):
|
|||||||
if not data:
|
if not data:
|
||||||
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
logger.error("ERROR 01: FINDVIDEOS: La Web no responde o la URL es erronea: " + item.url)
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 01: FINDVIDEOS:. La Web no responde o la URL es erronea. Si la Web está activa, reportar el error con el log'))
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
|
||||||
|
|
||||||
matches = re.compile(patron, re.DOTALL).findall(data)
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches de torrents
|
||||||
|
titles = item.emergency_urls[2] #Restauramos títulos de servidores Directos
|
||||||
|
urls = item.emergency_urls[3] #Restauramos urls de servidores Directos
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
|
if not item.armagedon:
|
||||||
|
matches = re.compile(patron, re.DOTALL).findall(data)
|
||||||
if not matches and not scrapertools.find_single_match(data, 'data-TPlayerNv="Opt\d+">.*? <span>(.*?)</span></li>'): #error
|
if not matches and not scrapertools.find_single_match(data, 'data-TPlayerNv="Opt\d+">.*? <span>(.*?)</span></li>'): #error
|
||||||
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
item = generictools.web_intervenida(item, data) #Verificamos que no haya sido clausurada
|
||||||
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
if item.intervencion: #Sí ha sido clausurada judicialmente
|
||||||
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist) #Llamamos al método para el pintado del error
|
||||||
|
else:
|
||||||
|
logger.error("ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web " + " / PATRON: " + patron + data)
|
||||||
|
itemlist.append(item.clone(action='', title=item.channel.capitalize() + ': ERROR 02: FINDVIDEOS: No hay enlaces o ha cambiado la estructura de la Web. Verificar en la Web esto último y reportar el error con el log'))
|
||||||
|
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls: #Hay urls de emergencia?
|
||||||
|
matches = item.emergency_urls[1] #Restauramos matches de torrents
|
||||||
|
titles = item.emergency_urls[2] #Restauramos títulos de servidores Directos
|
||||||
|
urls = item.emergency_urls[3] #Restauramos urls de servidores Directos
|
||||||
|
item.armagedon = True #Marcamos la situación como catastrófica
|
||||||
|
else:
|
||||||
|
if item.videolibray_emergency_urls: #Si es llamado desde creación de Videoteca...
|
||||||
|
return item #Devolvemos el Item de la llamada
|
||||||
|
else:
|
||||||
|
return itemlist #si no hay más datos, algo no funciona, pintamos lo que tenemos
|
||||||
|
|
||||||
#logger.debug("PATRON: " + patron)
|
#logger.debug("PATRON: " + patron)
|
||||||
#logger.debug(matches)
|
#logger.debug(matches)
|
||||||
#logger.debug(data)
|
logger.debug(data)
|
||||||
|
|
||||||
|
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls = [] #Iniciamos emergency_urls
|
||||||
|
item.emergency_urls.append([]) #Reservamos el espacio para los .torrents locales
|
||||||
|
item.emergency_urls.append(matches) #Salvamnos matches de los vídeos...
|
||||||
|
|
||||||
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
#Llamamos al método para crear el título general del vídeo, con toda la información obtenida de TMDB
|
||||||
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
if not item.videolibray_emergency_urls:
|
||||||
|
item, itemlist = generictools.post_tmdb_findvideos(item, itemlist)
|
||||||
|
|
||||||
#Ahora tratamos los enlaces .torrent
|
#Ahora tratamos los enlaces .torrent
|
||||||
for scrapedurl, scrapedserver, language, quality in matches: #leemos los torrents con la diferentes calidades
|
for scrapedurl, scrapedserver, language, quality in matches: #leemos los torrents con la diferentes calidades
|
||||||
#Generamos una copia de Item para trabajar sobre ella
|
#Generamos una copia de Item para trabajar sobre ella
|
||||||
item_local = item.clone()
|
item_local = item.clone()
|
||||||
|
|
||||||
if 'torrent' not in scrapedserver.lower(): #Si es un servidor Directo, lo dejamos para luego
|
if 'torrent' not in scrapedserver.lower(): #Si es un servidor Directo, lo dejamos para luego
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_local.url = scrapedurl
|
item_local.url = scrapedurl
|
||||||
if '.io/' in item_local.url:
|
if '.io/' in item_local.url:
|
||||||
item_local.url = re.sub(r'http.?:\/\/\w+\.\w+\/', host, item_local.url) #Aseguramos el dominio del canal
|
item_local.url = re.sub(r'http.?:\/\/\w+\.\w+\/', host, item_local.url) #Aseguramos el dominio del canal
|
||||||
|
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls[0].append(item_local.url) #guardamos la url y pasamos a la siguiente
|
||||||
|
continue
|
||||||
|
if item.emergency_urls and not item.videolibray_emergency_urls:
|
||||||
|
item_local.torrent_alt = item.emergency_urls[0][0] #Guardamos la url del .Torrent ALTERNATIVA
|
||||||
|
if item.armagedon:
|
||||||
|
item_local.url = item.emergency_urls[0][0] #Restauramos la url
|
||||||
|
if len(item.emergency_urls[0]) > 1:
|
||||||
|
del item.emergency_urls[0][0]
|
||||||
|
|
||||||
#Detectamos idiomas
|
#Detectamos idiomas
|
||||||
if ("latino" in scrapedurl.lower() or "latino" in language.lower()) and "LAT" not in item_local.language:
|
if ("latino" in scrapedurl.lower() or "latino" in language.lower()) and "LAT" not in item_local.language:
|
||||||
item_local.language += ['LAT']
|
item_local.language += ['LAT']
|
||||||
@@ -554,17 +598,19 @@ def findvideos(item):
|
|||||||
item_local.quality = quality
|
item_local.quality = quality
|
||||||
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'):
|
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'):
|
||||||
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
||||||
|
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||||
|
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||||
|
|
||||||
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
#Buscamos si ya tiene tamaño, si no, los buscamos en el archivo .torrent
|
||||||
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
size = scrapertools.find_single_match(item_local.quality, '\s\[(\d+,?\d*?\s\w\s?[b|B])\]')
|
||||||
if not size:
|
if not size and not item.armagedon:
|
||||||
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
size = generictools.get_torrent_size(item_local.url) #Buscamos el tamaño en el .torrent
|
||||||
if size:
|
if size:
|
||||||
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
item_local.title = re.sub(r'\s\[\d+,?\d*?\s\w[b|B]\]', '', item_local.title) #Quitamos size de título, si lo traía
|
||||||
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
item_local.title = '%s [%s]' % (item_local.title, size) #Agregamos size al final del título
|
||||||
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
size = size.replace('GB', 'G B').replace('Gb', 'G b').replace('MB', 'M B').replace('Mb', 'M b')
|
||||||
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
item_local.quality = re.sub(r'\s\[\d+,?\d*?\s\w\s?[b|B]\]', '', item_local.quality) #Quitamos size de calidad, si lo traía
|
||||||
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
item_local.quality = '%s [%s]' % (item_local.quality, size) #Agregamos size al final de la calidad
|
||||||
|
|
||||||
#Ahora pintamos el link del Torrent
|
#Ahora pintamos el link del Torrent
|
||||||
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
item_local.title = '[COLOR yellow][?][/COLOR] [COLOR yellow][Torrent][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (item_local.quality, str(item_local.language))
|
||||||
@@ -577,9 +623,9 @@ def findvideos(item):
|
|||||||
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
item_local.quality = re.sub(r'\s?\[COLOR \w+\]\s?\[\/COLOR\]', '', item_local.quality).strip()
|
||||||
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
item_local.quality = item_local.quality.replace("--", "").replace("[]", "").replace("()", "").replace("(/)", "").replace("[/]", "").strip()
|
||||||
|
|
||||||
item_local.alive = "??" #Calidad del link sin verificar
|
item_local.alive = "??" #Calidad del link sin verificar
|
||||||
item_local.action = "play" #Visualizar vídeo
|
item_local.action = "play" #Visualizar vídeo
|
||||||
item_local.server = "torrent" #Servidor Torrent
|
item_local.server = "torrent" #Servidor Torrent
|
||||||
|
|
||||||
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
itemlist_t.append(item_local.clone()) #Pintar pantalla, si no se filtran idiomas
|
||||||
|
|
||||||
@@ -601,8 +647,15 @@ def findvideos(item):
|
|||||||
#Ahora tratamos los Servidores Directos
|
#Ahora tratamos los Servidores Directos
|
||||||
itemlist_t = [] #Itemlist total de enlaces
|
itemlist_t = [] #Itemlist total de enlaces
|
||||||
itemlist_f = [] #Itemlist de enlaces filtrados
|
itemlist_f = [] #Itemlist de enlaces filtrados
|
||||||
titles = re.compile('data-TPlayerNv="Opt\d+">.*? <span>(.*?)</span></li>', re.DOTALL).findall(data)
|
if not item.armagedon:
|
||||||
urls = re.compile('id="Opt\d+"><iframe[^>]+src="([^"]+)"', re.DOTALL).findall(data)
|
titles = re.compile('data-..layer..="Opt\d+">(?:<span>)?.*?\s?(?:<strong>.*?<\/strong>)?(?:<\/span>)?<span>(.*?)<\/span><\/li>', re.DOTALL).findall(data)
|
||||||
|
urls = re.compile('id="Opt\d+"><iframe[^>]+src="([^"]+)"', re.DOTALL).findall(data)
|
||||||
|
|
||||||
|
#Si es un lookup para cargar las urls de emergencia en la Videoteca...
|
||||||
|
if item.videolibray_emergency_urls:
|
||||||
|
item.emergency_urls.append(titles) #Salvamnos matches de los títulos...
|
||||||
|
item.emergency_urls.append(urls) #Salvamnos matches de las urls...
|
||||||
|
return item #... y nos vamos
|
||||||
|
|
||||||
#Recorremos la lista de servidores Directos, excluyendo YouTube para trailers
|
#Recorremos la lista de servidores Directos, excluyendo YouTube para trailers
|
||||||
if len(titles) == len(urls):
|
if len(titles) == len(urls):
|
||||||
@@ -618,8 +671,11 @@ def findvideos(item):
|
|||||||
title = titles[0]
|
title = titles[0]
|
||||||
|
|
||||||
if "goo.gl" in urls[i]:
|
if "goo.gl" in urls[i]:
|
||||||
urls[i] = httptools.downloadpage(urls[i], follow_redirects=False, only_headers=True)\
|
try:
|
||||||
.headers.get("location", "")
|
urls[i] = httptools.downloadpage(urls[i], follow_redirects=False, only_headers=True)\
|
||||||
|
.headers.get("location", "")
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
videourl = servertools.findvideos(urls[i]) #Buscamos la url del vídeo
|
videourl = servertools.findvideos(urls[i]) #Buscamos la url del vídeo
|
||||||
|
|
||||||
@@ -661,6 +717,8 @@ def findvideos(item):
|
|||||||
item_local.quality = quality #Añadimos la calidad
|
item_local.quality = quality #Añadimos la calidad
|
||||||
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'): #Añadimos la duración
|
if scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])'): #Añadimos la duración
|
||||||
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
item_local.quality += ' [/COLOR][COLOR white]%s' % scrapertools.find_single_match(item.quality, '(\[\d+:\d+\ h])')
|
||||||
|
if item.armagedon: #Si es catastrófico, lo marcamos
|
||||||
|
item_local.quality = '[/COLOR][COLOR hotpink][E] [COLOR limegreen]%s' % item_local.quality
|
||||||
item_local.title = '[COLOR yellow][%s][/COLOR] [COLOR yellow][%s][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (alive, server.capitalize(), item_local.quality, str(item_local.language))
|
item_local.title = '[COLOR yellow][%s][/COLOR] [COLOR yellow][%s][/COLOR] [COLOR limegreen][%s][/COLOR] [COLOR red]%s[/COLOR]' % (alive, server.capitalize(), item_local.quality, str(item_local.language))
|
||||||
|
|
||||||
#Preparamos título y calidad, quitamos etiquetas vacías
|
#Preparamos título y calidad, quitamos etiquetas vacías
|
||||||
|
|||||||
@@ -576,3 +576,24 @@ def remove_tags(title):
|
|||||||
return title_without_tags
|
return title_without_tags
|
||||||
else:
|
else:
|
||||||
return title
|
return title
|
||||||
|
|
||||||
|
|
||||||
|
def remove_smb_credential(path):
|
||||||
|
"""
|
||||||
|
devuelve el path sin contraseña/usuario para paths de SMB
|
||||||
|
@param path: ruta
|
||||||
|
@type path: str
|
||||||
|
@return: cadena sin credenciales
|
||||||
|
@rtype: str
|
||||||
|
"""
|
||||||
|
logger.info()
|
||||||
|
|
||||||
|
if not path.startswith("smb://"):
|
||||||
|
return path
|
||||||
|
|
||||||
|
path_without_credentials = scrapertools.find_single_match(path, '^smb:\/\/(?:[^;\n]+;)?(?:[^:@\n]+[:|@])?(?:[^@\n]+@)?(.*?$)')
|
||||||
|
|
||||||
|
if path_without_credentials:
|
||||||
|
return ('smb://' + path_without_credentials)
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|||||||
@@ -828,7 +828,7 @@ def caching_torrents(url, torrents_path=None, timeout=10, lookup=False, data_tor
|
|||||||
return torrents_path #Si hay un error, devolvemos el "path" vacío
|
return torrents_path #Si hay un error, devolvemos el "path" vacío
|
||||||
torrent_file = response.data
|
torrent_file = response.data
|
||||||
|
|
||||||
if not scrapertools.find_single_match(torrent_file, '^d\d+:\w+\d+:'): #No es un archivo .torrent (RAR, ZIP, HTML,..., vacío)
|
if not scrapertools.find_single_match(torrent_file, '^d\d+:.*?\d+:'): #No es un archivo .torrent (RAR, ZIP, HTML,..., vacío)
|
||||||
logger.error('No es un archivo Torrent: ' + url)
|
logger.error('No es un archivo Torrent: ' + url)
|
||||||
torrents_path = ''
|
torrents_path = ''
|
||||||
if data_torrent:
|
if data_torrent:
|
||||||
|
|||||||
@@ -1164,7 +1164,7 @@ def post_tmdb_findvideos(item, itemlist):
|
|||||||
title_gen = '[COLOR yellow]%s [/COLOR][ALT]: %s' % (item.category.capitalize(), title_gen)
|
title_gen = '[COLOR yellow]%s [/COLOR][ALT]: %s' % (item.category.capitalize(), title_gen)
|
||||||
#elif (config.get_setting("quit_channel_name", "videolibrary") == 1 or item.channel == channel_py) and item.contentChannel == "videolibrary":
|
#elif (config.get_setting("quit_channel_name", "videolibrary") == 1 or item.channel == channel_py) and item.contentChannel == "videolibrary":
|
||||||
else:
|
else:
|
||||||
title_gen = '%s: %s' % (item.category.capitalize(), title_gen)
|
title_gen = '[COLOR white]%s: %s' % (item.category.capitalize(), title_gen)
|
||||||
|
|
||||||
#Si intervención judicial, alerto!!!
|
#Si intervención judicial, alerto!!!
|
||||||
if item.intervencion:
|
if item.intervencion:
|
||||||
@@ -1768,7 +1768,6 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
item_back = item.clone()
|
item_back = item.clone()
|
||||||
it_back = item.clone()
|
it_back = item.clone()
|
||||||
ow_force_param = True
|
ow_force_param = True
|
||||||
channel_enabled = False
|
|
||||||
update_stat = 0
|
update_stat = 0
|
||||||
delete_stat = 0
|
delete_stat = 0
|
||||||
canal_org_des_list = []
|
canal_org_des_list = []
|
||||||
@@ -1883,13 +1882,15 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if item.url: #Viene de actualización de videoteca de series
|
if item.url and not channel_py in item.url and it.emergency_urls: #Viene de actualización de videoteca de series
|
||||||
#Analizamos si el canal ya tiene las urls de emergencia: guardar o borrar
|
#Analizamos si el canal ya tiene las urls de emergencia: guardar o borrar
|
||||||
if (config.get_setting("emergency_urls", item.channel) == 1 and (not item.emergency_urls or (item.emergency_urls and not item.emergency_urls.get(channel_alt, False)))) or (config.get_setting("emergency_urls", item.channel) == 2 and item.emergency_urls.get(channel_alt, False)) or config.get_setting("emergency_urls", item.channel) == 3 or emergency_urls_force:
|
if (config.get_setting("emergency_urls", item.channel) == 1 and (not item.emergency_urls or (item.emergency_urls and not item.emergency_urls.get(channel_alt, False)))) or (config.get_setting("emergency_urls", item.channel) == 2 and item.emergency_urls.get(channel_alt, False)) or config.get_setting("emergency_urls", item.channel) == 3 or emergency_urls_force:
|
||||||
intervencion += ", ('1', '%s', '%s', '', '', '', '', '', '', '', '*', '%s', 'emerg')" % (channel_alt, channel_alt, config.get_setting("emergency_urls", item.channel))
|
intervencion += ", ('1', '%s', '%s', '', '', '', '', '', '', '', '*', '%s', 'emerg')" % (channel_alt, channel_alt, config.get_setting("emergency_urls", item.channel))
|
||||||
|
|
||||||
elif it.library_urls: #Viene de "listar peliculas´"
|
elif it.library_urls: #Viene de "listar peliculas´"
|
||||||
for canal_vid, url_vid in it.library_urls.items(): #Se recorre "item.library_urls" para buscar canales candidatos
|
for canal_vid, url_vid in it.library_urls.items(): #Se recorre "item.library_urls" para buscar canales candidatos
|
||||||
|
if canal_vid == channel_py: #Si tiene Newcpt1 en canal, es un error
|
||||||
|
continue
|
||||||
canal_vid_alt = "'%s'" % canal_vid
|
canal_vid_alt = "'%s'" % canal_vid
|
||||||
if canal_vid_alt in fail_over_list: #Se busca si es un clone de newpct1
|
if canal_vid_alt in fail_over_list: #Se busca si es un clone de newpct1
|
||||||
channel_bis = channel_py
|
channel_bis = channel_py
|
||||||
@@ -1903,7 +1904,7 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
#Ahora tratamos las webs intervenidas, tranformamos la url, el nfo y borramos los archivos obsoletos de la serie
|
#Ahora tratamos las webs intervenidas, tranformamos la url, el nfo y borramos los archivos obsoletos de la serie
|
||||||
if channel not in intervencion and channel_py_alt not in intervencion and category not in intervencion and channel_alt != 'videolibrary': #lookup
|
if (channel not in intervencion and channel_py_alt not in intervencion and category not in intervencion and channel_alt != 'videolibrary') or not item.infoLabels: #lookup
|
||||||
return (item, it, overwrite) #... el canal/clone está listado
|
return (item, it, overwrite) #... el canal/clone está listado
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
@@ -1915,7 +1916,7 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
for activo, canal_org, canal_des, url_org, url_des, patron1, patron2, patron3, patron4, patron5, content_inc, content_exc, ow_force in intervencion_list:
|
for activo, canal_org, canal_des, url_org, url_des, patron1, patron2, patron3, patron4, patron5, content_inc, content_exc, ow_force in intervencion_list:
|
||||||
opt = ''
|
opt = ''
|
||||||
#Es esta nuestra entrada?
|
#Es esta nuestra entrada?
|
||||||
if activo == '1' and (canal_org == channel_alt or canal_org == item.channel or channel_alt == 'videolibrary' or ow_force == 'del' or ow_force == 'emerg'):
|
if activo == '1' and (canal_org == channel_alt or canal_org == item.category.lower() or channel_alt == 'videolibrary' or ow_force == 'del' or ow_force == 'emerg'):
|
||||||
|
|
||||||
if ow_force == 'del' or ow_force == 'emerg': #Si es un borrado de estructuras erroneas, hacemos un proceso aparte
|
if ow_force == 'del' or ow_force == 'emerg': #Si es un borrado de estructuras erroneas, hacemos un proceso aparte
|
||||||
canal_des_def = canal_des #Si hay canal de sustitución para item.library_urls, lo usamos
|
canal_des_def = canal_des #Si hay canal de sustitución para item.library_urls, lo usamos
|
||||||
@@ -1981,10 +1982,15 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
continue
|
continue
|
||||||
if item.contentType in content_exc: #Está el contenido excluido?
|
if item.contentType in content_exc: #Está el contenido excluido?
|
||||||
continue
|
continue
|
||||||
|
channel_enabled = 0
|
||||||
|
channel_enabled_alt = 1
|
||||||
if item.channel != channel_py:
|
if item.channel != channel_py:
|
||||||
channel_enabled = channeltools.is_enabled(channel_alt) #Verificamos que el canal esté inactivo
|
try:
|
||||||
channel_enabled_alt = config.get_setting('enabled', channel_alt)
|
if channeltools.is_enabled(channel_alt): channel_enabled = 1 #Verificamos que el canal esté inactivo
|
||||||
channel_enabled = channel_enabled * channel_enabled_alt #Si está inactivo en algún sitio, tomamos eso
|
if config.get_setting('enabled', channel_alt) == False: channel_enabled_alt = 0
|
||||||
|
channel_enabled = channel_enabled * channel_enabled_alt #Si está inactivo en algún sitio, tomamos eso
|
||||||
|
except:
|
||||||
|
pass
|
||||||
if channel_enabled == 1 and canal_org != canal_des: #Si el canal está activo, puede ser solo...
|
if channel_enabled == 1 and canal_org != canal_des: #Si el canal está activo, puede ser solo...
|
||||||
continue #... una intervención que afecte solo a una región
|
continue #... una intervención que afecte solo a una región
|
||||||
if ow_force == 'no' and it.library_urls: #Esta regla solo vale para findvideos...
|
if ow_force == 'no' and it.library_urls: #Esta regla solo vale para findvideos...
|
||||||
@@ -2012,6 +2018,9 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
url += scrapertools.find_single_match(url_total, patron5) #La aplicamos a url
|
url += scrapertools.find_single_match(url_total, patron5) #La aplicamos a url
|
||||||
if url:
|
if url:
|
||||||
url_total = url #Guardamos la suma de los resultados intermedios
|
url_total = url #Guardamos la suma de los resultados intermedios
|
||||||
|
if item.channel == channel_py or channel in fail_over_list: #Si es Newpct1...
|
||||||
|
if item.contentType == "tvshow":
|
||||||
|
url_total = re.sub(r'\/\d+\/?$', '', url_total) #parece que con el título encuentra la serie, normalmente...
|
||||||
update_stat += 1 #Ya hemos actualizado algo
|
update_stat += 1 #Ya hemos actualizado algo
|
||||||
canal_org_des_list += [(canal_org, canal_des, url_total, opt, ow_force)] #salvamos el resultado para su proceso
|
canal_org_des_list += [(canal_org, canal_des, url_total, opt, ow_force)] #salvamos el resultado para su proceso
|
||||||
|
|
||||||
@@ -2019,13 +2028,10 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
if (update_stat > 0 and path != False) or item.ow_force == '1':
|
if (update_stat > 0 and path != False) or item.ow_force == '1':
|
||||||
logger.error('** Lista de Actualizaciones a realizar: ' + str(canal_org_des_list))
|
logger.error('** Lista de Actualizaciones a realizar: ' + str(canal_org_des_list))
|
||||||
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por todas las "parejas" cambiadas
|
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por todas las "parejas" cambiadas
|
||||||
|
url_total_def = url_total
|
||||||
if ow_force_def != 'del' and ow_force_def != 'emerg':
|
if ow_force_def != 'del' and ow_force_def != 'emerg':
|
||||||
url_total_def = url_total
|
|
||||||
if (item.channel == channel_py or channel in fail_over_list): #Si es Newpct1...
|
|
||||||
if item.contentType == "tvshow":
|
|
||||||
url_total_def = re.sub(r'\/\d+\/?$', '', url_total) #parece que con el título encuentra la serie, normalmente...
|
|
||||||
if item.url:
|
if item.url:
|
||||||
item.url = url_total_def #Salvamos la url convertida
|
item.url = url_total #Salvamos la url convertida
|
||||||
if item.library_urls:
|
if item.library_urls:
|
||||||
item.library_urls.pop(canal_org_def, None)
|
item.library_urls.pop(canal_org_def, None)
|
||||||
item.library_urls.update({canal_des_def: url_total})
|
item.library_urls.update({canal_des_def: url_total})
|
||||||
@@ -2048,10 +2054,18 @@ def redirect_clone_newpct1(item, head_nfo=None, it=None, path=False, overwrite=F
|
|||||||
#Verificamos que las webs de los canales estén activas antes de borrar los .json, para asegurar que se pueden regenerar
|
#Verificamos que las webs de los canales estén activas antes de borrar los .json, para asegurar que se pueden regenerar
|
||||||
i = 0
|
i = 0
|
||||||
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por las "parejas" a borrar
|
for canal_org_def, canal_des_def, url_total, opt_def, ow_force_def in canal_org_des_list: #pasamos por las "parejas" a borrar
|
||||||
|
if "magnet:" in url_total or type(url_total) != str: #Si la url es un Magnet, o es una lista, pasamos
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
response = httptools.downloadpage(url_total, only_headers=True)
|
response = httptools.downloadpage(url_total, only_headers=True)
|
||||||
except:
|
except:
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
logger.error('Web ' + canal_des_def.upper() + ' ERROR. Regla no procesada: ' + str(canal_org_des_list[i]))
|
||||||
|
item = item_back.clone() #Restauro las imágenes inciales
|
||||||
|
it = it_back.clone()
|
||||||
|
item.torrent_caching_fail = True #Marcamos el proceso como fallido
|
||||||
|
return (item, it, False)
|
||||||
if not response.sucess:
|
if not response.sucess:
|
||||||
logger.error('Web ' + canal_des_def.upper() + ' INACTIVA. Regla no procesada: ' + str(canal_org_des_list[i]))
|
logger.error('Web ' + canal_des_def.upper() + ' INACTIVA. Regla no procesada: ' + str(canal_org_des_list[i]))
|
||||||
item = item_back.clone() #Restauro las imágenes inciales
|
item = item_back.clone() #Restauro las imágenes inciales
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
from nmb.NetBIOS import NetBIOS
|
from nmb.NetBIOS import NetBIOS
|
||||||
from platformcode import logger
|
from platformcode import logger
|
||||||
from smb.SMBConnection import SMBConnection
|
from smb.SMBConnection import SMBConnection
|
||||||
|
|
||||||
|
GitHub = 'https://github.com/miketeo/pysmb' #buscar aquí de vez en cuando la última versiónde SMB-pysmb, y actualizar en Alfa
|
||||||
|
vesion_actual_pysmb = '1.1.25' #actualizada el 25/11/2018
|
||||||
|
|
||||||
remote = None
|
remote = None
|
||||||
|
|
||||||
|
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
# logger.info("Url: %s" % url)
|
# logger.info("Url: %s" % url)
|
||||||
url = url.strip()
|
url = url.strip()
|
||||||
import re
|
|
||||||
patron = "^smb://(?:([^;\n]+);)?(?:([^:@\n]+)[:|@])?(?:([^@\n]+)@)?([^/]+)/([^/\n]+)([/]?.*?)$"
|
patron = "^smb://(?:([^;\n]+);)?(?:([^:@\n]+)[:|@])?(?:([^@\n]+)@)?([^/]+)/([^/\n]+)([/]?.*?)$"
|
||||||
domain, user, password, server_name, share_name, path = re.compile(patron, re.DOTALL).match(url).groups()
|
domain, user, password, server_name, share_name, path = re.compile(patron, re.DOTALL).match(url).groups()
|
||||||
|
|
||||||
@@ -29,8 +32,7 @@ def parse_url(url):
|
|||||||
|
|
||||||
|
|
||||||
def get_server_name_ip(server):
|
def get_server_name_ip(server):
|
||||||
import re
|
if re.compile("^\d+.\d+.\d+.\d+$").findall(server) or re.compile("^([^\.]+\.(?:[^\.]+\.)?(?:\w+)?)$").findall(server):
|
||||||
if re.compile("^\d+.\d+.\d+.\d+$").findall(server):
|
|
||||||
server_ip = server
|
server_ip = server
|
||||||
server_name = None
|
server_name = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import logging
|
|
||||||
import random
|
|
||||||
import select
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
import os, logging, random, socket, time, select
|
||||||
from base import NBNS, NotConnectedError
|
from base import NBNS, NotConnectedError
|
||||||
from nmb_constants import TYPE_SERVER
|
from nmb_constants import TYPE_CLIENT, TYPE_SERVER, TYPE_WORKSTATION
|
||||||
|
|
||||||
|
|
||||||
class NetBIOS(NBNS):
|
class NetBIOS(NBNS):
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import logging
|
|
||||||
import random
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
import os, logging, random, socket, time
|
||||||
from twisted.internet import reactor, defer
|
from twisted.internet import reactor, defer
|
||||||
from twisted.internet.protocol import DatagramProtocol
|
from twisted.internet.protocol import DatagramProtocol
|
||||||
|
|
||||||
from base import NBNS
|
|
||||||
from nmb_constants import TYPE_SERVER
|
from nmb_constants import TYPE_SERVER
|
||||||
|
from base import NBNS
|
||||||
|
|
||||||
IP_QUERY, NAME_QUERY = range(2)
|
IP_QUERY, NAME_QUERY = range(2)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
|
import struct, logging, random
|
||||||
from nmb_constants import *
|
from nmb_constants import *
|
||||||
from nmb_structs import *
|
from nmb_structs import *
|
||||||
from utils import encode_name
|
from utils import encode_name
|
||||||
|
|
||||||
|
|
||||||
class NMBSession:
|
class NMBSession:
|
||||||
|
|
||||||
log = logging.getLogger('NMB.NMBSession')
|
log = logging.getLogger('NMB.NMBSession')
|
||||||
@@ -78,6 +77,9 @@ class NMBSession:
|
|||||||
self.onNMBSessionOK()
|
self.onNMBSessionOK()
|
||||||
elif packet.type == NEGATIVE_SESSION_RESPONSE:
|
elif packet.type == NEGATIVE_SESSION_RESPONSE:
|
||||||
self.onNMBSessionFailed()
|
self.onNMBSessionFailed()
|
||||||
|
elif packet.type == SESSION_KEEPALIVE:
|
||||||
|
# Discard keepalive packets - [RFC1002]: 5.2.2.1
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
|
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import re
|
|
||||||
import string
|
import string, re
|
||||||
|
|
||||||
|
|
||||||
def encode_name(name, type, scope = None):
|
def encode_name(name, type, scope = None):
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import select
|
|
||||||
import socket
|
|
||||||
import types
|
|
||||||
|
|
||||||
from base import SMB, NotConnectedError, SMBTimeout
|
import os, logging, select, socket, struct, errno
|
||||||
|
from smb_constants import *
|
||||||
from smb_structs import *
|
from smb_structs import *
|
||||||
|
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||||
|
|
||||||
|
|
||||||
class SMBConnection(SMB):
|
class SMBConnection(SMB):
|
||||||
@@ -154,15 +153,23 @@ class SMBConnection(SMB):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def listPath(self, service_name, path,
|
def listPath(self, service_name, path,
|
||||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE,
|
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||||
pattern = '*', timeout = 30):
|
pattern = '*', timeout = 30):
|
||||||
"""
|
"""
|
||||||
Retrieve a directory listing of files/folders at *path*
|
Retrieve a directory listing of files/folders at *path*
|
||||||
|
|
||||||
|
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||||
|
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||||
|
|
||||||
|
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||||
|
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||||
|
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||||
|
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||||
|
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||||
|
|
||||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||||
The default *search* value will query for all read-only, hidden, system, archive files and directories.
|
|
||||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||||
:return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
:return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||||
"""
|
"""
|
||||||
@@ -253,6 +260,37 @@ class SMBConnection(SMB):
|
|||||||
|
|
||||||
return results[0]
|
return results[0]
|
||||||
|
|
||||||
|
def getSecurity(self, service_name, path, timeout = 30):
|
||||||
|
"""
|
||||||
|
Retrieve the security descriptor of the file at *path* on the *service_name*.
|
||||||
|
|
||||||
|
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||||
|
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||||
|
:return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file.
|
||||||
|
"""
|
||||||
|
if not self.sock:
|
||||||
|
raise NotConnectedError('Not connected to server')
|
||||||
|
|
||||||
|
results = [ ]
|
||||||
|
|
||||||
|
def cb(info):
|
||||||
|
self.is_busy = False
|
||||||
|
results.append(info)
|
||||||
|
|
||||||
|
def eb(failure):
|
||||||
|
self.is_busy = False
|
||||||
|
raise failure
|
||||||
|
|
||||||
|
self.is_busy = True
|
||||||
|
try:
|
||||||
|
self._getSecurity(service_name, path, cb, eb, timeout)
|
||||||
|
while self.is_busy:
|
||||||
|
self._pollForNetBIOSPacket(timeout)
|
||||||
|
finally:
|
||||||
|
self.is_busy = False
|
||||||
|
|
||||||
|
return results[0]
|
||||||
|
|
||||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||||
"""
|
"""
|
||||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||||
@@ -385,9 +423,10 @@ class SMBConnection(SMB):
|
|||||||
It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request.
|
It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request.
|
||||||
This function is very helpful when deleting files/folders that are read-only.
|
This function is very helpful when deleting files/folders that are read-only.
|
||||||
Note: this function is currently only implemented for SMB2! Technically, it sets the FILE_ATTRIBUTE_NORMAL flag, therefore clearing all other flags. (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information)
|
Note: this function is currently only implemented for SMB2! Technically, it sets the FILE_ATTRIBUTE_NORMAL flag, therefore clearing all other flags. (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information)
|
||||||
|
|
||||||
:param string/unicode service_name: Contains the name of the shared folder.
|
:param string/unicode service_name: Contains the name of the shared folder.
|
||||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||||
Wildcards may be used in th filename component of the path.
|
Wildcards may be used in the filename component of the path.
|
||||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
@@ -495,7 +534,7 @@ class SMBConnection(SMB):
|
|||||||
"""
|
"""
|
||||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||||
|
|
||||||
:param string data: Data to send to the remote server.
|
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||||
:return: The *data* parameter
|
:return: The *data* parameter
|
||||||
"""
|
"""
|
||||||
if not self.sock:
|
if not self.sock:
|
||||||
@@ -546,15 +585,24 @@ class SMBConnection(SMB):
|
|||||||
data = data + d
|
data = data + d
|
||||||
read_len -= len(d)
|
read_len -= len(d)
|
||||||
except select.error, ex:
|
except select.error, ex:
|
||||||
if type(ex) is types.TupleType:
|
if isinstance(ex, types.TupleType):
|
||||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||||
raise ex
|
raise ex
|
||||||
else:
|
else:
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
type, flags, length = struct.unpack('>BBH', data)
|
type_, flags, length = struct.unpack('>BBH', data)
|
||||||
if flags & 0x01:
|
if type_ == 0x0:
|
||||||
length = length | 0x10000
|
# This is a Direct TCP packet
|
||||||
|
# The length is specified in the header from byte 8. (0-indexed)
|
||||||
|
# we read a structure assuming NBT, so to get the real length
|
||||||
|
# combine the length and flag fields together
|
||||||
|
length = length + (flags << 16)
|
||||||
|
else:
|
||||||
|
# This is a NetBIOS over TCP (NBT) packet
|
||||||
|
# The length is specified in the header from byte 16. (0-indexed)
|
||||||
|
if flags & 0x01:
|
||||||
|
length = length | 0x10000
|
||||||
|
|
||||||
read_len = length
|
read_len = length
|
||||||
while read_len > 0:
|
while read_len > 0:
|
||||||
@@ -573,7 +621,7 @@ class SMBConnection(SMB):
|
|||||||
data = data + d
|
data = data + d
|
||||||
read_len -= len(d)
|
read_len -= len(d)
|
||||||
except select.error, ex:
|
except select.error, ex:
|
||||||
if type(ex) is types.TupleType:
|
if isinstance(ex, types.TupleType):
|
||||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||||
raise ex
|
raise ex
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import mimetools
|
|
||||||
import mimetypes
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import urllib2
|
|
||||||
from urllib import (unquote, addinfourl, splitport, splitattr, splituser, splitpasswd)
|
|
||||||
|
|
||||||
|
import os, sys, socket, urllib2, mimetypes, mimetools, tempfile
|
||||||
|
from urllib import (unwrap, unquote, splittype, splithost, quote,
|
||||||
|
addinfourl, splitport, splittag,
|
||||||
|
splitattr, ftpwrapper, splituser, splitpasswd, splitvalue)
|
||||||
from nmb.NetBIOS import NetBIOS
|
from nmb.NetBIOS import NetBIOS
|
||||||
|
|
||||||
from smb.SMBConnection import SMBConnection
|
from smb.SMBConnection import SMBConnection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
|
||||||
|
import os, logging, time
|
||||||
from twisted.internet import reactor, defer
|
from twisted.internet import reactor, defer
|
||||||
from twisted.internet.protocol import ClientFactory, Protocol
|
from twisted.internet.protocol import ClientFactory, Protocol
|
||||||
|
from smb_constants import *
|
||||||
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
|
||||||
from smb_structs import *
|
from smb_structs import *
|
||||||
|
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||||
|
|
||||||
|
|
||||||
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
||||||
|
|
||||||
@@ -174,15 +177,23 @@ class SMBProtocolFactory(ClientFactory):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def listPath(self, service_name, path,
|
def listPath(self, service_name, path,
|
||||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE,
|
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||||
pattern = '*', timeout = 30):
|
pattern = '*', timeout = 30):
|
||||||
"""
|
"""
|
||||||
Retrieve a directory listing of files/folders at *path*
|
Retrieve a directory listing of files/folders at *path*
|
||||||
|
|
||||||
|
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||||
|
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||||
|
|
||||||
|
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||||
|
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||||
|
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||||
|
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||||
|
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||||
|
|
||||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||||
The default *search* value will query for all read-only, hidden, system, archive files and directories.
|
|
||||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||||
@@ -365,7 +376,7 @@ class SMBProtocolFactory(ClientFactory):
|
|||||||
"""
|
"""
|
||||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||||
|
|
||||||
:param string data: Data to send to the remote server.
|
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
||||||
"""
|
"""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
import hmac
|
|
||||||
import random
|
|
||||||
import struct
|
|
||||||
|
|
||||||
|
import types, hmac, binascii, struct, random
|
||||||
from utils.pyDes import des
|
from utils.pyDes import des
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -165,11 +163,12 @@ def generateChallengeResponseV2(password, user, server_challenge, server_info, d
|
|||||||
d.update(password.encode('UTF-16LE'))
|
d.update(password.encode('UTF-16LE'))
|
||||||
ntlm_hash = d.digest() # The NT password hash
|
ntlm_hash = d.digest() # The NT password hash
|
||||||
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
||||||
temp = client_timestamp + client_challenge + domain.encode('UTF-16LE') + server_info
|
temp = '\x01\x01' + '\0'*6 + client_timestamp + client_challenge + '\0'*4 + server_info
|
||||||
|
ntproofstr = hmac.new(response_key, server_challenge + temp).digest()
|
||||||
|
|
||||||
nt_challenge_response = hmac.new(response_key, server_challenge + temp).digest()
|
nt_challenge_response = ntproofstr + temp
|
||||||
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge
|
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge
|
||||||
session_key = hmac.new(response_key, nt_challenge_response).digest()
|
session_key = hmac.new(response_key, ntproofstr).digest()
|
||||||
|
|
||||||
return nt_challenge_response, lm_challenge_response, session_key
|
return nt_challenge_response, lm_challenge_response, session_key
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,367 @@
|
|||||||
|
"""
|
||||||
|
This module implements security descriptors, and the partial structures
|
||||||
|
used in them, as specified in [MS-DTYP].
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
# Security descriptor control flags
|
||||||
|
# [MS-DTYP]: 2.4.6
|
||||||
|
SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
|
||||||
|
SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
|
||||||
|
SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
|
||||||
|
SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
|
||||||
|
SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
|
||||||
|
SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
|
||||||
|
SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
|
||||||
|
SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
|
||||||
|
SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
|
||||||
|
SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
|
||||||
|
SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
|
||||||
|
SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
|
||||||
|
SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
|
||||||
|
SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
|
||||||
|
SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
|
||||||
|
SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
|
||||||
|
|
||||||
|
# ACE types
|
||||||
|
# [MS-DTYP]: 2.4.4.1
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED = 0x00
|
||||||
|
ACE_TYPE_ACCESS_DENIED = 0x01
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT = 0x02
|
||||||
|
ACE_TYPE_SYSTEM_ALARM = 0x03
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
|
||||||
|
ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
|
||||||
|
ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
|
||||||
|
ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
|
||||||
|
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
|
||||||
|
ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
|
||||||
|
ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
|
||||||
|
ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
|
||||||
|
ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
|
||||||
|
ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
|
||||||
|
|
||||||
|
# ACE flags
|
||||||
|
# [MS-DTYP]: 2.4.4.1
|
||||||
|
ACE_FLAG_OBJECT_INHERIT = 0x01
|
||||||
|
ACE_FLAG_CONTAINER_INHERIT = 0x02
|
||||||
|
ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
|
||||||
|
ACE_FLAG_INHERIT_ONLY = 0x08
|
||||||
|
ACE_FLAG_INHERITED = 0x10
|
||||||
|
ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
|
||||||
|
ACE_FLAG_FAILED_ACCESS = 0x80
|
||||||
|
|
||||||
|
# Pre-defined well-known SIDs
|
||||||
|
# [MS-DTYP]: 2.4.2.4
|
||||||
|
SID_NULL = "S-1-0-0"
|
||||||
|
SID_EVERYONE = "S-1-1-0"
|
||||||
|
SID_LOCAL = "S-1-2-0"
|
||||||
|
SID_CONSOLE_LOGON = "S-1-2-1"
|
||||||
|
SID_CREATOR_OWNER = "S-1-3-0"
|
||||||
|
SID_CREATOR_GROUP = "S-1-3-1"
|
||||||
|
SID_OWNER_SERVER = "S-1-3-2"
|
||||||
|
SID_GROUP_SERVER = "S-1-3-3"
|
||||||
|
SID_OWNER_RIGHTS = "S-1-3-4"
|
||||||
|
SID_NT_AUTHORITY = "S-1-5"
|
||||||
|
SID_DIALUP = "S-1-5-1"
|
||||||
|
SID_NETWORK = "S-1-5-2"
|
||||||
|
SID_BATCH = "S-1-5-3"
|
||||||
|
SID_INTERACTIVE = "S-1-5-4"
|
||||||
|
SID_SERVICE = "S-1-5-6"
|
||||||
|
SID_ANONYMOUS = "S-1-5-7"
|
||||||
|
SID_PROXY = "S-1-5-8"
|
||||||
|
SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
|
||||||
|
SID_PRINCIPAL_SELF = "S-1-5-10"
|
||||||
|
SID_AUTHENTICATED_USERS = "S-1-5-11"
|
||||||
|
SID_RESTRICTED_CODE = "S-1-5-12"
|
||||||
|
SID_TERMINAL_SERVER_USER = "S-1-5-13"
|
||||||
|
SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
|
||||||
|
SID_THIS_ORGANIZATION = "S-1-5-15"
|
||||||
|
SID_IUSR = "S-1-5-17"
|
||||||
|
SID_LOCAL_SYSTEM = "S-1-5-18"
|
||||||
|
SID_LOCAL_SERVICE = "S-1-5-19"
|
||||||
|
SID_NETWORK_SERVICE = "S-1-5-20"
|
||||||
|
SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
|
||||||
|
SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
|
||||||
|
SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
|
||||||
|
SID_BUILTIN_USERS = "S-1-5-32-545"
|
||||||
|
SID_BUILTIN_GUESTS = "S-1-5-32-546"
|
||||||
|
SID_POWER_USERS = "S-1-5-32-547"
|
||||||
|
SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
|
||||||
|
SID_SERVER_OPERATORS = "S-1-5-32-549"
|
||||||
|
SID_PRINTER_OPERATORS = "S-1-5-32-550"
|
||||||
|
SID_BACKUP_OPERATORS = "S-1-5-32-551"
|
||||||
|
SID_REPLICATOR = "S-1-5-32-552"
|
||||||
|
SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
|
||||||
|
SID_REMOTE_DESKTOP = "S-1-5-32-555"
|
||||||
|
SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
|
||||||
|
SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
|
||||||
|
SID_PERFMON_USERS = "S-1-5-32-558"
|
||||||
|
SID_PERFLOG_USERS = "S-1-5-32-559"
|
||||||
|
SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
|
||||||
|
SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
|
||||||
|
SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
|
||||||
|
SID_IIS_IUSRS = "S-1-5-32-568"
|
||||||
|
SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
|
||||||
|
SID_EVENT_LOG_READERS = "S-1-5-32-573"
|
||||||
|
SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
|
||||||
|
SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
|
||||||
|
SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
|
||||||
|
SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
|
||||||
|
SID_HYPER_V_ADMINS = "S-1-5-32-578"
|
||||||
|
SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
|
||||||
|
SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
|
||||||
|
SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
|
||||||
|
SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
|
||||||
|
SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
|
||||||
|
SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
|
||||||
|
SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
|
||||||
|
SID_NT_SERVICE = "S-1-5-80"
|
||||||
|
SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
|
||||||
|
SID_LOCAL_ACCOUNT = "S-1-5-113"
|
||||||
|
SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
|
||||||
|
SID_OTHER_ORGANIZATION = "S-1-5-1000"
|
||||||
|
SID_ALL_APP_PACKAGES = "S-1-15-2-1"
|
||||||
|
SID_ML_UNTRUSTED = "S-1-16-0"
|
||||||
|
SID_ML_LOW = "S-1-16-4096"
|
||||||
|
SID_ML_MEDIUM = "S-1-16-8192"
|
||||||
|
SID_ML_MEDIUM_PLUS = "S-1-16-8448"
|
||||||
|
SID_ML_HIGH = "S-1-16-12288"
|
||||||
|
SID_ML_SYSTEM = "S-1-16-16384"
|
||||||
|
SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
|
||||||
|
SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
|
||||||
|
SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
|
||||||
|
SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
|
||||||
|
SID_KEY_TRUST_IDENTITY = "S-1-18-4"
|
||||||
|
SID_KEY_PROPERTY_MFA = "S-1-18-5"
|
||||||
|
SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
|
||||||
|
|
||||||
|
|
||||||
|
class SID(object):
|
||||||
|
"""
|
||||||
|
A Windows security identifier. Represents a single principal, such a
|
||||||
|
user or a group, as a sequence of numbers consisting of the revision,
|
||||||
|
identifier authority, and a variable-length list of subauthorities.
|
||||||
|
|
||||||
|
See [MS-DTYP]: 2.4.2
|
||||||
|
"""
|
||||||
|
def __init__(self, revision, identifier_authority, subauthorities):
|
||||||
|
#: Revision, should always be 1.
|
||||||
|
self.revision = revision
|
||||||
|
#: An integer representing the identifier authority.
|
||||||
|
self.identifier_authority = identifier_authority
|
||||||
|
#: A list of integers representing all subauthorities.
|
||||||
|
self.subauthorities = subauthorities
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
String representation, as specified in [MS-DTYP]: 2.4.2.1
|
||||||
|
"""
|
||||||
|
if self.identifier_authority >= 2**32:
|
||||||
|
id_auth = '%#x' % (self.identifier_authority,)
|
||||||
|
else:
|
||||||
|
id_auth = self.identifier_authority
|
||||||
|
auths = [self.revision, id_auth] + self.subauthorities
|
||||||
|
return 'S-' + '-'.join(str(subauth) for subauth in auths)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'SID(%r)' % (str(self),)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data, return_tail=False):
|
||||||
|
revision, subauth_count = struct.unpack('<BB', data[:2])
|
||||||
|
identifier_authority = struct.unpack('>Q', '\x00\x00' + data[2:8])[0]
|
||||||
|
subauth_data = data[8:]
|
||||||
|
subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
|
||||||
|
for i in range(subauth_count)]
|
||||||
|
sid = cls(revision, identifier_authority, subauthorities)
|
||||||
|
if return_tail:
|
||||||
|
return sid, subauth_data[4 * subauth_count :]
|
||||||
|
return sid
|
||||||
|
|
||||||
|
|
||||||
|
class ACE(object):
|
||||||
|
"""
|
||||||
|
Represents a single access control entry.
|
||||||
|
|
||||||
|
See [MS-DTYP]: 2.4.4
|
||||||
|
"""
|
||||||
|
HEADER_FORMAT = '<BBH'
|
||||||
|
|
||||||
|
def __init__(self, type_, flags, mask, sid, additional_data):
|
||||||
|
#: An integer representing the type of the ACE. One of the
|
||||||
|
#: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
|
||||||
|
#: from [MS-DTYP] 2.4.4.1.
|
||||||
|
self.type = type_
|
||||||
|
#: An integer bitmask with ACE flags, corresponds to the
|
||||||
|
#: ``AceFlags`` field.
|
||||||
|
self.flags = flags
|
||||||
|
#: An integer representing the ``ACCESS_MASK`` as specified in
|
||||||
|
#: [MS-DTYP] 2.4.3.
|
||||||
|
self.mask = mask
|
||||||
|
#: The :class:`SID` of a trustee.
|
||||||
|
self.sid = sid
|
||||||
|
#: A dictionary of additional fields present in the ACE, depending
|
||||||
|
#: on the type. The following fields can be present:
|
||||||
|
#:
|
||||||
|
#: * ``flags``
|
||||||
|
#: * ``object_type``
|
||||||
|
#: * ``inherited_object_type``
|
||||||
|
#: * ``application_data``
|
||||||
|
#: * ``attribute_data``
|
||||||
|
self.additional_data = additional_data
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
|
||||||
|
self.type, self.flags, self.mask, self.sid,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isInheritOnly(self):
|
||||||
|
"""Convenience property which indicates if this ACE is inherit
|
||||||
|
only, meaning that it doesn't apply to the object itself."""
|
||||||
|
return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data):
|
||||||
|
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||||
|
header = data[:header_size]
|
||||||
|
type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
|
||||||
|
|
||||||
|
assert len(data) >= size
|
||||||
|
|
||||||
|
body = data[header_size:size]
|
||||||
|
additional_data = {}
|
||||||
|
|
||||||
|
# In all ACE types, the mask immediately follows the header.
|
||||||
|
mask = struct.unpack('<I', body[:4])[0]
|
||||||
|
body = body[4:]
|
||||||
|
|
||||||
|
# All OBJECT-type ACEs contain additional flags, and two GUIDs as
|
||||||
|
# the following fields.
|
||||||
|
if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
|
||||||
|
ACE_TYPE_ACCESS_DENIED_OBJECT,
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||||
|
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||||
|
additional_data['flags'] = struct.unpack('<I', body[:4])[0]
|
||||||
|
additional_data['object_type'] = body[4:20]
|
||||||
|
additional_data['inherited_object_type'] = body[20:36]
|
||||||
|
body = body[36:]
|
||||||
|
|
||||||
|
# Then the SID in all types.
|
||||||
|
sid, body = SID.from_bytes(body, return_tail=True)
|
||||||
|
|
||||||
|
# CALLBACK-type ACEs (and for some obscure reason,
|
||||||
|
# SYSTEM_AUDIT_OBJECT) have a final tail of application data.
|
||||||
|
if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
|
||||||
|
ACE_TYPE_ACCESS_DENIED_CALLBACK,
|
||||||
|
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||||
|
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
|
||||||
|
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||||
|
additional_data['application_data'] = body
|
||||||
|
|
||||||
|
# SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
|
||||||
|
if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
|
||||||
|
additional_data['attribute_data'] = body
|
||||||
|
|
||||||
|
return cls(type_, flags, mask, sid, additional_data)
|
||||||
|
|
||||||
|
|
||||||
|
class ACL(object):
|
||||||
|
"""
|
||||||
|
Access control list, encapsulating a sequence of access control
|
||||||
|
entries.
|
||||||
|
|
||||||
|
See [MS-DTYP]: 2.4.5
|
||||||
|
"""
|
||||||
|
HEADER_FORMAT = '<BBHHH'
|
||||||
|
|
||||||
|
def __init__(self, revision, aces):
|
||||||
|
#: Integer value of the revision.
|
||||||
|
self.revision = revision
|
||||||
|
#: List of :class:`ACE` instances.
|
||||||
|
self.aces = aces
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "ACL(%r)" % (self.aces,)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data):
|
||||||
|
revision = None
|
||||||
|
aces = []
|
||||||
|
|
||||||
|
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||||
|
header, remaining = data[:header_size], data[header_size:]
|
||||||
|
revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
|
||||||
|
|
||||||
|
assert len(data) >= size
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
ace_size = struct.unpack('<H', remaining[2:4])[0]
|
||||||
|
ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
|
||||||
|
aces.append(ACE.from_bytes(ace_data))
|
||||||
|
|
||||||
|
return cls(revision, aces)
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityDescriptor(object):
|
||||||
|
"""
|
||||||
|
Represents a security descriptor.
|
||||||
|
|
||||||
|
See [MS-DTYP]: 2.4.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
HEADER_FORMAT = '<BBHIIII'
|
||||||
|
|
||||||
|
def __init__(self, flags, owner, group, dacl, sacl):
|
||||||
|
#: Integer bitmask of control flags. Corresponds to the
|
||||||
|
#: ``Control`` field in [MS-DTYP] 2.4.6.
|
||||||
|
self.flags = flags
|
||||||
|
#: Instance of :class:`SID` representing the owner user.
|
||||||
|
self.owner = owner
|
||||||
|
#: Instance of :class:`SID` representing the owner group.
|
||||||
|
self.group = group
|
||||||
|
#: Instance of :class:`ACL` representing the discretionary access
|
||||||
|
#: control list, which specifies access restrictions of an object.
|
||||||
|
self.dacl = dacl
|
||||||
|
#: Instance of :class:`ACL` representing the system access control
|
||||||
|
#: list, which specifies audit logging of an object.
|
||||||
|
self.sacl = sacl
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data):
|
||||||
|
owner = None
|
||||||
|
group = None
|
||||||
|
dacl = None
|
||||||
|
sacl = None
|
||||||
|
|
||||||
|
header = data[:struct.calcsize(cls.HEADER_FORMAT)]
|
||||||
|
(revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
|
||||||
|
dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
|
||||||
|
|
||||||
|
assert revision == 1
|
||||||
|
assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
|
||||||
|
for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
|
||||||
|
assert 0 <= offset < len(data)
|
||||||
|
|
||||||
|
if owner_offset:
|
||||||
|
owner = SID.from_bytes(data[owner_offset:])
|
||||||
|
if group_offset:
|
||||||
|
group = SID.from_bytes(data[group_offset:])
|
||||||
|
if dacl_offset:
|
||||||
|
dacl = ACL.from_bytes(data[dacl_offset:])
|
||||||
|
if sacl_offset:
|
||||||
|
sacl = ACL.from_bytes(data[sacl_offset:])
|
||||||
|
|
||||||
|
return cls(flags, owner, group, dacl, sacl)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
from pyasn1.codec.der import encoder, decoder
|
|
||||||
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
||||||
|
from pyasn1.codec.der import encoder, decoder
|
||||||
|
|
||||||
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,12 @@ SMB2_COMMAND_NAMES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Values for dialect_revision field in SMB2NegotiateResponse class
|
# Values for dialect_revision field in SMB2NegotiateResponse class
|
||||||
SMB2_DIALECT_2 = 0x0202
|
SMB2_DIALECT_2 = 0x0202 # 2.0.2 - First SMB2 version
|
||||||
SMB2_DIALECT_21 = 0x0210
|
SMB2_DIALECT_21 = 0x0210 # 2.1 - Windows 7
|
||||||
SMB2_DIALECT_2ALL = 0x02FF
|
SMB2_DIALET_30 = 0x0300 # 3.0 - Windows 8
|
||||||
|
SMB2_DIALECT_302 = 0x0302 # 3.0.2 - Windows 8.1
|
||||||
|
SMB2_DIALECT_311 = 0x0311 # 3.1.1 - Windows 10
|
||||||
|
SMB2_DIALECT_2ALL = 0x02FF # Wildcard (for negotiation only)
|
||||||
|
|
||||||
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
||||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
||||||
@@ -66,6 +69,17 @@ SMB2_SHARE_TYPE_PRINTER = 0x03
|
|||||||
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
||||||
SMB2_SHARE_CAP_DFS = 0x0008
|
SMB2_SHARE_CAP_DFS = 0x0008
|
||||||
|
|
||||||
|
|
||||||
|
# SMB 2.1 / 3 Capabilities flags
|
||||||
|
SMB2_GLOBAL_CAP_DFS = 0x01
|
||||||
|
SMB2_GLOBAL_CAP_LEASING = 0x02
|
||||||
|
SMB2_GLOBAL_CAP_LARGE_MTU = 0x04
|
||||||
|
SMB2_GLOBAL_CAP_MULTI_CHANNEL = 0x08
|
||||||
|
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES = 0x10
|
||||||
|
SMB2_GLOBAL_CAP_DIRECTORY_LEASING = 0x20
|
||||||
|
SMB2_GLOBAL_CAP_ENCRYPTION = 0x40
|
||||||
|
|
||||||
|
|
||||||
# Values for OpLockLevel field in SMB2CreateRequest class
|
# Values for OpLockLevel field in SMB2CreateRequest class
|
||||||
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
||||||
SMB2_OPLOCK_LEVEL_II = 0x01
|
SMB2_OPLOCK_LEVEL_II = 0x01
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import binascii
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
from smb2_constants import *
|
import os, sys, struct, types, logging, binascii, time, uuid
|
||||||
from smb_constants import *
|
from StringIO import StringIO
|
||||||
from smb_structs import ProtocolError
|
from smb_structs import ProtocolError
|
||||||
|
from smb_constants import *
|
||||||
|
from smb2_constants import *
|
||||||
from utils import convertFILETIMEtoEpoch
|
from utils import convertFILETIMEtoEpoch
|
||||||
|
|
||||||
|
|
||||||
@@ -26,8 +23,15 @@ class SMB2Message:
|
|||||||
log = logging.getLogger('SMB.SMB2Message')
|
log = logging.getLogger('SMB.SMB2Message')
|
||||||
protocol = 2
|
protocol = 2
|
||||||
|
|
||||||
def __init__(self, payload = None):
|
|
||||||
|
def __init__(self, conn = None, payload = None):
|
||||||
|
"""
|
||||||
|
Initialise a new SMB2 Message.
|
||||||
|
conn - reference to the connection, the SMB class
|
||||||
|
payload - the message payload, if any
|
||||||
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.conn = conn
|
||||||
if payload:
|
if payload:
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.payload.initMessage(self)
|
self.payload.initMessage(self)
|
||||||
@@ -63,6 +67,10 @@ class SMB2Message:
|
|||||||
self.pid = 0
|
self.pid = 0
|
||||||
self.tid = 0
|
self.tid = 0
|
||||||
|
|
||||||
|
# credit related
|
||||||
|
self.credit_charge = 0
|
||||||
|
self.credit_request = 1
|
||||||
|
|
||||||
# Not used in this class. Maintained for compatibility with SMBMessage class
|
# Not used in this class. Maintained for compatibility with SMBMessage class
|
||||||
self.flags2 = 0
|
self.flags2 = 0
|
||||||
self.uid = 0
|
self.uid = 0
|
||||||
@@ -72,18 +80,66 @@ class SMB2Message:
|
|||||||
def encode(self):
|
def encode(self):
|
||||||
"""
|
"""
|
||||||
Encode this SMB2 message into a series of bytes suitable to be embedded with a NetBIOS session message.
|
Encode this SMB2 message into a series of bytes suitable to be embedded with a NetBIOS session message.
|
||||||
|
AssertionError will be raised if this SMB message has not been initialized with an SMB instance
|
||||||
AssertionError will be raised if this SMB message has not been initialized with a Payload instance
|
AssertionError will be raised if this SMB message has not been initialized with a Payload instance
|
||||||
|
|
||||||
|
The header format is:
|
||||||
|
- Protocol ID
|
||||||
|
- Structure Size
|
||||||
|
- Credit Charge
|
||||||
|
- Status / Channel Sequence
|
||||||
|
- Command
|
||||||
|
- Credit Request / Credit Response
|
||||||
|
- Flags
|
||||||
|
- Next Compound
|
||||||
|
- MessageId
|
||||||
|
- Reserved
|
||||||
|
- TreeId
|
||||||
|
- Session ID
|
||||||
|
- Signature
|
||||||
|
|
||||||
@return: a string containing the encoded SMB2 message
|
@return: a string containing the encoded SMB2 message
|
||||||
"""
|
"""
|
||||||
assert self.payload
|
assert self.payload
|
||||||
|
assert self.conn
|
||||||
|
|
||||||
self.pid = os.getpid()
|
self.pid = os.getpid()
|
||||||
self.payload.prepare(self)
|
self.payload.prepare(self)
|
||||||
|
|
||||||
|
# If Connection.Dialect is not "2.0.2" and if Connection.SupportsMultiCredit is TRUE, the
|
||||||
|
# CreditCharge field in the SMB2 header MUST be set to ( 1 + (OutputBufferLength - 1) / 65536 )
|
||||||
|
# This only applies to SMB2ReadRequest, SMB2WriteRequest, SMB2IoctlRequest and SMB2QueryDirectory
|
||||||
|
# See: MS-SMB2 3.2.4.1.5: For all other requests, the client MUST set CreditCharge to 1, even if the
|
||||||
|
# payload size of a request or the anticipated response is greater than 65536.
|
||||||
|
if self.conn.smb2_dialect != SMB2_DIALECT_2:
|
||||||
|
if self.conn.cap_multi_credit:
|
||||||
|
# self.credit_charge will be set by some commands if necessary (Read/Write/Ioctl/QueryDirectory)
|
||||||
|
# If not set, but dialect is SMB 2.1 or above, we must set it to 1
|
||||||
|
if self.credit_charge is 0:
|
||||||
|
self.credit_charge = 1
|
||||||
|
else:
|
||||||
|
# If >= SMB 2.1, but server does not support multi credit operations we must set to 1
|
||||||
|
self.credit_charge = 1
|
||||||
|
|
||||||
|
if self.mid > 3:
|
||||||
|
self.credit_request = 127
|
||||||
|
|
||||||
headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
|
headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||||
'\xFESMB', self.HEADER_SIZE, 0, self.status, self.command, 0, self.flags) + \
|
'\xFESMB', # Protocol ID
|
||||||
struct.pack(self.SYNC_HEADER_STRUCT_FORMAT, self.next_command_offset, self.mid, self.pid, self.tid, self.session_id, self.signature)
|
self.HEADER_SIZE, # Structure Size
|
||||||
|
self.credit_charge, # Credit Charge
|
||||||
|
self.status, # Status / Channel Sequence
|
||||||
|
self.command, # Command
|
||||||
|
self.credit_request, # Credit Request / Credit Response
|
||||||
|
self.flags, # Flags
|
||||||
|
) + \
|
||||||
|
struct.pack(self.SYNC_HEADER_STRUCT_FORMAT,
|
||||||
|
self.next_command_offset, # Next Compound
|
||||||
|
self.mid, # Message ID
|
||||||
|
self.pid, # Process ID
|
||||||
|
self.tid, # Tree ID
|
||||||
|
self.session_id, # Session ID
|
||||||
|
self.signature) # Signature
|
||||||
return headers_data + self.data
|
return headers_data + self.data
|
||||||
|
|
||||||
def decode(self, buf):
|
def decode(self, buf):
|
||||||
@@ -109,7 +165,8 @@ class SMB2Message:
|
|||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
protocol, struct_size, self.credit_charge, self.status, \
|
protocol, struct_size, self.credit_charge, self.status, \
|
||||||
self.command, self.credit_re, self.flags = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])
|
self.command, self.credit_response, \
|
||||||
|
self.flags = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])
|
||||||
|
|
||||||
if protocol != '\xFESMB':
|
if protocol != '\xFESMB':
|
||||||
raise ProtocolError('Invalid 4-byte SMB2 protocol field', buf)
|
raise ProtocolError('Invalid 4-byte SMB2 protocol field', buf)
|
||||||
@@ -192,6 +249,53 @@ class Structure:
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class SMB2NegotiateRequest(Structure):
|
||||||
|
"""
|
||||||
|
2.2.3 SMB2 NEGOTIATE Request
|
||||||
|
The SMB2 NEGOTIATE Request packet is used by the client to notify the server what dialects of the SMB 2 Protocol
|
||||||
|
the client understands. This request is composed of an SMB2 header, as specified in section 2.2.1,
|
||||||
|
followed by this request structure:
|
||||||
|
|
||||||
|
SMB2 Negotiate Request Packet structure:
|
||||||
|
StructureSize (2 bytes)
|
||||||
|
DialectCount (2 bytes)
|
||||||
|
SecurityMode (2 bytes)
|
||||||
|
Reserved (2 bytes)
|
||||||
|
Capabilities (4 bytes)
|
||||||
|
ClientGuid (16 bytes)
|
||||||
|
ClientStartTime (8 bytes):
|
||||||
|
ClientStartTime (8 bytes):
|
||||||
|
Dialects (variable): An array of one or more 16-bit integers
|
||||||
|
|
||||||
|
References:
|
||||||
|
===========
|
||||||
|
- [MS-SMB2]: 2.2.3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
STRUCTURE_FORMAT = "<HHHHI16sQHH"
|
||||||
|
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||||
|
|
||||||
|
def initMessage(self, message):
|
||||||
|
Structure.initMessage(self, message)
|
||||||
|
message.command = SMB2_COM_NEGOTIATE
|
||||||
|
|
||||||
|
def prepare(self, message):
|
||||||
|
# TODO! Do we need to save the GUID and present it later in other requests?
|
||||||
|
# The SMB docs don't exactly explain what the guid is for
|
||||||
|
message.data = struct.pack(self.STRUCTURE_FORMAT,
|
||||||
|
36, # Structure size. Must be 36 as mandated by [MS-SMB2] 2.2.3
|
||||||
|
2, # DialectCount
|
||||||
|
0x01, # Security mode
|
||||||
|
0, # Reserved
|
||||||
|
0x00, # Capabilities
|
||||||
|
uuid.uuid4().bytes, # Client GUID
|
||||||
|
0, # Client start time
|
||||||
|
SMB2_DIALECT_2,
|
||||||
|
SMB2_DIALECT_21)
|
||||||
|
|
||||||
|
|
||||||
class SMB2NegotiateResponse(Structure):
|
class SMB2NegotiateResponse(Structure):
|
||||||
"""
|
"""
|
||||||
Contains information on the SMB2_NEGOTIATE response from server
|
Contains information on the SMB2_NEGOTIATE response from server
|
||||||
@@ -226,6 +330,7 @@ class SMB2NegotiateResponse(Structure):
|
|||||||
self.server_start_time = convertFILETIMEtoEpoch(self.server_start_time)
|
self.server_start_time = convertFILETIMEtoEpoch(self.server_start_time)
|
||||||
self.system_time = convertFILETIMEtoEpoch(self.system_time)
|
self.system_time = convertFILETIMEtoEpoch(self.system_time)
|
||||||
self.security_blob = message.raw_data[security_buf_offset:security_buf_offset+security_buf_len]
|
self.security_blob = message.raw_data[security_buf_offset:security_buf_offset+security_buf_len]
|
||||||
|
message.conn.smb2_dialect = self.dialect_revision
|
||||||
|
|
||||||
|
|
||||||
class SMB2SessionSetupRequest(Structure):
|
class SMB2SessionSetupRequest(Structure):
|
||||||
@@ -273,6 +378,14 @@ class SMB2SessionSetupResponse(Structure):
|
|||||||
STRUCTURE_FORMAT = "<HHHH"
|
STRUCTURE_FORMAT = "<HHHH"
|
||||||
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isGuestSession(self):
|
||||||
|
return (self.session_flags & 0x0001) > 0 # SMB2_SESSION_FLAG_IS_GUEST
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isAnonymousSession(self):
|
||||||
|
return (self.session_flags & 0x0002) > 0 # SMB2_SESSION_FLAG_IS_NULL
|
||||||
|
|
||||||
def decode(self, message):
|
def decode(self, message):
|
||||||
assert message.command == SMB2_COM_SESSION_SETUP
|
assert message.command == SMB2_COM_SESSION_SETUP
|
||||||
|
|
||||||
@@ -468,6 +581,13 @@ class SMB2WriteRequest(Structure):
|
|||||||
0, # WriteChannelInfoLength
|
0, # WriteChannelInfoLength
|
||||||
self.flags) + self.data
|
self.flags) + self.data
|
||||||
|
|
||||||
|
# MS-SMB2 3.2.4.7
|
||||||
|
# If a client requests writing to a file, Connection.Dialect is not "2.0.2", and if
|
||||||
|
# Connection.SupportsMultiCredit is TRUE, the CreditCharge field in the SMB2 header MUST be set
|
||||||
|
# to ( 1 + (Length - 1) / 65536 )
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int(1 + (len(self.data) -1) / 65536)
|
||||||
|
|
||||||
|
|
||||||
class SMB2WriteResponse(Structure):
|
class SMB2WriteResponse(Structure):
|
||||||
"""
|
"""
|
||||||
@@ -526,6 +646,13 @@ class SMB2ReadRequest(Structure):
|
|||||||
0 # ReadChannelInfoLength
|
0 # ReadChannelInfoLength
|
||||||
) + '\0'
|
) + '\0'
|
||||||
|
|
||||||
|
# MS-SMB2 3.2.4.6
|
||||||
|
# If a client requests reading from a file, Connection.Dialect is not "2.0.2", and if
|
||||||
|
# Connection.SupportsMultiCredit is TRUE, the CreditCharge field in the SMB2 header MUST be set
|
||||||
|
# to ( 1 + (Length - 1) / 65536 )
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int(1 + (self.read_len -1) / 65536)
|
||||||
|
|
||||||
|
|
||||||
class SMB2ReadResponse(Structure):
|
class SMB2ReadResponse(Structure):
|
||||||
"""
|
"""
|
||||||
@@ -582,6 +709,11 @@ class SMB2IoctlRequest(Structure):
|
|||||||
0 # Reserved
|
0 # Reserved
|
||||||
) + self.in_data
|
) + self.in_data
|
||||||
|
|
||||||
|
# If Connection.SupportsMultiCredit is TRUE, the CreditCharge field in the SMB2 header
|
||||||
|
# SHOULD be set to (max(InputCount, MaxOutputResponse) - 1) / 65536 + 1
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int((max(len(self.in_data), self.max_out_size) - 1) / 65536 + 1)
|
||||||
|
|
||||||
|
|
||||||
class SMB2IoctlResponse(Structure):
|
class SMB2IoctlResponse(Structure):
|
||||||
"""
|
"""
|
||||||
@@ -690,6 +822,12 @@ class SMB2QueryDirectoryRequest(Structure):
|
|||||||
len(self.filename)*2,
|
len(self.filename)*2,
|
||||||
self.output_buf_len) + self.filename.encode('UTF-16LE')
|
self.output_buf_len) + self.filename.encode('UTF-16LE')
|
||||||
|
|
||||||
|
# MS-SMB2 3.2.4.17
|
||||||
|
# If Connection.Dialect is not "2.0.2" and if Connection.SupportsMultiCredit is TRUE, the
|
||||||
|
# CreditCharge field in the SMB2 header MUST be set to ( 1 + (OutputBufferLength - 1) / 65536 )
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int(1 + (self.output_buf_len -1) / 65536)
|
||||||
|
|
||||||
|
|
||||||
class SMB2QueryDirectoryResponse(Structure):
|
class SMB2QueryDirectoryResponse(Structure):
|
||||||
"""
|
"""
|
||||||
@@ -752,6 +890,12 @@ class SMB2QueryInfoRequest(Structure):
|
|||||||
self.fid # FileId
|
self.fid # FileId
|
||||||
) + self.input_buf
|
) + self.input_buf
|
||||||
|
|
||||||
|
# MS-SMB2 3.2.4.17
|
||||||
|
# If Connection.Dialect is not "2.0.2" and if Connection.SupportsMultiCredit is TRUE, the
|
||||||
|
# CreditCharge field in the SMB2 header MUST be set to ( 1 + (OutputBufferLength - 1) / 65536 )
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int(1 + ((self.output_buf_len + len(self.input_buf)) -1) / 65536)
|
||||||
|
|
||||||
|
|
||||||
class SMB2QueryInfoResponse(Structure):
|
class SMB2QueryInfoResponse(Structure):
|
||||||
"""
|
"""
|
||||||
@@ -810,6 +954,12 @@ class SMB2SetInfoRequest(Structure):
|
|||||||
self.fid # FileId
|
self.fid # FileId
|
||||||
) + self.data
|
) + self.data
|
||||||
|
|
||||||
|
# MS-SMB2 3.2.4.17
|
||||||
|
# If Connection.Dialect is not "2.0.2" and if Connection.SupportsMultiCredit is TRUE, the
|
||||||
|
# CreditCharge field in the SMB2 header MUST be set to ( 1 + (OutputBufferLength - 1) / 65536 )
|
||||||
|
if message.conn.smb2_dialect != SMB2_DIALECT_2 and message.conn.cap_multi_credit:
|
||||||
|
message.credit_charge = int(1 + (len(self.data) -1) / 65536)
|
||||||
|
|
||||||
class SMB2SetInfoResponse(Structure):
|
class SMB2SetInfoResponse(Structure):
|
||||||
"""
|
"""
|
||||||
References:
|
References:
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ FILE_APPEND_DATA = 0x04
|
|||||||
FILE_READ_EA = 0x08
|
FILE_READ_EA = 0x08
|
||||||
FILE_WRITE_EA = 0x10
|
FILE_WRITE_EA = 0x10
|
||||||
FILE_EXECUTE = 0x20
|
FILE_EXECUTE = 0x20
|
||||||
|
FILE_DELETE_CHILD = 0x40
|
||||||
FILE_READ_ATTRIBUTES = 0x80
|
FILE_READ_ATTRIBUTES = 0x80
|
||||||
FILE_WRITE_ATTRIBUTES = 0x0100
|
FILE_WRITE_ATTRIBUTES = 0x0100
|
||||||
DELETE = 0x010000
|
DELETE = 0x010000
|
||||||
@@ -225,9 +226,13 @@ SMB_FILE_ATTRIBUTE_NORMAL = 0x00
|
|||||||
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
||||||
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||||
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
||||||
SMB_FILE_ATTRIBUTE_VOLUME = 0x08
|
SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations
|
||||||
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||||
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
||||||
|
# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files for
|
||||||
|
# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs.
|
||||||
|
SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000
|
||||||
|
# Do not use the following values for listPath() operations as they are not supported for SMB2
|
||||||
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
||||||
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
||||||
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
||||||
@@ -237,3 +242,16 @@ SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
|
|||||||
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
||||||
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
||||||
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
||||||
|
|
||||||
|
# Bitmask for security information fields, specified as
|
||||||
|
# AdditionalInformation in SMB2
|
||||||
|
# [MS-SMB]: 2.2.7.4
|
||||||
|
# [MS-SMB2]: 2.2.37
|
||||||
|
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||||
|
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||||
|
DACL_SECURITY_INFORMATION = 0x00000004
|
||||||
|
SACL_SECURITY_INFORMATION = 0x00000008
|
||||||
|
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||||
|
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||||
|
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||||
|
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import binascii
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import time
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
|
import os, sys, struct, types, logging, binascii, time
|
||||||
|
from StringIO import StringIO
|
||||||
from smb_constants import *
|
from smb_constants import *
|
||||||
|
|
||||||
|
|
||||||
# Set to True if you want to enable support for extended security. Required for Windows Vista and later
|
# Set to True if you want to enable support for extended security. Required for Windows Vista and later
|
||||||
SUPPORT_EXTENDED_SECURITY = True
|
SUPPORT_EXTENDED_SECURITY = True
|
||||||
|
|
||||||
# Set to True if you want to enable SMB2 protocol.
|
# Set to True if you want to enable SMB2 protocol.
|
||||||
SUPPORT_SMB2 = True
|
SUPPORT_SMB2 = True
|
||||||
|
|
||||||
|
# Set to True if you want to enable SMB2.1 and above protocol.
|
||||||
|
SUPPORT_SMB2x = True
|
||||||
|
|
||||||
# Supported dialects
|
# Supported dialects
|
||||||
DIALECTS = [ ]
|
NT_LAN_MANAGER_DIALECT = 0 # 'NT LM 0.12' is always the first element in the dialect list and must always be included (MS-SMB 2.2.4.5.1)
|
||||||
for i, ( name, dialect ) in enumerate([ ( 'NT_LAN_MANAGER_DIALECT', 'NT LM 0.12' ), ]):
|
|
||||||
DIALECTS.append(dialect)
|
|
||||||
globals()[name] = i
|
|
||||||
|
|
||||||
DIALECTS2 = [ ]
|
|
||||||
for i, ( name, dialect ) in enumerate([ ( 'SMB2_DIALECT', 'SMB 2.002' ) ]):
|
|
||||||
DIALECTS2.append(dialect)
|
|
||||||
globals()[name] = i + len(DIALECTS)
|
|
||||||
|
|
||||||
|
# Return the list of support SMB dialects based on the SUPPORT_x constants
|
||||||
|
def init_dialects_list():
|
||||||
|
dialects = [ 'NT LM 0.12' ]
|
||||||
|
if SUPPORT_SMB2:
|
||||||
|
dialects.append('SMB 2.002')
|
||||||
|
if SUPPORT_SMB2x:
|
||||||
|
dialects.append('SMB 2.???')
|
||||||
|
return dialects
|
||||||
|
|
||||||
class UnsupportedFeature(Exception):
|
class UnsupportedFeature(Exception):
|
||||||
"""
|
"""
|
||||||
@@ -111,8 +111,9 @@ class SMBMessage:
|
|||||||
log = logging.getLogger('SMB.SMBMessage')
|
log = logging.getLogger('SMB.SMBMessage')
|
||||||
protocol = 1
|
protocol = 1
|
||||||
|
|
||||||
def __init__(self, payload = None):
|
def __init__(self, conn, payload = None):
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.conn = conn
|
||||||
if payload:
|
if payload:
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.payload.initMessage(self)
|
self.payload.initMessage(self)
|
||||||
@@ -293,10 +294,7 @@ class ComNegotiateRequest(Payload):
|
|||||||
def prepare(self, message):
|
def prepare(self, message):
|
||||||
assert message.payload == self
|
assert message.payload == self
|
||||||
message.parameters_data = ''
|
message.parameters_data = ''
|
||||||
if SUPPORT_SMB2:
|
message.data = ''.join(map(lambda s: '\x02'+s+'\x00', init_dialects_list()))
|
||||||
message.data = ''.join(map(lambda s: '\x02'+s+'\x00', DIALECTS + DIALECTS2))
|
|
||||||
else:
|
|
||||||
message.data = ''.join(map(lambda s: '\x02'+s+'\x00', DIALECTS))
|
|
||||||
|
|
||||||
|
|
||||||
class ComNegotiateResponse(Payload):
|
class ComNegotiateResponse(Payload):
|
||||||
@@ -1283,7 +1281,7 @@ class ComEchoRequest(Payload):
|
|||||||
- [MS-CIFS]: 2.2.4.39.1
|
- [MS-CIFS]: 2.2.4.39.1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, echo_data = '', echo_count = 1):
|
def __init__(self, echo_data = b'', echo_count = 1):
|
||||||
self.echo_count = echo_count
|
self.echo_count = echo_count
|
||||||
self.echo_data = echo_data
|
self.echo_data = echo_data
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
__author__ = 'Thomas Dixon'
|
__author__ = 'Thomas Dixon'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
|
|
||||||
import copy
|
import copy, struct, sys
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
|
|
||||||
digest_size = 32
|
digest_size = 32
|
||||||
blocksize = 1
|
blocksize = 1
|
||||||
|
|||||||
@@ -1106,6 +1106,8 @@ def play_torrent(item, xlistitem, mediaurl):
|
|||||||
url_stat = False
|
url_stat = False
|
||||||
torrents_path = ''
|
torrents_path = ''
|
||||||
videolibrary_path = config.get_videolibrary_path() #Calculamos el path absoluto a partir de la Videoteca
|
videolibrary_path = config.get_videolibrary_path() #Calculamos el path absoluto a partir de la Videoteca
|
||||||
|
if videolibrary_path.lower().startswith("smb://"): #Si es una conexión SMB, usamos userdata local
|
||||||
|
videolibrary_path = config.get_data_path() #Calculamos el path absoluto a partir de Userdata
|
||||||
if not filetools.exists(videolibrary_path): #Si no existe el path, pasamos al modo clásico
|
if not filetools.exists(videolibrary_path): #Si no existe el path, pasamos al modo clásico
|
||||||
videolibrary_path = False
|
videolibrary_path = False
|
||||||
else:
|
else:
|
||||||
@@ -1139,7 +1141,7 @@ def play_torrent(item, xlistitem, mediaurl):
|
|||||||
folder = movies #películas
|
folder = movies #películas
|
||||||
else:
|
else:
|
||||||
folder = series #o series
|
folder = series #o series
|
||||||
item.url = filetools.join(videolibrary_path, folder, item.url) #dirección del .torrent local en la Videoteca
|
item.url = filetools.join(config.get_videolibrary_path(), folder, item.url) #dirección del .torrent local en la Videoteca
|
||||||
if filetools.copy(item.url, torrents_path, silent=True): #se copia a la carpeta generíca para evitar problemas de encode
|
if filetools.copy(item.url, torrents_path, silent=True): #se copia a la carpeta generíca para evitar problemas de encode
|
||||||
item.url = torrents_path
|
item.url = torrents_path
|
||||||
if "torrentin" in torrent_options[seleccion][1]: #Si es Torrentin, hay que añadir un prefijo
|
if "torrentin" in torrent_options[seleccion][1]: #Si es Torrentin, hay que añadir un prefijo
|
||||||
|
|||||||
@@ -329,6 +329,7 @@ def mark_content_as_watched_on_alfa(path):
|
|||||||
from channels import videolibrary
|
from channels import videolibrary
|
||||||
from core import videolibrarytools
|
from core import videolibrarytools
|
||||||
from core import scrapertools
|
from core import scrapertools
|
||||||
|
from core import filetools
|
||||||
import re
|
import re
|
||||||
"""
|
"""
|
||||||
marca toda la serie o película como vista o no vista en la Videoteca de Alfa basado en su estado en la Videoteca de Kodi
|
marca toda la serie o película como vista o no vista en la Videoteca de Alfa basado en su estado en la Videoteca de Kodi
|
||||||
@@ -375,6 +376,7 @@ def mark_content_as_watched_on_alfa(path):
|
|||||||
nfo_name = scrapertools.find_single_match(path2, '\]\/(.*?)$') #Construyo el nombre del .nfo
|
nfo_name = scrapertools.find_single_match(path2, '\]\/(.*?)$') #Construyo el nombre del .nfo
|
||||||
path1 = path1.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
path1 = path1.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
||||||
path2 = path2.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
path2 = path2.replace(nfo_name, '') #para la SQL solo necesito la carpeta
|
||||||
|
path2 = filetools.remove_smb_credential(path2) #Si el archivo está en un servidor SMB, quiamos las credenciales
|
||||||
|
|
||||||
#Ejecutmos la sentencia SQL
|
#Ejecutmos la sentencia SQL
|
||||||
sql = 'select strFileName, playCount from %s where (strPath like "%s" or strPath like "%s")' % (contentType, path1, path2)
|
sql = 'select strFileName, playCount from %s where (strPath like "%s" or strPath like "%s")' % (contentType, path1, path2)
|
||||||
@@ -486,7 +488,8 @@ def update(folder_content=config.get_setting("folder_tvshows"), folder=""):
|
|||||||
else:
|
else:
|
||||||
update_path = filetools.join(videolibrarypath, folder_content, folder) + "/"
|
update_path = filetools.join(videolibrarypath, folder_content, folder) + "/"
|
||||||
|
|
||||||
payload["params"] = {"directory": update_path}
|
if not update_path.startswith("smb://"):
|
||||||
|
payload["params"] = {"directory": update_path}
|
||||||
|
|
||||||
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
|
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(500)
|
||||||
|
|||||||
Reference in New Issue
Block a user