04-ago: [videoclub] comando pelis (novedades castellano + comando listo + adultos efimeros 5min) + unidad NAS montada en sobremesa + lecciones

This commit is contained in:
juanjo
2026-08-04 00:40:47 +02:00
parent b2094ddb18
commit 46af244e56
4 changed files with 340 additions and 1 deletions

216
vigilancia/pelis.py Normal file
View File

@@ -0,0 +1,216 @@
"""
pelis.py — Listado de novedades con buena calidad en castellano del catalogo
RealDebrid/Torrentio, con el comando 'descargar <url>' ya montado para copiar
y pegar en Telegram. Lo usa el comando 'pelis' de pi_bot.py.
- Filtra: NO adulto, buena calidad, castellano.
- Trocea la salida en mensajes <= CHUNK_MAX para que Telegram no corte.
- Los items adultos se devuelven por separado (efimeros, nunca se guardan).
Autonomo (sin dependencias): usa solo urllib y la logica de clasificacion
duplicada de videoclub/videoclub.py para no depender del repo de negocio.
"""
import json
import os
import re
import urllib.request
from pathlib import Path
BASE = Path(__file__).resolve().parent
CHUNK_MAX = 3500 # limite de Telegram (~4096), con margen
# Reutiliza el .env del propio directorio (carga simple, sin python-dotenv)
ENV = {}
try:
for line in open(BASE / ".env"):
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
ENV[k.strip()] = v.strip()
except Exception:
pass
TORRENTIO_RD_TOKEN = os.getenv("TORRENTIO_RD_TOKEN") or ENV.get("TORRENTIO_RD_TOKEN", "")
TORRENTIO_CONFIG = os.getenv("TORRENTIO_CONFIG") or ENV.get("TORRENTIO_CONFIG", "language=spanish")
CATALOGO = ("https://torrentio.strem.fun/"
f"{TORRENTIO_CONFIG}|realdebrid={TORRENTIO_RD_TOKEN}/"
"catalog/other/torrentio-realdebrid.json")
YEAR_RE = re.compile(r"\((19\d\d|20\d\d)\)")
YEAR_BARE_RE = re.compile(r"\.(19\d\d|20\d\d)\.")
SERIE_RE = re.compile(r"(S\d{1,2}E\d{1,3}|Cap[\.\s]|cap[\.\s]|Temporada|temp\.|\bS0?1E\d|\dx\d\d)", re.IGNORECASE)
ADULTO_RE = re.compile(r"(\.XXX\.|XXX\.|private\.|brazzers|onlytarts|spankbang|xhamster|porntrex|missav|sexandsubmission|evilangel|monstersofcock|itsanal|thelifeerotic)", re.IGNORECASE)
SERIE_PALABRAS = {"star trek", "stargate", "heidi", "euphoria", "from", "the pitt",
"supergirl", "monarch", "paradise", "desaparecida", "vanished",
"strange new worlds", "star trek strange", "the studio",
"one punch man", "conan", "detective conan"}
EXTRA_SUCIO = ["wolfmax4k.com", "pctfenix", "pctfenix.com", "yts", "yts.mx",
"rarbg", "verpeliculasonline", "viruse", "proyecto", "kikorip",
"camrip", "telesync", "hdtv", "bluray", "remux", "webrip", "bdrip",
"repack", "proper", "xtrem"]
# Calidad mala -> se excluye de "buena calidad"
CALIDAD_MALA = re.compile(
r"(CAMRip|TS\.|HDTS|Telesync|KinoRip|Screener|HDCAM|HC\b|r5|telesync)", re.IGNORECASE)
# Idiomas que indican castellano/espanol/dual
CASTELLANO = re.compile(r"(Esp|Castellano|Span|dual|lat)", re.IGNORECASE)
def clean_filename(name):
"""Devuelve (titulo_limpio, year, tipo)."""
name = re.sub(r"\.(mkv|mp4|avi|mov|webm|m4v)$", "", name.strip(), flags=re.IGNORECASE)
year = None
m = YEAR_RE.search(name)
if m:
year = m.group(1)
name = name.replace(m.group(0), " ")
else:
m = YEAR_BARE_RE.search(name)
if m:
year = m.group(1)
name = name.replace(m.group(0), " ")
es_serie = bool(SERIE_RE.search(name)) or any(p in name.lower() for p in SERIE_PALABRAS)
es_adulto = bool(ADULTO_RE.search(name))
name = re.sub(r"\[[^\]]*\]", " ", name)
name = re.sub(r"[\(\)\[\]]", " ", name)
name = name.replace("_", " ").replace(".", " ")
name = re.sub(r"\s+", " ", name).strip()
tokens = [t for t in name.split(" ") if t.lower() not in EXTRA_SUCIO]
name = " ".join(tokens).strip(" -")
name = re.sub(r"\s+", " ", name).strip()
tipo = "adulto" if es_adulto else ("serie" if es_serie else "pelicula")
return name, year, tipo
def fetch_catalogo():
"""Descarga el catalogo + enlaces directos RD. Devuelve lista de items."""
if not TORRENTIO_RD_TOKEN:
return []
url = CATALOGO.format()
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=40) as r:
data = json.loads(r.read().decode("utf-8"))
enlaces = {}
try:
req2 = urllib.request.Request(
"https://api.real-debrid.com/rest/1.0/torrents",
headers={"Authorization": f"Bearer {TORRENTIO_RD_TOKEN}"})
with urllib.request.urlopen(req2, timeout=40) as r2:
for t in json.loads(r2.read().decode("utf-8")):
links = t.get("links") or []
if links:
enlaces[t["id"]] = links[0]
except Exception as e:
print(f"[pelis] No se pudieron obtener enlaces RD: {e}")
items = []
for m in data.get("metas", []):
name = m.get("name", "")
clean, year, tipo = clean_filename(name)
rd_id = m.get("id", "").split(":", 1)[-1]
items.append({
"id": m.get("id"),
"name": name,
"clean": clean,
"year": year,
"tipo": tipo,
"enlace": enlaces.get(rd_id),
"buena_calidad": not bool(CALIDAD_MALA.search(name)),
"castellano": bool(CASTELLANO.search(name)),
})
return items
def seleccionar(items, ano_min=2024):
"""Novedades con buena calidad en castellano (excluye adulto y series sueltas)."""
out = []
for it in items:
if it["tipo"] == "adulto":
continue
if not it["buena_calidad"] or not it["castellano"]:
continue
try:
y = int(it["year"]) if it["year"] else 0
except ValueError:
y = 0
if y < ano_min:
continue
if it["tipo"] == "serie" and it.get("enlace") and "S01E0" in it["name"]:
continue
out.append(it)
out.sort(key=lambda x: (int(x["year"] or 0), x["clean"] or ""), reverse=True)
return out
def separar_adultos(items):
"""Separa los items adultos del resto."""
normales, adultos = [], []
for it in items:
(adultos if it["tipo"] == "adulto" else normales).append(it)
return normales, adultos
def formatear_linea(it):
"""Línea lista para copiar/pegar: titulo + 'descargar <url>'."""
y = f" ({it['year']})" if it["year"] else ""
enlace = it.get("enlace") or ""
linea = f"{it['clean']}{y}"
if enlace:
linea += f"\n<code>descargar {enlace}</code>"
return linea
def trocear(lista, max_len=CHUNK_MAX):
"""Convierte items en mensajes <= max_len. Cada item no se parte."""
mensajes, actual = [], []
long = 0
for it in lista:
linea = formatear_linea(it)
if actual and long + len(linea) + 1 > max_len:
mensajes.append("\n".join(actual))
actual, long = [], 0
actual.append(linea)
long += len(linea) + 1
if actual:
mensajes.append("\n".join(actual))
return mensajes
def trocear_efimero(texto, items, max_len=CHUNK_MAX):
"""Trocea un texto con lineas 'titulo + descargar <url>' sin partir lineas."""
lineas = texto.split("\n")
mensajes, actual = [], []
long = 0
for linea in lineas:
if actual and long + len(linea) + 1 > max_len:
mensajes.append("\n".join(actual))
actual, long = [], 0
actual.append(linea)
long += len(linea) + 1
if actual:
mensajes.append("\n".join(actual))
return mensajes
def generar(fecha=None, ano_min=2024):
"""Devuelve (mensajes_normales, adultos_list). Uso desde pi_bot."""
items = fetch_catalogo()
normales, adultos = separar_adultos(items)
sel = seleccionar(normales, ano_min=ano_min)
mensajes = trocear(sel)
if not mensajes:
mensajes = ["No hay novedades con buena calidad en castellano."]
return mensajes, adultos
if __name__ == "__main__":
msgs, adult = generar()
for i, m in enumerate(msgs, 1):
print(f"--- Mensaje {i}/{len(msgs)} ({len(m)} chars) ---")
print(m)
print()
print(f"ADULTOS: {len(adult)} (efimeros, no se listan aqui)")