Files
biblioteca_conocimiento_lab…/vigilancia/pi_bot.py

316 lines
11 KiB
Python

#!/usr/bin/env python3
import os
import subprocess
import threading
import time
import urllib.parse
import requests
BASE = os.path.dirname(os.path.abspath(__file__))
LOG = os.path.join(BASE, "bot.log")
OFFSET_FILE = os.path.join(BASE, "bot.offset")
TV_API = "http://127.0.0.1:8600/tv/mensaje"
def log(linea):
try:
with open(LOG, "a") as f:
f.write(f"{time.strftime('%H:%M:%S')} {linea}\n")
except Exception:
pass
def load_env():
env = {}
try:
for line in open(os.path.join(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
return env
ENV = load_env()
TOKEN = ENV.get("TELEGRAM_TOKEN", "")
CHAT_ID = ENV.get("TELEGRAM_CHAT_ID", "")
ALERTA_TEMP = float(ENV.get("ALERTA_TEMP", "80"))
def sh(cmd):
try:
return subprocess.run(cmd, shell=True, capture_output=True,
text=True, timeout=10).stdout.strip()
except Exception:
return "n/a"
def estado():
temp = sh("vcgencmd measure_temp")
upt = sh("uptime -p")
load = open("/proc/loadavg").read().split()[:3]
free = sh("free -h | awk 'NR==2{print $2\" total, \"$7\" disponible\"}'")
disk = sh("df -h / | awk 'NR==2{print $3\" usados de \"$2\" (\"$5\")\"}'")
ips = sh("hostname -I")
temp_c = None
try:
temp_c = float(temp.replace("'C", "").replace("temp=", ""))
except Exception:
pass
msg = (
"📊 Estado de <b>minipc</b>:\n"
f"🌡️ Temperatura: <b>{temp}</b>\n"
f"🔌 Uptime: {upt}\n"
f"⚙️ Carga: {', '.join(load)}\n"
f"🧠 RAM: {free}\n"
f"💾 Disco /: {disk}\n"
f"🌐 IPs: {ips}"
)
return msg, temp_c
def enviar(texto, chat_id):
try:
r = requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
data={"chat_id": chat_id, "text": texto, "parse_mode": "HTML"},
timeout=10,
)
d = r.json()
if d.get("ok"):
return d["result"].get("message_id")
except Exception as e:
print("Error enviando:", e)
return None
def borrar(chat_id, message_id):
try:
requests.post(
f"https://api.telegram.org/bot{TOKEN}/deleteMessage",
data={"chat_id": chat_id, "message_id": message_id},
timeout=10,
)
except Exception as e:
print("Error borrando:", e)
def mandar_tv(mensaje, simple):
try:
url = f"{TV_API}?texto={urllib.parse.quote(mensaje)}"
if simple:
url += "&simple=1"
r = requests.get(url, timeout=8)
return r.status_code == 200
except Exception as e:
log(f"tv error: {e}")
return False
def mandar_youtube(url):
try:
r = requests.get(f"http://127.0.0.1:8600/tv/youtube?url={url}", timeout=10)
return r.status_code == 200
except Exception as e:
log(f"youtube error: {e}")
return False
def procesar(texto, chat_id):
t = texto.lower().strip()
log(f"RECIBIDO chat={chat_id} texto={texto!r}")
if t in ("estado", "/estado", "status", "/status"):
msg, _ = estado()
enviar(msg, chat_id)
elif "temperatura" in t or "temp" in t:
temp = sh("vcgencmd measure_temp")
enviar(f"🌡️ Temperatura de la Pi: <b>{temp}</b>", chat_id)
elif t.startswith("tv ") or t.startswith("/tv "):
msj = texto.split(" ", 1)[1].strip()
if not msj:
enviar("Uso: <b>tv &lt;mensaje&gt;</b> (mensaje rapido a la tele)", chat_id)
else:
ok = mandar_tv(msj, simple=True)
enviar("📺 Mensaje enviado a la tele ✅" if ok
else "❌ No se pudo enviar a la tele", chat_id)
elif t.startswith("alarma ") or t.startswith("/alarma "):
msj = texto.split(" ", 1)[1].strip()
if not msj:
enviar("Uso: <b>alarma &lt;mensaje&gt;</b> (secuencia completa: sirena x3)", chat_id)
else:
ok = mandar_tv(msj, simple=False)
enviar("🚨 Alarma lanzada en la tele ✅" if ok
else "❌ No se pudo lanzar la alarma", chat_id)
elif t.startswith("panorama") or t.startswith("/panorama"):
enviar("📷 Capturando snapshots de las cámaras...", chat_id)
try:
import subprocess as sp
r = sp.run(["python3", "/home/pi/minipc/vigilancia/panorama.py"],
capture_output=True, text=True, timeout=60, cwd="/home/pi/minipc/vigilancia")
log(f"panorama: {r.stdout.strip()[-200:]}")
except Exception as e:
log(f"panorama error: {e}")
elif t in ("limpieza", "/limpieza", "limpiar", "/limpiar"):
enviar("🧹 Ejecutando limpieza...", chat_id)
try:
from limpiar import limpiar
resultado = limpiar()
enviar(f"<pre>{resultado}</pre>", chat_id)
except Exception as e:
log(f"limpieza error: {e}")
enviar(f"❌ Error: {e}", chat_id)
elif t.startswith("youtube ") or t.startswith("/youtube "):
url = texto.split(" ", 1)[1].strip()
if not url:
enviar("Uso: <b>youtube &lt;URL&gt;</b>", chat_id)
else:
ok = mandar_youtube(url)
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
elif t.startswith("descargar ") or t.startswith("/descargar "):
uri = texto.split(" ", 1)[1].strip()
if not uri:
enviar("Uso: <b>descargar &lt;enlace|magnet|uri&gt;</b> (anade a Download Station del NAS)", chat_id)
else:
import nas_dl
ok = nas_dl.add_torrent(uri)
enviar("📥 Añadido a Download Station del NAS ✅" if ok
else "❌ No se pudo añadir al NAS", chat_id)
elif t in ("pelis", "/pelis"):
procesar_pelis(chat_id)
elif t in ("ayuda", "/ayuda", "help", "/help"):
enviar("Comandos:\n"
"<b>estado</b> — estado de la Pi\n"
"<b>temperatura</b> — temperatura\n"
"<b>tv &lt;mensaje&gt;</b> — mensaje rápido a la tele\n"
"<b>alarma &lt;mensaje&gt;</b> — secuencia completa (sirena x3)\n"
"<b>youtube &lt;URL&gt;</b> — reproduce YouTube y apaga al terminar\n"
"<b>descargar &lt;enlace&gt;</b> — añade a Download Station del NAS\n"
"<b>pelis</b> — novedades en castellano con comando listo\n"
"<b>panorama</b> — fotos de las cámaras\n"
"<b>limpieza</b> — limpia procesos atascados de la Pi\n"
"<b>reboot</b> — reinicia la Pi (necesita confirmar con /confirmar-reboot)\n"
"<b>servicios</b> — estado de los servicios del sistema",
chat_id)
elif t in ("servicios", "/servicios"):
estado_svc = sh("systemctl is-active pi-bot tv-api autossh-tunel")
nombres = "pi-bot\ntv-api\nautossh-tunel"
activos = estado_svc.strip().splitlines()
lineas = []
for n, e in zip(["pi-bot", "tv-api", "autossh-tunel"], activos):
icono = "" if e == "active" else ""
lineas.append(f"{icono} {n}: {e}")
envio = "\n".join(lineas)
enviar(f"📦 Servicios:\n{envio}\n\n🔌 Túnel VPS: {'OK' if activos and activos[2]=='active' else 'caído'}", chat_id)
elif t == "reboot" or t == "/reboot":
enviar("⚠️ Para reiniciar la Pi responde <b>/confirmar-reboot</b> en 30 segundos.", chat_id)
elif t == "confirmar-reboot" or t == "/confirmar-reboot":
enviar("🔄 Reiniciando la Pi... volveré en un momento.", chat_id)
log("REBOOT solicitado")
sh("echo 'raspberry' | sudo -S reboot")
def procesar_pelis(chat_id):
"""Comando 'pelis': novedades con buena calidad en castellano, con el
comando 'descargar <url>' listo para copiar. Los adultos se envian en
un mensaje efimero que se borra a los 5 minutos y NO se loguea."""
try:
import pelis
except Exception as e:
log(f"pelis import error: {e}")
enviar("❌ No se pudo cargar el modulo pelis", chat_id)
return
try:
enviar("🎬 Consultando novedades del catálogo...", chat_id)
mensajes, adultos = pelis.generar()
except Exception as e:
log(f"pelis generar error: {e}")
enviar(f"❌ Error consultando el catálogo: {e}", chat_id)
return
for m in mensajes:
enviar(m, chat_id)
# Adultos: efimeros (5 min), sin loguear, nunca persisten
if adultos:
lineas = [f"🔞 Contenido adulto ({len(adultos)} novedades) — se borrará en 5 min:"]
for it in adultos[:30]:
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>"
lineas.append(linea)
if len(adultos) > 30:
lineas.append(f"... y {len(adultos) - 30} más")
texto = "\n".join(lineas)
for parte in pelis.trocear_efimero(texto, adultos):
mid = enviar(parte, chat_id)
if mid:
threading.Timer(300, borrar, args=(chat_id, mid)).start()
def monitor_temperatura():
while True:
time.sleep(300)
msg, temp_c = estado()
if temp_c is not None and temp_c > ALERTA_TEMP:
enviar(f"🚨 ALERTA: la Pi está a <b>{temp_c:.1f}°C</b> "
f"(límite {ALERTA_TEMP}°C)\n{msg}", CHAT_ID)
def cargar_offset():
try:
with open(OFFSET_FILE) as f:
return int(f.read().strip())
except Exception:
return None
def guardar_offset(offset):
try:
with open(OFFSET_FILE, "w") as f:
f.write(str(offset))
except Exception:
pass
def main():
if not TOKEN:
print("Falta TELEGRAM_TOKEN en .env")
return
threading.Thread(target=monitor_temperatura, daemon=True).start()
offset = cargar_offset()
log("Bot iniciado" + (f" (offset {offset})" if offset else ""))
print("Bot iniciado. Esperando mensajes...")
while True:
try:
params = {"timeout": 25}
if offset:
params["offset"] = offset
r = requests.get(
f"https://api.telegram.org/bot{TOKEN}/getUpdates",
params=params, timeout=35,
).json()
for upd in r.get("result", []):
offset = upd["update_id"] + 1
guardar_offset(offset)
log(f"UPDATE {upd.get('update_id')}")
msg = upd.get("message") or upd.get("channel_post")
if not msg:
continue
texto = msg.get("text")
chat_id = msg["chat"]["id"]
if texto:
procesar(texto, chat_id)
except Exception as e:
log(f"ERROR {e}")
print("Error:", e)
time.sleep(3)
if __name__ == "__main__":
main()