diff --git a/vigilancia/nas_dl.py b/vigilancia/nas_dl.py
index 6db70a2..1a3863f 100644
--- a/vigilancia/nas_dl.py
+++ b/vigilancia/nas_dl.py
@@ -139,6 +139,70 @@ def add_torrent(uri, session=None):
return False
+def human_size(n):
+ """Formatea bytes a una unidad legible."""
+ n = float(n or 0)
+ for u in ("B", "KB", "MB", "GB", "TB"):
+ if n < 1024:
+ return f"{n:.1f}{u}"
+ n /= 1024
+ return f"{n:.1f}PB"
+
+
+def listar_descargas(hablar_solo_activas=True, session=None):
+ """Devuelve (lineas_texto, activas). Lista las tareas de Download Station
+ con estado, porcentaje de progreso, tamaño descargado y velocidad.
+ `progress` se calcula con downloaded_pieces/total_pieces (fiable en NAS)."""
+ if not session:
+ session = login()
+ if not session:
+ return ["❌ No se pudo autenticar con Download Station."], []
+ try:
+ r = _api("/webapi/DownloadStation/task.cgi", {
+ "api": "SYNO.DownloadStation.Task",
+ "version": "3",
+ "method": "list",
+ "additional": "detail,transfer",
+ }, session=session)
+ if not r.get("success"):
+ return [f"❌ Error al listar: {r.get('error')}"], []
+ except Exception as e:
+ return [f"❌ Error al consultar el NAS: {e}"], []
+ tareas = r.get("data", {}).get("tasks", []) or []
+ lineas, activas, resto = [], [], []
+ for t in tareas:
+ titulo = t.get("title") or ""
+ estado = t.get("status") or "?"
+ detail = (t.get("additional") or {}).get("detail") or {}
+ transfer = (t.get("additional") or {}).get("transfer") or {}
+ total_pieces = detail.get("total_pieces") or 0
+ done_pieces = transfer.get("downloaded_pieces") or 0
+ pct = (done_pieces * 100.0 / total_pieces) if total_pieces else 0
+ size = transfer.get("size_downloaded") or 0
+ speed = transfer.get("speed_download") or 0
+ if estado == "downloading":
+ activas.append(f"▶️ {pct:.1f}% {human_size(size)} ⬇️{speed // 1024} KB/s\n"
+ f" {titulo}")
+ else:
+ resto.append((estado, pct, size, titulo))
+ if activas:
+ lineas.append("📥 DESCARGANDO")
+ lineas.extend(activas)
+ lineas.append("")
+ if not hablar_solo_activas:
+ lineas.append("📁 Otras tareas:")
+ for estado, pct, size, titulo in resto:
+ icono = {"finished": "✅", "error": "❌", "paused": "⏸️",
+ "seeding": "🌱"}.get(estado, "•")
+ if estado == "finished":
+ lineas.append(f"{icono} {titulo}")
+ else:
+ lineas.append(f"{icono} {estado} ({pct:.0f}%) — {titulo}")
+ if not activas:
+ lineas.append("No hay torrents descargándose ahora mismo.")
+ return lineas, activas
+
+
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py
index a7085a4..5b22e87 100644
--- a/vigilancia/pi_bot.py
+++ b/vigilancia/pi_bot.py
@@ -80,17 +80,20 @@ def estado():
def enviar(texto, chat_id):
- try:
- r = requests.post(
- f"https://api.telegram.org/bot{TOKEN}/sendMessage",
- data={"chat_id": chat_id, "text": texto, "parse_mode": "HTML"},
- timeout=10,
- )
- d = r.json()
- if d.get("ok"):
- return d["result"].get("message_id")
- except Exception as e:
- print("Error enviando:", e)
+ # Trocea mensajes largos (Telegram limita a ~4096 chars) para no fallar en silencio
+ for i in range(0, len(texto), 4000):
+ bloque = texto[i:i + 4000]
+ try:
+ r = requests.post(
+ f"https://api.telegram.org/bot{TOKEN}/sendMessage",
+ data={"chat_id": chat_id, "text": bloque, "parse_mode": "HTML"},
+ timeout=10,
+ )
+ d = r.json()
+ if d.get("ok"):
+ return d["result"].get("message_id")
+ except Exception as e:
+ print("Error enviando:", e)
return None
@@ -204,6 +207,10 @@ def procesar(texto, chat_id):
else:
ok = mandar_youtube(url)
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
+ elif t in ("descargar", "/descargar"):
+ import nas_dl
+ lineas, _act = nas_dl.listar_descargas(hablar_solo_activas=True)
+ enviar("\n".join(lineas), chat_id)
elif t.startswith("descargar ") or t.startswith("/descargar "):
uri = texto.split(" ", 1)[1].strip()
if not uri:
@@ -253,31 +260,33 @@ def procesar(texto, chat_id):
" ✏️ Ej: /serie breaking bad\n"
"7. novedades — últimas novedades del catálogo\n"
" ✏️ Ej: /novedades\n\n"
- "━━━ 🖥️ NAS / DESCARGAS ━━━\n"
- "8. descargar <enlace> — añade a Download Station\n"
- " ✏️ Ej: /descargar magnet:?xt=urn:btih:...\n\n"
- "━━━ 📺 TV ━━━\n"
- "9. tv <mensaje> — mensaje rápido a la tele\n"
- " ✏️ Ej: /tv la cena está lista\n"
- "10. alarma <mensaje> — secuencia completa (sirena x3)\n"
- " ✏️ Ej: /alarma vienen a por vosotros\n"
- "11. youtube <URL> — reproduce YouTube en la TV\n"
- " ✏️ Ej: /youtube https://youtu.be/xxxx\n\n"
- "━━━ 🛠️ SISTEMA ━━━\n"
- "12. estado — estado de la Pi\n"
- " ✏️ Ej: /estado\n"
- "13. temperatura — temperatura de la Pi\n"
- " ✏️ Ej: /temperatura\n"
- "14. panorama — fotos de las cámaras\n"
- " ✏️ Ej: /panorama\n"
- "15. limpieza — limpia procesos atascados\n"
- " ✏️ Ej: /limpieza\n"
- "16. servicios — estado de los servicios\n"
- " ✏️ Ej: /servicios\n"
- "17. reboot — reinicia la Pi\n"
- " ✏️ Ej: /reboot → confirmar con /confirmar-reboot\n"
- "18. ayuda — este índice\n"
- " ✏️ Ej: /ayuda",
+ "━━━ 🖥️ NAS / DESCARGAS ━━━\n"
+ "8. descargar — torrents descargándose y su progreso\n"
+ " ✏️ Ej: /descargar\n"
+ "9. descargar <enlace> — añade a Download Station\n"
+ " ✏️ Ej: /descargar magnet:?xt=urn:btih:...\n\n"
+ "━━━ 📺 TV ━━━\n"
+ "10. tv <mensaje> — mensaje rápido a la tele\n"
+ " ✏️ Ej: /tv la cena está lista\n"
+ "11. alarma <mensaje> — secuencia completa (sirena x3)\n"
+ " ✏️ Ej: /alarma vienen a por vosotros\n"
+ "12. youtube <URL> — reproduce YouTube en la TV\n"
+ " ✏️ Ej: /youtube https://youtu.be/xxxx\n\n"
+ "━━━ 🛠️ SISTEMA ━━━\n"
+ "13. estado — estado de la Pi\n"
+ " ✏️ Ej: /estado\n"
+ "14. temperatura — temperatura de la Pi\n"
+ " ✏️ Ej: /temperatura\n"
+ "15. panorama — fotos de las cámaras\n"
+ " ✏️ Ej: /panorama\n"
+ "16. limpieza — limpia procesos atascados\n"
+ " ✏️ Ej: /limpieza\n"
+ "17. servicios — estado de los servicios\n"
+ " ✏️ Ej: /servicios\n"
+ "18. reboot — reinicia la Pi\n"
+ " ✏️ Ej: /reboot → confirmar con /confirmar-reboot\n"
+ "19. ayuda — este índice\n"
+ " ✏️ Ej: /ayuda",
chat_id)
elif t in ("servicios", "/servicios"):
estado_svc = sh("systemctl is-active pi-bot tv-api autossh-tunel")