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:
Alfa
2018-11-28 11:24:40 -05:00
committed by GitHub
40 changed files with 9936 additions and 8741 deletions
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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",
+48 -14
View File
@@ -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",
+113 -55
View File
@@ -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&ntilde;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&ntilde;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('&#038;', '&').replace('.io/', sufix).replace('.com/', sufix) subtitle = scrapertools.find_single_match(data, patron).replace('&#038;', '&').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('&#038;', '&').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('&#038;', '&').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",
+73 -26
View File
@@ -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}|(<!--.*?-->)|&nbsp;", "", 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}|(<!--.*?-->)|&nbsp;", "", 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",
+77 -19
View File
@@ -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
+21
View File
@@ -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
+1 -1
View File
@@ -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:
+27 -13
View File
@@ -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
+5 -3
View File
@@ -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)
+4 -2
View File
@@ -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
+5 -6
View File
@@ -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)