#!/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 minipc:\n" f"🌡️ Temperatura: {temp}\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: {temp}", chat_id) elif t.startswith("tv ") or t.startswith("/tv "): msj = texto.split(" ", 1)[1].strip() if not msj: enviar("Uso: tv <mensaje> (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: alarma <mensaje> (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"
{resultado}
", 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: youtube <URL>", 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: descargar <enlace|magnet|uri> (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" "estado — estado de la Pi\n" "temperatura — temperatura\n" "tv <mensaje> — mensaje rápido a la tele\n" "alarma <mensaje> — secuencia completa (sirena x3)\n" "youtube <URL> — reproduce YouTube y apaga al terminar\n" "descargar <enlace> — añade a Download Station del NAS\n" "pelis — novedades en castellano con comando listo\n" "panorama — fotos de las cámaras\n" "limpieza — limpia procesos atascados de la Pi\n" "reboot — reinicia la Pi (necesita confirmar con /confirmar-reboot)\n" "servicios — 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 /confirmar-reboot 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 ' 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"\ndescargar {enlace}" 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 {temp_c:.1f}°C " 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()