"""Orbitalo Hausmeister — Telegram Bot für Homelab-Management.""" import asyncio import logging import sys import os sys.path.insert(0, os.path.dirname(__file__)) from telegram import BotCommand, Update from telegram.ext import ( Application, CommandHandler, MessageHandler, filters, ContextTypes, ) BOT_COMMANDS = [ BotCommand("status", "Alle Container"), BotCommand("errors", "Aktuelle Fehler"), BotCommand("ct", "Container-Detail (/ct 109)"), BotCommand("health", "Health-Check (/health wordpress)"), BotCommand("logs", "Letzte Logs (/logs rss-manager)"), BotCommand("silence", "Stille Hosts"), BotCommand("report", "Tagesbericht"), BotCommand("check", "Monitoring-Check"), BotCommand("start", "Hilfe anzeigen"), ] import context import llm import monitor from core import config logging.basicConfig( format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", level=logging.INFO, ) log = logging.getLogger("hausmeister") ALLOWED_CHAT_IDS: set[int] = set() def _load_token_and_chat(): cfg = config.parse_config() token = cfg.raw.get("TG_HAUSMEISTER_TOKEN", "") chat_id = cfg.raw.get("TG_CHAT_ID", "") if chat_id: ALLOWED_CHAT_IDS.add(int(chat_id)) return token def _authorized(update: Update) -> bool: if not ALLOWED_CHAT_IDS: return True return update.effective_chat.id in ALLOWED_CHAT_IDS async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return await update.message.reply_text( "🔧 Orbitalo Hausmeister-Bot\n\n" "Befehle:\n" "/status — Alle Container\n" "/errors — Aktuelle Fehler\n" "/ct — Container-Detail\n" "/health — Health-Check\n" "/logs — Letzte Logs\n" "/silence — Stille Hosts\n" "/report — Tagesbericht\n" "/check — Monitoring-Check\n\n" "Oder einfach eine Frage stellen!" ) async def cmd_status(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return await update.message.reply_text("⏳ Lade Container-Status...") try: text = context.gather_status() if len(text) > 4000: text = text[:4000] + "\n..." await update.message.reply_text(text) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_errors(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return await update.message.reply_text("⏳ Suche Fehler...") try: text = context.gather_errors(hours=2) await update.message.reply_text(text[:4000]) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_ct(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return args = ctx.args if not args: await update.message.reply_text("Bitte CT-Nummer angeben: /ct 109") return try: text = context.gather_container_status(args[0]) await update.message.reply_text(text) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_health(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return args = ctx.args if not args: await update.message.reply_text("Bitte Hostname angeben: /health wordpress") return try: text = context.gather_health(args[0]) await update.message.reply_text(text) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_logs(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return args = ctx.args if not args: await update.message.reply_text("Bitte Hostname angeben: /logs rss-manager") return try: text = context.gather_logs(args[0]) await update.message.reply_text(text[:4000]) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_silence(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return try: text = context.gather_silence() await update.message.reply_text(text) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_report(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return await update.message.reply_text("⏳ Erstelle Tagesbericht...") try: text = monitor.format_report() await update.message.reply_text(text[:4000]) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def cmd_check(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not _authorized(update): return await update.message.reply_text("⏳ Prüfe Systeme...") try: alerts = monitor.check_all() if alerts: text = f"⚠️ {len(alerts)} Alarme:\n\n" + "\n".join(alerts) else: text = "✅ Keine Alarme — alles läuft." await update.message.reply_text(text) except Exception as e: await update.message.reply_text(f"Fehler: {e}") async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE): """Freitext-Fragen → Kontext sammeln → LLM → Antwort.""" if not _authorized(update): return question = update.message.text if not question: return await update.message.reply_text("🤔 Denke nach...") try: data = context.gather_context_for_question(question) answer = llm.ask(question, data) await update.message.reply_text(answer[:4000]) except Exception as e: log.exception("Fehler bei Freitext") await update.message.reply_text(f"Fehler: {e}") def main(): token = _load_token_and_chat() if not token: log.error("TG_HAUSMEISTER_TOKEN fehlt in homelab.conf!") sys.exit(1) log.info("Starte Orbitalo Hausmeister-Bot...") app = Application.builder().token(token).build() app.add_handler(CommandHandler("start", cmd_start)) app.add_handler(CommandHandler("status", cmd_status)) app.add_handler(CommandHandler("errors", cmd_errors)) app.add_handler(CommandHandler("ct", cmd_ct)) app.add_handler(CommandHandler("health", cmd_health)) app.add_handler(CommandHandler("logs", cmd_logs)) app.add_handler(CommandHandler("silence", cmd_silence)) app.add_handler(CommandHandler("report", cmd_report)) app.add_handler(CommandHandler("check", cmd_check)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) async def post_init(application): await application.bot.set_my_commands(BOT_COMMANDS) log.info("Kommandomenü registriert") app.post_init = post_init log.info("Bot läuft — polling gestartet") app.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": main()