Wiki source code of Version 2.0
Last modified by René Schmidt on 2025/10/11 20:07
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | import asyncio | ||
| 2 | import logging | ||
| 3 | import json | ||
| 4 | import random | ||
| 5 | import time | ||
| 6 | from datetime import datetime | ||
| 7 | from aiohttp import web, ClientSession | ||
| 8 | from nio import AsyncClient, MatrixRoom, RoomMessageText | ||
| 9 | |||
| 10 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 11 | # Logging-Setup | ||
| 12 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 13 | logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") | ||
| 14 | logger = logging.getLogger(~_~_name~_~_) | ||
| 15 | |||
| 16 | # Reduziere Noise von anderen Loggern | ||
| 17 | logging.getLogger("nio").setLevel(logging.ERROR) # Verstecke nio warnings | ||
| 18 | logging.getLogger("aiohttp").setLevel(logging.WARNING) | ||
| 19 | |||
| 20 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 21 | # Konfiguration - HIER ANPASSEN! | ||
| 22 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 23 | HOMESERVER = "https:~/~/matrix.rs-servertech.com" | ||
| 24 | USER_ID = "@terry:matrix.rs-servertech.com" | ||
| 25 | ACCESS_TOKEN = "HIER_DEIN_NEUER_TOKEN_EINTRAGEN" # ← Hole Token mit curl! | ||
| 26 | DEVICE_ID = "MATRIXBOT" | ||
| 27 | |||
| 28 | # n8n Konfiguration | ||
| 29 | N8N_WEBHOOK_URL = "https:~/~/n8n.rs-servertech.com/webhook/matrix-bot" | ||
| 30 | WEBHOOK_PORT = 8080 | ||
| 31 | WEBHOOK_SECRET = "mein-sicherer-key-2024-xyz" # ← Ändere zu eigenem Secret! | ||
| 32 | DEFAULT_ROOM_ID = "!dYNoYyZHeJfkqfsSFH:matrix.rs-servertech.com" | ||
| 33 | |||
| 34 | # KI Konfiguration - Ollama (Selfhosted) | ||
| 35 | OLLAMA_URL = "http:~/~/localhost:11434" # Deine Ollama URL | ||
| 36 | OLLAMA_MODEL = "llama3.2" # Dein Modell (llama3.2, mistral, phi3, etc.) | ||
| 37 | AI_ENABLED = True # Auf True für KI-Chat | ||
| 38 | |||
| 39 | # Bot-Verhalten | ||
| 40 | BOT_PREFIX = "!" | ||
| 41 | RESPOND_TO_ALL = False # MUSS False sein! | ||
| 42 | RESPOND_TO_MENTIONS = True # Nur auf @terry | ||
| 43 | RESPOND_TO_DM = False # KEINE automatischen Antworten in DMs! | ||
| 44 | |||
| 45 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 46 | # Globale Variablen | ||
| 47 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 48 | client: AsyncClient = None | ||
| 49 | app = web.Application() | ||
| 50 | conversation_history = {} | ||
| 51 | active_conversations = {} # Speichert aktivierte Konversationen pro User | ||
| 52 | |||
| 53 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 54 | # KI-Integration (Ollama) | ||
| 55 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 56 | async def ask_ai(message: str, user_id: str) -> str: | ||
| 57 | """Sendet Nachricht an Ollama und erhält Antwort.""" | ||
| 58 | if not AI_ENABLED: | ||
| 59 | return None | ||
| 60 | |||
| 61 | if user_id not in conversation_history: | ||
| 62 | conversation_history[user_id] = [] | ||
| 63 | |||
| 64 | conversation_history[user_id].append({"role": "user", "content": message}) | ||
| 65 | |||
| 66 | # Limitiere History | ||
| 67 | if len(conversation_history[user_id]) > 20: | ||
| 68 | conversation_history[user_id] = conversation_history[user_id][-20:] | ||
| 69 | |||
| 70 | try: | ||
| 71 | # Ollama API Format | ||
| 72 | messages = [ | ||
| 73 | {"role": "system", "content": "Du bist Terry, ein hilfreicher Matrix-Bot. Antworte kurz, freundlich und auf Deutsch. Sei persönlich und natürlich im Gespräch."}, | ||
| 74 | *conversation_history[user_id] | ||
| 75 | ] | ||
| 76 | |||
| 77 | payload = { | ||
| 78 | "model": OLLAMA_MODEL, | ||
| 79 | "messages": messages, | ||
| 80 | "stream": False, | ||
| 81 | "options": { | ||
| 82 | "temperature": 0.7, | ||
| 83 | "num_predict": 300 # Max Tokens | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | async with ClientSession() as session: | ||
| 88 | async with session.post( | ||
| 89 | f"{OLLAMA_URL}/api/chat", | ||
| 90 | json=payload, | ||
| 91 | timeout=60 # Ollama kann länger brauchen | ||
| 92 | ) as resp: | ||
| 93 | if resp.status == 200: | ||
| 94 | data = await resp.json() | ||
| 95 | answer = data["message"]["content"] | ||
| 96 | |||
| 97 | conversation_history[user_id].append({"role": "assistant", "content": answer}) | ||
| 98 | |||
| 99 | logger.info(f"🧠 Ollama Antwort: {answer[:50]}...") | ||
| 100 | return answer | ||
| 101 | else: | ||
| 102 | text = await resp.text() | ||
| 103 | logger.error(f"Ollama Fehler {resp.status}: {text}") | ||
| 104 | return None | ||
| 105 | |||
| 106 | except asyncio.TimeoutError: | ||
| 107 | logger.error("Ollama Timeout - Modell lädt oder ist zu langsam") | ||
| 108 | return "⏱️ Entschuldige, ich brauche gerade etwas länger zum Nachdenken..." | ||
| 109 | except Exception as e: | ||
| 110 | logger.error(f"KI-Fehler: {e}") | ||
| 111 | return None | ||
| 112 | |||
| 113 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 114 | # Webhook-Handler (n8n → Bot) | ||
| 115 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 116 | async def webhook_handler(request): | ||
| 117 | """Empfängt Messages von n8n.""" | ||
| 118 | try: | ||
| 119 | data = await request.json() | ||
| 120 | |||
| 121 | if data.get("secret") != WEBHOOK_SECRET: | ||
| 122 | logger.warning(f"❌ Unauthorized webhook: {request.remote}") | ||
| 123 | return web.json_response({"error": "Unauthorized"}, status=401) | ||
| 124 | |||
| 125 | message = data.get("message") | ||
| 126 | room_id = data.get("room_id", DEFAULT_ROOM_ID) | ||
| 127 | |||
| 128 | if not message: | ||
| 129 | return web.json_response({"error": "No message"}, status=400) | ||
| 130 | |||
| 131 | logger.info(f"📥 n8n → Bot: {message[:50]}...") | ||
| 132 | |||
| 133 | await client.room_send( | ||
| 134 | room_id, | ||
| 135 | "m.room.message", | ||
| 136 | {"msgtype": "m.text", "body": message} | ||
| 137 | ) | ||
| 138 | |||
| 139 | logger.info(f"✓ Message an {room_id} gesendet") | ||
| 140 | return web.json_response({"status": "success", "room": room_id}) | ||
| 141 | |||
| 142 | except json.JSONDecodeError: | ||
| 143 | return web.json_response({"error": "Invalid JSON"}, status=400) | ||
| 144 | except Exception as e: | ||
| 145 | logger.error(f"❌ Webhook Fehler: {e}") | ||
| 146 | return web.json_response({"error": str(e)}, status=500) | ||
| 147 | |||
| 148 | async def health_check(request): | ||
| 149 | """Health-Check Endpoint.""" | ||
| 150 | return web.json_response({ | ||
| 151 | "status": "online", | ||
| 152 | "bot": USER_ID, | ||
| 153 | "rooms": len(client.rooms) if client else 0, | ||
| 154 | "ai_enabled": AI_ENABLED, | ||
| 155 | "webhook_port": WEBHOOK_PORT, | ||
| 156 | "n8n_configured": N8N_WEBHOOK_URL != "https:~/~/n8n.rs-servertech.com/webhook/matrix-bot" | ||
| 157 | }) | ||
| 158 | |||
| 159 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 160 | # n8n Trigger (Bot → n8n) | ||
| 161 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 162 | async def trigger_n8n(trigger_type: str, data: dict): | ||
| 163 | """Sendet Event an n8n.""" | ||
| 164 | try: | ||
| 165 | payload = { | ||
| 166 | "trigger": trigger_type, | ||
| 167 | "data": data, | ||
| 168 | "timestamp": time.time() | ||
| 169 | } | ||
| 170 | |||
| 171 | async with ClientSession() as session: | ||
| 172 | async with session.post(N8N_WEBHOOK_URL, json=payload, timeout=10) as resp: | ||
| 173 | if resp.status == 200: | ||
| 174 | result = await resp.json() | ||
| 175 | logger.info(f"✓ n8n Trigger: {trigger_type}") | ||
| 176 | return result | ||
| 177 | else: | ||
| 178 | logger.warning(f"n8n returned {resp.status}") | ||
| 179 | return None | ||
| 180 | except Exception as e: | ||
| 181 | logger.error(f"n8n Fehler: {e}") | ||
| 182 | return None | ||
| 183 | |||
| 184 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 185 | # Command Handler | ||
| 186 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 187 | async def handle_command(room: MatrixRoom, event: RoomMessageText, message: str): | ||
| 188 | """Verarbeitet Befehle und Messages.""" | ||
| 189 | |||
| 190 | response = None | ||
| 191 | parts = message.split() | ||
| 192 | command = parts[0].lower() if parts else "" | ||
| 193 | args = parts[1:] if len(parts) > 1 else [] | ||
| 194 | |||
| 195 | # Befehle mit ! oder / | ||
| 196 | if message.startswith(BOT_PREFIX) or message.startswith("/"): | ||
| 197 | |||
| 198 | if command in ["!start", "!hilfe", "/start", "/hilfe"]: | ||
| 199 | response = f"""🤖 ~*~*Matrix Bot - Telegram Style~*~* | ||
| 200 | |||
| 201 | ~*~*Befehle:~*~* | ||
| 202 | • `/start` `/hilfe` - Diese Hilfe | ||
| 203 | • `/status` - Bot Status | ||
| 204 | • `/reset` - Chat löschen | ||
| 205 | • `/würfel` - Würfel 🎲 | ||
| 206 | • `/münze` - Münze werfen | ||
| 207 | • `/rechne [...]` - Rechner | ||
| 208 | • `/zeit` - Uhrzeit | ||
| 209 | • `/witz` - Witz | ||
| 210 | • `/ping` - Test | ||
| 211 | |||
| 212 | ~*~*n8n:~*~* | ||
| 213 | • `/webhook` - Webhook Info | ||
| 214 | • `/n8n [cmd]` - n8n Befehl | ||
| 215 | |||
| 216 | ~*~*Raum:~*~* `{room.room_id}` | ||
| 217 | |||
| 218 | KI-Chat: {'✅ Aktiv' if AI_ENABLED else '❌ Inaktiv'} | ||
| 219 | Schreib mir einfach! 💬""" | ||
| 220 | |||
| 221 | elif command in ["!status", "/status"]: | ||
| 222 | ai = "✅ Aktiv" if AI_ENABLED else "❌ Inaktiv" | ||
| 223 | |||
| 224 | # Zähle aktive Konversationen | ||
| 225 | active_count = len(active_conversations) | ||
| 226 | |||
| 227 | response = f"""📊 ~*~*Status~*~* | ||
| 228 | |||
| 229 | ~*~*Matrix:~*~* ✅ Online | ||
| 230 | • User: {USER_ID} | ||
| 231 | • Räume: {len(client.rooms)} | ||
| 232 | |||
| 233 | ~*~*Features:~*~* | ||
| 234 | • KI: {ai} (Ollama: {OLLAMA_MODEL}) | ||
| 235 | • n8n: ✅ Konfiguriert | ||
| 236 | • Webhook: Port {WEBHOOK_PORT} | ||
| 237 | |||
| 238 | ~*~*Konversationen:~*~* | ||
| 239 | • Aktiv: {active_count} | ||
| 240 | • Schreib 'hey terry' um zu starten | ||
| 241 | • Schreib 'stop' um zu beenden""""" | ||
| 242 | |||
| 243 | elif command in ["!reset", "/reset"]: | ||
| 244 | if event.sender in conversation_history: | ||
| 245 | del conversation_history[event.sender] | ||
| 246 | response = "🔄 Chat-Verlauf gelöscht!" | ||
| 247 | |||
| 248 | elif command in ["!würfel", "/würfel"]: | ||
| 249 | result = random.randint(1, 6) | ||
| 250 | response = f"🎲 ~*~*{result}~*~*" | ||
| 251 | |||
| 252 | elif command in ["!münze", "/münze"]: | ||
| 253 | result = random.choice(["Kopf 🪙", "Zahl 🔢"]) | ||
| 254 | response = f"{result}" | ||
| 255 | |||
| 256 | elif command in ["!zeit", "/zeit"]: | ||
| 257 | response = f"🕐 ~*~*{datetime.now().strftime('%H:%M:%S')}~*~* Uhr" | ||
| 258 | |||
| 259 | elif command in ["!ping", "/ping"]: | ||
| 260 | response = "🏓 Pong!" | ||
| 261 | |||
| 262 | elif command in ["!witz", "/witz"]: | ||
| 263 | witze = [ | ||
| 264 | "Warum können Geister so schlecht lügen? Weil sie so leicht zu durchschauen sind! 👻", | ||
| 265 | "Was ist grün und steht vor der Tür? Ein Klopfsalat! 🥬", | ||
| 266 | "Warum summen Bienen? Weil sie den Text nicht kennen! 🐝", | ||
| 267 | "Was macht ein Clown im Büro? Faxen! 🤡", | ||
| 268 | "Welche Handwerker essen am meisten? Die Maurer! 🧱" | ||
| 269 | ] | ||
| 270 | response = random.choice(witze) | ||
| 271 | |||
| 272 | elif command in ["!rechne", "/rechne"]: | ||
| 273 | if not args: | ||
| 274 | response = "❌ Beispiel: `/rechne 5 + 3`" | ||
| 275 | else: | ||
| 276 | try: | ||
| 277 | expr = " ".join(args) | ||
| 278 | if all(c in "0123456789+-*/(). " for c in expr): | ||
| 279 | result = eval(expr) | ||
| 280 | response = f"🧮 `{expr}` = ~*~*{result}~*~*" | ||
| 281 | else: | ||
| 282 | response = "❌ Nur Zahlen und +-*/() erlaubt" | ||
| 283 | except: | ||
| 284 | response = "❌ Rechenfehler" | ||
| 285 | |||
| 286 | elif command in ["!webhook", "/webhook"]: | ||
| 287 | response = f"""📡 ~*~*Webhook Info~*~* | ||
| 288 | |||
| 289 | ~*~*n8n → Bot:~*~* | ||
| 290 | ``` | ||
| 291 | POST http:~/~/YOUR_IP:{WEBHOOK_PORT}/webhook | ||
| 292 | ~{~{ | ||
| 293 | "secret": "{WEBHOOK_SECRET}", | ||
| 294 | "message": "Text", | ||
| 295 | "room_id": "{room.room_id}" | ||
| 296 | }} | ||
| 297 | ``` | ||
| 298 | |||
| 299 | ~*~*Health:~*~* http:~/~/YOUR_IP:{WEBHOOK_PORT}/health | ||
| 300 | ~*~*Raum-ID:~*~* `{room.room_id}`""" | ||
| 301 | |||
| 302 | elif command in ["!n8n", "/n8n"]: | ||
| 303 | if not args: | ||
| 304 | response = "❌ Beispiel: `/n8n status`" | ||
| 305 | else: | ||
| 306 | cmd = " ".join(args) | ||
| 307 | result = await trigger_n8n("command", { | ||
| 308 | "command": cmd, | ||
| 309 | "room_id": room.room_id, | ||
| 310 | "sender": event.sender | ||
| 311 | }) | ||
| 312 | |||
| 313 | if result and "response" in result: | ||
| 314 | response = f"✅ n8n:\n{result['response']}" | ||
| 315 | elif result: | ||
| 316 | response = f"✅ Befehl gesendet: `{cmd}`" | ||
| 317 | else: | ||
| 318 | response = "❌ n8n nicht erreichbar" | ||
| 319 | |||
| 320 | # Normale Nachricht (kein Befehl) | ||
| 321 | else: | ||
| 322 | user_key = f"{event.sender}_{room.room_id}" | ||
| 323 | |||
| 324 | # Prüfe Aktivierungswörter | ||
| 325 | activation_words = ["hey terry", "hallo terry", "hi terry", "terry"] | ||
| 326 | is_activation = any(message.lower().startswith(word) for word in activation_words) | ||
| 327 | |||
| 328 | # Prüfe Deaktivierungswörter | ||
| 329 | deactivation_words = ["stop", "bye terry", "tschüss terry", "schluss"] | ||
| 330 | is_deactivation = any(word in message.lower() for word in deactivation_words) | ||
| 331 | |||
| 332 | # Deaktiviere wenn gewünscht | ||
| 333 | if is_deactivation and user_key in active_conversations: | ||
| 334 | del active_conversations[user_key] | ||
| 335 | response = "👋 Okay, ich bin wieder still. Schreib 'hey terry' um mich zu aktivieren!" | ||
| 336 | |||
| 337 | # Aktiviere Konversation | ||
| 338 | elif is_activation: | ||
| 339 | active_conversations[user_key] = time.time() | ||
| 340 | logger.info(f"✅ Konversation aktiviert für {event.sender}") | ||
| 341 | |||
| 342 | # Entferne "terry" aus der Nachricht für saubere Antwort | ||
| 343 | clean_message = message | ||
| 344 | for word in activation_words: | ||
| 345 | clean_message = clean_message.lower().replace(word, "").strip() | ||
| 346 | |||
| 347 | if clean_message: | ||
| 348 | # Es gibt noch Text nach "hey terry" | ||
| 349 | if AI_ENABLED: | ||
| 350 | ai_response = await ask_ai(clean_message, event.sender) | ||
| 351 | response = ai_response if ai_response else "🤖 Ollama antwortet nicht." | ||
| 352 | else: | ||
| 353 | response = "👋 Hi! Ich bin jetzt aktiviert. Schreib einfach drauf los!\n\nTippe 'stop' um mich zu deaktivieren." | ||
| 354 | else: | ||
| 355 | response = "👋 Hi! Ich bin jetzt aktiviert. Schreib einfach drauf los!\n\nTippe 'stop' um mich zu deaktivieren." | ||
| 356 | |||
| 357 | # Konversation ist aktiv - antworte automatisch | ||
| 358 | elif user_key in active_conversations: | ||
| 359 | logger.info(f"💬 Aktive Konversation: {message[:30]}...") | ||
| 360 | |||
| 361 | if AI_ENABLED: | ||
| 362 | ai_response = await ask_ai(message, event.sender) | ||
| 363 | response = ai_response if ai_response else "🤖 Ollama Problem. Tippe 'stop' um zu beenden." | ||
| 364 | else: | ||
| 365 | response = "🤖 KI ist nicht aktiv. Tippe 'stop' um zu beenden." | ||
| 366 | |||
| 367 | # Nicht aktiviert und nicht erwähnt - keine Antwort | ||
| 368 | else: | ||
| 369 | logger.debug(f"Ignoriere Nachricht - nicht aktiviert") | ||
| 370 | return | ||
| 371 | |||
| 372 | # Antwort senden | ||
| 373 | if response: | ||
| 374 | try: | ||
| 375 | await client.room_send( | ||
| 376 | room.room_id, | ||
| 377 | "m.room.message", | ||
| 378 | {"msgtype": "m.text", "body": response} | ||
| 379 | ) | ||
| 380 | logger.info(f"✓ Antwort → {event.sender.split(':')[0]}") | ||
| 381 | except Exception as e: | ||
| 382 | logger.error(f"❌ Senden fehlgeschlagen: {e}") | ||
| 383 | |||
| 384 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 385 | # Message Callback | ||
| 386 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 387 | async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None: | ||
| 388 | """Reagiert auf Messages.""" | ||
| 389 | |||
| 390 | # WICHTIG: Ignoriere eigene Messages | ||
| 391 | if event.sender == client.user_id: | ||
| 392 | logger.debug(f"Ignoriere eigene Nachricht von {event.sender}") | ||
| 393 | return | ||
| 394 | |||
| 395 | # Ignoriere alte Messages beim Start | ||
| 396 | if hasattr(event, 'server_timestamp'): | ||
| 397 | current_time = time.time() * 1000 | ||
| 398 | age = current_time - event.server_timestamp | ||
| 399 | if age > 5000: # Älter als 5 Sekunden | ||
| 400 | return | ||
| 401 | |||
| 402 | message = event.body.strip() | ||
| 403 | logger.info(f"📨 {event.sender.split(':')[0]}: {message[:50]}...") | ||
| 404 | |||
| 405 | await handle_command(room, event, message) | ||
| 406 | |||
| 407 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 408 | # Webserver | ||
| 409 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 410 | async def start_webserver(): | ||
| 411 | """Startet Webhook-Server.""" | ||
| 412 | app.router.add_post('/webhook', webhook_handler) | ||
| 413 | app.router.add_get('/health', health_check) | ||
| 414 | |||
| 415 | runner = web.AppRunner(app) | ||
| 416 | await runner.setup() | ||
| 417 | site = web.TCPSite(runner, '0.0.0.0', WEBHOOK_PORT) | ||
| 418 | await site.start() | ||
| 419 | |||
| 420 | logger.info(f"🌐 Webhook: Port {WEBHOOK_PORT}") | ||
| 421 | |||
| 422 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 423 | # Matrix Client | ||
| 424 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 425 | async def start_matrix_client(): | ||
| 426 | """Startet Matrix Client.""" | ||
| 427 | global client | ||
| 428 | |||
| 429 | client = AsyncClient(HOMESERVER, USER_ID, device_id=DEVICE_ID) | ||
| 430 | client.access_token = ACCESS_TOKEN | ||
| 431 | client.add_event_callback(message_callback, RoomMessageText) | ||
| 432 | |||
| 433 | await client.sync(timeout=30000, full_state=True) | ||
| 434 | |||
| 435 | logger.info(f"✓ In {len(client.rooms)} Räumen:") | ||
| 436 | for room_id, room in client.rooms.items(): | ||
| 437 | logger.info(f" • {room.display_name or room.room_id}") | ||
| 438 | |||
| 439 | await client.sync_forever(timeout=30000) | ||
| 440 | |||
| 441 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 442 | # Main | ||
| 443 | # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-- | ||
| 444 | async def main(): | ||
| 445 | """Hauptfunktion.""" | ||
| 446 | logger.info("=" * 70) | ||
| 447 | logger.info("🤖 Matrix Bot - Telegram Style v3.0") | ||
| 448 | logger.info("=" * 70) | ||
| 449 | logger.info(f"🔗 {HOMESERVER}") | ||
| 450 | logger.info(f"👤 {USER_ID}") | ||
| 451 | logger.info(f"🧠 KI: {'✅' if AI_ENABLED else '❌'}") | ||
| 452 | logger.info(f"🌐 Webhook: Port {WEBHOOK_PORT}") | ||
| 453 | logger.info(f"🎯 n8n: https:~/~/n8n.rs-servertech.com") | ||
| 454 | logger.info(f"📍 Raum: {DEFAULT_ROOM_ID}") | ||
| 455 | logger.info("=" * 70) | ||
| 456 | |||
| 457 | try: | ||
| 458 | await asyncio.gather( | ||
| 459 | start_webserver(), | ||
| 460 | start_matrix_client() | ||
| 461 | ) | ||
| 462 | except KeyboardInterrupt: | ||
| 463 | logger.info("\n⏹️ Bot beendet") | ||
| 464 | finally: | ||
| 465 | if client: | ||
| 466 | await client.close() | ||
| 467 | |||
| 468 | if ~_~_name~_~_ == "~_~_main~_~_": | ||
| 469 | try: | ||
| 470 | asyncio.run(main()) | ||
| 471 | except KeyboardInterrupt: | ||
| 472 | pass |