Files
biblioteca_conocimiento_lab…/vigilancia/menu_idd.py

527 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""menu_idd.py - Menu de botones inline para el bot IDD (Telegram).
Capa nueva sobre pi_bot.py: los botones NO duplican logica, disparan los
mismos comandos de texto llamando a pi_bot.procesar(). Asi las integraciones
(NAS, router, camaras, TV, Tapo, parental, AiMesh, QoS, limites) siguen
exactamente igual. Se importa pi_bot de forma perezosa para evitar ciclos.
"""
import json
import time
API = "https://api.telegram.org/bot"
# Prefijo de los callback_data del menu: "m:<ruta>"
PREFIJO = "m:"
def _bot_token():
import pi_bot
return pi_bot.TOKEN
def _api(method):
return f"{API}{_bot_token()}/{method}"
def _log(linea):
import pi_bot
try:
pi_bot.log(f"menu: {linea}")
except Exception:
pass
def _txt(codigo, chat_id):
"""Recupera el estado de la ultima busqueda de pi_bot para este chat."""
import pi_bot
try:
if codigo == "ultimos_titulos":
estado = pi_bot.ultima_titulos.get(chat_id) or {}
return estado.get("results") or []
if codigo == "ultimos_torrents":
return pi_bot.ultimos_torrents.get(chat_id) or {}
except Exception:
return {} if codigo == "ultimos_torrents" else []
return [] if codigo == "ultimos_torrents" else {}
def _btn(texto, data):
return {"text": texto, "callback_data": data}
def _mk(filas):
return {"inline_keyboard": filas}
# ---------------------------------------------------------------- teclados --
FILA_VOLVER = [_btn("⬅️ Menú", PREFIJO + "ini")]
def teclado_principal():
return _mk([
[
_btn("🎬 Videoclub", PREFIJO + "cat:videoclub"),
_btn("📥 NAS/Descargas", PREFIJO + "cat:nas"),
],
[
_btn("📺 TV", PREFIJO + "cat:tv"),
_btn("👪 Control parental", PREFIJO + "cat:parental"),
_btn("🛠️ Sistema", PREFIJO + "cat:sistema"),
],
[_btn("📚 Ayuda (texto)", PREFIJO + "acc:ayuda")],
])
def teclado_videoclub(chat_id):
filas = [
[
_btn("🔎 Buscar", PREFIJO + "txt:busca"),
_btn("🎬 Película", PREFIJO + "txt:pelicula"),
_btn("📺 Serie", PREFIJO + "txt:serie"),
],
[
_btn("🎭 Por género", PREFIJO + "acc:genero"),
_btn("🆕 Novedades", PREFIJO + "acc:novedades"),
],
]
titulos = _txt("ultimos_titulos", chat_id)
if titulos:
filas.append([
_btn("🧲 Torrents…", PREFIJO + "sub:torrents"),
_btn("⬇️ Bajar…", PREFIJO + "sub:bajar"),
])
filas.append(FILA_VOLVER)
return _mk(filas)
def teclado_nas():
return _mk([
[
_btn("📥 Panel descargas", PREFIJO + "acc:descargas"),
_btn("➕ Añadir enlace", PREFIJO + "txt:descargar"),
],
[
_btn("🔌 Termo ON", PREFIJO + "acc:termo:on"),
_btn("🔌 Termo OFF", PREFIJO + "acc:termo:off"),
_btn("🔌 Termo estado", PREFIJO + "acc:termo:estado"),
],
FILA_VOLVER,
])
def teclado_tv():
return _mk([
[
_btn("📺 Mensaje", PREFIJO + "txt:tv"),
_btn("🚨 Alarma", PREFIJO + "txt:alarma"),
],
[_btn("▶️ YouTube", PREFIJO + "txt:youtube")],
FILA_VOLVER,
])
def teclado_parental():
return _mk([
[_btn("📋 Listar estado", PREFIJO + "acc:parental_listar")],
[
_btn("🔒 Desactivar…", PREFIJO + "sub:desactivar"),
_btn("🔓 Activar…", PREFIJO + "sub:activar"),
],
[_btn("⏸️ Pausa…", PREFIJO + "sub:pausa")],
FILA_VOLVER,
])
def teclado_sistema():
return _mk([
[
_btn("📊 Estado", PREFIJO + "acc:estado"),
_btn("🌡️ Temperatura", PREFIJO + "acc:temperatura"),
],
[
_btn("📡 Router…", PREFIJO + "sub:router"),
_btn("🌐 AiMesh", PREFIJO + "acc:aimesh"),
],
[
_btn("⚙️ QoS", PREFIJO + "acc:qos"),
_btn("🚦 Límites", PREFIJO + "acc:limite"),
_btn("🛡️ Seguridad", PREFIJO + "acc:seguridad"),
],
[
_btn("📸 Panorama", PREFIJO + "acc:panorama"),
_btn("🧹 Limpieza", PREFIJO + "acc:limpieza"),
],
[_btn("🗑️ Limpiar pantalla", PREFIJO + "acc:limpiar")],
FILA_VOLVER,
])
def teclado_router():
return _mk([
[
_btn("📊 Estado", PREFIJO + "acc:router:estado"),
_btn("📶 Clientes", PREFIJO + "acc:router:clientes"),
_btn("📡 Wi-Fi", PREFIJO + "acc:router:wifi"),
],
[
_btn("📋 Inventario", PREFIJO + "acc:router:inventario"),
_btn("👪 Parental", PREFIJO + "acc:router:parental"),
],
[
_btn("🔄 Reiniciar", PREFIJO + "acc:router:reiniciar"),
_btn("🔁 Reservar IPs", PREFIJO + "acc:router:reservar"),
],
[_btn("⬅️ Sistema", PREFIJO + "cat:sistema")],
])
def teclado_nombres(accion):
return _mk([
[_btn("👧 Inés", PREFIJO + f"sub:{accion}:Inés"),
_btn("👩 Carolina", PREFIJO + f"sub:{accion}:Carolina")],
[_btn("✏️ Otro…", PREFIJO + f"txt:{accion}")],
[_btn("⬅️ Control parental", PREFIJO + "cat:parental")],
])
def teclado_minutos_pausa(nombre):
return _mk([
[
_btn("15 min", PREFIJO + "pausa_min:15"),
_btn("30 min", PREFIJO + "pausa_min:30"),
_btn("60 min", PREFIJO + "pausa_min:60"),
],
[_btn("✏️ Otro…", PREFIJO + "pausa_min:otro")],
[_btn("⬅️ Nombres", PREFIJO + "cat:parental")],
])
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
def teclado_cancelar():
return _mk([[_btn("❌ Cancelar", PREFIJO + "cancelar")]])
# --------------------------------------------------------------- helpers ---
def _enviar_edit(chat_id, message_id, texto, teclado):
import requests
try:
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)
except Exception as e:
_log(f"edit error: {e}")
def _enviar_nuevo(chat_id, texto, teclado=None):
import requests
data = {
"chat_id": chat_id,
"text": texto,
"parse_mode": "HTML",
}
if teclado:
data["reply_markup"] = json.dumps(teclado)
try:
requests.post(_api("sendMessage"), data=data, timeout=10)
except Exception as e:
_log(f"send error: {e}")
# ----------------------------------------------------------------- menu ----
def enviar_menu(chat_id):
_enviar_nuevo(
chat_id,
"🧭 <b>Menú IDD</b>\nElige una categoría:",
teclado_principal(),
)
def _header(categoria):
nombres = {
"videoclub": "🎬 <b>Videoclub</b>",
"nas": "📥 <b>NAS / Descargas</b>",
"tv": "📺 <b>TV</b>",
"parental": "👪 <b>Control parental</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 = {
"videoclub": lambda: teclado_videoclub(chat_id),
"nas": teclado_nas,
"tv": teclado_tv,
"parental": teclado_parental,
"sistema": teclado_sistema,
"router": teclado_router,
}
fn = teclado.get(categoria)
if not fn:
return False
_enviar_edit(chat_id, message_id, _header(categoria), fn())
return True
def procesar_callback(data, chat_id, message_id):
"""data llega sin el prefijo 'm:'. Gestiona navegacion y acciones."""
partes = data.split(":")
ruta = partes[0]
_log(f"callback {data} chat={chat_id}")
if ruta == "ini":
_enviar_edit(chat_id, message_id, "🧭 <b>Menú IDD</b>\nElige una categoría:", teclado_principal())
return
if ruta == "cat":
return _abrir_categoria(chat_id, message_id, partes[1])
if ruta == "sub":
# sub:torrents / sub:bajar / sub:router / sub:parental:<acc>
sub = partes[1]
if sub in ("router",):
_enviar_edit(chat_id, message_id, _header("router"), teclado_router())
return
if sub in ("torrents", "bajar"):
return _mostrar_resultados(chat_id, message_id, sub)
if len(partes) == 2 and sub in ("desactivar", "activar", "pausa"):
_enviar_edit(chat_id, message_id,
f"¿Sobre qué dispositivo? ({sub})",
teclado_nombres(sub))
return
# sub:<accion>:<nombre> -> accion parental con nombre directo
if len(partes) >= 3 and sub in ("desactivar", "activar", "pausa"):
return _ejecutar_parental(chat_id, message_id, sub, partes[2])
_log(f"sub desconocida: {data}")
return
if ruta == "txt":
# pide un dato por teclado: lo captura el siguiente mensaje
accion = partes[1]
_guardar_pendiente(chat_id, accion)
preguntas = {
"busca": "🔎 ¿Qué busco? Escribe el nombre.",
"pelicula": "🎬 ¿Qué película busco?",
"serie": "📺 ¿Qué serie busco?",
"tv": "📺 ¿Qué mensaje envío a la tele?",
"alarma": "🚨 ¿Qué mensaje de alarma lanzo?",
"youtube": "▶️ Pega la URL de YouTube.",
"descargar": "🧲 Pega el enlace magnet o URL a descargar.",
"desactivar": "🔒 ¿A quién bloqueo? Escribe el nombre.",
"activar": "🔓 ¿A quién desbloqueo? Escribe el nombre.",
}
_enviar_nuevo(chat_id, preguntas.get(accion, "Escribe el dato:"),
teclado_cancelar())
return
if ruta == "pausa_min":
nombre = _get_pausa_nombre(chat_id)
if not nombre:
_enviar_nuevo(chat_id, "Empieza de nuevo: /menu", teclado_cancelar())
return
if partes[1] == "otro":
_guardar_pendiente(chat_id, "pausa_minutos")
_enviar_nuevo(chat_id, "⏱️ ¿Cuántos minutos? (1-1440)", teclado_cancelar())
return
minutos = partes[1]
import pi_bot
_enviar_nuevo(chat_id, f"⏸️ Pausa {nombre} {minutos} minutos…", None)
pi_bot.procesar(f"parental pausa {nombre} {minutos}", chat_id)
return
if ruta == "acc":
return _ejecutar_accion(chat_id, message_id, partes[1:])
if ruta == "cancelar":
_limpiar_pendiente(chat_id)
_borrar(chat_id, message_id)
return
def _mostrar_resultados(chat_id, message_id, tipo):
titulos = _txt("ultimos_titulos", chat_id)
if tipo == "torrents":
if titulos:
_enviar_edit(chat_id, message_id,
"🧲 Elige el título para ver sus torrents:",
teclado_resultados(titulos, "torrents"))
else:
_enviar_nuevo(chat_id, "Primero haz una <b>/busca</b> o usa <code>/torrents &lt;n&gt;</code>.", None)
return
torrents = _txt("ultimos_torrents", chat_id)
if torrents:
_enviar_edit(chat_id, message_id,
f"⬇️ Elige torrent de «{torrents.get('titulo')}»:",
teclado_resultados(torrents.get("torrents", []), "bajar"))
else:
_enviar_nuevo(chat_id, "Primero consulta <code>/torrents &lt;n&gt;</code> de un título.", None)
def _ejecutar_parental(chat_id, message_id, accion, nombre):
if accion == "pausa":
_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}"
import pi_bot
pi_bot.procesar(comando, chat_id)
def _ejecutar_accion(chat_id, message_id, partes):
import pi_bot
accion = partes[0]
sub = partes[1] if len(partes) > 1 else ""
directos = {
"ayuda": "ayuda",
"genero": "genero",
"novedades": "novedades",
"descargas": "descargas",
"estado": "estado",
"temperatura": "temperatura",
"panorama": "panorama",
"limpieza": "limpieza",
"limpiar": "limpiar",
"aimesh": "aiMesh estado",
"qos": "qos estado",
"limite": "limite listar",
"seguridad": "seguridad estado",
"parental_listar": "parental listar",
}
if accion in directos:
pi_bot.procesar(directos[accion], chat_id)
return
if accion == "termo":
pi_bot.procesar(f"termo {sub}", chat_id)
return
if accion == "router":
mapeo = {
"estado": "router estado",
"clientes": "router clientes",
"wifi": "router wifi",
"inventario": "router inventario",
"parental": "router parental",
"reiniciar": "router reiniciar",
"reservar": "router reservar",
}
pi_bot.procesar(mapeo.get(sub, "router ayuda"), chat_id)
return
if accion in ("torrents", "bajar"):
numero = int(sub) if sub.isdigit() else 0
if numero:
pi_bot.procesar(f"{accion} {numero}", chat_id)
return
# --------------------------------------------- pendiente de texto libre ----
def _pend(chat_id):
import pi_bot
return pi_bot.menu_pendiente
def _guardar_pendiente(chat_id, accion):
_pend(chat_id)[chat_id] = accion
def _limpiar_pendiente(chat_id):
_pend(chat_id).pop(chat_id, None)
def pendiente_activo(chat_id):
return chat_id in _pend(chat_id)
def cancelar_pendiente(chat_id):
_limpiar_pendiente(chat_id)
def construir_comando(chat_id, texto):
"""Devuelve el texto del comando para el texto libre recibido, o None."""
accion = _pend(chat_id).get(chat_id)
if not accion:
return None
texto = texto.strip()
if accion in ("pausa_minutos",):
if not texto.isdigit():
return "INVALIDO"
return None # manejado aparte
prefijos = {
"busca": "busca",
"pelicula": "pelicula",
"serie": "serie",
"tv": "tv",
"alarma": "alarma",
"youtube": "youtube",
"descargar": "descargar",
"desactivar": "parental desactivar",
"activar": "parental activar",
}
prefijo = prefijos.get(accion)
if prefijo is None:
return None
return f"{prefijo} {texto}"
def resolver_pausa_minutos(chat_id, texto):
"""Si el pendiente es pausa_minutos y el texto es valido, lanza la pausa."""
accion = _pend(chat_id).get(chat_id)
if accion != "pausa_minutos":
return False
if not texto.strip().isdigit():
return False
minutos = int(texto.strip())
if minutos < 1 or minutos > 1440:
return False
nombre = _get_pausa_nombre(chat_id)
_limpiar_pendiente(chat_id)
if not nombre:
return True
import pi_bot
pi_bot.procesar(f"parental pausa {nombre} {minutos}", chat_id)
return True
def pausa_estado(chat_id):
import pi_bot
return pi_bot.menu_pausa_nombre.get(chat_id)
def _set_pausa_nombre(chat_id, nombre):
import pi_bot
pi_bot.menu_pausa_nombre[chat_id] = nombre
def _get_pausa_nombre(chat_id):
import pi_bot
return pi_bot.menu_pausa_nombre.get(chat_id)
def _borrar(chat_id, message_id):
import requests
try:
requests.post(_api("deleteMessage"),
data={"chat_id": chat_id, "message_id": message_id},
timeout=10)
except Exception:
pass