diff --git a/vigilancia/asus_api_control.py b/vigilancia/asus_api_control.py
index 26799ed..ce72057 100644
--- a/vigilancia/asus_api_control.py
+++ b/vigilancia/asus_api_control.py
@@ -7,6 +7,7 @@ import unicodedata
import aiohttp
from asusrouter import AsusData, AsusRouter
+from asusrouter.modules.parental_control import PCRuleType, ParentalControlRule
def _load_env():
@@ -71,6 +72,41 @@ async def _parental_data():
await session.close()
+async def _set_block(mac, name, blocked):
+ username = _env("ASUS_USERNAME")
+ password = _env("ASUS_PASSWORD")
+ if not username or not password:
+ raise RuntimeError(
+ "Falta ASUS_USERNAME o ASUS_PASSWORD en el .env de la Raspberry"
+ )
+ session = aiohttp.ClientSession()
+ router = AsusRouter(
+ hostname=_env("ASUS_ROUTER_IP", "192.168.50.1"),
+ username=username,
+ password=password,
+ session=session,
+ )
+ try:
+ await router.async_connect()
+ rule = ParentalControlRule(
+ mac=mac,
+ name=name if blocked else None,
+ type=PCRuleType.BLOCK if blocked else PCRuleType.REMOVE,
+ )
+ return await router.async_set_state(rule)
+ finally:
+ await router.async_disconnect()
+ await session.close()
+
+
def parental_status():
data = asyncio.run(_parental_data())
return json.dumps(data, ensure_ascii=False, indent=2, default=str)
+
+
+def cambiar_bloqueo(nombre, blocked):
+ device = resolver_dispositivo(nombre)
+ if not device:
+ raise ValueError(f"Dispositivo no reconocido: {nombre}")
+ asyncio.run(_set_block(device["mac"], device["name"], blocked))
+ return f"{'Bloqueado' if blocked else 'Activado'}: {device['name']}"
diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py
index 4e9ab35..c813611 100644
--- a/vigilancia/pi_bot.py
+++ b/vigilancia/pi_bot.py
@@ -15,6 +15,7 @@ ultimos_torrents = {} # chat_id -> {'titulo':..., 'torrents':[...]}
flujo_genero = {} # chat_id -> 'esperando_tipo' | 'esperando_genero' | None
flujo_genero_tipo = {} # chat_id -> 'movie' | 'series' (tipo ya elegido)
panel_descargas = {} # chat_id -> estado del panel editable
+temporizadores_parental = {} # alias normalizado -> Timer de desbloqueo
BASE = os.path.dirname(os.path.abspath(__file__))
LOG = os.path.join(BASE, "bot.log")
@@ -458,23 +459,48 @@ def procesar(texto, chat_id):
else:
enviar("AcciΓ³n de router no reconocida. Usa /router ayuda.", chat_id)
elif comando == "parental":
+ partes_parental = argumento.split()
+ accion_parental = partes_parental[0].lower() if partes_parental else "listar"
try:
- from router_control import control_parental
- enviar(
- "πͺ RUTINAS PARENTALES\n"
- "Alias reconocidos: InΓ©s, INES, Carolina.\n"
- "Rutinas previstas:\n"
- "/parental listar\n"
- "/parental activar <rutina>\n"
- "/parental desactivar <rutina>\n"
- "/parental pausa <rutina> <minutos>\n\n"
- "Las acciones por nombre quedan preparadas para la primera rutina.\n\n"
- "Estado actual:\n" + control_parental(),
- chat_id,
- )
+ from asus_api_control import cambiar_bloqueo, normalizar_nombre
+ if accion_parental == "listar":
+ from router_control import control_parental
+ enviar("πͺ CONTROL PARENTAL ASUS\n" + control_parental(), chat_id)
+ elif accion_parental in ("desactivar", "bloquear") and len(partes_parental) >= 2:
+ alias = " ".join(partes_parental[1:])
+ enviar(f"π {cambiar_bloqueo(alias, True)}", chat_id)
+ elif accion_parental in ("activar", "desbloquear") and len(partes_parental) >= 2:
+ alias = " ".join(partes_parental[1:])
+ timer = temporizadores_parental.pop(normalizar_nombre(alias), None)
+ if timer:
+ timer.cancel()
+ enviar(f"π {cambiar_bloqueo(alias, False)}", chat_id)
+ elif accion_parental == "pausa" and len(partes_parental) >= 3:
+ alias = " ".join(partes_parental[1:-1])
+ minutos = int(partes_parental[-1])
+ if minutos < 1 or minutos > 1440:
+ raise ValueError("Los minutos deben estar entre 1 y 1440")
+ cambiar_bloqueo(alias, True)
+ clave = normalizar_nombre(alias)
+ anterior = temporizadores_parental.pop(clave, None)
+ if anterior:
+ anterior.cancel()
+ timer = threading.Timer(
+ minutos * 60,
+ lambda: cambiar_bloqueo(alias, False),
+ )
+ timer.daemon = True
+ temporizadores_parental[clave] = timer
+ timer.start()
+ enviar(f"βΈοΈ Bloqueado {alias} durante {minutos} minutos.", chat_id)
+ else:
+ enviar(
+ "Uso: /parental pausa InΓ©s 60, "
+ "/parental desactivar Carolina o "
+ "/parental activar InΓ©s.", chat_id)
except Exception as e:
log(f"parental error: {e}")
- enviar(f"β Error consultando parental: {e}", chat_id)
+ enviar(f"β Error en control parental: {e}", chat_id)
elif t in ("pelis", "/pelis"):
procesar_pelis(chat_id)
elif t in ("series", "/series"):
@@ -532,10 +558,10 @@ def procesar(texto, chat_id):
"βββ πͺ CONTROL PARENTAL βββ\n"
"14. parental listar β estado y dispositivos\n"
" βοΈ Ej: /parental listar\n"
- "15. parental activar|desactivar β rutinas por nombre (preparado)\n"
- " βοΈ Ej: /parental activar noches_escolares\n"
- "16. parental pausa β pausa temporal (preparado)\n"
- " βοΈ Ej: /parental pausa noches_escolares 60\n\n"
+ "15. parental desactivar|activar β bloquea o desbloquea\n"
+ " βοΈ Ej: /parental desactivar InΓ©s\n"
+ "16. parental pausa β bloquea X minutos\n"
+ " βοΈ Ej: /parental pausa Carolina 60\n\n"
"βββ π οΈ SISTEMA βββ\n"
"17. estado β estado de la Pi\n"
" βοΈ Ej: /estado\n"