03-ago: [videoclub] descarga a NAS Synology via Download Station + comando 'descargar' en pi_bot (deploy en Pi)

This commit is contained in:
juanjo
2026-08-03 23:35:33 +02:00
parent c2caae0ba7
commit f452ece724
5 changed files with 178 additions and 76 deletions

View File

@@ -1,86 +1,27 @@
""" """
nas_dl.py — Anade descargas al NAS Synology via API de Download Station. nas_dl.py — Carga el modulo compartido vigilancia/nas_dl.py
(anade descargas al NAS Synology via API de Download Station).
Login: POST /webapi/auth.cgi (SYNO.API.Auth, version 6, session DownloadStation) Las credenciales NAS se leen del .env de vigilancia/ o, como respaldo,
Crear: POST /webapi/DownloadStation/task.cgi (SYNO.DownloadStation.Task, create) del .env de videoclub/.
La URI puede ser un enlace HTTP directo (p. ej. enlace de RealDebrid),
un magnet o una URL de fichero .torrent.
""" """
import json import importlib.util
import os
import sys import sys
import urllib.parse
import urllib.request
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv BASE = Path(__file__).resolve().parent
VIGILANCIA = BASE.parent / "vigilancia"
REAL = VIGILANCIA / "nas_dl.py"
load_dotenv(Path(__file__).resolve().parent / ".env") if not REAL.exists():
raise ImportError("No se encontro vigilancia/nas_dl.py")
NAS_HOST = os.getenv("NAS_HOST", "192.168.50.31").strip().rstrip("/") _spec = importlib.util.spec_from_file_location("vigilancia_nas_dl", REAL)
NAS_PORT = os.getenv("NAS_PORT", "5000").strip() _vig_nas = importlib.util.module_from_spec(_spec)
NAS_USER = os.getenv("NAS_USER", "").strip() sys.modules["vigilancia_nas_dl"] = _vig_nas
NAS_PASS = os.getenv("NAS_PASS", "").strip() _spec.loader.exec_module(_vig_nas)
NAS_HTTPS = os.getenv("NAS_HTTPS", "0").strip() == "1"
BASE = f"{'https' if NAS_HTTPS else 'http'}://{NAS_HOST}:{NAS_PORT}" add_torrent = _vig_nas.add_torrent
login = _vig_nas.login
__all__ = ["add_torrent", "login"]
def _api(path, params, session=None):
data = urllib.parse.urlencode(params).encode()
headers = {"Content-Type": "application/x-www-form-urlencoded"}
if session:
headers["Cookie"] = session
req = urllib.request.Request(f"{BASE}{path}", data=data, headers=headers)
with urllib.request.urlopen(req, timeout=20) as r:
return json.loads(r.read().decode())
def login():
"""Devuelve la cookie de sesion DownloadStation o None."""
if not NAS_USER or not NAS_PASS:
print("[NAS] Faltan NAS_USER/NAS_PASS en .env")
return None
r = _api("/webapi/auth.cgi", {
"api": "SYNO.API.Auth",
"version": "6",
"method": "login",
"account": NAS_USER,
"passwd": NAS_PASS,
"session": "DownloadStation",
"format": "cookie",
})
if r.get("success"):
# la cookie viene en Set-Cookie; la reconstruimos con el sid devuelto
sid = r.get("data", {}).get("sid")
return f"sid={sid}" if sid else None
print(f"[NAS] Login fallido: {r.get('error')}")
return None
def add_torrent(uri, session=None):
"""Anade una URI (http/magnet/.torrent) a Download Station."""
if not session:
session = login()
if not session:
return False
r = _api("/webapi/DownloadStation/task.cgi", {
"api": "SYNO.DownloadStation.Task",
"version": "3",
"method": "create",
"uri": uri,
}, session=session)
if r.get("success"):
print(f"[NAS] Tarea anadida: {uri[:80]}")
return True
print(f"[NAS] Error al anadir tarea: {r.get('error')}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Uso: python nas_dl.py <uri|magnet|enlace>")
sys.exit(1)
ok = add_torrent(sys.argv[1])
sys.exit(0 if ok else 1)

View File

@@ -71,3 +71,10 @@ VOZ_MENSAJE_CONOCIDO={nombres}, ¿qué estás haciendo? No entres ahí.
# Monkey de VoiceMonkey que apaga la tele tras el anuncio (p.ej. apagar_tele) # Monkey de VoiceMonkey que apaga la tele tras el anuncio (p.ej. apagar_tele)
ALEXA_APAGAR_TV_MONKEY= ALEXA_APAGAR_TV_MONKEY=
# NAS Synology Download Station (comando 'descargar' de pi_bot)
NAS_HOST=192.168.50.31
NAS_PORT=5000
NAS_USER=
NAS_PASS=
NAS_HTTPS=0

34
vigilancia/deploy_pi.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# deploy_pi.sh — Despliegue manual en la Raspberry Pi (minipc).
# Uso en la Pi: bash /home/pi/minipc/vigilancia/deploy_pi.sh
set -e
REPO=/home/pi/minipc
ENV_FILE=$REPO/vigilancia/.env
echo "==> git pull en $REPO"
cd "$REPO"
git pull
echo "==> Comprobar credenciales NAS en $ENV_FILE"
if ! grep -q "^NAS_HOST=" "$ENV_FILE" 2>/dev/null; then
echo "AVISO: falta NAS_HOST/NAS_USER/NAS_PASS en $ENV_FILE"
echo "Anade al final del .env:"
echo " NAS_HOST=192.168.50.31"
echo " NAS_PORT=5000"
echo " NAS_USER=<usuario>"
echo " NAS_PASS=<password>"
echo " NAS_HTTPS=0"
exit 1
fi
echo "==> Reiniciar pi-bot"
sudo systemctl restart pi-bot
sleep 2
systemctl is-active pi-bot
echo "==> Comprobar sintaxis nas_dl.py"
cd "$REPO/vigilancia"
python3 -m py_compile nas_dl.py pi_bot.py && echo "OK sintaxis"
echo "==> Listo. Prueba: envia a @juanjer_casa_bot: descargar <enlace|magnet>"

110
vigilancia/nas_dl.py Normal file
View File

@@ -0,0 +1,110 @@
"""
nas_dl.py — Anade descargas al NAS Synology via API de Download Station.
Usado por pi_bot.py (comando 'descargar') y por videoclub.py.
Login: POST /webapi/auth.cgi (SYNO.API.Auth, version 6, session DownloadStation)
Crear: POST /webapi/DownloadStation/task.cgi (SYNO.DownloadStation.Task, create)
La URI puede ser un enlace HTTP directo (p. ej. enlace de RealDebrid),
un magnet o una URL de fichero .torrent.
"""
import json
import os
import urllib.parse
import urllib.request
# Carga .env del directorio del script si existe (no requiere python-dotenv)
BASE = os.path.dirname(os.path.abspath(__file__))
def _load_env(path):
env = {}
try:
for line in open(path):
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
except Exception:
pass
return env
ENV = _load_env(os.path.join(BASE, ".env"))
# Respaldo: si no hay credenciales NAS aqui, probar el .env de videoclub/
_VIDEOCLUB_ENV = _load_env(os.path.join(BASE, "..", "videoclub", ".env"))
for _k in ("NAS_HOST", "NAS_PORT", "NAS_USER", "NAS_PASS", "NAS_HTTPS"):
if not ENV.get(_k):
ENV[_k] = _VIDEOCLUB_ENV.get(_k, "")
NAS_HOST = os.getenv("NAS_HOST", ENV.get("NAS_HOST", "192.168.50.31")).strip().rstrip("/")
NAS_PORT = os.getenv("NAS_PORT", ENV.get("NAS_PORT", "5000")).strip()
NAS_USER = os.getenv("NAS_USER", ENV.get("NAS_USER", "")).strip()
NAS_PASS = os.getenv("NAS_PASS", ENV.get("NAS_PASS", "")).strip()
NAS_HTTPS = os.getenv("NAS_HTTPS", ENV.get("NAS_HTTPS", "0")).strip() == "1"
BASE_URL = f"{'https' if NAS_HTTPS else 'http'}://{NAS_HOST}:{NAS_PORT}"
def _api(path, params, session=None):
data = urllib.parse.urlencode(params).encode()
headers = {"Content-Type": "application/x-www-form-urlencoded"}
if session:
headers["Cookie"] = session
req = urllib.request.Request(f"{BASE_URL}{path}", data=data, headers=headers)
with urllib.request.urlopen(req, timeout=20) as r:
return json.loads(r.read().decode())
def login():
"""Devuelve la cookie de sesion DownloadStation o None."""
if not NAS_USER or not NAS_PASS:
print("[NAS] Faltan NAS_USER/NAS_PASS en .env")
return None
try:
r = _api("/webapi/auth.cgi", {
"api": "SYNO.API.Auth",
"version": "6",
"method": "login",
"account": NAS_USER,
"passwd": NAS_PASS,
"session": "DownloadStation",
"format": "cookie",
})
if r.get("success"):
sid = r.get("data", {}).get("sid")
return f"sid={sid}" if sid else None
print(f"[NAS] Login fallido: {r.get('error')}")
except Exception as e:
print(f"[NAS] Error de login: {e}")
return None
def add_torrent(uri, session=None):
"""Anade una URI (http/magnet/.torrent) a Download Station."""
if not session:
session = login()
if not session:
return False
try:
r = _api("/webapi/DownloadStation/task.cgi", {
"api": "SYNO.DownloadStation.Task",
"version": "3",
"method": "create",
"uri": uri,
}, session=session)
if r.get("success"):
print(f"[NAS] Tarea anadida: {uri[:80]}")
return True
print(f"[NAS] Error al anadir tarea: {r.get('error')}")
except Exception as e:
print(f"[NAS] Error: {e}")
return False
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Uso: python nas_dl.py <uri|magnet|enlace>")
sys.exit(1)
ok = add_torrent(sys.argv[1])
sys.exit(0 if ok else 1)

View File

@@ -154,6 +154,15 @@ def procesar(texto, chat_id):
else: else:
ok = mandar_youtube(url) ok = mandar_youtube(url)
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id) enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
elif t.startswith("descargar ") or t.startswith("/descargar "):
uri = texto.split(" ", 1)[1].strip()
if not uri:
enviar("Uso: <b>descargar &lt;enlace|magnet|uri&gt;</b> (anade a Download Station del NAS)", chat_id)
else:
import nas_dl
ok = nas_dl.add_torrent(uri)
enviar("📥 Añadido a Download Station del NAS ✅" if ok
else "❌ No se pudo añadir al NAS", chat_id)
elif t in ("ayuda", "/ayuda", "help", "/help"): elif t in ("ayuda", "/ayuda", "help", "/help"):
enviar("Comandos:\n" enviar("Comandos:\n"
"<b>estado</b> — estado de la Pi\n" "<b>estado</b> — estado de la Pi\n"
@@ -161,6 +170,7 @@ def procesar(texto, chat_id):
"<b>tv &lt;mensaje&gt;</b> — mensaje rápido a la tele\n" "<b>tv &lt;mensaje&gt;</b> — mensaje rápido a la tele\n"
"<b>alarma &lt;mensaje&gt;</b> — secuencia completa (sirena x3)\n" "<b>alarma &lt;mensaje&gt;</b> — secuencia completa (sirena x3)\n"
"<b>youtube &lt;URL&gt;</b> — reproduce YouTube y apaga al terminar\n" "<b>youtube &lt;URL&gt;</b> — reproduce YouTube y apaga al terminar\n"
"<b>descargar &lt;enlace&gt;</b> — añade a Download Station del NAS\n"
"<b>panorama</b> — fotos de las cámaras\n" "<b>panorama</b> — fotos de las cámaras\n"
"<b>limpieza</b> — limpia procesos atascados de la Pi\n" "<b>limpieza</b> — limpia procesos atascados de la Pi\n"
"<b>reboot</b> — reinicia la Pi (necesita confirmar con /confirmar-reboot)\n" "<b>reboot</b> — reinicia la Pi (necesita confirmar con /confirmar-reboot)\n"