From 15f19a7900563cdc533bed3cea0a8cf59bdc5004 Mon Sep 17 00:00:00 2001 From: juanjo Date: Sat, 5 Sep 2026 19:38:03 +0200 Subject: [PATCH] 05-sep: [vigilancia] comandos /aiMesh /qos /limite /seguridad para el ASUS desde Telegram --- docs/raspberry_pi.md | 38 +++ videoclub/listados/2026-09-05.md | 60 ++++ videoclub/snapshots/2026-09-05.json | 413 ++++++++++++++++++++++++++++ vigilancia/asus_api_control.py | 114 ++++++++ vigilancia/pi_bot.py | 125 ++++++++- vigilancia/requirements.txt | 2 + vigilancia/router_control.py | 245 +++++++++++++++++ 7 files changed, 987 insertions(+), 10 deletions(-) create mode 100644 videoclub/listados/2026-09-05.md create mode 100644 videoclub/snapshots/2026-09-05.json diff --git a/docs/raspberry_pi.md b/docs/raspberry_pi.md index 557731e..ea33924 100644 --- a/docs/raspberry_pi.md +++ b/docs/raspberry_pi.md @@ -82,6 +82,42 @@ Desde Telegram, el bot consulta el ASUS mediante la clave de servicio de la Rasp El reinicio exige la palabra `confirmar`. Las consultas actuales no cambian la configuracion. +## Funcionalidades Mesh / QoS / Limite / Seguridad implementadas (05-sep-2026) + +Cuatro comandos nuevos del bot, desplegados y verificados: + +```text +/aiMesh estado # router + nodos, señal y roaming +/aiMesh reiniciar confirmar # reinicia un nodo por nombre +/aiMesh reconstruir confirmar # reconstruye la malla + +/qos estado +/qos adaptive confirmar # auto gaming/streaming (QoS) +/qos tradicional confirmar # prioridades manuales +/qos apagar confirmar # restaura aceleracion NAT + +/limite listar # reglas activas (Mbps) +/limite poner # en Mbps +/limite quitar + +/seguridad estado # AiProtection + filtro de palabras +/seguridad bloquear # p. ej. tiktok +/seguridad desbloquear +``` + +Detalles tecnicos: + +- **AiMesh** se lee con la libreria `asusrouter` (v1.21.3 ya presente en la Pi) via `AsusData.AIMESH`. Los nodos se resuelven por alias tolerante a tildes/minusculas (`bedroom`, `living room`). Las acciones usan `AsusSystem.NODE_REBOOT` (con `device_list=`), `AIMESH_REBUILD` y `AIMESH_REBOOT`. +- **Estado actual detectado:** router principal RT-AX88U PRO (fw 3.0.0.6.102) + nodos `Bedroom` (192.168.50.76, enlace 2.4GHz rssi -59) y `Living Room` (192.168.50.13, enlace 2.4GHz rssi -69), ambos ZenWiFi AX Mini. +- **Bandwidth limiter y QoS** se gestionan por NVRAM via SSH. Formato real de `qos_bw_rulelist` (confirmado en vivo): reglas `enable>MAC>dl_kbps>ul_kbps>indice` separadas por `<`, sin `<` inicial. Al cambiar reglas se aplica `service restart_qos`. +- **El router ya tenia el limiter activo con 7 reglas** (Caroline-tablet 25 Mbps, y 6 dispositivos a 30 Mbps). `/limite listar` las muestra. El modulo reconstruye el payload sin perder ninguna. +- Conversiones en `Mbps x 1024` (coherentes con la UI del router: 30720 kbps = 30 Mbps). +- **QoS y bandwidth limiter desactivan la aceleracion NAT** (aviso incluido en la respuesta del bot). +- `/seguridad` usa el filtro de contenidos `keyword_rulelist` (AiProtection) con `service restart_bwdpi`. El bloqueo por categorias Trend Micro requiere la base web del router y queda como posible evolucion. +- `asusrouter==1.21.3` y `aiohttp` anadidos a `vigilancia/requirements.txt`. + +Las acciones destructivas (reiniciar nodo, reconstruir, cambiar QoS, quitar ultima regla) piden `confirmar` como en el patron `router reiniciar`. + ## Capacidades del RT-AX88U PRO El router puede centralizar muchas funciones para Telegram: @@ -205,6 +241,8 @@ Cada unidad debe tener un registro con hostname, MAC, IP Ethernet, IP Wi-Fi, ser Catalogo de capacidades del RT-AX88U PRO consultado con el usuario, agrupado por area, para implementar desde Telegram cuando se pida. Ninguna de estas se ha implementado todavia salvo que se indique. +> **Actualizacion 05-sep-2026 (tarde):** los comandos `/aiMesh`, `/seguridad`, `/limite` y `/qos` ya estan implementados y desplegados. Ver seccion «Funcionalidades Mesh / QoS / Limite / Seguridad implementadas» mas abajo. + **Red y Wi-Fi** - Apagar/encender cada banda Wi-Fi (2.4/5 GHz) por separado. - Ajustar canales y potencia de senal para optimizar cobertura. diff --git a/videoclub/listados/2026-09-05.md b/videoclub/listados/2026-09-05.md new file mode 100644 index 0000000..38f709a --- /dev/null +++ b/videoclub/listados/2026-09-05.md @@ -0,0 +1,60 @@ +# Videoclub — 2026-09-05 + +**Total disponibles: 51** | Novedades: 0 + +## Películas (37) + +- Backrooms 2160p 4K WEB x265 10bit AAC5 1 (2026) `https://real-debrid.com/d/WC7MIGNKYMMEAFED` +- Disclosure Day 1080p ViruseProject (2026) `https://real-debrid.com/d/7AKRWAMVG2A7OFA6` +- Disclosure Day NO-ADS 1080p x265 MD-AAC5 1+2 0 ITA-ENG MmTRaXx DD-CREW-IlCosaro (2026) `https://real-debrid.com/d/ZWPJNGU5EKGA61D4` +- Downloads +- El dia de la revelacion Disclosure Day (2026) `https://real-debrid.com/d/AHBLKPLNL56KOBBC` +- El día de la revelacion Disclosure Day (2026) `https://real-debrid.com/d/NSHXB2IFNLNEK7C8` +- El Pasajero Nocturno (2026) `https://real-debrid.com/d/52MMX6GHV6U5639B` +- El Pasajero Nocturno (2026) `https://real-debrid.com/d/MBLS4EMXARKVM5C4` +- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1` +- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1` +- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1` +- Greenland `https://real-debrid.com/d/LUZ3G5KBQZYES397` +- Greenland 2 `https://real-debrid.com/d/FS4OAHZSNYHFK568` +- Greenland El Ultimo Refugio (2020) `https://real-debrid.com/d/MJR44YRCTRDMY2C9` +- He-Man y los Masters del Universo (2026) `https://real-debrid.com/d/APGDE3Z7ADR3U58B` +- Iron Lung 2026 1080p `https://real-debrid.com/d/I6BVHXX7IFSUE1DB` +- La maldicion de Shelby Oaks (2026) `https://real-debrid.com/d/T4FFNHSB2XBV4D92` +- La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/QDPUO2UWMDWX6C6E` +- Luger (2026) `https://real-debrid.com/d/VWUUZIQGVLO76357` +- Obsession 2160p UHD x265 10bit DV HDR r00t (2026) `https://real-debrid.com/d/XFJULZ3HFQ6VG8A4` +- Obsession 2160p UHD x265 10bit DV HDR TrueHD Atmos 7 1 r00t (2026) `https://real-debrid.com/d/LMJHM23CEYXZOA66` +- Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/LESH2XGOLRWYKEC8` +- Posesion infernal En llamas (2026) `https://real-debrid.com/d/RF2IRCGKIKISI174` +- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A` +- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A` +- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A` +- Resident Evil `https://real-debrid.com/d/6VMIEUSOVVIXYBD2` +- Salvacion (2026) `https://real-debrid.com/d/BF3FGPVABGL6A610` +- Salvacion (2026) `https://real-debrid.com/d/4Q6AFXH6KSKRO2AF` +- Scary Movie (2026) `https://real-debrid.com/d/7FHGHGOXV7ELY3E6` +- Spider-Man No Way Home 4Kremux2160 atomixhq link `https://real-debrid.com/d/S7GR3CYFAWOWC7AC` +- Steven Spielberg el rey midas de Hollywood wolfmax4k com (2024) `https://real-debrid.com/d/WWCMHPUY4FPFAF67` +- Super Mario Galaxy La película (2026) `https://real-debrid.com/d/VNTK5JCJGRHJOF0E` +- super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/3GQUITYMNB4TUE1C` +- The End of Oak Street 1080p HC V2 x264-DKS (2026) `https://real-debrid.com/d/JI3KPXS4HEY7Y234` +- TheAdventuresOfTomSawyer `https://real-debrid.com/d/XJVUAD7LIIMFKDCC` +- Twilight Saga 2008-2012 2160p H265 HDR10 AC3 5 1 ITA ENG sub ita eng Sp33dy94-MIRCrew `https://real-debrid.com/d/II6EYH6KIMNUI10A` + +## Series (14) + +- Euphoria `https://real-debrid.com/d/YOFHJHFSKN6HOB11` +- From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio - sub `https://real-debrid.com/d/B64L3AL7NWVN41D1` +- Heidi Girl of the Alps Alps no Shoujo Heidi Season 1 BD 1080p HEVC x265 10bit Eng-Subs -Judas `https://real-debrid.com/d/3V5DNKCHKQOPGF89` +- Monarch El Legado De Los Monstruos wolfmax4k com `https://real-debrid.com/d/LRB4AEEJCYOCU1E0` +- Star Trek Strange New Worlds `https://real-debrid.com/d/UCVH7W3HSXDKS4E5` +- Stargate Sg-1 05x09 Entre Dos Fuegos `https://real-debrid.com/d/TYLADWGS6AEMQ28C` +- Stargate Sg-1 05x10 2001 `https://real-debrid.com/d/LHQ7LEN7VCRSS1C4` +- Stargate Sg-1 05x12 Agujero X-Tremo `https://real-debrid.com/d/7BTWUVXH2KH7KCC5` +- Supergirl (2026) `https://real-debrid.com/d/65RUKRSI3YERQDCB` +- Supergirl (2026) `https://real-debrid.com/d/ZKB3MUWMEM52G885` +- The Studio `https://real-debrid.com/d/6AQCY2YCPY6FE9A9` +- The Studio `https://real-debrid.com/d/L7DDTFXUCCPF47A5` +- The Studio `https://real-debrid.com/d/P3J5H2OCFG76WC67` +- The Studio wolfmax4k com `https://real-debrid.com/d/KHLUHJ4W7JMKA663` diff --git a/videoclub/snapshots/2026-09-05.json b/videoclub/snapshots/2026-09-05.json new file mode 100644 index 0000000..147e2dc --- /dev/null +++ b/videoclub/snapshots/2026-09-05.json @@ -0,0 +1,413 @@ +{ + "fecha": "2026-09-05", + "items": [ + { + "id": "realdebrid:Downloads", + "name": "Downloads", + "clean": "Downloads", + "year": null, + "tipo": "pelicula", + "enlace": null + }, + { + "id": "realdebrid:O47SZPHDEIJD4", + "name": "Posesion infernal En llamas (2026) [4k 2160p][Esp].mkv", + "clean": "Posesion infernal En llamas", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/RF2IRCGKIKISI174" + }, + { + "id": "realdebrid:5DLXRYNWASICQ", + "name": "The.End.of.Oak.Street.2026.1080p.HC.V2.x264-DKS.mkv", + "clean": "The End of Oak Street 1080p HC V2 x264-DKS", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/JI3KPXS4HEY7Y234" + }, + { + "id": "realdebrid:TEGQKYCCQBXXO", + "name": "Supergirl (2026) [Bluray 720p][Esp].mkv", + "clean": "Supergirl", + "year": "2026", + "tipo": "serie", + "enlace": "https://real-debrid.com/d/65RUKRSI3YERQDCB" + }, + { + "id": "realdebrid:HARMOP47F3AP2", + "name": "Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv", + "clean": "Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/LESH2XGOLRWYKEC8" + }, + { + "id": "realdebrid:V7XUZQ6APSG4Q", + "name": "Scary Movie (2026) [4k 2160p][Esp].mkv", + "clean": "Scary Movie", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/7FHGHGOXV7ELY3E6" + }, + { + "id": "realdebrid:MBGEYHZC4MKYU", + "name": "Star Trek Strange New Worlds [HDTV 1080p][Cap.401].mkv", + "clean": "Star Trek Strange New Worlds", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/UCVH7W3HSXDKS4E5" + }, + { + "id": "realdebrid:HEIP5O2XB5V66", + "name": "He-Man y los Masters del Universo (2026) [Bluray 1080p][Esp].mkv", + "clean": "He-Man y los Masters del Universo", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/APGDE3Z7ADR3U58B" + }, + { + "id": "realdebrid:SLJ2UJGPDFZLW", + "name": "Supergirl (2026) [Bluray 1080p][Esp].mkv", + "clean": "Supergirl", + "year": "2026", + "tipo": "serie", + "enlace": "https://real-debrid.com/d/ZKB3MUWMEM52G885" + }, + { + "id": "realdebrid:FC5FZS2J4HUFS", + "name": "El dia de la revelacion (Disclosure Day) (2026) [Bluray 1080p][Esp].mkv", + "clean": "El dia de la revelacion Disclosure Day", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/AHBLKPLNL56KOBBC" + }, + { + "id": "realdebrid:A5XIYLPNAZNZM", + "name": "El día de la revelacion (Disclosure Day) (2026) [4k 2160p][Esp].mkv", + "clean": "El día de la revelacion Disclosure Day", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/NSHXB2IFNLNEK7C8" + }, + { + "id": "realdebrid:7XL55PFQOE4OO", + "name": "El Pasajero Nocturno (2026) [Bluray 1080p][Esp].mkv", + "clean": "El Pasajero Nocturno", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/52MMX6GHV6U5639B" + }, + { + "id": "realdebrid:5IHX4PFCFP5QY", + "name": "El Pasajero Nocturno (2026) [Bluray][Esp].avi", + "clean": "El Pasajero Nocturno", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/MBLS4EMXARKVM5C4" + }, + { + "id": "realdebrid:S52E6GZH2FSNK", + "name": "Disclosure.Day.(2026).(NO-ADS).1080p.x265.MD-AAC5.1+2.0.ITA-ENG.MmTRaXx(DD-CREW-IlCosaro).mkv", + "clean": "Disclosure Day NO-ADS 1080p x265 MD-AAC5 1+2 0 ITA-ENG MmTRaXx DD-CREW-IlCosaro", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/ZWPJNGU5EKGA61D4" + }, + { + "id": "realdebrid:FOTT5OF2CNCQO", + "name": "Disclosure.Day.2026.1080p.CAMRip.ViruseProject.mkv", + "clean": "Disclosure Day 1080p ViruseProject", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/7AKRWAMVG2A7OFA6" + }, + { + "id": "realdebrid:FEWY5OUINFE6I", + "name": "Obsession (2026) (2160p UHD BluRay x265 10bit DV HDR r00t).mkv", + "clean": "Obsession 2160p UHD x265 10bit DV HDR r00t", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/XFJULZ3HFQ6VG8A4" + }, + { + "id": "realdebrid:GE3KLARBGACYC", + "name": "Obsession (2026) (2160p UHD BluRay x265 10bit DV HDR TrueHD Atmos 7.1 r00t)", + "clean": "Obsession 2160p UHD x265 10bit DV HDR TrueHD Atmos 7 1 r00t", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/LMJHM23CEYXZOA66" + }, + { + "id": "realdebrid:REQOOWL7R3X5M", + "name": "Backrooms.2026.2160p.4K.WEB.x265.10bit.AAC5.1-[YTS.GG - YTS.BZ].mkv", + "clean": "Backrooms 2160p 4K WEB x265 10bit AAC5 1", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/WC7MIGNKYMMEAFED" + }, + { + "id": "realdebrid:HEVMDYWAXLSLM", + "name": "Luger (2026) [Bluray 720p][Esp].mkv", + "clean": "Luger", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/VWUUZIQGVLO76357" + }, + { + "id": "realdebrid:MBSDHRJMTC3S2", + "name": "Spider-Man No Way Home 4Kremux2160.atomixhq.link.mkv", + "clean": "Spider-Man No Way Home 4Kremux2160 atomixhq link", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/S7GR3CYFAWOWC7AC" + }, + { + "id": "realdebrid:YVLXIF6ILFL2U", + "name": "Iron Lung 2026 1080p.mkv", + "clean": "Iron Lung 2026 1080p", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/I6BVHXX7IFSUE1DB" + }, + { + "id": "realdebrid:DLUAAXNZJ2ZHW", + "name": "From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio [ES+EN] - sub [ES+EN].mkv", + "clean": "From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio - sub", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/B64L3AL7NWVN41D1" + }, + { + "id": "realdebrid:UP7DPBWVMH7JS", + "name": "La maldicion de Shelby Oaks (2026) [4k 2160p][Esp].mkv", + "clean": "La maldicion de Shelby Oaks", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/T4FFNHSB2XBV4D92" + }, + { + "id": "realdebrid:PMBQI365H7PU6", + "name": "Steven Spielberg el rey midas de Hollywood (2024) [HDTV 1080p][Esp](wolfmax4k.com).mkv", + "clean": "Steven Spielberg el rey midas de Hollywood wolfmax4k com", + "year": "2024", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/WWCMHPUY4FPFAF67" + }, + { + "id": "realdebrid:OWWSTBTFG4ZFO", + "name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv", + "clean": "Fiasco total La tragedia del festival Astroworld", + "year": "2025", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1" + }, + { + "id": "realdebrid:M4UVGSNUKTLA6", + "name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv", + "clean": "Fiasco total La tragedia del festival Astroworld", + "year": "2025", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1" + }, + { + "id": "realdebrid:7HQN7ELMLWTDI", + "name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv", + "clean": "Fiasco total La tragedia del festival Astroworld", + "year": "2025", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1" + }, + { + "id": "realdebrid:BRNWEZP2TGOEQ", + "name": "La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv", + "clean": "La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/QDPUO2UWMDWX6C6E" + }, + { + "id": "realdebrid:3MNHGJ3UM73VY", + "name": "super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv", + "clean": "super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/3GQUITYMNB4TUE1C" + }, + { + "id": "realdebrid:PKZ3B7VEZXWZQ", + "name": "Super Mario Galaxy La película (2026) [4k 2160p][Esp].mkv", + "clean": "Super Mario Galaxy La película", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/VNTK5JCJGRHJOF0E" + }, + { + "id": "realdebrid:6RE4EY5ZP2ILA", + "name": "Proyecto Salvacion (2026) [Bluray 1080p][Esp].mkv", + "clean": "Salvacion", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/BF3FGPVABGL6A610" + }, + { + "id": "realdebrid:ASX6FSY2ZZD36", + "name": "Heidi Girl of the Alps (Alps no Shoujo Heidi) (Season 1) (BD 1080p)(HEVC x265 10bit)(Eng-Subs)-Judas[TGx]", + "clean": "Heidi Girl of the Alps Alps no Shoujo Heidi Season 1 BD 1080p HEVC x265 10bit Eng-Subs -Judas", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/3V5DNKCHKQOPGF89" + }, + { + "id": "realdebrid:K4CJIWAJF3NKS", + "name": "Greenland El Ultimo Refugio (2020) [BluRay Rip][AC3 5.1 Castellano][www.PctFenix.com].avi", + "clean": "Greenland El Ultimo Refugio", + "year": "2020", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/MJR44YRCTRDMY2C9" + }, + { + "id": "realdebrid:KDWFL34NA3MXE", + "name": "Greenland [4K UHDremux][2160p][HDR10][AC3 5.1 Castellano-True HD 7.1-Ingles+Subs][ES-EN]", + "clean": "Greenland", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/LUZ3G5KBQZYES397" + }, + { + "id": "realdebrid:KW4WNKPELJM5O", + "name": "Greenland 2 [4k 2160p][Esp].mkv", + "clean": "Greenland 2", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/FS4OAHZSNYHFK568" + }, + { + "id": "realdebrid:WEFMCETRL5BZM", + "name": "Resident Evil [FullUHD 2160p][Castellano AC3 5.1-Ingles DolbyTrueHD 7.1+Subs]", + "clean": "Resident Evil", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/6VMIEUSOVVIXYBD2" + }, + { + "id": "realdebrid:CFPM3BI4C7EXO", + "name": "Proyecto Salvacion (2026) [4k 2160p][Esp].mkv", + "clean": "Salvacion", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/4Q6AFXH6KSKRO2AF" + }, + { + "id": "realdebrid:5W6E6YBRA7V22", + "name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv", + "clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A" + }, + { + "id": "realdebrid:3GJHZMZQUQ544", + "name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv", + "clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A" + }, + { + "id": "realdebrid:YAFM2WGO4IR64", + "name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv", + "clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG", + "year": "2026", + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A" + }, + { + "id": "realdebrid:RBESCUG7LBCH2", + "name": "Twilight Saga (2008-2012) 2160p H265 HDR10 AC3 5.1 ITA.ENG sub ita.eng Sp33dy94-MIRCrew", + "clean": "Twilight Saga 2008-2012 2160p H265 HDR10 AC3 5 1 ITA ENG sub ita eng Sp33dy94-MIRCrew", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/II6EYH6KIMNUI10A" + }, + { + "id": "realdebrid:ZYLMP5J4H3VE6", + "name": "Monarch El Legado De Los Monstruos [4k 2160p][Cap.101](wolfmax4k.com).mkv", + "clean": "Monarch El Legado De Los Monstruos wolfmax4k com", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/LRB4AEEJCYOCU1E0" + }, + { + "id": "realdebrid:37ZFJ63K6W7PM", + "name": "Stargate_Sg-1_05x12_Agujero_X-Tremo.avi", + "clean": "Stargate Sg-1 05x12 Agujero X-Tremo", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/7BTWUVXH2KH7KCC5" + }, + { + "id": "realdebrid:6JJIFVQ5MZQEI", + "name": "TheAdventuresOfTomSawyer", + "clean": "TheAdventuresOfTomSawyer", + "year": null, + "tipo": "pelicula", + "enlace": "https://real-debrid.com/d/XJVUAD7LIIMFKDCC" + }, + { + "id": "realdebrid:5KFSC2OVN37MO", + "name": "The Studio [HDTV 1080p][Cap.105]", + "clean": "The Studio", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/6AQCY2YCPY6FE9A9" + }, + { + "id": "realdebrid:3AS7OJ3EXXKMC", + "name": "Stargate_Sg-1_05x10_2001.avi", + "clean": "Stargate Sg-1 05x10 2001", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/LHQ7LEN7VCRSS1C4" + }, + { + "id": "realdebrid:UYIBLKOIL5HD6", + "name": "The Studio [HDTV 1080p][Cap.104]", + "clean": "The Studio", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/L7DDTFXUCCPF47A5" + }, + { + "id": "realdebrid:CQ5CWIAKCM3ZS", + "name": "The Studio [HDTV 1080p][Cap.103]", + "clean": "The Studio", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/P3J5H2OCFG76WC67" + }, + { + "id": "realdebrid:TMZRDVHVHUHRQ", + "name": "The Studio [HDTV 1080p][cap.102](wolfmax4k.com).mkv", + "clean": "The Studio wolfmax4k com", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/KHLUHJ4W7JMKA663" + }, + { + "id": "realdebrid:S5HO3BW6ADQYI", + "name": "Euphoria [4k 2160p][Cap.302].mkv", + "clean": "Euphoria", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/YOFHJHFSKN6HOB11" + }, + { + "id": "realdebrid:CCDJGDZJK3FRA", + "name": "Stargate_Sg-1_05x09_Entre_Dos_Fuegos.avi", + "clean": "Stargate Sg-1 05x09 Entre Dos Fuegos", + "year": null, + "tipo": "serie", + "enlace": "https://real-debrid.com/d/TYLADWGS6AEMQ28C" + } + ] +} \ No newline at end of file diff --git a/vigilancia/asus_api_control.py b/vigilancia/asus_api_control.py index ce72057..c871c71 100644 --- a/vigilancia/asus_api_control.py +++ b/vigilancia/asus_api_control.py @@ -1,6 +1,7 @@ """Lectura segura de datos ASUSWRT mediante la API web del router.""" import asyncio +import html import json import os import unicodedata @@ -8,6 +9,7 @@ import unicodedata import aiohttp from asusrouter import AsusData, AsusRouter from asusrouter.modules.parental_control import PCRuleType, ParentalControlRule +from asusrouter.modules.system import AsusSystem def _load_env(): @@ -110,3 +112,115 @@ def cambiar_bloqueo(nombre, blocked): 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']}" + + +async def _aimesh_data(): + 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() + return await router.async_get_data(AsusData.AIMESH, force=True) + finally: + await router.async_disconnect() + await session.close() + + +async def _aimesh_action(cmd, mac=None): + 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() + if cmd == "reconstruir": + return await router.async_set_state(AsusSystem.AIMESH_REBUILD) + if cmd == "reiniciar_todo": + return await router.async_set_state(AsusSystem.AIMESH_REBOOT) + return await router.async_set_state( + AsusSystem.NODE_REBOOT, device_list=mac + ) + finally: + await router.async_disconnect() + await session.close() + + +def _nodo_texto(node): + tipo = "ROUTER" if getattr(node, "type", "") == "router" else "NODO" + alias = node.alias or node.model or "Sin nombre" + lineas = [f"{tipo} · {alias} ({node.model})"] + estado = "conectado" if node.status else "apagado" + lineas.append(f" estado: {estado}") + if node.ip: + lineas.append(f" ip: {node.ip}") + if node.fw: + lineas.append(f" firmware: {node.fw}") + if node.mac: + lineas.append(f" mac: {node.mac}") + parent = node.parent or {} + if parent: + rssi = parent.get("rssi", "?") + conexion = parent.get("connection", "?") + lineas.append(f" enlace: {conexion} · rssi={rssi}") + for banda, ssid in (node.ap or {}).items(): + lineas.append(f" wifi {banda}: {ssid}") + return "\n".join(lineas) + + +def aimesh_estado(): + data = asyncio.run(_aimesh_data()) or {} + if not data: + return "
Sin datos AiMesh
" + nodos = list(data.values()) + nodos.sort(key=lambda n: getattr(n, "level", 0)) + texto = "\n\n".join(_nodo_texto(n) for n in nodos) + return f"
{html.escape(texto)}
" + + +def _resolver_nodo(nombre): + data = asyncio.run(_aimesh_data()) or {} + clave = normalizar_nombre(nombre) + for mac, node in data.items(): + if mac and mac.lower() == nombre.lower().strip(): + return node + alias = normalizar_nombre(node.alias or "") + if clave and (alias == clave or clave in alias or alias in clave): + return node + return None + + +def aimesh_reiniciar_nodo(nombre): + node = _resolver_nodo(nombre) + if not node: + raise ValueError(f"Nodo AiMesh no encontrado: {nombre}") + asyncio.run(_aimesh_action("nodo", mac=node.mac)) + return f"🔄 Nodo reiniciado: {node.alias or node.model} ({node.mac})" + + +def aimesh_reconstruir(): + asyncio.run(_aimesh_action("reconstruir")) + return "🛠️ AiMesh reconstruida. Los nodos se reconectarán en unos minutos." + + +def aimesh_reiniciar_todo(): + asyncio.run(_aimesh_action("reiniciar_todo")) + return "⚠️ Router y todos los nodos se están reiniciando..." diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py index 5a50b7b..81e76f8 100644 --- a/vigilancia/pi_bot.py +++ b/vigilancia/pi_bot.py @@ -497,10 +497,115 @@ def procesar(texto, chat_id): enviar( "Uso: /parental pausa Inés 60, " "/parental desactivar Carolina o " - "/parental activar Inés.", chat_id) +"/parental activar Inés.", chat_id) except Exception as e: log(f"parental error: {e}") enviar(f"❌ Error en control parental: {e}", chat_id) + elif comando == "aiMesh": + partes_mesh = argumento.split() + accion_mesh = partes_mesh[0].lower() if partes_mesh else "estado" + try: + from asus_api_control import ( + aimesh_estado, + aimesh_reconstruir, + aimesh_reiniciar_nodo, + ) + if accion_mesh in ("estado", "nodos", "listar"): + enviar("🌐 AIMESH ASUS\n" + aimesh_estado(), chat_id) + elif accion_mesh == "reiniciar" and len(partes_mesh) >= 2: + if partes_mesh[-1] != "confirmar": + enviar("Para reiniciar un nodo responde: /aiMesh reiniciar <nodo> confirmar", chat_id) + else: + nodo = " ".join(partes_mesh[1:-1]) + enviar(f"🔄 Reiniciando el nodo {nodo}...", chat_id) + enviar(aimesh_reiniciar_nodo(nodo), chat_id) + elif accion_mesh == "reconstruir": + if len(partes_mesh) < 2 or partes_mesh[-1] != "confirmar": + enviar("Para reconstruir la malla responde: /aiMesh reconstruir confirmar", chat_id) + else: + enviar("⚠️ Reconstruyendo la malla AiMesh...", chat_id) + enviar(aimesh_reconstruir(), chat_id) + else: + enviar( + "Uso:\n/aiMesh estado — nodos y señal\n" + "/aiMesh reiniciar <nodo> confirmar\n" + "/aiMesh reconstruir confirmar", chat_id) + except Exception as e: + log(f"aiMesh error: {e}") + enviar(f"❌ Error en AiMesh: {e}", chat_id) + elif comando == "qos": + partes_qos = argumento.lower().split() + accion_qos = partes_qos[0] if partes_qos else "estado" + try: + from router_control import qos_apagar, qos_estado, qos_set + if accion_qos == "estado": + enviar("⚙️ QOS ASUS\n" + qos_estado(), chat_id) + elif accion_qos in ("adaptive", "tradicional") and len(partes_qos) >= 2 and partes_qos[-1] == "confirmar": + enviar("⏳ Aplicando QoS...", chat_id) + enviar(qos_set(accion_qos), chat_id) + elif accion_qos == "apagar" and len(partes_qos) >= 2 and partes_qos[-1] == "confirmar": + enviar("⏳ Apagando QoS...", chat_id) + enviar(qos_apagar(), chat_id) + else: + enviar( + "Uso:\n/qos estado\n" + "/qos adaptive confirmar — auto gaming/streaming\n" + "/qos tradicional confirmar — prioridades manuales\n" + "/qos apagar confirmar", chat_id) + except Exception as e: + log(f"qos error: {e}") + enviar(f"❌ Error en QoS: {e}", chat_id) + elif comando == "limite": + partes_lim = argumento.split() + accion_lim = partes_lim[0].lower() if partes_lim else "listar" + try: + from router_control import limite_listar, limite_poner, limite_quitar + if accion_lim == "listar": + enviar("🚦 LÍMITES DE VELOCIDAD\n" + limite_listar(), chat_id) + elif accion_lim in ("poner", "set") and len(partes_lim) >= 4: + try: + dl = float(partes_lim[-2]) + ul = float(partes_lim[-1]) + except ValueError: + enviar("Las velocidades deben ser números en Mbps.", chat_id) + else: + dispositivo = " ".join(partes_lim[1:-2]) or partes_lim[1] + enviar(limite_poner(dispositivo, dl, ul), chat_id) + elif accion_lim in ("quitar", "del", "remove") and len(partes_lim) >= 2: + dispositivo = " ".join(partes_lim[1:]) + enviar(limite_quitar(dispositivo), chat_id) + else: + enviar( + "Uso:\n/limite listar\n" + "/limite poner <dispositivo> <bajada> <subida> (Mbps)\n" + " ✏️ Ej: /limite poner NAS 50 20\n" + "/limite quitar <dispositivo>", chat_id) + except Exception as e: + log(f"limite error: {e}") + enviar(f"❌ Error en límites: {e}", chat_id) + elif comando == "seguridad": + partes_seg = argumento.lower().split() + accion_seg = partes_seg[0] if partes_seg else "estado" + try: + from router_control import ( + seguridad_estado, + seguridad_keyword, + ) + if accion_seg == "estado": + enviar("🛡️ SEGURIDAD ASUS\n" + seguridad_estado(), chat_id) + elif accion_seg in ("bloquear", "desbloquear") and len(partes_seg) >= 2: + palabra = " ".join(partes_seg[1:]) + activar = accion_seg == "bloquear" + enviar(seguridad_keyword(palabra, activar), chat_id) + else: + enviar( + "Uso (filtro de contenidos AiProtection):\n" + "/seguridad estado\n" + "/seguridad bloquear <palabra> — p. ej. tiktok\n" + "/seguridad desbloquear <palabra>", chat_id) + except Exception as e: + log(f"seguridad error: {e}") + enviar(f"❌ Error en seguridad: {e}", chat_id) elif t in ("pelis", "/pelis"): procesar_pelis(chat_id) elif t in ("series", "/series"): @@ -579,15 +684,15 @@ def procesar(texto, chat_id): " ✏️ Ej: /reboot → confirmar con /confirmar-reboot\n" "24. ayuda — este índice\n" " ✏️ Ej: /ayuda\n\n" - "━━━ 🆕 ROUTER ASUS — PRÓXIMAMENTE ━━━\n" - "25. qos — prioridad de ancho de banda por tipo de tráfico\n" - " ✏️ Ej: /qos modo adaptive — gaming/streaming con prioridad\n" - "26. aiMesh — red en malla con tus 2 módulos ASUS\n" - " ✏️ Ej: /aiMesh estado — nodos, señal y roaming unificado\n" - "27. seguridad — AiProtection: bloqueo de webs por categoría\n" - " ✏️ Ej: /seguridad bloquear Inés redes sociales\n" - "28. limite — límite de velocidad por dispositivo\n" - " ✏️ Ej: /limite poner NAS 50 20 — 50/20 Mbps", +"━━━ 🌐 ROUTER ASUS — MESH / QoS / SEGURIDAD ━━━\n" + "25. qos estado — ancho de banda con prioridades\n" + " ✏️ Ej: /qos adaptive confirmar — auto gaming/streaming\n" + "26. aiMesh estado — nodos, señal y roaming unificado\n" + " ✏️ Ej: /aiMesh reiniciar nodo confirmar\n" + "27. seguridad estado — AiProtection: filtro de webs\n" + " ✏️ Ej: /seguridad bloquear tiktok\n" + "28. limite listar — límite de velocidad por dispositivo\n" + " ✏️ Ej: /limite poner NAS 50 20 — 50/20 Mbps", chat_id) elif t in ("servicios", "/servicios"): estado_svc = sh("systemctl is-active pi-bot tv-api autossh-tunel") diff --git a/vigilancia/requirements.txt b/vigilancia/requirements.txt index f19b758..1989b90 100644 --- a/vigilancia/requirements.txt +++ b/vigilancia/requirements.txt @@ -2,3 +2,5 @@ opencv-python requests python-dotenv ultralytics +asusrouter==1.21.3 +aiohttp diff --git a/vigilancia/router_control.py b/vigilancia/router_control.py index a123a9a..7385614 100644 --- a/vigilancia/router_control.py +++ b/vigilancia/router_control.py @@ -143,6 +143,245 @@ def reiniciar(): return _run("reboot") +QOS_TIPOS = { + "0": "Traditional (por prioridades)", + "1": "Adaptive (auto gaming/streaming)", + "2": "Bandwidth limiter", + "3": "GeForce Now", +} + + +def _set_nvram(entradas, servicio=None): + partes = [ + f"nvram set {clave}={shlex.quote(str(valor))}" + for clave, valor in entradas + ] + partes.append("nvram commit") + if servicio: + partes.append(f"service {servicio}") + return _run("; ".join(partes)) + + +def _nombre_por_mac(mac): + _devices, _by_mac = _inventario() + for ip, m, nombre in _devices: + if m == mac.upper(): + return nombre + try: + from asus_api_control import DEVICE_ALIASES + for _alias, info in DEVICE_ALIASES.items(): + if info["mac"].upper() == mac.upper(): + return info["name"] + except Exception: + pass + return f"unknown-{mac}" + + +def _resolver_mac(dispositivo): + valor = str(dispositivo or "").strip() + if valor.lower().count(":") == 5: + return valor.upper() + try: + from asus_api_control import normalizar_nombre, resolver_dispositivo + except Exception: + raise ValueError(f"Dispositivo no reconocido: {dispositivo}") + info = resolver_dispositivo(valor) + if info: + return info["mac"].upper() + _devices, _by_mac = _inventario() + clave = normalizar_nombre(valor) + for ip, m, nombre in _devices: + n = normalizar_nombre(nombre) + if clave and (n == clave or clave in n or n in clave): + return m + raise ValueError( + f"Dispositivo no reconocido: {dispositivo}. Usa MAC o un alias." + ) + + +def qos_estado(): + enable = (_run("nvram get qos_enable") or "").strip() + tipo = (_run("nvram get qos_type") or "").strip() + activado = "sí" if enable == "1" else "no" + modo = QOS_TIPOS.get(tipo, f"desconocido ({tipo})") + texto = f"Activado: {activado}\nModo: {modo}" + return f"
{html.escape(texto)}
" + + +def qos_set(modo): + conf = { + "adaptive": ("1", "1"), + "tradicional": ("0", "0"), + } + if modo not in conf: + raise ValueError("Modo no válido: usa adaptive o tradicional") + qos_enable, qos_type = conf[modo] + resultado = _set_nvram( + [("qos_enable", qos_enable), ("qos_type", qos_type)], "restart_qos" + ) + if resultado.startswith("Error "): + return resultado + nombre = QOS_TIPOS[qos_type] + return ( + f"QoS activado en modo {nombre}. " + "Ten en cuenta que QoS desactiva la aceleración NAT." + ) + + +def qos_apagar(): + resultado = _set_nvram([("qos_enable", "0")], "restart_qos") + if resultado.startswith("Error "): + return resultado + return "QoS desactivado. Aceleración NAT restaurada." + + +def _limite_parse(): + raw = _run("nvram get qos_bw_rulelist") or "" + reglas = [] + for parte in str(raw).split("<"): + parte = parte.strip() + if not parte: + continue + campos = parte.rstrip(">").split(">") + if len(campos) < 4: + continue + en, mac, dl, ul = campos[:4] + if str(mac).count(":") != 5: + continue + reglas.append({ + "enable": en, + "mac": mac.upper(), + "dl": dl, + "ul": ul, + }) + return raw, reglas + + +def _limite_payload(reglas): + partes = [ + f"{r['enable']}>{r['mac']}>{r['dl']}>{r['ul']}>{idx}" + for idx, r in enumerate(reglas) + ] + return "<".join(partes) + + +def _limite_guardar(reglas): + payload = _limite_payload(reglas) + if reglas: + entradas = [ + ("qos_enable", "1"), + ("qos_type", "2"), + ("qos_bw_rulelist", payload), + ] + else: + entradas = [ + ("qos_enable", "0"), + ("qos_bw_rulelist", payload), + ] + return _set_nvram(entradas, "restart_qos") + + +def limite_listar(): + _raw, reglas = _limite_parse() + if not reglas: + return "
No hay reglas de límite activas.
" + lineas = ["ACTIVADO DISPOSITIVO/MAC BAJADA SUBIDA"] + for r in reglas: + nombre = _nombre_por_mac(r["mac"]) + dl = f"{int(r['dl']) / 1024:.0f}" if r["dl"].isdigit() else r["dl"] + ul = f"{int(r['ul']) / 1024:.0f}" if r["ul"].isdigit() else r["ul"] + lineas.append( + f"{'sí' if r['enable'] == '1' else 'no':<9} " + f"{nombre:<20} {dl:>7} {ul:>7} Mbps" + ) + return f"
{html.escape(chr(10).join(lineas))}
" + + +def limite_poner(dispositivo, dl_mbps, ul_mbps): + mac = _resolver_mac(dispositivo) + if dl_mbps <= 0 or ul_mbps <= 0: + raise ValueError("Las velocidades deben ser mayores que 0") + _raw, reglas = _limite_parse() + reglas = [r for r in reglas if r["mac"] != mac] + reglas.append({ + "enable": "1", + "mac": mac, + "dl": str(int(dl_mbps * 1024)), + "ul": str(int(ul_mbps * 1024)), + }) + resultado = _limite_guardar(reglas) + if resultado.startswith("Error "): + return resultado + return ( + f"Límite aplicado a {_nombre_por_mac(mac)}: " + f"{dl_mbps:.0f} Mbps bajada / {ul_mbps:.0f} Mbps subida. " + "El bandwidth limiter desactiva la aceleración NAT." + ) + + +def limite_quitar(dispositivo): + mac = _resolver_mac(dispositivo) + _raw, reglas = _limite_parse() + if not any(r["mac"] == mac for r in reglas): + return f"No hay límite activo para {dispositivo}." + reglas = [r for r in reglas if r["mac"] != mac] + resultado = _limite_guardar(reglas) + if resultado.startswith("Error "): + return resultado + if not reglas: + return f"Límite retirado de {_nombre_por_mac(mac)}. " + return f"Límite retirado de {_nombre_por_mac(mac)}." + + +def seguridad_estado(): + claves = ( + "bwdpi_enable", + "yadns_enable_x", + "yadns_mode", + ) + lineas = [] + for clave in claves: + lineas.append(f"{clave}={_run(f'nvram get {clave}')}") + keywords = _run("nvram get keyword_rulelist") or "" + palabras = [p for p in str(keywords).split("<") if p.strip()] + lineas.append( + "palabras filtradas: " + + (", ".join(palabras) if palabras else "(ninguna)") + ) + return f"
{html.escape(chr(10).join(lineas))}
" + + +def _keywords_actuales(): + raw = _run("nvram get keyword_rulelist") or "" + return [p.strip().lower() for p in str(raw).split("<") if p.strip()] + + +def seguridad_keyword(palabra, activar): + palabra = palabra.strip().lower() + palabras = _keywords_actuales() + if activar: + if palabra in palabras: + return f"La palabra {palabra} ya estaba en el filtro." + palabras.append(palabra) + else: + if palabra not in palabras: + return f"La palabra {palabra} no estaba en el filtro." + palabras = [p for p in palabras if p != palabra] + payload = "".join(f"<{p}>" for p in palabras) + entradas = [("keyword_rulelist", payload)] + if palabras: + entradas.insert(0, ("bwdpi_enable", "1")) + resultado = _set_nvram(entradas, "restart_bwdpi") + if resultado.startswith("Error "): + return resultado + accion = "bloqueada" if activar else "desbloqueada" + if palabras: + resto = f" Filtro activo: {', '.join(palabras)}." + else: + resto = " Filtro de contenidos vacío." + return f"Palabra {palabra} {accion}.{resto}" + + if __name__ == "__main__": action = sys.argv[1] if len(sys.argv) > 1 else "estado" actions = { @@ -153,5 +392,11 @@ if __name__ == "__main__": "reservar": reservar_dhcp, "reservas": reservas, "parental": control_parental, + "qos_estado": qos_estado, + "qos_adaptive": lambda: qos_set("adaptive"), + "qos_tradicional": lambda: qos_set("tradicional"), + "qos_apagar": qos_apagar, + "limite_listar": limite_listar, + "seguridad_estado": seguridad_estado, } print(actions.get(action, estado)())