Compare commits
37 Commits
b37ed956f6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
578bae9a1f | ||
|
|
defbc2e4b2 | ||
|
|
93aa46a5d1 | ||
|
|
697e835287 | ||
|
|
db8f37cab8 | ||
|
|
50d8197eea | ||
|
|
4562caa585 | ||
|
|
fd3f190534 | ||
|
|
f6116d1af2 | ||
|
|
09e8f1d9c3 | ||
|
|
94c967c343 | ||
|
|
bed840417d | ||
|
|
9387e97167 | ||
|
|
3193c30340 | ||
|
|
b982e69175 | ||
|
|
f16102748c | ||
|
|
6678383e8f | ||
|
|
086a97c3f9 | ||
|
|
d77a25cfb4 | ||
|
|
b75f71e8aa | ||
|
|
2ac60433ae | ||
|
|
9b4afd9f91 | ||
|
|
7f0769edd2 | ||
|
|
df1f7f0d93 | ||
|
|
2801d2a412 | ||
|
|
df2fbadcc3 | ||
|
|
d995af2007 | ||
|
|
f69742ac59 | ||
|
|
1ff1c43f3c | ||
|
|
0166f2333a | ||
|
|
61b2bbde12 | ||
|
|
bc435b3666 | ||
|
|
2b1fdcc8fd | ||
|
|
77f077854c | ||
|
|
bdc6cf8f48 | ||
|
|
c82737cb13 | ||
|
|
9c9303d082 |
29
AGENTS.md
29
AGENTS.md
@@ -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)*
|
||||
|
||||
@@ -92,16 +92,22 @@
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
"ollama": {
|
||||
"ollama": {
|
||||
"name": "Ollama",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"models": {
|
||||
"gemma4": {
|
||||
"name": "Gemma 4 Local"
|
||||
},
|
||||
"deepseek-coder-v2:16b": {
|
||||
"name": "DeepSeek Coder Local"
|
||||
}
|
||||
"jj-flash": {
|
||||
"name": "1 MÁS POTENTE — GLM 4.7 Flash (MoE 29B, ctx 32K, 47 t/s)"
|
||||
},
|
||||
"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": {
|
||||
"baseURL": "http://localhost:11434/v1"
|
||||
|
||||
130
docs/tareas-hijas-google-drive.md
Normal file
130
docs/tareas-hijas-google-drive.md
Normal 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").
|
||||
110
documentacion/omniroute-guia-autorouting-combos.md
Normal file
110
documentacion/omniroute-guia-autorouting-combos.md
Normal 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 comportamiento 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.
|
||||
2
documentacion/omniroute_fix_status.txt
Normal file
2
documentacion/omniroute_fix_status.txt
Normal 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)
|
||||
39
scripts/check_omniroute_fix.ps1
Normal file
39
scripts/check_omniroute_fix.ps1
Normal 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
|
||||
}
|
||||
161
scripts/tareas_hijas/drive.py
Normal file
161
scripts/tareas_hijas/drive.py
Normal 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
14
scripts/tunel-pi-api.ps1
Normal 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
|
||||
}
|
||||
@@ -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 <numero></b> para ver torrents.", ""]
|
||||
"Responde <b>/torrents <numero></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 ""
|
||||
|
||||
60
videoclub/listados/2026-09-06.md
Normal file
60
videoclub/listados/2026-09-06.md
Normal 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`
|
||||
60
videoclub/listados/2026-09-08.md
Normal file
60
videoclub/listados/2026-09-08.md
Normal 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`
|
||||
60
videoclub/listados/2026-09-09.md
Normal file
60
videoclub/listados/2026-09-09.md
Normal 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`
|
||||
60
videoclub/listados/2026-09-10.md
Normal file
60
videoclub/listados/2026-09-10.md
Normal 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`
|
||||
60
videoclub/listados/2026-09-13.md
Normal file
60
videoclub/listados/2026-09-13.md
Normal 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`
|
||||
413
videoclub/snapshots/2026-09-06.json
Normal file
413
videoclub/snapshots/2026-09-06.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
413
videoclub/snapshots/2026-09-08.json
Normal file
413
videoclub/snapshots/2026-09-08.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
413
videoclub/snapshots/2026-09-09.json
Normal file
413
videoclub/snapshots/2026-09-09.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
413
videoclub/snapshots/2026-09-10.json
Normal file
413
videoclub/snapshots/2026-09-10.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
413
videoclub/snapshots/2026-09-13.json
Normal file
413
videoclub/snapshots/2026-09-13.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
220
vigilancia/aspiradora.py
Normal 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))
|
||||
@@ -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():
|
||||
|
||||
@@ -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 <n></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 <n></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)
|
||||
|
||||
|
||||
|
||||
114
vigilancia/meross_control.py
Normal file
114
vigilancia/meross_control.py
Normal 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))
|
||||
@@ -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__":
|
||||
|
||||
@@ -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 <n></code> — torrents del resultado n\n"
|
||||
" ✏️ Ej: <code>/torrents 1</code>\n"
|
||||
"<b>4.</b> <code>bajar <n></code> — descargar al NAS el torrent n\n"
|
||||
" ✏️ Ej: <code>/bajar 1</code>\n"
|
||||
"<b>4.</b> <code>bajar <n></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 <nombre></code> — buscar película (catálogo)\n"
|
||||
" ✏️ Ej: <code>/pelicula ant-man</code>\n"
|
||||
"<b>6.</b> <code>serie <nombre></code> — buscar serie (catálogo)\n"
|
||||
@@ -785,7 +848,14 @@ def procesar(texto, chat_id):
|
||||
"<b>9.</b> <code>descargar <enlace></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 <mensaje></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)
|
||||
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 grupos:
|
||||
total_eps = sum(len(v) for v in grupos.values())
|
||||
log(f"BUSCADOR series '{nombre}': {len(grupos)} series, {total_eps} capitulos")
|
||||
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,11 +1273,31 @@ 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 <nombre></b> y luego "
|
||||
"<b>/torrents <numero></b>.", chat_id)
|
||||
enviar("Primero usa <b>/busca</b>, <b>/pelicula</b>, <b>/serie</b>, "
|
||||
"<b>/novedades</b> o <b>/genero</b> y luego "
|
||||
"<b>/torrents <numero></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 <nombre></b>.", chat_id)
|
||||
return
|
||||
try:
|
||||
gol = _import_global_buscar()
|
||||
@@ -1083,7 +1305,6 @@ def procesar_torrents(n, chat_id):
|
||||
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 <nombre></b> y luego "
|
||||
"<b>/torrents <numero></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 <numero></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 <numero></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 <numero></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 <N></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:
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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():
|
||||
|
||||
110
vps_opencode_bot/aviso_voz.py
Normal file
110
vps_opencode_bot/aviso_voz.py
Normal 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)
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user