diff --git a/videoclub/videoclub_buscador.py b/videoclub/videoclub_buscador.py index 42f3f57..4764f1c 100644 --- a/videoclub/videoclub_buscador.py +++ b/videoclub/videoclub_buscador.py @@ -51,7 +51,8 @@ CALIDAD_MALA = re.compile( r"(CAMRip|TS\.|HDTS|Telesync|KinoRip|Screener|HDCAM|HC\b|r5|360p|240p)", re.IGNORECASE) CASTELLANO = re.compile( - r"(Esp|Castellano|Span|Spanish|dual|lat|\bES\b|audio)", re.IGNORECASE) + r"(Esp[añ]ol|Castellano|Spanish|Latino|DualLat|\bdual\b|\bESS?\b|\bESP\b)", + re.IGNORECASE) INGLES = re.compile(r"(Eng|English|Ingl[eé]s|\bEN\b)", re.IGNORECASE) OTRO_IDIOMA = re.compile( r"(Ita\b|MIRCrew|RO\s?EN|South Wind|Silver Zero|Francais|Deutsch|Portugu[eê]s)", @@ -193,7 +194,7 @@ def buscar_peliculas(termino, items=None): continue if not it["buena_calidad"]: continue - if not (it.get("castellano") or bool(INGLES.search(it.get("name", "")))): + if not it.get("castellano"): continue clean = (it.get("clean") or "").lower() name = (it.get("name") or "").lower() @@ -294,11 +295,13 @@ def formatear_series(grupos): etiqueta = f"S{temp:02d}E{cap:02d}" else: etiqueta = it["clean"] - lineas.append(f" {num_global}. {etiqueta} — {resto}") + idioma = " · cast." if it.get("castellano") else "" + lineas.append(f" {num_global}. {etiqueta} — {resto}{idioma}") num_global += 1 lineas.append("") lineas.append("Responde /torrents NUMERO de episodio para ver opciones y descargar.") + lineas.append("O /temporada N (temporada completa) o /bajar X-Y (rango).") return trocear_texto(lineas) diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py index e685f93..aa0c2ae 100644 --- a/vigilancia/pi_bot.py +++ b/vigilancia/pi_bot.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +import re import subprocess import sys import threading @@ -800,8 +801,11 @@ def procesar(texto, chat_id): n = _int_arg(texto) procesar_torrents(n, chat_id) elif t.startswith("bajar ") or t.startswith("/bajar "): + spec = texto.split(" ", 1)[1].strip() if " " in texto else "" + procesar_bajar(spec, chat_id) + elif t.startswith("temporada ") or t.startswith("/temporada "): n = _int_arg(texto) - procesar_bajar(n, chat_id) + procesar_temporada(n, chat_id) elif comando == "aspiradora": procesar_aspiradora(argumento, chat_id) elif comando in ("menu", "menú", "inicio", "start") and not argumento: @@ -823,8 +827,8 @@ def procesar(texto, chat_id): " ✏️ Ej: /genero → 1 (pelis) → 12 (Horror)\n" "3. torrents <n> — torrents del resultado n\n" " ✏️ Ej: /torrents 1\n" - "4. bajar <n> — descargar al NAS el torrent n\n" - " ✏️ Ej: /bajar 1\n" +"4. bajar <n> — descargar al NAS el torrent n\n" + " ✏️ Ej: /bajar 1, /bajar 2-5, /temporada 1\n" "5. pelicula <nombre> — buscar película (catálogo)\n" " ✏️ Ej: /pelicula ant-man\n" "6. serie <nombre> — buscar serie (catálogo)\n" @@ -1274,51 +1278,148 @@ def procesar_torrents(n, chat_id): log(f"TORRENTS '{r['name']}': {len(torrents)} torrents") -def procesar_bajar(n, chat_id): - """Descarga el torrent numero n al NAS (Download Station via magnet).""" +def procesar_bajar(spec, chat_id): + """Descarga al NAS: numero, rango (2-5) o episodios directos.""" estado = ultimos_torrents.get(chat_id) - if not estado or n is None or n < 1 or n > len(estado["torrents"]): - enviar("Primero usa /busca, /pelicula, /serie, " - "/novedades o /genero, luego " - "/torrents <numero>.", chat_id) - return - t = estado["torrents"][n - 1] - if t.get("enlace") and not t.get("info_hash"): - # Enlace directo de catalogo: al NAS sin magnet. - try: - import nas_dl - ok = nas_dl.add_torrent(t["enlace"]) - except Exception as e: - log(f"bajar error: {e}") - enviar(f"❌ Error al anadir al NAS: {e}", chat_id) - return - if ok: - enviar(f"📥 Añadido al NAS: {t.get('filename') or estado['titulo']}.", + titulos = ultima_titulos.get(chat_id) or {} + if estado and estado.get("torrents"): + idx = _parse_indices(spec, len(estado["torrents"])) + if not idx: + enviar("Uso: /bajar <numero> o /bajar X-Y.", chat_id) - else: - enviar("❌ No se pudo añadir al NAS.", chat_id) + return + buenas, malas = [], [] + for i in idx: + uri, etiqueta = _bajar_torrent(estado["torrents"][i - 1], + estado.get("titulo") or "") + if not uri: + malas.append(etiqueta) + continue + ok, _ = _bajar_uri(uri, etiqueta) + (buenas if ok else malas).append(etiqueta) + log(f"BAJAR '{estado.get('titulo')}' torrent {i}: ok={ok}") + partes = [] + if buenas: + partes.append(f"📥 Añadidos al NAS ({len(buenas)}):\n" + + "\n".join(f"• {b}" for b in buenas)) + if malas: + partes.append("❌ Fallaron:\n" + + "\n".join(f"• {m}" for m in malas)) + enviar("\n".join(partes) or "❌ Nada que descargar.", chat_id) return + if titulos.get("origen") == "catalogo" and titulos.get("results"): + resultados = titulos["results"] + idx = _parse_indices(spec, len(resultados)) + if not idx: + enviar("Uso: /bajar <numero>, /bajar X-Y o " + "/temporada N.", chat_id) + return + buenas, malas = [], [] + for i in idx: + it = resultados[i - 1] + nombre = it.get("clean") or it.get("name") or f"episodio {i}" + if not it.get("enlace"): + malas.append(f"{nombre} (sin enlace)") + continue + ok, _ = _bajar_uri(it["enlace"], nombre) + (buenas if ok else malas).append(nombre) + log(f"BAJAR catalogo '{nombre}': ok={ok}") + partes = [] + if buenas: + partes.append(f"📥 Añadidos al NAS ({len(buenas)}):\n" + + "\n".join(f"• {b}" for b in buenas)) + if malas: + partes.append("❌ Fallaron:\n" + + "\n".join(f"• {m}" for m in malas)) + enviar("\n".join(partes) or "❌ Nada que descargar.", chat_id) + return + enviar("Primero usa /busca, /pelicula, /serie, " + "/novedades o /genero, luego " + "/torrents <numero>.", chat_id) + + +def _parse_indices(spec, total, maximo=10): + """'3' -> [3]; '2-5' o '2:5' -> [2..5]. Recorta al total (max 10).""" + spec = str(spec or "").strip() + m = re.match(r"(\d+)\s*[-:]\s*(\d+)$", spec) + if m: + a, b = int(m.group(1)), int(m.group(2)) + if a > b: + a, b = b, a + elif spec.isdigit(): + a = b = int(spec) + else: + return [] + a = max(a, 1) + b = min(b, total) + if a > b: + return [] + return list(range(a, min(b, a + maximo - 1) + 1)) + + +def _bajar_uri(uri, etiqueta): + """Anade un magnet/enlace al NAS. Devuelve (ok, etiqueta).""" + try: + import nas_dl + ok = nas_dl.add_torrent(uri) + except Exception as e: + log(f"bajar error: {e}") + return False, f"{etiqueta} (error: {e})" + return bool(ok), etiqueta + + +def _bajar_torrent(t, titulo): + """Resuelve un item de ultimos_torrents a (uri, etiqueta).""" + if t.get("enlace") and not t.get("info_hash"): + return t["enlace"], t.get("filename") or titulo try: gol = _import_global_buscar() except Exception as e: log(f"global_buscar import error: {e}") - enviar("❌ No se pudo cargar el modulo", chat_id) + return None, "sin modulo" + tam = f"{t['size']:.1f}GB" if t.get("size") else "?" + return gol.magnet_de(t), f"{t.get('filename') or titulo} ({t.get('calidad')}, {tam})" + + +def procesar_temporada(n, chat_id): + """Descarga la temporada N completa de la ultima serie del catalogo.""" + titulos = ultima_titulos.get(chat_id) or {} + if titulos.get("origen") != "catalogo" or not titulos.get("results"): + enviar("Primero busca una /serie y luego " + "/temporada <N>.", chat_id) + return + if n is None or n < 1: + enviar("Uso: /temporada <N>.", chat_id) return - magnet = gol.magnet_de(t) - log(f"BAJAR '{estado['titulo']}' torrent {n}: {magnet[:60]}...") try: - import nas_dl - ok = nas_dl.add_torrent(magnet) + sys.path.insert(0, os.path.join(BASE, "..", "videoclub")) + import videoclub_buscador as vcb except Exception as e: - log(f"bajar error: {e}") - enviar(f"❌ Error al anadir al NAS: {e}", chat_id) + log(f"videoclub_buscador import error: {e}") + enviar("❌ No se pudo cargar el modulo videoclub", chat_id) return - if ok: - tamaño = f"{t['size']:.1f}GB" if t.get("size") else "?" - enviar(f"📥 Añadido al NAS: {t['filename'] or estado['titulo']} " - f"({t['calidad']}, {tamaño}).", chat_id) - else: - enviar("❌ No se pudo añadir al NAS.", chat_id) + capitulos = [] + for it in titulos["results"]: + try: + temp, _cap = vcb.parsear_episodio(it.get("name") or "") + except Exception: + temp = 0 + if temp == n and it.get("enlace"): + capitulos.append(it) + if not capitulos: + enviar(f"❌ No hay capitulos con enlace en la temporada {n}.", + chat_id) + return + buenas, malas = [], [] + for it in capitulos[:25]: + nombre = it.get("clean") or it.get("name") or "capitulo" + ok, _ = _bajar_uri(it["enlace"], nombre) + (buenas if ok else malas).append(nombre) + partes = [f"📥 Temporada {n} al NAS ({len(buenas)} capitulos)."] + if malas: + partes.append("❌ Fallaron: " + ", ".join(malas)) + enviar("\n".join(partes), chat_id) + log(f"TEMPORADA {n}: {len(buenas)} ok, {len(malas)} fallo") def monitor_temperatura():