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():