Compare commits

..

37 Commits

Author SHA1 Message Date
Juanjo Minguez
578bae9a1f Regla 24: impresora de red Brother HL-L2350DW (ruta VPS->Pi->IPP) 2026-10-06 20:52:27 +02:00
minguezsanzjuanjose
defbc2e4b2 04-oct: [conocimiento] CCNN Carolina Tema 1 - flujo temario/capturas + Revisado 2026-10-04 19:25:08 +02:00
minguezsanzjuanjose
93aa46a5d1 04-oct: [fix] bot Ines es SOLO Telegram (no vuelca a Drive) 2026-10-04 17:28:20 +02:00
minguezsanzjuanjose
697e835287 04-oct: [conocimiento] sistema tareas hijas (Drive) + helpers + bot Ines VPS documentado 2026-10-04 17:26:23 +02:00
juanminsanz
db8f37cab8 13-sep: [conocimiento] guia OmniRoute autorouting auto/* + fix checker + listados videoclub 2026-09-13 23:33:30 +02:00
juanjo
50d8197eea 06-sep: [feat] gestion recursos opencode-tg: watchdog inactividad reinicia serve + limites systemd 2026-09-06 20:24:05 +02:00
juanjo
4562caa585 06-sep: [perf] opencode-tg: poll 1s, fin desde 2s, envio fragmentos 0.1s 2026-09-06 20:20:41 +02:00
juanjo
fd3f190534 06-sep: [feat] aviso de voz al terminar (Dalia/edge-tts) en opencode-tg + script aviso_voz 2026-09-06 20:17:32 +02:00
juanjo
f6116d1af2 06-sep: [fix] opencode_bot sin verbose, texto final + tiempo, sin duplicados entre turnos 2026-09-06 20:09:51 +02:00
juanjo
09e8f1d9c3 06-sep: [fix] handle genero action in menu, trigger genre flow directly 2026-09-06 19:34:54 +02:00
juanjo
94c967c343 06-sep: [fix] add genero command handling in procesar() 2026-09-06 19:24:59 +02:00
juanjo
bed840417d 06-sep: [fix] remueve pop(ultimos_torrents) al reingresar menú videoclub 2026-09-06 19:13:59 +02:00
juanjo
9387e97167 06-sep: [fix] menu usa estado vivo (__main__) en vez de copia vacia 2026-09-06 18:11:52 +02:00
juanjo
3193c30340 06-sep: [menu] Bajar solo si hay torrents; volver limpia bajar 2026-09-06 18:01:59 +02:00
juanjo
b982e69175 06-sep: [videoclub] serie salta a online si hay menos de 3 capitulos 2026-09-06 17:59:11 +02:00
juanjo
f16102748c 06-sep: [menu] cancelar reabre el menu + traza de edits fallidos 2026-09-06 17:54:57 +02:00
juanjo
6678383e8f 06-sep: [videoclub] serie/peli con fallback global si no esta en tu catalogo 2026-09-06 17:46:54 +02:00
juanjo
086a97c3f9 06-sep: [videoclub] temporada completa, rangos bajar X-Y, pelis solo castellano 2026-09-06 17:40:25 +02:00
juanjo
d77a25cfb4 06-sep: [menu] pendiente con slash vuelve al menu + categoria segun comando 2026-09-06 17:33:50 +02:00
juanjo
b75f71e8aa 06-sep: [fix] teclado_resultados con inline_keyboard (botones torrents/bajar) 2026-09-06 17:26:01 +02:00
juanjo
2ac60433ae 06-sep: [fix] borrado NAS real (id plano + error por tarea), blqdev reconstruye lista, ayudas torrents 2026-09-06 16:32:49 +02:00
juanjo
9b4afd9f91 06-sep: [domotica] Mesillas Meross (comando + submenu) 2026-09-06 16:05:52 +02:00
juanjo
7f0769edd2 06-sep: [domotica] Pantalla como dispositivo aparte (comando + submenu) 2026-09-06 15:33:20 +02:00
juanjo
df1f7f0d93 06-sep: [parental] Bloquear dispositivo: inventario vivo con toggle por numero + alias TV/Xbox 2026-09-06 15:06:20 +02:00
juanjo
2801d2a412 06-sep: [parental] fijar_bloqueos lectura-escritura unica: bloqueos seguidos no se pisan 2026-09-06 14:58:55 +02:00
juanjo
df2fbadcc3 06-sep: [aspiradora] pack2: vaciado, auto, stats, consumibles, potencia, agua, voz 2026-09-06 14:45:22 +02:00
juanjo
d995af2007 06-sep: [parental] bloqueo total: parental API + iptables IP/MAC con verificacion 2026-09-06 14:25:34 +02:00
juanjo
f69742ac59 06-sep: [parental] bloqueo 24/7 toda la semana (timemap completo, no solo mier/juev) 2026-09-06 14:21:21 +02:00
juanjo
1ff1c43f3c 06-sep: [menu] toda accion vuelve al menu de su categoria (menu_cat + reenviar) 2026-09-06 14:12:52 +02:00
juanjo
0166f2333a 06-sep: [menu] Domotica por categorias (Aspiradora, Termo) con submenus 2026-09-06 14:05:51 +02:00
juanjo
61b2bbde12 06-sep: [aspiradora] mapping MIoT real r2205 (bateria siid3) + menu reabre categoria tras consulta 2026-09-06 14:00:10 +02:00
juanjo
bc435b3666 06-sep: [menu] seccion Domotica (aspiradora+termo), tablets Ines+Carolina en parental, fin-accion limpia chat + vuelve a /menu 2026-09-06 13:53:28 +02:00
juanjo
2b1fdcc8fd 06-sep: [infra] script tunel-pi-api.ps1 (api localhost:8600 + shell Pi via VPS) 2026-09-06 13:44:40 +02:00
juanjo
77f077854c 06-sep: [aspiradora] modulo Dreame D10 Plus (r2205 map mc1808) + comando /aspiradora en bot IDD 2026-09-06 13:28:15 +02:00
juanjo
bdc6cf8f48 06-sep: [videoclub] flujo unificado torrents/bajar en novedades+pelicula+serie, sin URLs, /limpiar borra todo el chat (IDD+OpenCode), parental CAROLINA con fallback MAC en vivo 2026-09-06 12:21:19 +02:00
juanjo
c82737cb13 06-sep: [config] modelos Ollama etiquetados y ordenados por potencia (1 Mas Potente a 4 Ligero) 2026-09-06 11:53:31 +02:00
juanjo
9c9303d082 06-sep: [config] modelos locales Ollama en opencode (jj-flash MoE 29B, jj-coder 14B, DS-Coder-V2-Lite, qwen3:8b) 2026-09-06 11:46:13 +02:00
30 changed files with 4473 additions and 145 deletions

View File

@@ -269,6 +269,7 @@ Si `reminders` esta vacio, recordar al usuario que configure el Shortcut en iOS
7. **Recordatorio horas semanales (VIERNES)** — si hoy es viernes, informar al usuario: "📋 VIERNES: recordatorio de meter todas las horas de la semana (facturación fin de mes, tanto autónomos como Softgenil)"
8. **Resumen al usuario con leyenda**
9. **Mostrar pendientes activos** — leer `pendientes.json` y mostrar resumen agrupado de los pendientes activos
10. **Comprobar fix OmniRoute** — si existe `documentacion/omniroute_fix_fixed.flag`, avisar al usuario: "✅ El bug de opencode que impedía registrar OmniRoute ya está arreglado (issues #38839/#25630). Se puede reactivar el catálogo dinámico del plugin y quitar los 38 modelos manuales de opencode.jsonc."
### B) El resumen (paso 8) DEBE comenzar SIEMPRE con esta leyenda:
@@ -470,3 +471,31 @@ curl "http://<IP-tailscale-del-PC>:8600/tv/mensaje?texto=<mensaje>"
---
*Creado: 26 Junio 2026 — Actualizado: 30 Jul 2026 (Regla 22: Categorización automática + comando superguardado)*
---
## Regla 24: Impresora de red Brother HL-L2350DW (AUTOMATICO)
Impresora del laboratorio de casa: **Brother HL-L2350DW** en `192.168.50.248` (monocroma). Acepta `image/pwg-raster` y `image/urf`; **NO** acepta PDF.
**El sobremesa no alcanza `192.168.50.x`** (está en la red Digi `192.168.1.x`). Para imprimir un PDF:
```bash
# 1) En el VPS: convertir a PWG con el filtro oficial CUPS (NO usar gs pwgraster)
/usr/lib/cups/filter/mupdftopwg 1 pi doc 1 Resolution=300dpi < doc.pdf > doc.pwg
# 2) Copiar a la Raspberry por el túnel inverso
scp -P 2222 -i /root/.ssh/pi_deploy doc.pwg pi@127.0.0.1:/tmp/doc.pwg
# 3) Enviar por IPP desde la Pi
python3 /tmp/ipp_send.py /tmp/doc.pwg image/pwg-raster # status 0x0000 = aceptado
# 4) Verificar con SNMP (si sube, ha impreso)
python3 /tmp/snmp_get.py 1.3.6.1.2.1.43.10.2.1.4.1.1
```
- Acceso: `ssh -i <contabo_key> root@185.187.169.109` → `ssh -i /root/.ssh/pi_deploy -p 2222 pi@127.0.0.1`.
- **Gotcha**: el device `pwgraster` de Ghostscript genera cabecera inválida (`RaS2PwgRaster`); la impresora acepta el trabajo (`0x0000`) pero no imprime. Usar `mupdftopwg`.
- `/ipp/print` puede dar `0x0411` (too large) en trabajos grandes; usar `/ipp/printer`.
- Detalle de la impresora: `tareas_hogar/00-Indice/Impresora.md`.
---
*Creado: 26 Junio 2026 — Actualizado: 06 Oct 2026 (Regla 24: impresora Brother HL-L2350DW)*

View File

@@ -96,11 +96,17 @@
"name": "Ollama",
"npm": "@ai-sdk/openai-compatible",
"models": {
"gemma4": {
"name": "Gemma 4 Local"
"jj-flash": {
"name": "1 MÁS POTENTE — GLM 4.7 Flash (MoE 29B, ctx 32K, 47 t/s)"
},
"deepseek-coder-v2:16b": {
"name": "DeepSeek Coder Local"
"jj-coder": {
"name": "2 POTENTE — Qwen 2.5 Coder 14B (ctx 8K, 47 t/s)"
},
"deepseek-coder-v2:16b-lite-instruct-q4_K_M": {
"name": "3 MEDIO-RÁPIDO — DeepSeek Coder V2 Lite 16B (100 t/s)"
},
"qwen3:8b": {
"name": "4 LIGERO — Qwen3 8B (rápido, poco VRAM)"
}
},
"options": {

View File

@@ -0,0 +1,130 @@
# Sistema de tareas de las hijas (Inés y Carolina) — Google Drive + IA
Documentación para **reproducir** el sistema en cualquier equipo. Última revisión: 04-oct-2026 (portátil).
---
## 1. Qué es
Ecosistema para gestionar las tareas escolares de las hijas:
- Los **correos de Classroom de Inés** se vigilan y avisan por Telegram.
- Las **tareas entregadas** (fotos/escaneos) se suben a **Google Drive** por asignatura.
- El asistente (opencode/IA) **lee** los documentos, **corrige** y **genera hojas nuevas** de repaso.
## 2. Cuentas y credenciales
| Elemento | Valor |
|----------|-------|
| Google Drive (personal) | `juan.minguez.sanz@gmail.com` |
| Proyecto Google Cloud | `tareasnenas` |
| Client secret | `biblioteca_negocio_prolongo/google/client_secret_personal.json` |
| Token OAuth | `biblioteca_negocio_prolongo/google/token_personal.json` |
| Scopes | `drive`, `gmail.readonly`, `documents` |
| Correo escolar de Inés | `ines.minguezlarhziale@alumnos.fundacionvictoria.edu.es` |
> El `token_personal.json` **se refresca solo** al usarlo (guardar el fichero tras refrescar). Acceso válido desde portátil y sobremesa sin depender del otro equipo.
## 3. Estructura en Google Drive
```
TAREAS/
├── CAROLINA/
│ ├── LENGUA/
│ │ ├── Por hacer/ ← hojas en blanco (SIN soluciones) para que las haga
│ │ └── Respondido/ ← lo ya entregado (escaneos/PDF) y el modelo hecho
│ └── MATEMATICAS/
└── INES/
├── FISICA Y QUÍMICA/<Tema>/ → Doc "FEEDBACK ..." + fotos
├── HISTORIA Y GEOGRAFÍA/ → Doc "Tareas - HISTORIA Y GEOGRAFÍA"
├── LENGUA/ → Doc "Tareas - LENGUA"
├── MATEMÁTICAS/ · INGLÉS/ · E.F/ · MÚSICA/ · PLÁSTICA/ · RELIGIÓN/ · TECNOLOGÍA/ · VALORES/
```
### Convención de organización (IMPORTANTE)
- Cada asignatura tiene subcarpetas **`Por hacer`** (pendiente) y **`Respondido`** (hecho).
- Cuando algo se responde, se mueve de `Por hacer` a `Respondido` (no se deja mezclado).
- Las hojas para las niñas van **sin soluciones**; el asistente guarda las respuestas correctas para corregir.
### Formato de los docs "Tareas - <asignatura>"
```
TAREAS INES - <ASIGNATURA>
Profesor/a: <nombre>
Actualizado: DD/MM/AAAA
TAREAS Y ACTIVIDADES
========================================
[Pendiente|Pasada|Revisar] <titulo>
Tipo: Anuncio|Tarea|Material | Fecha: DD mes AAAA
```
## 4. Bot del VPS (Inés) — `classroom-watcher`
Servicio systemd en el VPS Contabo que vigila el correo de Inés.
| Dato | Valor |
|------|-------|
| Ruta | `/opt/classroom_watcher/` |
| Script | `classroom_watcher.py` |
| Servicio | `classroom-watcher.service` (activo, enabled) |
| Ficheros | `token_personal.json`, `client_secret_personal.json`, `.env` (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`), `seen_messages.json`, `pending_alerts.json`, `watcher.log` |
| Filtro Gmail | `from:ines.minguezlarhziale classroom (tarea OR "Nueva tarea" OR "fecha de entrega" OR "Nuevo anuncio")` |
| Horario avisos | 15:00–22:00 |
| Revisión Gmail | cada 5 min |
| Reaviso | cada 30 min hasta que el usuario responde **S** |
| Asignatura | `detectar_asignatura()` mapea profesor→asignatura y si no, por asunto |
**Diseño intencionado:** el bot **solo avisa por Telegram**. NO vuelca los correos a Google Drive (decisión del usuario, 04-oct-2026). El Drive de tareas se gestiona aparte (entrada manual / por IA).
### Gestión del servicio (en el VPS)
```bash
systemctl status classroom-watcher
systemctl restart classroom-watcher
journalctl -u classroom-watcher -n 50 --no-pager
tail -n 50 /opt/classroom_watcher/watcher.log
```
### Otros servicios relacionados en el VPS
- `/opt/email-watcher/` (`email-watcher.service`, **disabled**) — incidencia EmailService FaccsaMissa de `jminguez@prolongo.es` → grupo IDD Telegram (ámbito empresa, NO hijas).
## 5. Spec 004 (diseño completo, en pausa)
`biblioteca_negocio_prolongo/specs/004-monitor-correo-ines/` (spec + plan + tasks):
- Flujo A: resumen IA de correos escolares → Discord DM de Inés + aviso Telegram al padre.
- Flujo B: entrega de foto en Drive → evaluación IA → mueve a "hechas" o avisa corrección.
- Prerequisitos manuales listados en `plan.md` (Azure AD Graph, Service Account, bot Discord...).
## 6. Cómo reproducirlo en otro equipo
1. Clonar `biblioteca_conocimiento_laboratorio` y `biblioteca_negocio_prolongo`.
2. Asegurar `google/client_secret_personal.json` y `google/token_personal.json` (o regenerar con `python google/google_auth.py`).
3. Instalar deps: `pip install google-api-python-client google-auth-oauthlib pymupdf`.
4. Usar `scripts/tareas_hijas/drive.py` (helpers) para listar/leer/crear/mover en Drive.
5. Para el bot: desplegar `/opt/classroom_watcher` en el VPS + `classroom-watcher.service` (ver §4).
6. Para corregir: descargar los PDF/imágenes, convertirlos a PNG (`pymupdf`) y analizarlos con el MCP **image-vision**; corregir contra la hoja.
## 7. Notas / gotchas
- La consola Windows falla con acentos (`charmap`): usar `python -X utf8` o exportar a fichero.
- Para crear Google Docs desde HTML: `files().create(mimeType='application/vnd.google-apps.document', media=text/html)`.
- Quitar soluciones de un Doc existente: `documents().batchUpdate` con `deleteContentRange` desde la cabecera "SOLUCIONES".
- Mover ficheros: `files().update(fileId, addParents, removeParents)`.
## 8. Conocimiento del Medio (Carolina) — flujo con temario
Carpeta: `TAREAS/CAROLINA/CONOCIMIENTO DEL MEDIO/`
Ciclo de vida de las tareas: **Por hacer → Respondido → Revisado**.
```
CONOCIMIENTO DEL MEDIO/
├── Por hacer/ pendiente para la niña
├── Respondido/ entregado (escaneos/fotos)
├── Revisado/ ya corregido por el asistente (+ Doc FEEDBACK)
└── Temario/ resumen del tema para estudiar + Capturas/ (páginas del libro)
```
Proceso:
1. El temario (páginas del libro) se aporta como capturas; si son del PC suelen estar en `C:\Users\<user>\Pictures\Screenshots`. Se suben a `Temario/Capturas/`.
2. Se crea un Doc `Resumen Tema N — <título>` en `Temario/`.
3. Las tareas entregadas (escaneos en `Respondido/`) se corrigen contra el resumen; al corregir se genera `FEEDBACK Tema N — <título>` y se mueven junto a él a `Revisado/`.
Ejemplo **Tema 1** (04-oct-2026): *La alimentación y la dieta saludable* — funciones de los alimentos (energéticos / constructores / reguladores), tipos por procesamiento (naturales / procesados / ultraprocesados) y dieta saludable (agua **1,5–2 L/día**). Corrección de Carolina: todo bien salvo la cantidad de agua (escribió "12 a 15 L").

View File

@@ -0,0 +1,110 @@
# OmniRoute — Guía autorouting y combos `auto/*`
Fecha: 13-sep-2026 · Estado: documentación estable para replicar en cualquier equipo (sobremesa / portátil / VPS)
---
## 1. Qué es OmniRoute en este setup
OmniRoute es un **proxy/router de modelos** self-hosted que corre en local. Hace routing entre tus proveedores conectados con fallback automático, circuit breakers, y combos virtuales.
- **Servidor**: paquete npm global `omniroute`
- Comando: `omniroute serve` → lanza `node .../omniroute/dist/server-ws.mjs`
- Escucha en: `http://localhost:20128` (OpenAI-compatible `/v1`)
- **Datos**: `C:\Users\juanm\.omniroute\`
- `storage.sqlite` (estado, streaks, kv de combos)
- `call_logs\AAAA-MM-DD\*.json` (cada request ruteado: modelo real elegido, tokens, fallbacks)
- `logs\application\app.log` (routing trace: decisiones, fallbacks, límites)
- `.env` (config servidor, secretos — nunca exponer)
- `server\.pid` / `supervisor\.pid`
- **Supervisor**: `omniroute.mjs serve` es el padre; el worker real es `server-ws.mjs`
- **Sin autostart por tarea programada en sobremesa** (se lanza manualmente). Replicar igual en portátil.
## 2. Estado actual: bug de opencode y los 38 modelos manuales
**Problema raíz**: el plugin `@omniroute/opencode-plugin` no puede registrar providers fuera del catálogo models.dev de opencode → los `auto/*` NO aparecen en el picker por defecto.
- Issues: anomalyco/opencode **#38839** (provider hook skipped for non-catalog providers) y **#25630** (Regression: plugin provider.models() hook no longer populates custom providers, post #25167).
**Workaround aplicado (decisión: mantener hasta que se arregle el bug)**:
- Los **38 modelos `auto/*`** están declarados **a mano** en `~/.config/opencode/opencode.jsonc` → sección `provider.omniroute`,
con label amigable (`Auto ♟ Best Coding`, etc.) y `baseURL: http://localhost:20128/v1`, `npm: @ai-sdk/openai-compatible`.
- El plugin se carga con: `providerId: omniroute`, `baseURL: http://localhost:20128`.
- El **enrutado y el failover funcionan igual aunque los modelos estén a mano**: la lógica de routing vive 100% en el servidor OmniRoute, opencode solo manda el nombre `auto/...` a `localhost:20128`. El picker manual solo afecta al catálogo visible, NO al comport­amiento de routing.
**Check automático del fix**:
- `scripts/check_omniroute_fix.ps1` (tarea programada `OmnirouteFixCheck`, diaria 09:00) consulta los issues #38839/#25630.
- Si alguno está cerrado → crea `documentacion/omniroute_fix_fixed.flag` y actualiza `documentacion/omniroute_fix_status.txt`.
- Cuando exista ese flag: avisar al usuario → reactivar catálogo dinámico del plugin y quitar los 38 modelos manuales de `opencode.jsonc`.
## 3. Cómo funciona el routing `auto/*` (mecánica por detrás)
Los `auto/*` **no son modelos reales**: son **alias virtuales de ruta**. OmniRoute los materializa al vuelo en cada request sin guardarlos en la BD (tabla `combos` vacía).
Flujo real (trace de `app.log`):
```
POST /v1/chat/completions | auto/best-coding | 28 msgs | 71 tools
Virtual auto-combo created: auto/best-coding (2 candidates) ← shortlist puntuada
Combo "auto/best-coding" [auto] with 13 models ← pool total (nested "auto")
auto with nested resolution: 13 total targets
Auto selection: oc/big-pickle | intent=medium task=default | strategy=lkgp | LKGP
Trying model 1/8: oc/big-pickle
ROUTING oc/big-pickle ⇒ opencode/big-pickle ← modelo REAL elegido
Model oc/big-pickle succeeded (880ms, 0 fallbacks)
```
- En cada request el **Auto-Combo Engine puntúa a los candidatos con 15 factores**: salud del provider, cuota restante, coste, latencia, task fit, calidad, disponibilidad de sesión, etc.
- **`lkgp` = Last-Known-Good**: sticky al último target que funcionó mientras siga OK (no cambia por aburrimiento).
- **Fallback automático**: si el elegido falla (429/5xx/timeout), re-engancha al siguiente candidato **dentro del mismo request** (sin que opencode se entere).
- **Recuperación**: circuit breakers/cooldowns auto-reset; el primer éxito limpia el estado de error. Un candidato agotado deja de ganar el scoring y vuelve cuando recupera cuota.
### Mapa de variantes (catalog real, `open-sse/services/autoCombo/builtinCatalog.ts`)
| Modelo | Variante | Optimiza |
|---|---|---|
| `auto` | default | Balanceado (LKGP) |
| `auto/best-coding` | `coding` | Quality-first para código |
| `auto/best-reasoning` | `smart` | Calidad + 10% exploración |
| `auto/best-fast` | `fast` | Menor latencia |
| `auto/best-vision` | `smart` (+filtro visión) | Calidad en inputs visuales |
| `auto/best-chat` | default | Chat general |
| `auto/best-coding-fast` | `fast` | Velocidad en código |
| `auto/best-free` | `cheap` | Solo tier free |
| `auto/best-chaos` | `chaos` | Despacho paralelo top-N |
| `auto/pro-*` | idem | Tier de pago forzado |
| `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/chat` | idem | Variantes básicas |
| `auto/coding:fast`, `auto/coding:pro`, `auto/vision`, `auto/multimodal`, `auto/reasoning` | sufijo | Categoría[:tier] |
| `auto/gemini`, `auto/llama`, `auto/glm`, `auto/claude-sonnet`, `auto/claude-opus`... | familia | Solo esa familia como candidatos |
Los 38 declarados a mano en opencode.jsonc son este catálogo `auto/*`.
## 4. Límites y quotas
- **Límite de contexto del combo `auto/*`: 200.000 tokens (`source=target`)** — confirmado en logs: `Combo context limit: 200000 (source=target)`.
- `source` = lo que opencode envía (conversación + tools)
- `target` = lo que el modelo de destino admite
- **Salida (out)**: sin tope fijo en el combo; depende del modelo real que rute en ese momento.
- **"¿Me bloquean por usarlo varios días?"**: NO. Los bloqueos (circuit breaker / model lockout) solo saltan ante fallos reales repetidos (429/5xx/timeout) y se auto-recuperan. El único bloqueo real sería agotar la cuota de TODOS los candidatos a la vez (cuenta opencode = `big-pickle` + reserva del pool).
## 5. Candidatos actuales para `auto/best-coding` (13-sep-2026)
- **Shortlist directa: 2 candidatos** (log: `2 candidates`). Ganador actual estable: `oc/big-pickle` → provider `opencode` (cuenta opencode, API key `OMNI-1`).
- **Pool anidado: 13 targets** (combo base `auto` con "nested resolution").
- **Intentos de fallback por request: hasta 8** (`Trying model 1/8`); si todos fallan → error al usuario.
- La identidad exacta del 2º candidato **no está persistida** en la BD: la construye el engine en runtime puntuando tus conexiones. Para verla en vivo: `call_logs` o `logs/application/app.log` (líneas con `Auto selection:`/`VRouting`).
## 6. Replicación en el portátil
Para que el asistente del portátil deje todo igual:
1. **Instalar servidor**: `npm i -g omniroute` (mismo paquete que en sobremesa).
2. **Config `.omniroute`**: copiar `C:\Users\juanm\.omniroute\.env` (secretos) y, si se quiere historial, `storage.sqlite` + `call_logs`.
3. **Lanzar**: `omniroute serve` (debe quedar escuchando en `http://localhost:20128`).
4. **Config opencode** (`~/.config/opencode/opencode.jsonc`):
- Plugin: `file:///C:/Users/juanm/AppData/Roaming/opencode/plugins/omniroute/dist/index.js` con `{ "providerId": "omniroute", "baseURL": "http://localhost:20128" }`
- Provider `omniroute`: `npm @ai-sdk/openai-compatible`, `baseURL http://localhost:20128/v1`, los 38 modelos `auto/*` (ver sección 3 para el catálogo; el JSON exacto está en el opencode.jsonc de la sobremesa).
5. **Fix checker**: copiar `scripts/check_omniroute_fix.ps1` + tarea `OmnirouteFixCheck` (diaria 09:00).
6. Comprobar en `logs/application/app.log` que aparece `Combo context limit: 200000 (source=target)`.
## 7. Origen de esta guía
Recopilado 13-sep-2026 en sesión RE (sobremesa): logs reales del servidor, `builtinCatalog.ts` del paquete npm, y verificación en vivo con requests de opencode bajo `omniroute/auto/best-coding`. Memoria Engram asociada (project `biblioteca_conocimiento_laboratorio`): "Cómo funcionan los modelos auto/* de OmniRoute (alias virtuales + routing)". Esta guía se sincroniza a todos los equipos vía git + Engram Cloud para que el asistente pueda aplicarla en el portátil.

View File

@@ -0,0 +1,2 @@
[2026-09-13 23:08] #38839 state=open :: fix: provider hook skipped for non-catalog providers
[2026-09-13 23:08] #25630 state=open :: Regression: plugin provider.models() hook no longer populates custom providers (post #25167)

View File

@@ -0,0 +1,39 @@
# Comprueba si el bug de opencode que impide a los plugins registrar
# providers fuera del catalogo models.dev (OmniRoute) ya esta arreglado.
#
# Issues: anomalyco/opencode #38839 (provider hook skipped for non-catalog providers)
# anomalyco/opencode #25630 (regression: provider.models() no puebla)
#
# Si alguno se cierra, escribe documentacion/omniroute_fix_fixed.flag
# y lo deja registrado para avisar al usuario en la proxima sesion / `re`.
$ErrorActionPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
$docDir = Join-Path $repoRoot "documentacion"
$flagFile = Join-Path $docDir "omniroute_fix_fixed.flag"
$logFile = Join-Path $docDir "omniroute_fix_status.txt"
$issues = @(38839, 25630)
$headers = @{ "User-Agent" = "omniroute-fix-check"; "Accept" = "application/vnd.github+json" }
$lines = @()
$fixed = $false
foreach ($n in $issues) {
try {
$r = Invoke-RestMethod -Uri "https://api.github.com/repos/anomalyco/opencode/issues/$n" -Headers $headers -TimeoutSec 20
$state = $r.state
$title = $r.title
$lines += "[$(Get-Date -Format 'yyyy-MM-dd HH:mm')] #$n state=$state :: $title"
if ($state -eq "closed") { $fixed = $true }
} catch {
$lines += "[$(Get-Date -Format 'yyyy-MM-dd HH:mm')] #$n ERROR: $($_.Exception.Message)"
}
}
$lines | Out-File -FilePath $logFile -Append -Encoding utf8
if ($fixed -and -not (Test-Path $flagFile)) {
"Bug opencode OmniRoute ARREGLADO (ver $logFile)" | Out-File -FilePath $flagFile -Encoding utf8
}

View File

@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
"""drive.py — Helpers para el sistema de tareas de las hijas (Google Drive).
Autentica con el token personal (proyecto Cloud "tareasnenas") y ofrece
utilidades para listar, leer, descargar, crear y mover ficheros en Drive.
Uso rapido:
python scripts/tareas_hijas/drive.py list "TAREAS/CAROLINA/LENGUA"
python scripts/tareas_hijas/drive.py read <DOC_ID> # exporta texto de un Google Doc
python scripts/tareas_hijas/drive.py pdf <FILE_ID> <out.pdf>
python scripts/tareas_hijas/drive.py make "Nombre doc" <FOLDER_ID> fichero.html
python scripts/tareas_hijas/drive.py move <FILE_ID> <FOLDER_ID>
Notas:
- Ejecutar con `python -X utf8` en Windows (evita errores de acentos).
- El token se refresca solo y se re-guarda en disco.
"""
from __future__ import annotations
import io
import sys
from pathlib import Path
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload
# Ruta por defecto al token personal dentro del repo de negocio.
DEFAULT_TOKEN = Path(
r"C:\Users\juanm\Documents\GitHub\biblioteca_negocio_prolongo\google\token_personal.json"
)
def _creds(token_path: Path = DEFAULT_TOKEN):
creds = Credentials.from_authorized_user_file(str(token_path))
if creds.expired and creds.refresh_token:
creds.refresh(Request())
token_path.write_text(creds.to_json(), encoding="utf-8")
return creds
def drive(token_path: Path = DEFAULT_TOKEN):
return build("drive", "v3", credentials=_creds(token_path), static_discovery=False)
def docs(token_path: Path = DEFAULT_TOKEN):
return build("docs", "v1", credentials=_creds(token_path), static_discovery=False)
def find_folder(name: str, parent: str | None = None, svc=None) -> str:
"""Devuelve el id de la primera carpeta con ese nombre (opcionalmente bajo `parent`)."""
svc = svc or drive()
q = f"name='{name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
if parent:
q += f" and '{parent}' in parents"
r = svc.files().list(q=q, fields="files(id,name)").execute()
files = r.get("files", [])
if not files:
raise SystemExit(f"[ERROR] Carpeta no encontrada: {name}")
return files[0]["id"]
def list_folder(fid: str, svc=None):
svc = svc or drive()
r = svc.files().list(
q=f"'{fid}' in parents and trashed=false",
fields="files(id,name,mimeType,size,modifiedTime)", orderBy="folder,name",
).execute()
for f in r.get("files", []):
kind = "DIR " if f["mimeType"] == "application/vnd.google-apps.folder" else "file"
print(f" {kind} {f['name']} id={f['id']}")
def read_doc(fid: str, svc=None) -> str:
svc = svc or drive()
return svc.files().export(fileId=fid, mimeType="text/plain").execute().decode("utf-8")
def download(fid: str, out: Path, svc=None):
svc = svc or drive()
req = svc.files().get_media(fileId=fid)
with io.FileIO(out, "wb") as fh:
dl = MediaIoBaseDownload(fh, req)
done = False
while not done:
_, done = dl.next_chunk()
def create_doc_from_html(name: str, parent: str, html: str, svc=None) -> dict:
svc = svc or drive()
media = MediaIoBaseUpload(io.BytesIO(html.encode("utf-8")), mimetype="text/html", resumable=False)
return svc.files().create(
body={"name": name, "parents": [parent], "mimeType": "application/vnd.google-apps.document"},
media_body=media, fields="id,name,webViewLink",
).execute()
def move(fid: str, new_parent: str, old_parent: str | None = None, svc=None):
svc = svc or drive()
return svc.files().update(
fileId=fid, addParents=new_parent, removeParents=old_parent, fields="id,parents"
).execute()
def trash(fid: str, svc=None):
svc = svc or drive()
return svc.files().update(fileId=fid, body={"trashed": True}).execute()
def strip_solutions(doc_id: str, marker: str = "SOLUCIONES", svc=None):
"""Borra todo el contenido de un Google Doc desde la cabecera `marker` hasta el final."""
svc = svc or docs()
d = svc.documents().get(documentId=doc_id).execute()
start = None
for el in d["body"]["content"]:
para = el.get("paragraph")
if not para:
continue
text = "".join(r.get("textRun", {}).get("content", "") for r in para.get("elements", []))
if marker in text:
start = el["startIndex"]
break
if start is None:
print("[aviso] No se encontró el marcador:", marker)
return
end = d["body"]["content"][-1]["endIndex"] - 1
if end > start:
svc.documents().batchUpdate(documentId=doc_id, body={"requests": [
{"deleteContentRange": {"range": {"startIndex": start, "endIndex": end}}}]}).execute()
print("[ok] Contenido desde '%s' eliminado" % marker)
def _main(argv):
if not argv:
print(__doc__)
return
cmd = argv[0]
if cmd == "list":
list_folder(argv[1] if len(argv) > 1 else find_folder("TAREAS"))
elif cmd == "find":
print(find_folder(argv[1], argv[2] if len(argv) > 2 else None))
elif cmd == "read":
print(read_doc(argv[1]))
elif cmd == "pdf":
download(argv[1], Path(argv[2]))
print("descargado", argv[2])
elif cmd == "make":
html = Path(argv[3]).read_text(encoding="utf-8")
print(create_doc_from_html(argv[1], argv[2], html)["webViewLink"])
elif cmd == "move":
move(argv[1], argv[2], argv[3] if len(argv) > 3 else None)
print("movido")
elif cmd == "strip":
strip_solutions(argv[1])
else:
print(__doc__)
if __name__ == "__main__":
_main(sys.argv[1:])

14
scripts/tunel-pi-api.ps1 Normal file
View File

@@ -0,0 +1,14 @@
# tunel-pi-api.ps1 — Tuneles PowerShell -> Pi (minipc) via VPS.
# Sin claves nuevas: reutiliza la llave contabo (PC) y pi_deploy (VPS).
# Uso:
# .\tunel-pi-api.ps1 api # http://localhost:8600 -> tv_api de la Pi (Ctrl+C cierra)
# .\tunel-pi-api.ps1 shell # shell interactiva en la Pi
param([string]$Modo = "api")
$KEY = "C:\Users\juanm\Documents\GitHub\contabo"
$VPS = "root@185.187.169.109"
if ($Modo -eq "shell") {
ssh -i $KEY -t $VPS "ssh -i /root/.ssh/pi_deploy -p 2222 pi@127.0.0.1"
} else {
Write-Output "API de la Pi en http://localhost:8600 (Ctrl+C para cerrar)"
ssh -i $KEY -L 8600:127.0.0.1:8600 -N $VPS
}

View File

@@ -247,7 +247,7 @@ def formatear_titulos(resultados):
if not resultados:
return ["No se encontraron resultados para ese termino."]
lineas = [f"🎬 <b>Resultados globales ({len(resultados)}):</b>",
"Responde <b>/bajar &lt;numero&gt;</b> para ver torrents.", ""]
"Responde <b>/torrents &lt;numero&gt;</b> para ver torrents.", ""]
for i, r in enumerate(resultados, 1):
icono = "📺" if r["type"] == "series" else "🎬"
y = f" ({r['year']})" if r.get("year") else ""

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-06
**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`

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-08
**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`

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-09
**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`

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-10
**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`

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-13
**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`

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-06",
"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"
}
]
}

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-08",
"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"
}
]
}

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-09",
"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"
}
]
}

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-10",
"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"
}
]
}

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-13",
"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"
}
]
}

View File

@@ -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()
@@ -252,20 +253,29 @@ def formatear_peliculas(resultados):
calidad = "4K" if "4k" in (it.get("name") or "").lower() or \
"2160p" in (it.get("name") or "").lower() else "HD"
idioma = "cast." if it.get("castellano") else "ing."
enlace = it.get("enlace") or ""
lineas.append(f"<b>{i}.</b> {it['clean']}{y} — {calidad}, {idioma}")
if enlace:
lineas.append(f" <code>descargar {enlace}</code>")
lineas.append("")
lineas.append("Responde con el <b>numero</b> para descargar.")
lineas.append("Responde <b>/torrents NUMERO</b> para ver opciones y descargar.")
return trocear_texto(lineas)
def aplanar_series(grupos):
"""Devuelve lista plana de episodios en el mismo orden de visualizacion
(grupos ordenados, episodios ordenados). La usa el bot para que el numero
elegido con /torrents coincida con lo mostrado."""
planos = []
for base in sorted(grupos, key=lambda x: x.lower()):
for it in grupos[base]:
planos.append(it)
return planos
def formatear_series(grupos):
"""Formatea resultados de series como lista de mensajes Telegram numerados."""
if not grupos:
return ["No se encontraron series con ese termino."]
lineas = []
num_global = 1
for base in sorted(grupos, key=lambda x: x.lower()):
eps = grupos[base]
temp_max = max(parsear_episodio(e["name"])[0] for e in eps)
@@ -273,7 +283,6 @@ def formatear_series(grupos):
lineas.append("")
temp_actual = 0
num_global = 1
for it in eps:
temp, cap = parsear_episodio(it["name"])
if temp != temp_actual:
@@ -282,18 +291,17 @@ def formatear_series(grupos):
lineas.append(f"<b>Temporada {temp}:</b>")
resto = _limpiar_titulo_ep(it["name"][SERIE_EP_RE.search(it["name"]).end():]) \
if SERIE_EP_RE.search(it["name"]) else it["clean"]
enlace = it.get("enlace") or ""
if temp > 0:
etiqueta = f"S{temp:02d}E{cap:02d}"
else:
etiqueta = it["clean"]
lineas.append(f" <b>{num_global}.</b> {etiqueta} — {resto}")
if enlace:
lineas.append(f" <code>descargar {enlace}</code>")
idioma = " · cast." if it.get("castellano") else ""
lineas.append(f" <b>{num_global}.</b> {etiqueta} — {resto}{idioma}")
num_global += 1
lineas.append("")
lineas.append("Responde con el <b>numero</b> de episodio para descargar.")
lineas.append("Responde <b>/torrents NUMERO</b> de episodio para ver opciones y descargar.")
lineas.append("O <b>/temporada N</b> (temporada completa) o <b>/bajar X-Y</b> (rango).")
return trocear_texto(lineas)
@@ -330,10 +338,9 @@ def formatear_novedades(items):
y = f" ({it['year']})" if it["year"] else ""
tipo = "📺" if it["tipo"] == "serie" else "🎬"
idioma = "cast." if it.get("castellano") else "ing."
enlace = it.get("enlace") or ""
lineas.append(f"{tipo} <b>{i}.</b> {it['clean']}{y} — {idioma}")
if enlace:
lineas.append(f" <code>descargar {enlace}</code>")
lineas.append("")
lineas.append("Responde <b>/torrents NUMERO</b> para ver opciones y descargar.")
return trocear_texto(lineas)

220
vigilancia/aspiradora.py Normal file
View File

@@ -0,0 +1,220 @@
"""Aspiradora Dreame D10 Plus (dreame.vacuum.r2205) via python-miio.
Usa el spec MIoT REAL del r2205 (NO el de mc1808: ahi la bateria esta en
otro siid y por eso marcaba 2%). Lee IP/token del .env del directorio.
Uso: python aspiradora.py estado|start|stop|home|pause|locate
"""
import os
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
if BASE not in sys.path:
sys.path.insert(0, BASE)
# Spec https://home.miot-spec.com/spec/dreame.vacuum.r2205
MAP_R2205 = {
"battery_level": {"siid": 3, "piid": 1},
"charging_state": {"siid": 3, "piid": 2},
"device_fault": {"siid": 2, "piid": 2},
"device_status": {"siid": 2, "piid": 1},
"cleaning_mode": {"siid": 4, "piid": 4},
"cleaning_time": {"siid": 4, "piid": 2},
"cleaning_area": {"siid": 4, "piid": 3},
"volume": {"siid": 7, "piid": 1},
"voice_id": {"siid": 7, "piid": 2},
"set_voice": {"siid": 7, "aiid": 4},
"auto_collect": {"siid": 15, "piid": 1},
"start_collect": {"siid": 15, "aiid": 1},
"first_clean_time": {"siid": 12, "piid": 1},
"total_clean_time": {"siid": 12, "piid": 2},
"total_clean_times": {"siid": 12, "piid": 3},
"total_clean_area": {"siid": 12, "piid": 4},
"brush_left_time": {"siid": 9, "piid": 1},
"brush_life_level": {"siid": 9, "piid": 2},
"brush_left_time2": {"siid": 10, "piid": 1},
"brush_life_level2": {"siid": 10, "piid": 2},
"filter_life_level": {"siid": 11, "piid": 1},
"filter_left_time": {"siid": 11, "piid": 2},
"mop_mode": {"siid": 4, "piid": 5},
"start_clean": {"siid": 2, "aiid": 1},
"stop_clean": {"siid": 4, "aiid": 2},
"pause": {"siid": 2, "aiid": 2},
"home": {"siid": 3, "aiid": 1},
"locate": {"siid": 7, "aiid": 1},
"play_sound": {"siid": 7, "aiid": 2},
}
ESTADOS = {
1: "limpiando", 2: "inactiva", 3: "pausada", 4: "error",
5: "volviendo a la base", 6: "cargando", 7: "fregando",
11: "creando mapa", 12: "limpieza de zona", 13: "carga completa",
14: "actualizando",
}
CARGAS = {1: "cargando", 2: "no cargando", 5: "volviendo a cargar"}
MODOS = {0: "silencioso", 1: "estándar", 2: "medio", 3: "fuerte"}
AGUAS = {1: "bajo", 2: "medio", 3: "alto"}
def _env(nombre, defecto=""):
valor = os.getenv(nombre, defecto).strip()
if not valor:
try:
with open(os.path.join(BASE, ".env")) as f:
for linea in f:
linea = linea.strip()
if linea and not linea.startswith("#") and "=" in linea:
k, v = linea.split("=", 1)
if k.strip() == nombre:
valor = v.strip()
break
except OSError:
pass
return valor
def conectar():
from miio.integrations.vacuum.dreame.dreamevacuum_miot import (
MIOT_MAPPING,
DreameVacuum,
)
ip = _env("DREAME_IP", "192.168.50.122")
token = _env("DREAME_TOKEN")
if not token:
raise RuntimeError("Falta DREAME_TOKEN en el .env")
# El mapping se resuelve por modelo: se registra el r2205 (spec real).
MIOT_MAPPING.setdefault("dreame.vacuum.r2205", MAP_R2205)
return DreameVacuum(ip, token, model="dreame.vacuum.r2205")
def estado():
vac = conectar()
d = vac.status().data
cod = d.get("device_status")
texto_estado = ESTADOS.get(cod, f"codigo {cod}")
carga = CARGAS.get(d.get("charging_state"),
f"codigo {d.get('charging_state')}")
modo = MODOS.get(d.get("cleaning_mode"), d.get("cleaning_mode"))
fallo = d.get("device_fault")
extra = "" if fallo in (0, None) else f"\n⚠️ Fallo: <b>{fallo}</b>"
return (f"🤖 Dreame D10 Plus: <b>{texto_estado}</b>\n"
f"🔋 Bateria: <b>{d.get('battery_level')}%</b> ({carga})\n"
f"🧹 Modo: {modo} | "
f"Tiempo: {d.get('cleaning_time')} min | "
f"Area: {d.get('cleaning_area')} m2{extra}")
def _get(vac, nombre):
d = vac.status().data
return d.get(nombre)
def stats():
vac = conectar()
d = vac.status().data
return (f"📊 <b>Estadísticas Dreame</b>\n"
f"Limpiezas totales: <b>{d.get('total_clean_times')}</b>\n"
f"Tiempo total: <b>{d.get('total_clean_time')} min</b>\n"
f"Area total: <b>{d.get('total_clean_area')} m2</b>")
def consumibles():
vac = conectar()
d = vac.status().data
return (f"🔧 <b>Consumibles</b>\n"
f"Cepillo: <b>{d.get('brush_life_level')}%</b> "
f"({d.get('brush_left_time')}h restantes)\n"
f"Lateral: <b>{d.get('brush_life_level2')}%</b> "
f"({d.get('brush_left_time2')}h restantes)\n"
f"Filtro: <b>{d.get('filter_life_level')}%</b> "
f"({d.get('filter_left_time')}h restantes)")
def auto_estado():
vac = conectar()
d = vac.status().data
return ("🗑️ Vaciado automático: "
f"<b>{'ON' if d.get('auto_collect') else 'OFF'}</b>")
def voz_estado():
vac = conectar()
d = vac.status().data
return (f"🔊 Voz actual: <b>{d.get('voice_id')}</b> | "
f"Volumen: <b>{d.get('volume')}</b>")
def main(argv):
if len(argv) < 2:
print("Uso: aspiradora.py estado|start|stop|home|pause|locate|"
"vaciar|auto|stats|consumibles|potencia|agua|voz")
return 1
cmd = argv[1].lower()
arg = argv[2] if len(argv) > 2 else ""
try:
vac = conectar()
if cmd == "estado":
print(estado().replace("<b>", "").replace("</b>", ""))
elif cmd == "start":
vac.start()
print("Limpieza iniciada.")
elif cmd == "stop":
vac.stop()
print("Limpieza detenida.")
elif cmd == "home":
vac.home()
print("Volviendo a la base.")
elif cmd == "pause":
vac.call_action("pause")
print("Pausada.")
elif cmd == "locate":
vac.call_action("play_sound")
print("Emitiendo sonido de localizacion.")
elif cmd == "vaciar":
vac.call_action("start_collect")
print("Vaciando en la base.")
elif cmd == "auto":
if arg.lower() in ("on", "1"):
vac.set_property_by(15, 1, 1)
print("Vaciado automatico activado.")
elif arg.lower() in ("off", "0"):
vac.set_property_by(15, 1, 0)
print("Vaciado automatico desactivado.")
else:
print(auto_estado().replace("<b>", "").replace("</b>", ""))
elif cmd == "stats":
print(stats().replace("<b>", "").replace("</b>", ""))
elif cmd == "consumibles":
print(consumibles().replace("<b>", "").replace("</b>", ""))
elif cmd == "potencia":
if arg.isdigit() and int(arg) in MODOS:
vac.set_property_by(4, 4, int(arg))
print(f"Potencia: {MODOS[int(arg)]}.")
else:
d = vac.status().data
print(f"Potencia actual: {MODOS.get(d.get('cleaning_mode'))} "
f"(0-3).")
elif cmd == "agua":
if arg.isdigit() and int(arg) in AGUAS:
vac.set_property_by(4, 5, int(arg))
print(f"Agua: {AGUAS[int(arg)]}.")
else:
d = vac.status().data
print(f"Agua actual: {AGUAS.get(d.get('mop_mode'))} (1-3).")
elif cmd == "voz":
if arg:
vac.set_voice(arg)
print(f"Voz cambiada a {arg} (si el pack existe).")
else:
print(voz_estado().replace("<b>", "").replace("</b>", ""))
else:
print("Uso: aspiradora.py estado|start|stop|home|pause|locate|"
"vaciar|auto|stats|consumibles|potencia|agua|voz")
return 1
except Exception as e:
print(f"ERROR: {e}")
return 2
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -35,13 +35,30 @@ DEVICE_ALIASES = {
"carolina": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
"caroline": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
"caroline tablet": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
"tele": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
"television": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
"tv salon": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
"xbox": {"name": "XBOX", "mac": "A8:8C:3E:7C:F3:C7"},
}
TIPOS = (
("📺", ("tele", "tv", "samsung", "lg", "androidtv", "bravia", "philips")),
("🎮", ("xbox", "playstation", "ps4", "ps5", "nintendo", "switch")),
("📱", ("iphone", "android", "galaxy", "xiaomi", "redmi", "pixel")),
("💻", ("pc", "laptop", "minguez", "juanjer", "nahid")),
("🏠", ("tapo", "dreame", "vacuum", "echo", "alexa", "google", "nest")),
)
def _env(name, default=""):
return os.getenv(name, FILE_ENV.get(name, default)).strip()
# Bloqueo 24/7 toda la semana (0=domingo..6=sabado). Sin esto la libreria
# aplica su timemap por defecto (solo miercoles/jueves por horas).
TIMEMAP_24_7 = "<".join(f"W{d}E23590000" for d in range(7))
def normalizar_nombre(nombre):
value = unicodedata.normalize("NFKD", nombre or "")
value = "".join(char for char in value if not unicodedata.combining(char))
@@ -93,6 +110,7 @@ async def _set_block(mac, name, blocked):
rule = ParentalControlRule(
mac=mac,
name=name if blocked else None,
timemap=TIMEMAP_24_7,
type=PCRuleType.BLOCK if blocked else PCRuleType.REMOVE,
)
return await router.async_set_state(rule)
@@ -106,12 +124,177 @@ def parental_status():
return json.dumps(data, ensure_ascii=False, indent=2, default=str)
def _mac_en_vivo(pistas):
"""Busca en los leases DHCP actuales un dispositivo cuyo hostname
contenga alguna pista. Devuelve (ip, mac, hostname) o None.
Sirve para tablets con MAC privada aleatoria (la MAC guardada rota)."""
try:
from router_control import _inventario
except Exception:
return None
try:
devices, _by_mac = _inventario()
except Exception:
return None
for ip, mac, nombre in devices:
n = normalizar_nombre(nombre)
for p in pistas:
if p and (p in n or n in p):
return ip, mac, nombre
return None
def _leer_reglas():
"""Lee las reglas parentales del router como dict mac -> campos."""
from router_control import _run
def get(var):
out = _run(f"nvram get {var}")
if out.startswith("Error"):
raise RuntimeError(out)
return out
macs = [x for x in get("MULTIFILTER_MAC").split(">") if x]
names = get("MULTIFILTER_DEVICENAME").split(">")
maps = get("MULTIFILTER_MACFILTER_DAYTIME_V2").split(">")
types = get("MULTIFILTER_ENABLE").split(">")
reglas = {}
for i, mac in enumerate(macs):
reglas[mac.upper()] = {
"name": names[i] if i < len(names) else "",
"timemap": maps[i] if i < len(maps) else TIMEMAP_24_7,
"type": types[i] if i < len(types) else "2",
}
return reglas
def fijar_bloqueos(cambios):
"""Aplica varios (mac, name, blocked) con una sola lectura-modificacion-
escritura: varios bloqueos seguidos no se pisan entre si."""
from router_control import _run
import shlex
reglas = _leer_reglas()
for mac, name, blocked in cambios:
mac = (mac or "").upper()
if not mac:
continue
if blocked:
reglas[mac] = {"name": name or "", "timemap": TIMEMAP_24_7,
"type": "2"}
else:
reglas.pop(mac, None)
orden = list(reglas)
cmds = [
f"nvram set MULTIFILTER_MAC={shlex.quote('>'.join(orden))}",
"nvram set MULTIFILTER_DEVICENAME=" + shlex.quote(
">".join(reglas[m]["name"] for m in orden)),
"nvram set MULTIFILTER_MACFILTER_DAYTIME_V2=" + shlex.quote(
">".join(reglas[m]["timemap"] for m in orden)),
f"nvram set MULTIFILTER_ENABLE={shlex.quote('>'.join(reglas[m]['type'] for m in orden))}",
"nvram commit",
"service restart_firewall",
]
_run("; ".join(cmds))
return _leer_reglas()
def _icono(nombre):
n = normalizar_nombre(nombre)
for icono, claves in TIPOS:
if any(c in n for c in claves):
return icono
return "🔌"
def listar_dispositivos(limite=14):
"""Inventario vivo: leases DHCP + alias conocidos + estado de bloqueo.
Devuelve lista de dicts (nombre, mac, ip, tipo, bloqueado)."""
try:
from router_control import _inventario, _nombre_por_mac
except Exception:
return []
try:
devices, _by_mac = _inventario()
except Exception:
devices = []
try:
reglas = _leer_reglas()
except Exception:
reglas = {}
vistos, salida = set(), []
for ip, mac, nombre in devices:
if mac in vistos:
continue
vistos.add(mac)
bonito = nombre
try:
bonito = _nombre_por_mac(mac)
except Exception:
pass
if bonito in ("pendiente-identificar", "*"):
bonito = nombre
salida.append({
"nombre": bonito, "mac": mac, "ip": ip,
"tipo": _icono(f"{bonito} {nombre}"),
"bloqueado": mac in reglas,
})
for alias, info in DEVICE_ALIASES.items():
if info["mac"] in vistos:
continue
vistos.add(info["mac"])
salida.append({
"nombre": f"{info['name']} (alias)", "mac": info["mac"],
"ip": "", "tipo": _icono(info["name"]),
"bloqueado": info["mac"] in reglas,
})
orden = {"📺": 0, "🎮": 1, "📱": 2, "💻": 3, "🏠": 4, "🔌": 5}
salida.sort(key=lambda d: (orden.get(d["tipo"], 9),
d["bloqueado"], d["nombre"].lower()))
return salida[:limite]
def bloquear_mac(mac, blocked, nombre=""):
"""Bloquea/desbloquea por MAC directa (regla 24/7 + iptables + verif)."""
from router_control import bloqueo_red
mac = (mac or "").upper()
if not mac:
raise ValueError("MAC vacia")
fijar_bloqueos([(mac, nombre or mac, blocked)])
reglas = _leer_reglas()
dentro = mac in reglas if blocked else mac not in reglas
marca = "✅" if dentro else "❌"
base = (f"{'Bloqueado' if blocked else 'Activado'}: "
f"{nombre or mac} ({mac}) {marca}")
try:
return f"{base}\n{bloqueo_red(nombre or mac, mac, blocked)}"
except Exception as e:
return f"{base}\n⚠️ Sin verificacion de red: {e}"
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']}"
mac = device["mac"]
etiqueta = device["name"]
vivo = _mac_en_vivo((normalizar_nombre(nombre),
normalizar_nombre(etiqueta)))
if vivo and vivo[1].upper() != mac.upper():
mac = vivo[1]
etiqueta = f"{etiqueta} [{vivo[2]} {vivo[0]}]"
try:
reglas = fijar_bloqueos([(mac, device["name"], blocked)])
except Exception as e:
return f"❌ No se pudo aplicar la regla: {e}"
dentro = mac.upper() in reglas if blocked else mac.upper() not in reglas
marca = "✅" if dentro else "❌"
base = (f"{'Bloqueado' if blocked else 'Activado'}: {etiqueta} "
f"({mac}) {marca}")
try:
from router_control import bloqueo_red
return f"{base}\n{bloqueo_red(nombre, mac, blocked)}"
except Exception as e:
return f"{base}\n⚠️ Sin verificacion de red: {e}"
async def _aimesh_data():

View File

@@ -8,6 +8,7 @@ exactamente igual. Se importa pi_bot de forma perezosa para evitar ciclos.
"""
import json
import sys
import time
API = "https://api.telegram.org/bot"
@@ -17,7 +18,7 @@ PREFIJO = "m:"
def _bot_token():
import pi_bot
pi_bot = _pi()
return pi_bot.TOKEN
@@ -25,8 +26,19 @@ def _api(method):
return f"{API}{_bot_token()}/{method}"
def _log(linea):
def _pi():
"""Devuelve el modulo vivo de pi_bot. El bot corre como __main__, asi
que `import pi_bot` cargaria una SEGUNDA copia con estado vacio:
hay que usar sys.modules['__main__'] cuando sea el bot."""
main = sys.modules.get("__main__")
if main is not None and hasattr(main, "ultima_titulos"):
return main
import pi_bot
return pi_bot
def _log(linea):
pi_bot = _pi()
try:
pi_bot.log(f"menu: {linea}")
except Exception:
@@ -35,7 +47,7 @@ def _log(linea):
def _txt(codigo, chat_id):
"""Recupera el estado de la ultima busqueda de pi_bot para este chat."""
import pi_bot
pi_bot = _pi()
try:
if codigo == "ultimos_titulos":
estado = pi_bot.ultima_titulos.get(chat_id) or {}
@@ -69,9 +81,12 @@ def teclado_principal():
[
_btn("📺 TV", PREFIJO + "cat:tv"),
_btn("👪 Control parental", PREFIJO + "cat:parental"),
_btn("🛠️ Sistema", PREFIJO + "cat:sistema"),
_btn("🏠 Domótica", PREFIJO + "cat:domotica"),
],
[
_btn("🛠️ Sistema", PREFIJO + "cat:sistema"),
_btn("📚 Ayuda (texto)", PREFIJO + "acc:ayuda"),
],
[_btn("📚 Ayuda (texto)", PREFIJO + "acc:ayuda")],
])
@@ -91,6 +106,10 @@ def teclado_videoclub(chat_id):
if titulos:
filas.append([
_btn("🧲 Torrents…", PREFIJO + "sub:torrents"),
])
torrents = _txt("ultimos_torrents", chat_id)
if torrents and torrents.get("torrents"):
filas.append([
_btn("⬇️ Bajar…", PREFIJO + "sub:bajar"),
])
filas.append(FILA_VOLVER)
@@ -103,15 +122,97 @@ def teclado_nas():
_btn("📥 Panel descargas", PREFIJO + "acc:descargas"),
_btn("➕ Añadir enlace", PREFIJO + "txt:descargar"),
],
FILA_VOLVER,
])
def teclado_domotica():
return _mk([
[
_btn("🔌 Termo ON", PREFIJO + "acc:termo:on"),
_btn("🔌 Termo OFF", PREFIJO + "acc:termo:off"),
_btn("🔌 Termo estado", PREFIJO + "acc:termo:estado"),
_btn("🤖 Aspiradora", PREFIJO + "cat:aspi"),
_btn("🔌 Termo", PREFIJO + "cat:termo"),
],
[
_btn("🖥️ Pantalla", PREFIJO + "cat:pantalla"),
_btn("🛏️ Mesillas", PREFIJO + "cat:mesillas"),
],
FILA_VOLVER,
])
def teclado_aspi():
return _mk([
[
_btn("📊 Estado", PREFIJO + "acc:aspiradora:estado"),
_btn("🧹 Aspirar", PREFIJO + "acc:aspiradora:start"),
],
[
_btn("⏸️ Pausar", PREFIJO + "acc:aspiradora:pause"),
_btn("🏠 A la base", PREFIJO + "acc:aspiradora:home"),
],
[
_btn("🗑️ Vaciar", PREFIJO + "acc:aspiradora:vaciar"),
_btn("📊 Stats", PREFIJO + "acc:aspiradora:stats"),
],
[
_btn("⚡ Potencia…", PREFIJO + "cat:potencia"),
_btn("🔧 Consumibles", PREFIJO + "acc:aspiradora:consumibles"),
],
[_btn("⬅️ Domótica", PREFIJO + "cat:domotica")],
])
def teclado_potencia():
return _mk([
[
_btn("🔈 Silencioso", PREFIJO + "acc:potencia:0"),
_btn("🔉 Estándar", PREFIJO + "acc:potencia:1"),
],
[
_btn("🔊 Medio", PREFIJO + "acc:potencia:2"),
_btn("📢 Fuerte", PREFIJO + "acc:potencia:3"),
],
[_btn("⬅️ Aspiradora", PREFIJO + "cat:aspi")],
])
def teclado_termo():
return _mk([
[
_btn("🔌 ON", PREFIJO + "acc:termo:on"),
_btn("🔌 OFF", PREFIJO + "acc:termo:off"),
_btn("📊 Estado", PREFIJO + "acc:termo:estado"),
],
[_btn("⬅️ Domótica", PREFIJO + "cat:domotica")],
])
def teclado_pantalla():
return _mk([
[
_btn("🖥️ ON", PREFIJO + "acc:pantalla:on"),
_btn("🖥️ OFF", PREFIJO + "acc:pantalla:off"),
_btn("📊 Estado", PREFIJO + "acc:pantalla:estado"),
],
[_btn("⬅️ Domótica", PREFIJO + "cat:domotica")],
])
def teclado_mesillas():
return _mk([
[_btn("📊 Estado mesillas", PREFIJO + "acc:mesilla:estado")],
[
_btn("🛏️ Juanjo ON", PREFIJO + "acc:mesilla:juanjo:on"),
_btn("🛏️ Juanjo OFF", PREFIJO + "acc:mesilla:juanjo:off"),
],
[
_btn("🛏️ Nahid ON", PREFIJO + "acc:mesilla:nahid:on"),
_btn("🛏️ Nahid OFF", PREFIJO + "acc:mesilla:nahid:off"),
],
[_btn("⬅️ Domótica", PREFIJO + "cat:domotica")],
])
def teclado_tv():
return _mk([
[
@@ -125,7 +226,12 @@ def teclado_tv():
def teclado_parental():
return _mk([
[
_btn("🔒 Bloquear tablets", PREFIJO + "acc:tablets:bloquear"),
_btn("🔓 Desbloquear tablets", PREFIJO + "acc:tablets:desbloquear"),
],
[_btn("📋 Listar estado", PREFIJO + "acc:parental_listar")],
[_btn("📺 Bloquear dispositivo…", PREFIJO + "acc:dispositivos")],
[
_btn("🔒 Desactivar…", PREFIJO + "sub:desactivar"),
_btn("🔓 Activar…", PREFIJO + "sub:activar"),
@@ -199,13 +305,46 @@ def teclado_minutos_pausa(nombre):
])
# Snapshot de la lista de dispositivos por chat (para toggle por numero).
menu_dispositivos = {}
def teclado_dispositivos(lista):
filas = []
for i, d in enumerate(lista, start=1):
marca = "🔒" if d["bloqueado"] else "🔓"
extra = f" {d['ip']}" if d.get("ip") else ""
filas.append([_btn(f"{i}. {marca} {d['tipo']} {d['nombre'][:28]}{extra}",
PREFIJO + f"acc:blqdev:{i}")])
filas.append([_btn("⬅️ Control parental", PREFIJO + "cat:parental")])
return _mk(filas)
def _mostrar_dispositivos(chat_id, message_id):
pi_bot = _pi()
try:
from asus_api_control import listar_dispositivos
lista = listar_dispositivos()
except Exception as e:
_enviar_nuevo(chat_id, f"❌ Error listando dispositivos: {e}", None)
return
if not lista:
_enviar_nuevo(chat_id, "No hay dispositivos en red.", None)
return
menu_dispositivos[chat_id] = lista
_enviar_edit(chat_id, message_id,
"📺 <b>Bloquear dispositivo</b>\nPulsa el número para "
"bloquear/desbloquear:",
teclado_dispositivos(lista))
def teclado_resultados(lista, prefijo):
filas = []
for i, item in enumerate(lista[:12], start=1):
nombre = item.get("name") or item.get("filename") or item.get("title") or item.get("clean") or f"Opción {i}"
filas.append([_btn(f"{i}. {nombre[:40]}", PREFIJO + f"acc:{prefijo}:{i}")])
filas.append([_btn("⬅️ Videoclub", PREFIJO + "cat:videoclub")])
return filas
return _mk(filas)
def teclado_cancelar():
@@ -217,13 +356,16 @@ def teclado_cancelar():
def _enviar_edit(chat_id, message_id, texto, teclado):
import requests
try:
requests.post(_api("editMessageText"), data={
r = requests.post(_api("editMessageText"), data={
"chat_id": chat_id,
"message_id": message_id,
"text": texto,
"parse_mode": "HTML",
"reply_markup": json.dumps(teclado),
}, timeout=10)
d = r.json()
if not d.get("ok"):
_log(f"edit {message_id} fallo: {d.get('description')}")
except Exception as e:
_log(f"edit error: {e}")
@@ -254,11 +396,46 @@ def _cerrar_menu(chat_id, message_id):
_log(f"cerrar menu error: {e}")
def _disp(chat_id, message_id, comando):
"""Ejecuta un comando del bot y cierra el menu (opcion elegida)."""
import pi_bot
def _disp(chat_id, message_id, comando, categoria=None):
"""Ejecuta un comando, cierra el menu viejo y devuelve al menu."""
pi_bot = _pi()
pi_bot.procesar(comando, chat_id)
_cerrar_menu(chat_id, message_id)
_reenviar_menu(chat_id, categoria)
def _fin_accion(chat_id, categoria=None):
"""Tras una accion terminal: deja ver el resultado, limpia toda la
pantalla y devuelve al menu de la categoria."""
import time as _time
pi_bot = _pi()
_time.sleep(4)
try:
pi_bot.limpiar_pantalla(chat_id)
except Exception:
pass
_reenviar_menu(chat_id, categoria)
def _reabrir(chat_id, message_id, categoria, comando):
"""Para consultas (estado, listar): ejecuta, borra los mensajes viejos
dejando solo el ultimo resultado y muestra de nuevo el menu de la
categoria (con su tecla de volver operativa)."""
pi_bot = _pi()
pi_bot.procesar(comando, chat_id)
try:
hist = pi_bot.historial_chat.get(chat_id, []) or []
for mid in hist[:-1]:
_borrar(chat_id, mid)
pi_bot.historial_chat[chat_id] = hist[-1:] if hist else []
ent = pi_bot.entrantes_chat.get(chat_id, []) or []
for mid in ent:
_borrar(chat_id, mid)
pi_bot.entrantes_chat[chat_id] = []
except Exception:
pass
_borrar(chat_id, message_id)
_reenviar_menu(chat_id, categoria)
# ----------------------------------------------------------------- menu ----
@@ -277,25 +454,99 @@ def _header(categoria):
"nas": "📥 <b>NAS / Descargas</b>",
"tv": "📺 <b>TV</b>",
"parental": "👪 <b>Control parental</b>",
"domotica": "🏠 <b>Domótica</b>",
"aspi": "🤖 <b>Aspiradora</b>",
"potencia": "⚡ <b>Potencia aspiradora</b>",
"termo": "🔌 <b>Termo</b>",
"pantalla": "🖥️ <b>Pantalla</b>",
"mesillas": "🛏️ <b>Mesillas</b>",
"sistema": "🛠️ <b>Sistema</b>",
"router": "📡 <b>Router ASUS</b>",
}
return nombres.get(categoria, "🧭 <b>Menú IDD</b>")
def _abrir_categoria(chat_id, message_id, categoria):
teclado = {
# Ultima categoria visitada por chat: a ella se vuelve tras cada accion.
menu_cat = {}
def _record_cat(chat_id, categoria):
menu_cat[chat_id] = categoria
def _reenviar_menu(chat_id, categoria=None):
"""Envia de nuevo el menu de la categoria (con su volver operativo)."""
pi_bot = _pi()
cat = categoria or menu_cat.get(chat_id)
try:
nt = len((pi_bot.ultima_titulos.get(chat_id) or {}).get("results", []))
nb = len((pi_bot.ultimos_torrents.get(chat_id) or {}).get("torrents", []))
_log(f"reenviar cat={cat} chat={chat_id} titulos={nt} torrents={nb}")
except Exception:
pass
if not cat:
_enviar_nuevo(chat_id, "🧭 <b>Menú IDD</b>\nElige una categoría:",
teclado_principal())
return
teclado = _teclado_categoria(chat_id, cat)
if teclado is None:
_enviar_nuevo(chat_id, "🧭 <b>Menú IDD</b>\nElige una categoría:",
teclado_principal())
return
_enviar_nuevo(chat_id, _header(cat), teclado)
def volver_al_menu(chat_id, categoria=None):
"""API para pi_bot: tras un texto libre del menu, devuelve al menu."""
_reenviar_menu(chat_id, categoria)
def categoria_de(comando):
"""Categoria del menu a la que pertenece un comando de texto."""
mapa = {
"busca": "videoclub", "pelicula": "videoclub",
"serie": "videoclub", "novedades": "videoclub",
"torrents": "videoclub", "bajar": "videoclub",
"genero": "videoclub",
"tv": "tv", "alarma": "tv", "youtube": "tv",
"descargar": "nas", "descargas": "nas", "borrar": "nas",
"parental": "parental",
"termo": "termo", "pantalla": "pantalla",
"aspiradora": "aspi", "meross": "mesillas",
"router": "router", "aimesh": "sistema", "qos": "sistema",
"limite": "sistema", "seguridad": "sistema",
"estado": "sistema", "temperatura": "sistema",
"panorama": "sistema", "limpieza": "sistema",
"limpiar": "sistema",
}
return mapa.get((comando or "").lower().strip())
def _teclado_categoria(chat_id, categoria):
teclados = {
"videoclub": lambda: teclado_videoclub(chat_id),
"nas": teclado_nas,
"tv": teclado_tv,
"parental": teclado_parental,
"domotica": teclado_domotica,
"aspi": teclado_aspi,
"potencia": teclado_potencia,
"termo": teclado_termo,
"pantalla": teclado_pantalla,
"mesillas": teclado_mesillas,
"sistema": teclado_sistema,
"router": teclado_router,
}
fn = teclado.get(categoria)
if not fn:
fn = teclados.get(categoria)
return fn() if fn else None
def _abrir_categoria(chat_id, message_id, categoria):
_record_cat(chat_id, categoria)
teclado = _teclado_categoria(chat_id, categoria)
if teclado is None:
return False
_enviar_edit(chat_id, message_id, _header(categoria), fn())
_enviar_edit(chat_id, message_id, _header(categoria), teclado)
return True
@@ -316,11 +567,14 @@ def procesar_callback(data, chat_id, message_id):
# sub:torrents / sub:bajar / sub:router / sub:parental:<acc>
sub = partes[1]
if sub in ("router",):
_record_cat(chat_id, "router")
_enviar_edit(chat_id, message_id, _header("router"), teclado_router())
return
if sub in ("torrents", "bajar"):
_record_cat(chat_id, "videoclub")
return _mostrar_resultados(chat_id, message_id, sub)
if len(partes) == 2 and sub in ("desactivar", "activar", "pausa"):
_record_cat(chat_id, "parental")
_enviar_edit(chat_id, message_id,
f"¿Sobre qué dispositivo? ({sub})",
teclado_nombres(sub))
@@ -335,6 +589,13 @@ def procesar_callback(data, chat_id, message_id):
# pide un dato por teclado: lo captura el siguiente mensaje
accion = partes[1]
_guardar_pendiente(chat_id, accion)
CAT_TXT = {
"busca": "videoclub", "pelicula": "videoclub", "serie": "videoclub",
"tv": "tv", "alarma": "tv", "youtube": "tv",
"descargar": "nas",
"desactivar": "parental", "activar": "parental",
}
_record_cat(chat_id, CAT_TXT.get(accion, "sistema"))
preguntas = {
"busca": "🔎 ¿Qué busco? Escribe el nombre.",
"pelicula": "🎬 ¿Qué película busco?",
@@ -363,8 +624,10 @@ def procesar_callback(data, chat_id, message_id):
_cerrar_menu(chat_id, message_id)
return
minutos = partes[1]
_disp(chat_id, message_id,
f"parental pausa {nombre} {minutos}")
pi_bot = _pi()
pi_bot.procesar(f"parental pausa {nombre} {minutos}", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "parental")
return
if ruta == "acc":
@@ -373,6 +636,7 @@ def procesar_callback(data, chat_id, message_id):
if ruta == "cancelar":
_limpiar_pendiente(chat_id)
_borrar(chat_id, message_id)
_reenviar_menu(chat_id)
return
@@ -384,7 +648,10 @@ def _mostrar_resultados(chat_id, message_id, tipo):
"🧲 Elige el título para ver sus torrents:",
teclado_resultados(titulos, "torrents"))
else:
_enviar_nuevo(chat_id, "Primero haz una <b>/busca</b> o usa <code>/torrents &lt;n&gt;</code>.", None)
_enviar_nuevo(chat_id, "Primero busca arriba con <b>🔎 Buscar</b>, "
"<b>🎬 Película</b>, <b>📺 Serie</b> o "
"<b>🆕 Novedades</b> y luego pulsa "
"<b>🧲 Torrents…</b>.", None)
return
torrents = _txt("ultimos_torrents", chat_id)
if torrents:
@@ -392,22 +659,27 @@ def _mostrar_resultados(chat_id, message_id, tipo):
f"⬇️ Elige torrent de «{torrents.get('titulo')}»:",
teclado_resultados(torrents.get("torrents", []), "bajar"))
else:
_enviar_nuevo(chat_id, "Primero consulta <code>/torrents &lt;n&gt;</code> de un título.", None)
_enviar_nuevo(chat_id, "Primero elige un título en <b>🧲 Torrents…</b> "
"y luego pulsa <b>⬇️ Bajar…</b>.", None)
def _ejecutar_parental(chat_id, message_id, accion, nombre):
if accion == "pausa":
_record_cat(chat_id, "parental")
_set_pausa_nombre(chat_id, nombre)
_enviar_edit(chat_id, message_id,
f"⏸️ Pausa para <b>{nombre}</b>. ¿Cuántos minutos?",
teclado_minutos_pausa(nombre))
return
comando = f"parental {'desactivar' if accion == 'desactivar' else 'activar'} {nombre}"
_disp(chat_id, message_id, comando)
pi_bot = _pi()
pi_bot.procesar(comando, chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "parental")
def _ejecutar_accion(chat_id, message_id, partes):
import pi_bot
pi_bot = _pi()
accion = partes[0]
sub = partes[1] if len(partes) > 1 else ""
directos = {
@@ -424,13 +696,99 @@ def _ejecutar_accion(chat_id, message_id, partes):
"qos": "qos estado",
"limite": "limite listar",
"seguridad": "seguridad estado",
"parental_listar": "parental listar",
}
if accion in directos:
_disp(chat_id, message_id, directos[accion])
if accion == "parental_listar":
_reabrir(chat_id, message_id, "parental", "parental listar")
return
if accion == "termo":
_disp(chat_id, message_id, f"termo {sub}")
CAT_ACC = {
"ayuda": None, "novedades": "videoclub",
"descargas": "nas", "estado": "sistema", "temperatura": "temperatura",
"panorama": "sistema", "limpieza": "sistema", "limpiar": "limpiar",
"aimesh": "sistema", "qos": "sistema", "limite": "limite listar",
"seguridad": "sistema",
}
if accion == "genero":
flujo_genero[chat_id] = "esperando_tipo"
flujo_genero_tipo.pop(chat_id, None)
enviar("🎭 Catálogo por <b>género</b> (30 más descargadas). Primero elige el tipo:\n"
"<b>1.</b> 🎬 Películas\n"
"<b>2.</b> 📺 Series\n\nResponde el <b>1</b> o el <b>2</b>.", chat_id)
return
if accion in directos:
_disp(chat_id, message_id, directos[accion], CAT_ACC.get(accion))
return
if accion in ("termo", "pantalla"):
if sub == "estado":
_reabrir(chat_id, message_id, accion, f"{accion} estado")
elif sub in ("on", "off"):
pi_bot.procesar(f"{accion} {sub}", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, accion)
return
if accion == "aspiradora":
if sub in ("estado", "stats", "consumibles"):
_reabrir(chat_id, message_id, "aspi", f"aspiradora {sub}")
elif sub in ("start", "stop", "home", "pause", "locate", "vaciar"):
pi_bot.procesar(f"aspiradora {sub}", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "aspi")
return
if accion == "potencia":
if sub.isdigit() and int(sub) in (0, 1, 2, 3):
pi_bot.procesar(f"aspiradora potencia {sub}", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "aspi")
return
if accion == "dispositivos":
_record_cat(chat_id, "parental")
_mostrar_dispositivos(chat_id, message_id)
return
if accion == "blqdev":
_record_cat(chat_id, "parental")
lista = menu_dispositivos.get(chat_id) or []
numero = int(sub) if sub.isdigit() else 0
if not lista and numero:
try:
from asus_api_control import listar_dispositivos as _ld
lista = _ld()
menu_dispositivos[chat_id] = lista
except Exception:
lista = []
if 1 <= numero <= len(lista):
d = lista[numero - 1]
try:
from asus_api_control import bloquear_mac, listar_dispositivos
bloquear_mac(d["mac"], not d["bloqueado"], d["nombre"])
lista = listar_dispositivos()
menu_dispositivos[chat_id] = lista
except Exception as e:
_enviar_nuevo(chat_id, f"❌ Error: {e}", None)
return
_enviar_edit(chat_id, message_id,
"📺 <b>Bloquear dispositivo</b>\nPulsa el número para "
"bloquear/desbloquear:",
teclado_dispositivos(lista))
else:
_mostrar_dispositivos(chat_id, message_id)
return
if accion == "mesilla":
# sub: estado | nombre | nombre:accion
rest = ":".join(partes[1:])
trozos = rest.split(":")
if len(trozos) == 1 and trozos[0] == "estado":
_reabrir(chat_id, message_id, "mesillas", "meross estado")
elif len(trozos) == 2 and trozos[1] in ("on", "off"):
pi_bot.procesar(f"meross {trozos[0]} {trozos[1]}", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "mesillas")
return
if accion == "tablets":
if sub in ("bloquear", "desbloquear"):
verbo = "desactivar" if sub == "bloquear" else "activar"
pi_bot.procesar(f"parental {verbo} Inés", chat_id)
pi_bot.procesar(f"parental {verbo} Carolina", chat_id)
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "parental")
return
if accion == "router":
mapeo = {
@@ -442,19 +800,19 @@ def _ejecutar_accion(chat_id, message_id, partes):
"reiniciar": "router reiniciar",
"reservar": "router reservar",
}
_disp(chat_id, message_id, mapeo.get(sub, "router ayuda"))
_disp(chat_id, message_id, mapeo.get(sub, "router ayuda"), "router")
return
if accion in ("torrents", "bajar"):
numero = int(sub) if sub.isdigit() else 0
if numero:
_disp(chat_id, message_id, f"{accion} {numero}")
_disp(chat_id, message_id, f"{accion} {numero}", "videoclub")
return
# --------------------------------------------- pendiente de texto libre ----
def _pend(chat_id):
import pi_bot
pi_bot = _pi()
return pi_bot.menu_pendiente
@@ -515,23 +873,24 @@ def resolver_pausa_minutos(chat_id, texto):
_limpiar_pendiente(chat_id)
if not nombre:
return True
import pi_bot
pi_bot = _pi()
pi_bot.procesar(f"parental pausa {nombre} {minutos}", chat_id)
_reenviar_menu(chat_id, "parental")
return True
def pausa_estado(chat_id):
import pi_bot
pi_bot = _pi()
return pi_bot.menu_pausa_nombre.get(chat_id)
def _set_pausa_nombre(chat_id, nombre):
import pi_bot
pi_bot = _pi()
pi_bot.menu_pausa_nombre[chat_id] = nombre
def _get_pausa_nombre(chat_id):
import pi_bot
pi_bot = _pi()
return pi_bot.menu_pausa_nombre.get(chat_id)

View File

@@ -0,0 +1,114 @@
"""Enchufes Meross (mesillas) via cuenta Meross en la nube.
Lee MEROSS_EMAIL/PASSWORD del .env. Solo lectura salvo on/off explicito.
Uso: python meross_control.py estado|on|off [nombre]
"""
import asyncio
import os
import ssl
import sys
BASE = os.path.dirname(os.path.abspath(__file__))
if BASE not in sys.path:
sys.path.insert(0, BASE)
def _env(nombre):
valor = os.getenv(nombre, "").strip()
if not valor:
try:
with open(os.path.join(BASE, ".env")) as f:
for linea in f:
linea = linea.strip()
if linea.startswith(nombre + "="):
valor = linea.split("=", 1)[1].strip()
break
except OSError:
pass
return valor
async def _dispositivos():
from meross_iot.http_api import MerossHttpClient
from meross_iot.manager import MerossManager
http = await MerossHttpClient.async_from_user_password(
api_base_url="https://iotx-eu.meross.com",
email=_env("MEROSS_EMAIL"), password=_env("MEROSS_PASSWORD"),
ssl_context=ssl.create_default_context())
mgr = MerossManager(http_client=http)
await mgr.async_init()
await mgr.async_device_discovery()
return mgr, http
def _estado_dev(d):
try:
v = getattr(d, "is_on", None)
if callable(v):
v = v()
if isinstance(v, bool):
return v
except Exception:
pass
return None
async def _actuar(nombre, accion):
mgr, http = await _dispositivos()
try:
objetivo = (nombre or "").lower()
elegidos = [d for d in mgr.find_devices()
if not objetivo or objetivo in (d.name or "").lower()]
if not elegidos:
return [f"❌ No encuentro '{nombre}' (solo mesillas)."]
lineas = []
for d in elegidos:
try:
await d.async_update()
except Exception:
pass
for d in elegidos:
if accion == "on":
try:
await d.async_turn_on()
except Exception as e:
lineas.append(f"❌ {d.name}: {str(e)[:100]}")
continue
elif accion == "off":
try:
await d.async_turn_off()
except Exception as e:
lineas.append(f"❌ {d.name}: {str(e)[:100]}")
continue
try:
await d.async_update()
except Exception:
pass
on = _estado_dev(d)
txt = "encendido" if on else ("apagado" if on is False else "?")
lineas.append(f"🛏️ {d.name}: <b>{txt}</b>")
return lineas
finally:
mgr.close()
await http.async_logout()
def controlar(nombre, accion):
"""Devuelve lista de lineas HTML con el resultado."""
try:
return asyncio.run(_actuar(nombre or "", accion or "estado"))
except Exception as e:
return [f"❌ Error Meross: {str(e)[:150]}"]
def main(argv):
if len(argv) < 2 or argv[1] not in ("estado", "on", "off"):
print("Uso: meross_control.py estado|on|off [nombre]")
return 1
for linea in controlar(argv[2] if len(argv) > 2 else "", argv[1]):
print(linea.replace("<b>", "").replace("</b>", ""))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -248,24 +248,27 @@ def listar_tareas(session=None):
def borrar_tarea(tid, session=None):
"""Borra una tarea de Download Station por su id.
Devuelve (ok, detalle)."""
Devuelve (ok, detalle). OJO: la API responde success:true aunque falle:
el error real va por tarea (error 0 = borrada). Con version=3 el id
va como texto plano, no como array JSON (eso da error 544)."""
if not session:
session = login()
if not session:
return False, "No se pudo autenticar con Download Station."
try:
import json as _json
r = _api("/webapi/DownloadStation/task.cgi", {
"api": "SYNO.DownloadStation.Task",
"version": "3",
"method": "delete",
"id": _json.dumps([tid]),
"id": tid,
}, session=session)
except Exception as e:
return False, str(e)
if r.get("success"):
detalle = r.get("data") or []
if r.get("success") and detalle and all(
e.get("error", -1) == 0 for e in detalle):
return True, "ok"
return False, str(r.get("error"))
return False, str(detalle or r.get("error"))
if __name__ == "__main__":

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
import threading
@@ -19,6 +20,7 @@ temporizadores_parental = {} # alias normalizado -> Timer de desbloqueo
menu_pendiente = {} # chat_id -> accion de texto libre esperada (menu botones)
menu_pausa_nombre = {} # chat_id -> nombre elegido en flujo de pausa
historial_chat = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar)
entrantes_chat = {} # chat_id -> [message_id...] enviados por el usuario (para /limpiar)
borrar_pend = {} # chat_id -> lista de tareas NAS esperando numero a borrar
BASE = os.path.dirname(os.path.abspath(__file__))
@@ -278,8 +280,10 @@ def borrar(chat_id, message_id):
def limpiar_pantalla(chat_id):
"""Borra los mensajes que el bot ha enviado en este chat (/limpiar)."""
"""Borra TODO el chat visible: mensajes del bot y del usuario (/limpiar).
Limite de Telegram: solo mensajes de menos de 48h se pueden borrar."""
lista = historial_chat.pop(chat_id, None) or []
lista += entrantes_chat.pop(chat_id, None) or []
borrados = 0
for mid in reversed(lista):
try:
@@ -292,7 +296,7 @@ def limpiar_pantalla(chat_id):
borrados += 1
except Exception:
pass
aviso = enviar(f"🧹 Pantalla limpia ({borrados} mensajes borrados).", chat_id)
aviso = enviar(f"🧹 Chat limpio ({borrados} mensajes borrados).", chat_id)
if aviso:
threading.Timer(6.0, borrar, args=(chat_id, aviso)).start()
@@ -345,6 +349,20 @@ def _ejecutar_borrado(chat_id, numero):
enviar(f"🗑️ Tarea borrada: <b>{titulo[:80]}</b>", chat_id)
else:
enviar(f"❌ No se pudo borrar <b>{titulo[:60]}</b>: {detalle}", chat_id)
# Cierra el panel de descargas, limpia toda la pantalla y deja
# el mensaje para volver al menu.
try:
estado_panel = panel_descargas.pop(chat_id, None)
if estado_panel and estado_panel.get("message_id"):
borrar(chat_id, estado_panel["message_id"])
except Exception:
pass
time.sleep(4)
try:
limpiar_pantalla(chat_id)
except Exception:
pass
enviar("✅ Hecho. Pulsa /menu para volver al menú.", chat_id)
return True
@@ -386,6 +404,14 @@ def procesar(texto, chat_id):
menu_idd.cancelar_pendiente(chat_id)
if t in ("cancelar", "cancel", "salir"):
return
# Venia del menu: procesar y devolver al menu.
procesar(texto, chat_id)
try:
menu_idd.volver_al_menu(chat_id,
menu_idd.categoria_de(comando))
except Exception:
pass
return
elif menu_idd.pendiente_activo(chat_id):
if menu_idd.resolver_pausa_minutos(chat_id, texto):
return
@@ -395,7 +421,14 @@ def procesar(texto, chat_id):
enviar("⏱️ Escribe un número de minutos entre 1 y 1440.", chat_id)
return
if cmd:
return procesar(cmd, chat_id)
procesar(cmd, chat_id)
try:
menu_idd.volver_al_menu(
chat_id,
menu_idd.categoria_de(cmd.split(None, 1)[0]))
except Exception:
pass
return
# Borrado de descargas pendiente (numero de tarea NAS)
if t.isdigit() and chat_id in borrar_pend:
_ejecutar_borrado(chat_id, int(t))
@@ -494,20 +527,39 @@ def procesar(texto, chat_id):
except Exception as e:
log(f"descargar error: {e}")
enviar(f"❌ Error en Download Station: {e}", chat_id)
elif comando == "termo":
elif comando in ("termo", "pantalla"):
accion = argumento.lower().strip()
acciones = {"on": "on", "encender": "on", "off": "off",
"apagar": "off", "estado": "status", "status": "status"}
if accion not in acciones:
enviar("Uso: <code>/termo on</code>, <code>/termo off</code> o <code>/termo estado</code>", chat_id)
enviar(f"Uso: <code>/{comando} on</code>, <code>/{comando} off</code> o <code>/{comando} estado</code>", chat_id)
else:
try:
from tapo_control import controlar_termo
resultado = controlar_termo(acciones[accion])
from tapo_control import controlar
resultado = controlar(comando, acciones[accion])
enviar(f"🔌 <b>{resultado}</b>", chat_id)
except Exception as e:
log(f"termo error: {e}")
enviar(f"❌ Error controlando el termo: {e}", chat_id)
log(f"{comando} error: {e}")
enviar(f"❌ Error controlando {comando}: {e}", chat_id)
elif comando == "meross":
try:
from meross_control import controlar as meross_controlar
tok = argumento.lower().strip().split()
if not tok:
nombre, accion = "", "estado"
elif tok[0] in ("estado", "on", "off") and len(tok) == 1:
nombre, accion = "", tok[0]
else:
nombre = tok[0]
accion = tok[1] if len(tok) > 1 else "estado"
if accion not in ("estado", "on", "off"):
enviar("Uso: <code>/meross [juanjo|nahid] estado|on|off</code>.", chat_id)
else:
for linea in meross_controlar(nombre, accion):
enviar(linea, chat_id)
except Exception as e:
log(f"meross error: {e}")
enviar(f"❌ Error Meross: {e}", chat_id)
elif comando == "router":
partes_router = argumento.lower().split()
accion_router = partes_router[0] if partes_router else "ayuda"
@@ -734,6 +786,12 @@ def procesar(texto, chat_id):
procesar_pelis(chat_id)
elif t in ("series", "/series"):
procesar_series(chat_id)
elif t in ("genero",):
flujo_genero[chat_id] = "esperando_tipo"
flujo_genero_tipo.pop(chat_id, None)
enviar("🎭 Catálogo por <b>género</b> (30 más descargadas). Primero elige el tipo:\n"
"<b>1.</b> 🎬 Películas\n"
"<b>2.</b> 📺 Series\n\nResponde el <b>1</b> o el <b>2</b>.", chat_id)
elif t.startswith("pelicula ") or t.startswith("/pelicula "):
nombre = texto.split(" ", 1)[1].strip()
procesar_buscador_pelicula(nombre, chat_id)
@@ -749,8 +807,13 @@ 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:
try:
from menu_idd import enviar_menu
@@ -770,8 +833,8 @@ def procesar(texto, chat_id):
" ✏️ Ej: <code>/genero</code> → 1 (pelis) → 12 (Horror)\n"
"<b>3.</b> <code>torrents &lt;n&gt;</code> — torrents del resultado n\n"
" ✏️ Ej: <code>/torrents 1</code>\n"
"<b>4.</b> <code>bajar &lt;n&gt;</code> — descargar al NAS el torrent n\n"
" ✏️ Ej: <code>/bajar 1</code>\n"
"<b>4.</b> <code>bajar &lt;n&gt;</code> — descargar al NAS el torrent n\n"
" ✏️ Ej: <code>/bajar 1</code>, <code>/bajar 2-5</code>, <code>/temporada 1</code>\n"
"<b>5.</b> <code>pelicula &lt;nombre&gt;</code> — buscar película (catálogo)\n"
" ✏️ Ej: <code>/pelicula ant-man</code>\n"
"<b>6.</b> <code>serie &lt;nombre&gt;</code> — buscar serie (catálogo)\n"
@@ -785,7 +848,14 @@ def procesar(texto, chat_id):
"<b>9.</b> <code>descargar &lt;enlace&gt;</code> — añade a Download Station\n"
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n"
"<b>10.</b> <code>termo on|off|estado</code> — controla el enchufe Tapo\n"
" ✏️ Ej: <code>/termo estado</code>\n\n"
" ✏️ Ej: <code>/termo estado</code>\n"
"<b>10c.</b> <code>pantalla on|off|estado</code> — enchufe de la pantalla\n"
" ✏️ Ej: <code>/pantalla estado</code>\n"
"<b>10d.</b> <code>meross [juanjo|nahid] estado|on|off</code> — enchufes mesillas\n"
" ✏️ Ej: <code>/meross estado</code>\n\n"
"━━━ 🏠 DOMÓTICA ━━━\n"
"<b>10b.</b> <code>aspiradora ...</code> — Dreame D10 Plus\n"
" ✏️ Ej: <code>/aspiradora estado</code>, <code>/aspiradora vaciar</code>, <code>/aspiradora stats</code>, <code>/aspiradora potencia 3</code>, <code>/aspiradora agua 2</code>, <code>/aspiradora voz</code>\n\n"
"━━━ 📺 TV ━━━\n"
"<b>11.</b> <code>tv &lt;mensaje&gt;</code> — mensaje rápido a la tele\n"
" ✏️ Ej: <code>/tv la cena está lista</code>\n"
@@ -943,7 +1013,27 @@ def procesar_buscador_pelicula(nombre, chat_id):
for m in mensajes:
enviar(m, chat_id)
if resultados:
ultima_titulos[chat_id] = {"query": nombre, "results": resultados,
"origen": "catalogo"}
log(f"BUSCADOR pelis '{nombre}': {len(resultados)} resultados")
return
# Fallback global: no esta en tu RealDebrid, se busca online.
enviar("📡 No está en tu catálogo, buscando online...", chat_id)
try:
gol = _import_global_buscar()
globales = [r for r in gol.buscar_titulos(nombre)
if r["type"] == "movie"]
mensajes = gol.formatear_titulos(globales)
except Exception as e:
log(f"buscador pelicula global error: {e}")
enviar(f"❌ Error en la busqueda global: {e}", chat_id)
return
for m in mensajes:
enviar(m, chat_id)
if globales:
ultima_titulos[chat_id] = {"query": nombre, "results": globales,
"origen": "global"}
log(f"BUSCADOR pelis global '{nombre}': {len(globales)} titulos")
def procesar_buscador_serie(nombre, chat_id):
@@ -958,16 +1048,38 @@ def procesar_buscador_serie(nombre, chat_id):
enviar(f"🔎 Buscando serie <b>{nombre}</b> en el catálogo...", chat_id)
try:
grupos = vcb.buscar_series(nombre)
mensajes = vcb.formatear_series(grupos)
except Exception as e:
log(f"buscador serie error: {e}")
enviar(f"❌ Error consultando el catálogo: {e}", chat_id)
return
total_eps = sum(len(v) for v in grupos.values())
if grupos and total_eps >= 3:
mensajes = vcb.formatear_series(grupos)
for m in mensajes:
enviar(m, chat_id)
if grupos:
total_eps = sum(len(v) for v in grupos.values())
ultima_titulos[chat_id] = {"query": nombre,
"results": vcb.aplanar_series(grupos),
"origen": "catalogo"}
log(f"BUSCADOR series '{nombre}': {len(grupos)} series, {total_eps} capitulos")
return
# Poco o nada en tu RealDebrid: se busca online.
enviar(f"📡 En tu catálogo hay {total_eps} capítulos, buscando online...",
chat_id)
try:
gol = _import_global_buscar()
globales = [r for r in gol.buscar_titulos(nombre)
if r["type"] == "series"]
mensajes = gol.formatear_titulos(globales)
except Exception as e:
log(f"buscador serie global error: {e}")
enviar(f"❌ Error en la busqueda global: {e}", chat_id)
return
for m in mensajes:
enviar(m, chat_id)
if globales:
ultima_titulos[chat_id] = {"query": nombre, "results": globales,
"origen": "global"}
log(f"BUSCADOR series global '{nombre}': {len(globales)} titulos")
def procesar_novedades(chat_id):
@@ -989,6 +1101,96 @@ def procesar_novedades(chat_id):
return
for m in mensajes:
enviar(m, chat_id)
if items:
ultima_titulos[chat_id] = {"query": "novedades", "results": items,
"origen": "catalogo"}
log(f"NOVEDADES: {len(items)} items")
def procesar_aspiradora(argumento, chat_id):
"""Dreame D10 Plus: estado|start|stop|home|pause|locate|vaciar|
auto on|off|stats|consumibles|potencia 0-3|agua 1-3|voz [id]."""
partes = (argumento or "estado").lower().strip().split(None, 1)
accion = partes[0]
arg = partes[1].strip() if len(partes) > 1 else ""
validas = ("estado", "start", "stop", "home", "pause", "locate",
"vaciar", "auto", "stats", "consumibles", "potencia",
"agua", "voz")
if accion not in validas:
enviar("Uso: <code>/aspiradora estado|start|stop|home|pause|locate|"
"vaciar|auto on|off|stats|consumibles|potencia 0-3|agua 1-3|"
"voz [id]</code>.", chat_id)
return
try:
import aspiradora
except Exception as e:
log(f"aspiradora import error: {e}")
enviar("❌ No se pudo cargar el modulo aspiradora", chat_id)
return
try:
if accion == "estado":
enviar(aspiradora.estado(), chat_id)
elif accion == "stats":
enviar(aspiradora.stats(), chat_id)
elif accion == "consumibles":
enviar(aspiradora.consumibles(), chat_id)
elif accion == "auto" and not arg:
enviar(aspiradora.auto_estado(), chat_id)
elif accion == "voz" and not arg:
enviar(aspiradora.voz_estado(), chat_id)
else:
vac = aspiradora.conectar()
if accion == "start":
vac.start()
enviar("🧹 Limpieza iniciada.", chat_id)
elif accion == "stop":
vac.stop()
enviar("⏹️ Limpieza detenida.", chat_id)
elif accion == "home":
vac.home()
enviar("🏠 Volviendo a la base.", chat_id)
elif accion == "pause":
vac.call_action("pause")
enviar("⏸️ Pausada.", chat_id)
elif accion == "locate":
vac.call_action("play_sound")
enviar("🔊 Emitiendo sonido de localización.", chat_id)
elif accion == "vaciar":
vac.call_action("start_collect")
enviar("🗑️ Vaciando en la base.", chat_id)
elif accion == "auto":
if arg in ("on", "1"):
vac.set_property_by(15, 1, 1)
enviar("🗑️ Vaciado automático <b>activado</b>.", chat_id)
else:
vac.set_property_by(15, 1, 0)
enviar("🗑️ Vaciado automático <b>desactivado</b>.",
chat_id)
elif accion == "potencia":
if arg.isdigit() and int(arg) in aspiradora.MODOS:
vac.set_property_by(4, 4, int(arg))
enviar(f"⚡ Potencia: "
f"<b>{aspiradora.MODOS[int(arg)]}</b>.", chat_id)
else:
enviar("Uso: <code>/aspiradora potencia 0-3</code> "
"(0 silencioso, 1 estándar, 2 medio, 3 fuerte).",
chat_id)
elif accion == "agua":
if arg.isdigit() and int(arg) in aspiradora.AGUAS:
vac.set_property_by(4, 5, int(arg))
enviar(f"💧 Agua: <b>{aspiradora.AGUAS[int(arg)]}</b>.",
chat_id)
else:
enviar("Uso: <code>/aspiradora agua 1-3</code> "
"(1 bajo, 2 medio, 3 alto).", chat_id)
elif accion == "voz":
vac.set_voice(arg)
enviar(f"🔊 Voz cambiada a <b>{arg}</b> "
f"(si el pack existe).", chat_id)
log(f"ASPIRADORA {accion} {arg} ok")
except Exception as e:
log(f"aspiradora error: {e}")
enviar(f"❌ Error con la aspiradora: {e}", chat_id)
def _int_arg(texto):
@@ -1071,19 +1273,38 @@ def _mostrar_catalogo_genero(chat_id, tipo, genero, categoria):
def procesar_torrents(n, chat_id):
"""Muestra los torrents (Torrentio) del titulo numero n de la ultima busqueda."""
"""Muestra las opciones de descarga del numero n de la ultima busqueda
(/busca, /pelicula, /serie, /novedades o /genero)."""
estado = ultima_titulos.get(chat_id)
if not estado or n is None or n < 1 or n > len(estado["results"]):
enviar("Primero usa <b>/busca &lt;nombre&gt;</b> y luego "
enviar("Primero usa <b>/busca</b>, <b>/pelicula</b>, <b>/serie</b>, "
"<b>/novedades</b> o <b>/genero</b> y luego "
"<b>/torrents &lt;numero&gt;</b>.", chat_id)
return
r = estado["results"][n - 1]
if not r.get("imdb_id") and r.get("enlace"):
# Item de catalogo: ya trae enlace directo, sin paso por Torrentio.
nombre = r.get("clean") or r.get("name") or "titulo"
ultimos_torrents[chat_id] = {
"titulo": nombre,
"torrents": [{"filename": nombre, "calidad": "directo",
"size": None, "enlace": r["enlace"]}],
}
enviar(f"🧲 <b>{nombre}</b> — enlace directo disponible:\n"
"<b>1.</b> Descarga directa al NAS\n\n"
"Responde <b>/bajar 1</b> para descargarlo.", chat_id)
log(f"TORRENTS catalogo '{nombre}': enlace directo")
return
if not r.get("imdb_id"):
enviar("❌ Ese resultado no tiene enlace de descarga. "
"Prueba con <b>/busca &lt;nombre&gt;</b>.", chat_id)
return
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
r = estado["results"][n - 1]
enviar(f"🧲 Buscando torrents de <b>{r['name']}</b>...", chat_id)
try:
torrents = gol.torrents_de(r["imdb_id"], r["type"])
@@ -1100,35 +1321,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 <b>/busca &lt;nombre&gt;</b> y luego "
"<b>/torrents &lt;numero&gt;</b>.", chat_id)
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: <b>/bajar &lt;numero&gt;</b> o <b>/bajar X-Y</b>.",
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: <b>/bajar &lt;numero&gt;</b>, <b>/bajar X-Y</b> o "
"<b>/temporada N</b>.", 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 <b>/busca</b>, <b>/pelicula</b>, <b>/serie</b>, "
"<b>/novedades</b> o <b>/genero</b>, luego "
"<b>/torrents &lt;numero&gt;</b>.", 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("La temporada completa solo vale para series de tu catálogo. "
"Online usa <b>/torrents N</b> y <b>/bajar X-Y</b>.", chat_id)
return
if n is None or n < 1:
enviar("Uso: <b>/temporada &lt;N&gt;</b>.", chat_id)
return
t = estado["torrents"][n - 1]
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: <b>{t['filename'] or estado['titulo']}</b> "
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():
@@ -1167,6 +1501,11 @@ def _atender_update(upd):
return
texto = msg.get("text")
chat_id = msg["chat"]["id"]
if msg.get("message_id"):
lista = entrantes_chat.setdefault(chat_id, [])
lista.append(msg["message_id"])
if len(lista) > 300:
entrantes_chat[chat_id] = lista[-300:]
if texto:
procesar(texto, chat_id)
except Exception as e:

View File

@@ -143,6 +143,59 @@ def reiniciar():
return _run("reboot")
def _ip_actual(nombre, mac):
"""IP vigente del dispositivo: primero lease en vivo (por MAC o por
nombre), si no la reserva DHCP fija. Devuelve (ip, origen) o (None, _)."""
try:
from asus_api_control import normalizar_nombre
except Exception:
normalizar_nombre = lambda x: (x or "").lower() # noqa
try:
devices, by_mac = _inventario()
except Exception:
devices, by_mac = [], {}
mac = (mac or "").upper()
for ip, m, _nombre in devices:
if m == mac:
return ip, "lease en vivo"
pistas = [p for p in (normalizar_nombre(nombre),) if p]
for ip, m, _nombre in devices:
n = normalizar_nombre(_nombre)
if any(p and (p in n or n in p) for p in pistas):
return ip, f"lease en vivo ({_nombre})"
if mac and by_mac.get(mac):
return by_mac[mac], "reserva DHCP"
return None, None
def bloqueo_red(nombre, mac, blocked):
"""Corta (o reabre) internet por IP+MAC en iptables y lo verifica.
Asi el bloqueo aguanta aunque la tablet rote la MAC: la IP fija cae
siempre y la MAC vigente tambien. Devuelve texto de verificacion."""
ip, origen = _ip_actual(nombre, mac)
if not ip:
return "⚠️ Sin IP localizada: solo regla parental aplicada."
mac = (mac or "").upper()
if blocked:
_run(f"iptables -C FORWARD -s {ip} -j DROP 2>/dev/null || "
f"iptables -I FORWARD -s {ip} -j DROP")
if mac:
_run(f"iptables -C FORWARD -m mac --mac-source {mac} -j DROP "
f"2>/dev/null || iptables -I FORWARD -m mac "
f"--mac-source {mac} -j DROP")
else:
_run(f"iptables -D FORWARD -s {ip} -j DROP 2>/dev/null; "
f"iptables -D FORWARD -m mac --mac-source {mac} -j DROP "
f"2>/dev/null; true")
comprueba = _run(f"iptables -C FORWARD -s {ip} -j DROP 2>/dev/null "
f"&& echo CORTADO || echo ABIERTO")
if blocked:
estado = "CORTADO ✅" if "CORTADO" in comprueba else "FALLO ❌"
return f"🌐 IP {ip} ({origen}): internet {estado}."
estado = "ABIERTO ✅" if "ABIERTO" in comprueba else "SIGUE CORTADO ❌"
return f"🌐 IP {ip} ({origen}): internet {estado}."
QOS_TIPOS = {
"0": "Traditional (por prioridades)",
"1": "Adaptive (auto gaming/streaming)",

View File

@@ -28,21 +28,28 @@ def _env(name):
return os.getenv(name, FILE_ENV.get(name, "")).strip()
async def _device():
host = _env("TAPO_TERMO_IP")
DISPOSITIVOS = {
"termo": "TAPO_TERMO_IP",
"pantalla": "TAPO_PANTALLA_IP",
}
async def _device(nombre="termo"):
var_ip = DISPOSITIVOS.get(nombre, "TAPO_TERMO_IP")
host = _env(var_ip)
username = _env("TAPO_USERNAME")
password = _env("TAPO_PASSWORD")
if not host or not username or not password:
raise RuntimeError(
"Falta configurar TAPO_TERMO_IP, TAPO_USERNAME y TAPO_PASSWORD en .env"
f"Falta configurar {var_ip}, TAPO_USERNAME y TAPO_PASSWORD en .env"
)
return await Discover.discover_single(
host, username=username, password=password, timeout=8
)
async def _run(action):
device = await _device()
async def _run(nombre, action):
device = await _device(nombre)
await device.update()
if action == "on":
await device.turn_on()
@@ -52,18 +59,23 @@ async def _run(action):
raise ValueError("Accion no soportada")
await device.update()
state = "encendido" if device.is_on else "apagado"
return f"termo: {state}"
return f"{nombre}: {state}"
def controlar_termo(action):
def controlar(nombre, action):
"""Ejecuta una accion Tapo y devuelve un mensaje seguro para Telegram."""
try:
return asyncio.run(_run(action))
return asyncio.run(_run(nombre, action))
except Exception as exc:
message = str(exc)
if "TAPO_" in message:
return f"Error de configuracion Tapo: {message}"
return f"No se pudo controlar el termo: {message[:180]}"
return f"No se pudo controlar {nombre}: {message[:180]}"
def controlar_termo(action):
"""Compatibilidad: el termo es el dispositivo por defecto."""
return controlar("termo", action)
async def _discover():

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
"""
aviso_voz.py — Genera y envia una nota de voz corta por Telegram al terminar
una respuesta de OpenCode, en vez del pitido/tono de notificacion.
Usa edge-tts (gratis/ilimitado, Microsoft) con la voz preferida del usuario
es-MX-DaliaNeural. Telegram exige las notas de voz en OGG/Opus, asi que el
mp3 generado por edge-tts se convierte con ffmpeg la primera vez y se cachea.
Configuracion (variables del .env del bot):
AVISO_VOZ=1 -> habilita el aviso de voz al terminar
AVISO_VOZ_FRASE=Listo. -> frase a sintetizar (opcional)
AVISO_VOZ_MP3=<ruta> -> usar un mp3 propio (voz ElevenLabs)
en vez de sintetizar (opcional)
El OGG cacheado vive en <dir>/sounds/aviso_fin.ogg (o junto al mp3 indicado).
Uso:
python3 aviso_voz.py <chat_id> -> envia la nota de voz al chat
"""
import asyncio
import os
import subprocess
import sys
import requests
BASE = os.path.dirname(os.path.abspath(__file__))
SND = os.path.join(BASE, "sounds")
ENV = {}
try:
for linea in open(os.path.join(BASE, ".env"), encoding="utf-8"):
linea = linea.strip()
if linea and not linea.startswith("#") and "=" in linea:
k, v = linea.split("=", 1)
ENV[k.strip()] = v.strip()
except Exception:
pass
TOKEN = ENV.get("TELEGRAM_BOT_TOKEN", "")
API = f"https://api.telegram.org/bot{TOKEN}"
FRASE = ENV.get("AVISO_VOZ_FRASE", "Listo.")
MP3_SOURCE = ENV.get("AVISO_VOZ_MP3", "")
def _voz_dalia(texto, salida_mp3):
"""Sintetiza `texto` con edge-tts voz es-MX-DaliaNeural a un mp3."""
import edge_tts
asyncio.run(edge_tts.Communicate(
texto, "es-MX-DaliaNeural", rate="-8%").save(salida_mp3))
def asegurar_ogg():
"""Devuelve la ruta del OGG/Opus de aviso, generandolo/cacheandolo."""
try:
os.makedirs(SND, exist_ok=True)
except Exception:
pass
# Si hay mp3 propio de voz, el cache es su mismo nombre con .ogg
if MP3_SOURCE and os.path.isfile(MP3_SOURCE):
ogg = os.path.splitext(MP3_SOURCE)[0] + ".ogg"
fuente = MP3_SOURCE
else:
ogg = os.path.join(SND, "aviso_fin.ogg")
fuente = os.path.join(SND, "aviso_fin.mp3")
if not os.path.exists(fuente):
try:
_voz_dalia(FRASE, fuente)
except Exception as e:
print(f"[aviso_voz] edge-tts error: {e}")
return None
if not os.path.exists(ogg):
try:
r = subprocess.run(
["ffmpeg", "-y", "-loglevel", "error", "-i", fuente,
"-c:a", "libopus", "-b:a", "48k", ogg],
capture_output=True, timeout=60)
if r.returncode != 0:
print(f"[aviso_voz] ffmpeg error: {r.stderr.decode(errors='replace')[:300]}")
return None
except Exception as e:
print(f"[aviso_voz] ffmpeg no disponible: {e}")
return None
return ogg
def enviar_voz(chat_id):
ogg = asegurar_ogg()
if not ogg:
return False
try:
with open(ogg, "rb") as f:
r = requests.post(f"{API}/sendVoice",
data={"chat_id": chat_id},
files={"voice": ("aviso.ogg", f,
"audio/ogg")},
timeout=40)
return r.json().get("ok", False)
except Exception as e:
print(f"[aviso_voz] sendVoice error: {e}")
return False
if __name__ == "__main__":
chat = sys.argv[1] if len(sys.argv) > 1 else ""
if not chat or not TOKEN:
print("Uso: python3 aviso_voz.py <chat_id> (con TELEGRAM_BOT_TOKEN en .env)")
sys.exit(1)
ok = enviar_voz(chat)
print(f"[aviso_voz] {'OK' if ok else 'FALLO'} -> chat {chat}")
sys.exit(0 if ok else 2)

View File

@@ -53,7 +53,14 @@ bloqueos = {} # chat_id -> True (procesando mensaje)
sesiones = {} # chat_id -> {'repo': slug, 'session': id, 'model': obj|None}
repos_cache = [] # lista de {'slug','dir'}
sentidos = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar)
entrantes = {} # chat_id -> [message_id...] enviados por el usuario (para /limpiar)
menu_msg = {} # chat_id -> message_id del menu abierto
ultima_actividad = time.time() # para liberar memoria por inactividad
def _marcar_actividad():
global ultima_actividad
ultima_actividad = time.time()
def log(linea):
@@ -121,7 +128,7 @@ def enviar(chat_id, texto, parse="HTML"):
_registrar(chat_id, tg("sendMessage",
{"chat_id": chat_id, "text": texto[i:i + 3900],
"parse_mode": parse}))
time.sleep(0.3)
time.sleep(0.1)
def enviar_doc(chat_id, ruta, caption=None):
@@ -159,6 +166,22 @@ def editar(chat_id, mid, texto, filas=None):
tg("editMessageText", data)
def _aviso_voz_fin(chat):
"""Aviso sonoro de fin de turno (nota de voz) si esta habilitado."""
if ENV.get("AVISO_VOZ", "0") != "1":
return
try:
subprocess.run(
[sys.executable, os.path.join(BASE, "aviso_voz.py"), str(chat)],
timeout=90, capture_output=True)
except Exception as e:
log(f"aviso_voz error: {e}")
def aviso_voz_fin(chat):
threading.Thread(target=_aviso_voz_fin, args=(chat,), daemon=True).start()
def typing(chat_id):
"""Manda sendChatAction 'typing' cada 4 s hasta que se detenga."""
stop = threading.Event()
@@ -399,45 +422,35 @@ def procesar_texto(chat, texto):
enviar(chat, f"❌ Error al lanzar OpenCode: {resp}")
return
vistos_texto = set()
vistos_tool = set()
# Respuesta SIN verbose: no se envian los comandos/tools intermedios ni
# fragmentos de streaming. Solo se recoge el texto del assistant generado
# en este turno (filtrado por t0 para no repetir mensajes de turnos
# anteriores) y se entrega al terminar junto con el tiempo empleado.
trozos = {} # pid -> (start, texto)
comienzo = time.time()
terminado = False
while time.time() - comienzo < 900:
time.sleep(3)
code, hist = oc_get(f"/session/{sid}/message?limit=4")
time.sleep(1)
code, hist = oc_get(f"/session/{sid}/message?limit=10")
if code == 200 and isinstance(hist, list):
for item in reversed(hist[-4:]):
for item in reversed(hist[-10:]):
info = item.get("info") or {}
if info.get("role") != "assistant":
continue
for p in item.get("parts") or []:
t = p.get("type")
pid = p.get("id") or ""
if t == "text" and p.get("text") and pid not in vistos_texto:
vistos_texto.add(pid)
enviar(chat, formatear_html(p["text"]))
elif t == "tool" and pid not in vistos_tool:
vistos_tool.add(pid)
comando = ((p.get("state") or {}).get("input")
or {}).get("command")
if comando:
corto = comando.strip().replace("\n", " ")[:90]
try:
import requests as _rq
_rq.post(f"{API}/sendMessage",
data={"chat_id": chat, "text":
f"⚙️ <i>{html.escape(corto)}</i>",
"parse_mode": "HTML"},
timeout=10)
except Exception:
pass
# fin del turno: la sesion deja de estar busy (tras un margen inicial)
if p.get("type") != "text" or not p.get("text"):
continue
inicio = (p.get("time") or {}).get("start") or 0
if inicio and inicio < t0:
continue # parte de un turno anterior: ignorar
pid = p.get("id") or f"{inicio}-{len(p['text'])}"
if pid not in trozos:
trozos[pid] = (inicio, p["text"])
code2, sts = oc_get("/session/status", timeout=10)
transcurrido = time.time() - comienzo
if (code2 == 200 and isinstance(sts, dict)
and sts.get(sid, {}).get("type") != "busy"
and transcurrido >= 5):
and transcurrido >= 2):
terminado = True
break
@@ -447,6 +460,14 @@ def procesar_texto(chat, texto):
"para cortarla y empezar de nuevo.")
return
# Texto consolidado del turno, ordenado por tiempo de inicio.
texto_final = "\n".join(t for _, t in
sorted(trozos.values(), key=lambda x: x[0]))
if texto_final.strip():
seg = time.time() - comienzo
enviar(chat, formatear_html(texto_final.strip()))
enviar(chat, f"⏱️ Hecho en <b>{seg:.1f}s</b>")
# ficheros tocados en el turno
adjuntos = []
try:
@@ -461,6 +482,9 @@ def procesar_texto(chat, texto):
for ruta in adjuntos[:6]:
enviar_doc(chat, ruta)
# Aviso sonoro de fin (nota de voz en el movil), en segundo plano.
aviso_voz_fin(chat)
def cambiar_repo(chat, slug):
ruta = os.path.join(GH, slug)
@@ -560,8 +584,10 @@ def cmd_salir(chat):
def cmd_limpiar(chat):
"""Borra los mensajes que el bot ha enviado en este chat."""
"""Borra TODO el chat visible: mensajes del bot y del usuario.
Limite de Telegram: solo mensajes de menos de 48h se pueden borrar."""
lista = sentidos.get(str(chat)) or []
lista += entrantes.get(str(chat)) or []
borrados = 0
for mid in reversed(lista):
try:
@@ -573,7 +599,8 @@ def cmd_limpiar(chat):
except Exception:
pass
sentidos[str(chat)] = []
enviar(chat, f"🧹 Pantalla limpia ({borrados} mensajes borrados).")
entrantes[str(chat)] = []
enviar(chat, f"🧹 Chat limpio ({borrados} mensajes borrados).")
# borra tambien este aviso al cabo de unos segundos
threading.Timer(6.0, _autolimpia, args=(chat,)).start()
@@ -819,6 +846,7 @@ def responder_callback(callback):
def procesar(texto, chat):
_marcar_actividad()
texto = texto.strip()
bajo = texto.lower()
if not bajo.startswith("/"):
@@ -856,12 +884,51 @@ def procesar(texto, chat):
enviar(chat, "No reconozco ese comando. Usa /ayuda.")
def _vigilante_inactividad():
"""Libera memoria/CPU: si el serve lleva OC_INACTIVIDAD_MIN minutos sin
uso, lo reinicia y olvida las sesiones (la proxima peticion crea una
sesion nueva). 0 = desactivado."""
try:
minutos = int(ENV.get("OC_INACTIVIDAD_MIN", "120"))
except Exception:
minutos = 120
if minutos <= 0:
return
umbral = minutos * 60
while True:
time.sleep(60)
if time.time() - ultima_actividad < umbral:
continue
log("inactividad: reiniciando opencode-serve para liberar memoria")
try:
subprocess.run(["systemctl", "restart", "opencode-serve"],
timeout=60, capture_output=True)
for _ in range(30):
if health_ok():
break
time.sleep(1)
except Exception as e:
log(f"vigilante restart error: {e}")
# olvidar sesiones: la proxima peticion arranca sesion nueva
sesiones.clear()
guardar_estado()
_marcar_actividad()
for chat in (ALLOWED.split(",") if ALLOWED else []):
try:
enviar(chat.strip(), "♻️ Reiniciado por inactividad para "
"liberar memoria. La próxima petición "
"arranca en sesión nueva.")
except Exception:
pass
def main():
if not TOKEN:
print("Falta TELEGRAM_BOT_TOKEN en .env")
return
cargar_estado()
listar_repos()
threading.Thread(target=_vigilante_inactividad, daemon=True).start()
log("Bot iniciado")
offset = cargar_offset()
while True:
@@ -887,6 +954,11 @@ def main():
log(f"Bloqueado chat {chat}")
continue
texto = msg.get("text")
if msg.get("message_id"):
lista = entrantes.setdefault(chat, [])
lista.append(msg["message_id"])
if len(lista) > 300:
entrantes[chat] = lista[-300:]
if texto:
threading.Thread(target=procesar, args=(texto, chat),
daemon=True).start()