Compare commits

...

3 Commits

8 changed files with 604 additions and 33 deletions

View File

@@ -92,16 +92,22 @@
} }
}, },
"provider": { "provider": {
"ollama": { "ollama": {
"name": "Ollama", "name": "Ollama",
"npm": "@ai-sdk/openai-compatible", "npm": "@ai-sdk/openai-compatible",
"models": { "models": {
"gemma4": { "jj-flash": {
"name": "Gemma 4 Local" "name": "1 MÁS POTENTE — GLM 4.7 Flash (MoE 29B, ctx 32K, 47 t/s)"
}, },
"deepseek-coder-v2:16b": { "jj-coder": {
"name": "DeepSeek Coder Local" "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": { "options": {
"baseURL": "http://localhost:11434/v1" "baseURL": "http://localhost:11434/v1"

View File

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

View File

@@ -0,0 +1,60 @@
# Videoclub — 2026-09-06
**Total disponibles: 51** | Novedades: 0
## Películas (37)
- Backrooms 2160p 4K WEB x265 10bit AAC5 1 (2026) `https://real-debrid.com/d/WC7MIGNKYMMEAFED`
- Disclosure Day 1080p ViruseProject (2026) `https://real-debrid.com/d/7AKRWAMVG2A7OFA6`
- Disclosure Day NO-ADS 1080p x265 MD-AAC5 1+2 0 ITA-ENG MmTRaXx DD-CREW-IlCosaro (2026) `https://real-debrid.com/d/ZWPJNGU5EKGA61D4`
- Downloads
- El dia de la revelacion Disclosure Day (2026) `https://real-debrid.com/d/AHBLKPLNL56KOBBC`
- El día de la revelacion Disclosure Day (2026) `https://real-debrid.com/d/NSHXB2IFNLNEK7C8`
- El Pasajero Nocturno (2026) `https://real-debrid.com/d/52MMX6GHV6U5639B`
- El Pasajero Nocturno (2026) `https://real-debrid.com/d/MBLS4EMXARKVM5C4`
- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1`
- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1`
- Fiasco total La tragedia del festival Astroworld (2025) `https://real-debrid.com/d/YCBMS3HYQPLU27E1`
- Greenland `https://real-debrid.com/d/LUZ3G5KBQZYES397`
- Greenland 2 `https://real-debrid.com/d/FS4OAHZSNYHFK568`
- Greenland El Ultimo Refugio (2020) `https://real-debrid.com/d/MJR44YRCTRDMY2C9`
- He-Man y los Masters del Universo (2026) `https://real-debrid.com/d/APGDE3Z7ADR3U58B`
- Iron Lung 2026 1080p `https://real-debrid.com/d/I6BVHXX7IFSUE1DB`
- La maldicion de Shelby Oaks (2026) `https://real-debrid.com/d/T4FFNHSB2XBV4D92`
- La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/QDPUO2UWMDWX6C6E`
- Luger (2026) `https://real-debrid.com/d/VWUUZIQGVLO76357`
- Obsession 2160p UHD x265 10bit DV HDR r00t (2026) `https://real-debrid.com/d/XFJULZ3HFQ6VG8A4`
- Obsession 2160p UHD x265 10bit DV HDR TrueHD Atmos 7 1 r00t (2026) `https://real-debrid.com/d/LMJHM23CEYXZOA66`
- Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/LESH2XGOLRWYKEC8`
- Posesion infernal En llamas (2026) `https://real-debrid.com/d/RF2IRCGKIKISI174`
- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A`
- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A`
- Project Hail Mary 2160p KinoRip h265 En Es Dual YG (2026) `https://real-debrid.com/d/OMCP4MAMM775Q17A`
- Resident Evil `https://real-debrid.com/d/6VMIEUSOVVIXYBD2`
- Salvacion (2026) `https://real-debrid.com/d/BF3FGPVABGL6A610`
- Salvacion (2026) `https://real-debrid.com/d/4Q6AFXH6KSKRO2AF`
- Scary Movie (2026) `https://real-debrid.com/d/7FHGHGOXV7ELY3E6`
- Spider-Man No Way Home 4Kremux2160 atomixhq link `https://real-debrid.com/d/S7GR3CYFAWOWC7AC`
- Steven Spielberg el rey midas de Hollywood wolfmax4k com (2024) `https://real-debrid.com/d/WWCMHPUY4FPFAF67`
- Super Mario Galaxy La película (2026) `https://real-debrid.com/d/VNTK5JCJGRHJOF0E`
- super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p `https://real-debrid.com/d/3GQUITYMNB4TUE1C`
- The End of Oak Street 1080p HC V2 x264-DKS (2026) `https://real-debrid.com/d/JI3KPXS4HEY7Y234`
- TheAdventuresOfTomSawyer `https://real-debrid.com/d/XJVUAD7LIIMFKDCC`
- Twilight Saga 2008-2012 2160p H265 HDR10 AC3 5 1 ITA ENG sub ita eng Sp33dy94-MIRCrew `https://real-debrid.com/d/II6EYH6KIMNUI10A`
## Series (14)
- Euphoria `https://real-debrid.com/d/YOFHJHFSKN6HOB11`
- From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio - sub `https://real-debrid.com/d/B64L3AL7NWVN41D1`
- Heidi Girl of the Alps Alps no Shoujo Heidi Season 1 BD 1080p HEVC x265 10bit Eng-Subs -Judas `https://real-debrid.com/d/3V5DNKCHKQOPGF89`
- Monarch El Legado De Los Monstruos wolfmax4k com `https://real-debrid.com/d/LRB4AEEJCYOCU1E0`
- Star Trek Strange New Worlds `https://real-debrid.com/d/UCVH7W3HSXDKS4E5`
- Stargate Sg-1 05x09 Entre Dos Fuegos `https://real-debrid.com/d/TYLADWGS6AEMQ28C`
- Stargate Sg-1 05x10 2001 `https://real-debrid.com/d/LHQ7LEN7VCRSS1C4`
- Stargate Sg-1 05x12 Agujero X-Tremo `https://real-debrid.com/d/7BTWUVXH2KH7KCC5`
- Supergirl (2026) `https://real-debrid.com/d/65RUKRSI3YERQDCB`
- Supergirl (2026) `https://real-debrid.com/d/ZKB3MUWMEM52G885`
- The Studio `https://real-debrid.com/d/6AQCY2YCPY6FE9A9`
- The Studio `https://real-debrid.com/d/L7DDTFXUCCPF47A5`
- The Studio `https://real-debrid.com/d/P3J5H2OCFG76WC67`
- The Studio wolfmax4k com `https://real-debrid.com/d/KHLUHJ4W7JMKA663`

View File

@@ -0,0 +1,413 @@
{
"fecha": "2026-09-06",
"items": [
{
"id": "realdebrid:Downloads",
"name": "Downloads",
"clean": "Downloads",
"year": null,
"tipo": "pelicula",
"enlace": null
},
{
"id": "realdebrid:O47SZPHDEIJD4",
"name": "Posesion infernal En llamas (2026) [4k 2160p][Esp].mkv",
"clean": "Posesion infernal En llamas",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/RF2IRCGKIKISI174"
},
{
"id": "realdebrid:5DLXRYNWASICQ",
"name": "The.End.of.Oak.Street.2026.1080p.HC.V2.x264-DKS.mkv",
"clean": "The End of Oak Street 1080p HC V2 x264-DKS",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/JI3KPXS4HEY7Y234"
},
{
"id": "realdebrid:TEGQKYCCQBXXO",
"name": "Supergirl (2026) [Bluray 720p][Esp].mkv",
"clean": "Supergirl",
"year": "2026",
"tipo": "serie",
"enlace": "https://real-debrid.com/d/65RUKRSI3YERQDCB"
},
{
"id": "realdebrid:HARMOP47F3AP2",
"name": "Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv",
"clean": "Obsession Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/LESH2XGOLRWYKEC8"
},
{
"id": "realdebrid:V7XUZQ6APSG4Q",
"name": "Scary Movie (2026) [4k 2160p][Esp].mkv",
"clean": "Scary Movie",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/7FHGHGOXV7ELY3E6"
},
{
"id": "realdebrid:MBGEYHZC4MKYU",
"name": "Star Trek Strange New Worlds [HDTV 1080p][Cap.401].mkv",
"clean": "Star Trek Strange New Worlds",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/UCVH7W3HSXDKS4E5"
},
{
"id": "realdebrid:HEIP5O2XB5V66",
"name": "He-Man y los Masters del Universo (2026) [Bluray 1080p][Esp].mkv",
"clean": "He-Man y los Masters del Universo",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/APGDE3Z7ADR3U58B"
},
{
"id": "realdebrid:SLJ2UJGPDFZLW",
"name": "Supergirl (2026) [Bluray 1080p][Esp].mkv",
"clean": "Supergirl",
"year": "2026",
"tipo": "serie",
"enlace": "https://real-debrid.com/d/ZKB3MUWMEM52G885"
},
{
"id": "realdebrid:FC5FZS2J4HUFS",
"name": "El dia de la revelacion (Disclosure Day) (2026) [Bluray 1080p][Esp].mkv",
"clean": "El dia de la revelacion Disclosure Day",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/AHBLKPLNL56KOBBC"
},
{
"id": "realdebrid:A5XIYLPNAZNZM",
"name": "El día de la revelacion (Disclosure Day) (2026) [4k 2160p][Esp].mkv",
"clean": "El día de la revelacion Disclosure Day",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/NSHXB2IFNLNEK7C8"
},
{
"id": "realdebrid:7XL55PFQOE4OO",
"name": "El Pasajero Nocturno (2026) [Bluray 1080p][Esp].mkv",
"clean": "El Pasajero Nocturno",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/52MMX6GHV6U5639B"
},
{
"id": "realdebrid:5IHX4PFCFP5QY",
"name": "El Pasajero Nocturno (2026) [Bluray][Esp].avi",
"clean": "El Pasajero Nocturno",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/MBLS4EMXARKVM5C4"
},
{
"id": "realdebrid:S52E6GZH2FSNK",
"name": "Disclosure.Day.(2026).(NO-ADS).1080p.x265.MD-AAC5.1+2.0.ITA-ENG.MmTRaXx(DD-CREW-IlCosaro).mkv",
"clean": "Disclosure Day NO-ADS 1080p x265 MD-AAC5 1+2 0 ITA-ENG MmTRaXx DD-CREW-IlCosaro",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/ZWPJNGU5EKGA61D4"
},
{
"id": "realdebrid:FOTT5OF2CNCQO",
"name": "Disclosure.Day.2026.1080p.CAMRip.ViruseProject.mkv",
"clean": "Disclosure Day 1080p ViruseProject",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/7AKRWAMVG2A7OFA6"
},
{
"id": "realdebrid:FEWY5OUINFE6I",
"name": "Obsession (2026) (2160p UHD BluRay x265 10bit DV HDR r00t).mkv",
"clean": "Obsession 2160p UHD x265 10bit DV HDR r00t",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/XFJULZ3HFQ6VG8A4"
},
{
"id": "realdebrid:GE3KLARBGACYC",
"name": "Obsession (2026) (2160p UHD BluRay x265 10bit DV HDR TrueHD Atmos 7.1 r00t)",
"clean": "Obsession 2160p UHD x265 10bit DV HDR TrueHD Atmos 7 1 r00t",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/LMJHM23CEYXZOA66"
},
{
"id": "realdebrid:REQOOWL7R3X5M",
"name": "Backrooms.2026.2160p.4K.WEB.x265.10bit.AAC5.1-[YTS.GG - YTS.BZ].mkv",
"clean": "Backrooms 2160p 4K WEB x265 10bit AAC5 1",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/WC7MIGNKYMMEAFED"
},
{
"id": "realdebrid:HEVMDYWAXLSLM",
"name": "Luger (2026) [Bluray 720p][Esp].mkv",
"clean": "Luger",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/VWUUZIQGVLO76357"
},
{
"id": "realdebrid:MBSDHRJMTC3S2",
"name": "Spider-Man No Way Home 4Kremux2160.atomixhq.link.mkv",
"clean": "Spider-Man No Way Home 4Kremux2160 atomixhq link",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/S7GR3CYFAWOWC7AC"
},
{
"id": "realdebrid:YVLXIF6ILFL2U",
"name": "Iron Lung 2026 1080p.mkv",
"clean": "Iron Lung 2026 1080p",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/I6BVHXX7IFSUE1DB"
},
{
"id": "realdebrid:DLUAAXNZJ2ZHW",
"name": "From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio [ES+EN] - sub [ES+EN].mkv",
"clean": "From - S02E01 - Strangers in a Strange Land - WEBDL-1080p - Spanish 2x01 - audio - sub",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/B64L3AL7NWVN41D1"
},
{
"id": "realdebrid:UP7DPBWVMH7JS",
"name": "La maldicion de Shelby Oaks (2026) [4k 2160p][Esp].mkv",
"clean": "La maldicion de Shelby Oaks",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/T4FFNHSB2XBV4D92"
},
{
"id": "realdebrid:PMBQI365H7PU6",
"name": "Steven Spielberg el rey midas de Hollywood (2024) [HDTV 1080p][Esp](wolfmax4k.com).mkv",
"clean": "Steven Spielberg el rey midas de Hollywood wolfmax4k com",
"year": "2024",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/WWCMHPUY4FPFAF67"
},
{
"id": "realdebrid:OWWSTBTFG4ZFO",
"name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv",
"clean": "Fiasco total La tragedia del festival Astroworld",
"year": "2025",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1"
},
{
"id": "realdebrid:M4UVGSNUKTLA6",
"name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv",
"clean": "Fiasco total La tragedia del festival Astroworld",
"year": "2025",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1"
},
{
"id": "realdebrid:7HQN7ELMLWTDI",
"name": "Fiasco total La tragedia del festival Astroworld (2025) [HDTV 1080p][Esp].mkv",
"clean": "Fiasco total La tragedia del festival Astroworld",
"year": "2025",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/YCBMS3HYQPLU27E1"
},
{
"id": "realdebrid:BRNWEZP2TGOEQ",
"name": "La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv",
"clean": "La momia de Lee Cronin Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/QDPUO2UWMDWX6C6E"
},
{
"id": "realdebrid:3MNHGJ3UM73VY",
"name": "super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5.1 BD-Rip hd 1080p.mkv",
"clean": "super mario galaxy la película Castellano-Ingles+Subt Castellano-Ingles H264 E-AC3 5 1 BD-Rip hd 1080p",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/3GQUITYMNB4TUE1C"
},
{
"id": "realdebrid:PKZ3B7VEZXWZQ",
"name": "Super Mario Galaxy La película (2026) [4k 2160p][Esp].mkv",
"clean": "Super Mario Galaxy La película",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/VNTK5JCJGRHJOF0E"
},
{
"id": "realdebrid:6RE4EY5ZP2ILA",
"name": "Proyecto Salvacion (2026) [Bluray 1080p][Esp].mkv",
"clean": "Salvacion",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/BF3FGPVABGL6A610"
},
{
"id": "realdebrid:ASX6FSY2ZZD36",
"name": "Heidi Girl of the Alps (Alps no Shoujo Heidi) (Season 1) (BD 1080p)(HEVC x265 10bit)(Eng-Subs)-Judas[TGx]",
"clean": "Heidi Girl of the Alps Alps no Shoujo Heidi Season 1 BD 1080p HEVC x265 10bit Eng-Subs -Judas",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/3V5DNKCHKQOPGF89"
},
{
"id": "realdebrid:K4CJIWAJF3NKS",
"name": "Greenland El Ultimo Refugio (2020) [BluRay Rip][AC3 5.1 Castellano][www.PctFenix.com].avi",
"clean": "Greenland El Ultimo Refugio",
"year": "2020",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/MJR44YRCTRDMY2C9"
},
{
"id": "realdebrid:KDWFL34NA3MXE",
"name": "Greenland [4K UHDremux][2160p][HDR10][AC3 5.1 Castellano-True HD 7.1-Ingles+Subs][ES-EN]",
"clean": "Greenland",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/LUZ3G5KBQZYES397"
},
{
"id": "realdebrid:KW4WNKPELJM5O",
"name": "Greenland 2 [4k 2160p][Esp].mkv",
"clean": "Greenland 2",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/FS4OAHZSNYHFK568"
},
{
"id": "realdebrid:WEFMCETRL5BZM",
"name": "Resident Evil [FullUHD 2160p][Castellano AC3 5.1-Ingles DolbyTrueHD 7.1+Subs]",
"clean": "Resident Evil",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/6VMIEUSOVVIXYBD2"
},
{
"id": "realdebrid:CFPM3BI4C7EXO",
"name": "Proyecto Salvacion (2026) [4k 2160p][Esp].mkv",
"clean": "Salvacion",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/4Q6AFXH6KSKRO2AF"
},
{
"id": "realdebrid:5W6E6YBRA7V22",
"name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv",
"clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A"
},
{
"id": "realdebrid:3GJHZMZQUQ544",
"name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv",
"clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A"
},
{
"id": "realdebrid:YAFM2WGO4IR64",
"name": "Project.Hail.Mary.2026.2160p.KinoRip.h265.En.Es.Dual YG.mkv",
"clean": "Project Hail Mary 2160p KinoRip h265 En Es Dual YG",
"year": "2026",
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/OMCP4MAMM775Q17A"
},
{
"id": "realdebrid:RBESCUG7LBCH2",
"name": "Twilight Saga (2008-2012) 2160p H265 HDR10 AC3 5.1 ITA.ENG sub ita.eng Sp33dy94-MIRCrew",
"clean": "Twilight Saga 2008-2012 2160p H265 HDR10 AC3 5 1 ITA ENG sub ita eng Sp33dy94-MIRCrew",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/II6EYH6KIMNUI10A"
},
{
"id": "realdebrid:ZYLMP5J4H3VE6",
"name": "Monarch El Legado De Los Monstruos [4k 2160p][Cap.101](wolfmax4k.com).mkv",
"clean": "Monarch El Legado De Los Monstruos wolfmax4k com",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/LRB4AEEJCYOCU1E0"
},
{
"id": "realdebrid:37ZFJ63K6W7PM",
"name": "Stargate_Sg-1_05x12_Agujero_X-Tremo.avi",
"clean": "Stargate Sg-1 05x12 Agujero X-Tremo",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/7BTWUVXH2KH7KCC5"
},
{
"id": "realdebrid:6JJIFVQ5MZQEI",
"name": "TheAdventuresOfTomSawyer",
"clean": "TheAdventuresOfTomSawyer",
"year": null,
"tipo": "pelicula",
"enlace": "https://real-debrid.com/d/XJVUAD7LIIMFKDCC"
},
{
"id": "realdebrid:5KFSC2OVN37MO",
"name": "The Studio [HDTV 1080p][Cap.105]",
"clean": "The Studio",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/6AQCY2YCPY6FE9A9"
},
{
"id": "realdebrid:3AS7OJ3EXXKMC",
"name": "Stargate_Sg-1_05x10_2001.avi",
"clean": "Stargate Sg-1 05x10 2001",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/LHQ7LEN7VCRSS1C4"
},
{
"id": "realdebrid:UYIBLKOIL5HD6",
"name": "The Studio [HDTV 1080p][Cap.104]",
"clean": "The Studio",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/L7DDTFXUCCPF47A5"
},
{
"id": "realdebrid:CQ5CWIAKCM3ZS",
"name": "The Studio [HDTV 1080p][Cap.103]",
"clean": "The Studio",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/P3J5H2OCFG76WC67"
},
{
"id": "realdebrid:TMZRDVHVHUHRQ",
"name": "The Studio [HDTV 1080p][cap.102](wolfmax4k.com).mkv",
"clean": "The Studio wolfmax4k com",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/KHLUHJ4W7JMKA663"
},
{
"id": "realdebrid:S5HO3BW6ADQYI",
"name": "Euphoria [4k 2160p][Cap.302].mkv",
"clean": "Euphoria",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/YOFHJHFSKN6HOB11"
},
{
"id": "realdebrid:CCDJGDZJK3FRA",
"name": "Stargate_Sg-1_05x09_Entre_Dos_Fuegos.avi",
"clean": "Stargate Sg-1 05x09 Entre Dos Fuegos",
"year": null,
"tipo": "serie",
"enlace": "https://real-debrid.com/d/TYLADWGS6AEMQ28C"
}
]
}

View File

@@ -252,20 +252,29 @@ def formatear_peliculas(resultados):
calidad = "4K" if "4k" in (it.get("name") or "").lower() or \ calidad = "4K" if "4k" in (it.get("name") or "").lower() or \
"2160p" in (it.get("name") or "").lower() else "HD" "2160p" in (it.get("name") or "").lower() else "HD"
idioma = "cast." if it.get("castellano") else "ing." idioma = "cast." if it.get("castellano") else "ing."
enlace = it.get("enlace") or ""
lineas.append(f"<b>{i}.</b> {it['clean']}{y} — {calidad}, {idioma}") lineas.append(f"<b>{i}.</b> {it['clean']}{y} — {calidad}, {idioma}")
if enlace:
lineas.append(f" <code>descargar {enlace}</code>")
lineas.append("") 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) 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): def formatear_series(grupos):
"""Formatea resultados de series como lista de mensajes Telegram numerados.""" """Formatea resultados de series como lista de mensajes Telegram numerados."""
if not grupos: if not grupos:
return ["No se encontraron series con ese termino."] return ["No se encontraron series con ese termino."]
lineas = [] lineas = []
num_global = 1
for base in sorted(grupos, key=lambda x: x.lower()): for base in sorted(grupos, key=lambda x: x.lower()):
eps = grupos[base] eps = grupos[base]
temp_max = max(parsear_episodio(e["name"])[0] for e in eps) temp_max = max(parsear_episodio(e["name"])[0] for e in eps)
@@ -273,7 +282,6 @@ def formatear_series(grupos):
lineas.append("") lineas.append("")
temp_actual = 0 temp_actual = 0
num_global = 1
for it in eps: for it in eps:
temp, cap = parsear_episodio(it["name"]) temp, cap = parsear_episodio(it["name"])
if temp != temp_actual: if temp != temp_actual:
@@ -282,18 +290,15 @@ def formatear_series(grupos):
lineas.append(f"<b>Temporada {temp}:</b>") lineas.append(f"<b>Temporada {temp}:</b>")
resto = _limpiar_titulo_ep(it["name"][SERIE_EP_RE.search(it["name"]).end():]) \ resto = _limpiar_titulo_ep(it["name"][SERIE_EP_RE.search(it["name"]).end():]) \
if SERIE_EP_RE.search(it["name"]) else it["clean"] if SERIE_EP_RE.search(it["name"]) else it["clean"]
enlace = it.get("enlace") or ""
if temp > 0: if temp > 0:
etiqueta = f"S{temp:02d}E{cap:02d}" etiqueta = f"S{temp:02d}E{cap:02d}"
else: else:
etiqueta = it["clean"] etiqueta = it["clean"]
lineas.append(f" <b>{num_global}.</b> {etiqueta} — {resto}") lineas.append(f" <b>{num_global}.</b> {etiqueta} — {resto}")
if enlace:
lineas.append(f" <code>descargar {enlace}</code>")
num_global += 1 num_global += 1
lineas.append("") 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.")
return trocear_texto(lineas) return trocear_texto(lineas)
@@ -330,10 +335,9 @@ def formatear_novedades(items):
y = f" ({it['year']})" if it["year"] else "" y = f" ({it['year']})" if it["year"] else ""
tipo = "📺" if it["tipo"] == "serie" else "🎬" tipo = "📺" if it["tipo"] == "serie" else "🎬"
idioma = "cast." if it.get("castellano") else "ing." idioma = "cast." if it.get("castellano") else "ing."
enlace = it.get("enlace") or ""
lineas.append(f"{tipo} <b>{i}.</b> {it['clean']}{y} — {idioma}") lineas.append(f"{tipo} <b>{i}.</b> {it['clean']}{y} — {idioma}")
if enlace: lineas.append("")
lineas.append(f" <code>descargar {enlace}</code>") lineas.append("Responde <b>/torrents NUMERO</b> para ver opciones y descargar.")
return trocear_texto(lineas) return trocear_texto(lineas)

View File

@@ -106,12 +106,39 @@ def parental_status():
return json.dumps(data, ensure_ascii=False, indent=2, default=str) 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 cambiar_bloqueo(nombre, blocked): def cambiar_bloqueo(nombre, blocked):
device = resolver_dispositivo(nombre) device = resolver_dispositivo(nombre)
if not device: if not device:
raise ValueError(f"Dispositivo no reconocido: {nombre}") raise ValueError(f"Dispositivo no reconocido: {nombre}")
asyncio.run(_set_block(device["mac"], device["name"], blocked)) mac = device["mac"]
return f"{'Bloqueado' if blocked else 'Activado'}: {device['name']}" 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]}]"
asyncio.run(_set_block(mac, device["name"], blocked))
return f"{'Bloqueado' if blocked else 'Activado'}: {etiqueta} ({mac})"
async def _aimesh_data(): async def _aimesh_data():

View File

@@ -19,6 +19,7 @@ temporizadores_parental = {} # alias normalizado -> Timer de desbloqueo
menu_pendiente = {} # chat_id -> accion de texto libre esperada (menu botones) menu_pendiente = {} # chat_id -> accion de texto libre esperada (menu botones)
menu_pausa_nombre = {} # chat_id -> nombre elegido en flujo de pausa menu_pausa_nombre = {} # chat_id -> nombre elegido en flujo de pausa
historial_chat = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar) 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 borrar_pend = {} # chat_id -> lista de tareas NAS esperando numero a borrar
BASE = os.path.dirname(os.path.abspath(__file__)) BASE = os.path.dirname(os.path.abspath(__file__))
@@ -278,8 +279,10 @@ def borrar(chat_id, message_id):
def limpiar_pantalla(chat_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 = historial_chat.pop(chat_id, None) or []
lista += entrantes_chat.pop(chat_id, None) or []
borrados = 0 borrados = 0
for mid in reversed(lista): for mid in reversed(lista):
try: try:
@@ -292,7 +295,7 @@ def limpiar_pantalla(chat_id):
borrados += 1 borrados += 1
except Exception: except Exception:
pass pass
aviso = enviar(f"🧹 Pantalla limpia ({borrados} mensajes borrados).", chat_id) aviso = enviar(f"🧹 Chat limpio ({borrados} mensajes borrados).", chat_id)
if aviso: if aviso:
threading.Timer(6.0, borrar, args=(chat_id, aviso)).start() threading.Timer(6.0, borrar, args=(chat_id, aviso)).start()
@@ -943,6 +946,8 @@ def procesar_buscador_pelicula(nombre, chat_id):
for m in mensajes: for m in mensajes:
enviar(m, chat_id) enviar(m, chat_id)
if resultados: if resultados:
ultima_titulos[chat_id] = {"query": nombre, "results": resultados,
"origen": "catalogo"}
log(f"BUSCADOR pelis '{nombre}': {len(resultados)} resultados") log(f"BUSCADOR pelis '{nombre}': {len(resultados)} resultados")
@@ -967,6 +972,9 @@ def procesar_buscador_serie(nombre, chat_id):
enviar(m, chat_id) enviar(m, chat_id)
if grupos: if grupos:
total_eps = sum(len(v) for v in grupos.values()) total_eps = sum(len(v) for v in grupos.values())
ultima_titulos[chat_id] = {"query": nombre,
"results": vcb.aplanar_series(grupos),
"origen": "catalogo"}
log(f"BUSCADOR series '{nombre}': {len(grupos)} series, {total_eps} capitulos") log(f"BUSCADOR series '{nombre}': {len(grupos)} series, {total_eps} capitulos")
@@ -989,6 +997,10 @@ def procesar_novedades(chat_id):
return return
for m in mensajes: for m in mensajes:
enviar(m, chat_id) enviar(m, chat_id)
if items:
ultima_titulos[chat_id] = {"query": "novedades", "results": items,
"origen": "catalogo"}
log(f"NOVEDADES: {len(items)} items")
def _int_arg(texto): def _int_arg(texto):
@@ -1071,11 +1083,31 @@ def _mostrar_catalogo_genero(chat_id, tipo, genero, categoria):
def procesar_torrents(n, chat_id): 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) estado = ultima_titulos.get(chat_id)
if not estado or n is None or n < 1 or n > len(estado["results"]): if not estado or n is None or n < 1 or n > len(estado["results"]):
enviar("Primero usa <b>/busca &lt;nombre&gt;</b> y luego " enviar("Primero usa <b>/busca</b>, <b>/pelicula</b>, <b>/serie</b>, "
"<b>/torrents &lt;numero&gt;</b>.", chat_id) "<b>/novedades</b> o <b>/genero</b> y luego "
"<b>/torrents &lt;numero&gt;</b>.", chat_id)
return
r = estado["results"][n - 1]
if not r.get("imdb_id") and r.get("enlace"):
# Item de catalogo: ya trae enlace directo, sin paso por Torrentio.
nombre = r.get("clean") or r.get("name") or "titulo"
ultimos_torrents[chat_id] = {
"titulo": nombre,
"torrents": [{"filename": nombre, "calidad": "directo",
"size": None, "enlace": r["enlace"]}],
}
enviar(f"🧲 <b>{nombre}</b> — enlace directo disponible:\n"
"<b>1.</b> Descarga directa al NAS\n\n"
"Responde <b>/bajar 1</b> para descargarlo.", chat_id)
log(f"TORRENTS catalogo '{nombre}': enlace directo")
return
if not r.get("imdb_id"):
enviar("❌ Ese resultado no tiene enlace de descarga. "
"Prueba con <b>/busca &lt;nombre&gt;</b>.", chat_id)
return return
try: try:
gol = _import_global_buscar() gol = _import_global_buscar()
@@ -1083,7 +1115,6 @@ def procesar_torrents(n, chat_id):
log(f"global_buscar import error: {e}") log(f"global_buscar import error: {e}")
enviar("❌ No se pudo cargar el modulo", chat_id) enviar("❌ No se pudo cargar el modulo", chat_id)
return return
r = estado["results"][n - 1]
enviar(f"🧲 Buscando torrents de <b>{r['name']}</b>...", chat_id) enviar(f"🧲 Buscando torrents de <b>{r['name']}</b>...", chat_id)
try: try:
torrents = gol.torrents_de(r["imdb_id"], r["type"]) torrents = gol.torrents_de(r["imdb_id"], r["type"])
@@ -1104,8 +1135,25 @@ def procesar_bajar(n, chat_id):
"""Descarga el torrent numero n al NAS (Download Station via magnet).""" """Descarga el torrent numero n al NAS (Download Station via magnet)."""
estado = ultimos_torrents.get(chat_id) estado = ultimos_torrents.get(chat_id)
if not estado or n is None or n < 1 or n > len(estado["torrents"]): if not estado or n is None or n < 1 or n > len(estado["torrents"]):
enviar("Primero usa <b>/busca &lt;nombre&gt;</b> y luego " enviar("Primero usa <b>/busca</b>, <b>/pelicula</b>, <b>/serie</b>, "
"<b>/torrents &lt;numero&gt;</b>.", chat_id) "<b>/novedades</b> o <b>/genero</b>, luego "
"<b>/torrents &lt;numero&gt;</b>.", chat_id)
return
t = estado["torrents"][n - 1]
if t.get("enlace") and not t.get("info_hash"):
# Enlace directo de catalogo: al NAS sin magnet.
try:
import nas_dl
ok = nas_dl.add_torrent(t["enlace"])
except Exception as e:
log(f"bajar error: {e}")
enviar(f"❌ Error al anadir al NAS: {e}", chat_id)
return
if ok:
enviar(f"📥 Añadido al NAS: <b>{t.get('filename') or estado['titulo']}</b>.",
chat_id)
else:
enviar("❌ No se pudo añadir al NAS.", chat_id)
return return
try: try:
gol = _import_global_buscar() gol = _import_global_buscar()
@@ -1113,7 +1161,6 @@ def procesar_bajar(n, chat_id):
log(f"global_buscar import error: {e}") log(f"global_buscar import error: {e}")
enviar("❌ No se pudo cargar el modulo", chat_id) enviar("❌ No se pudo cargar el modulo", chat_id)
return return
t = estado["torrents"][n - 1]
magnet = gol.magnet_de(t) magnet = gol.magnet_de(t)
log(f"BAJAR '{estado['titulo']}' torrent {n}: {magnet[:60]}...") log(f"BAJAR '{estado['titulo']}' torrent {n}: {magnet[:60]}...")
try: try:
@@ -1167,6 +1214,11 @@ def _atender_update(upd):
return return
texto = msg.get("text") texto = msg.get("text")
chat_id = msg["chat"]["id"] 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: if texto:
procesar(texto, chat_id) procesar(texto, chat_id)
except Exception as e: except Exception as e:

View File

@@ -53,6 +53,7 @@ bloqueos = {} # chat_id -> True (procesando mensaje)
sesiones = {} # chat_id -> {'repo': slug, 'session': id, 'model': obj|None} sesiones = {} # chat_id -> {'repo': slug, 'session': id, 'model': obj|None}
repos_cache = [] # lista de {'slug','dir'} repos_cache = [] # lista de {'slug','dir'}
sentidos = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar) 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 menu_msg = {} # chat_id -> message_id del menu abierto
@@ -560,8 +561,10 @@ def cmd_salir(chat):
def cmd_limpiar(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 = sentidos.get(str(chat)) or []
lista += entrantes.get(str(chat)) or []
borrados = 0 borrados = 0
for mid in reversed(lista): for mid in reversed(lista):
try: try:
@@ -573,7 +576,8 @@ def cmd_limpiar(chat):
except Exception: except Exception:
pass pass
sentidos[str(chat)] = [] 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 # borra tambien este aviso al cabo de unos segundos
threading.Timer(6.0, _autolimpia, args=(chat,)).start() threading.Timer(6.0, _autolimpia, args=(chat,)).start()
@@ -887,6 +891,11 @@ def main():
log(f"Bloqueado chat {chat}") log(f"Bloqueado chat {chat}")
continue continue
texto = msg.get("text") 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: if texto:
threading.Thread(target=procesar, args=(texto, chat), threading.Thread(target=procesar, args=(texto, chat),
daemon=True).start() daemon=True).start()