Wiki-Quellcode von Matrix Bot Terry

Zuletzt geändert von René Schmidt am 2025/10/11 19:30

Zeige letzte Bearbeiter
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.DEBUG, 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.WARNING)
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  # False = nur auf Erwähnungen/Direktnachrichten
42 RESPOND_TO_MENTIONS = True  # True = antwortet auf @terry
43 RESPOND_TO_DM = True  # True = antwortet in Privatnachrichten
44
45 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
46 # Globale Variablen
47 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
48 client: AsyncClient = None
49 app = web.Application()
50 conversation_history = {}
51
52 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
53 # KI-Integration (Ollama)
54 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
55 async def ask_ai(message: str, user_id: str) -> str:
56 """Sendet Nachricht an Ollama und erhält Antwort."""
57 if not AI_ENABLED:
58 return None
59
60 if user_id not in conversation_history:
61 conversation_history[user_id] = []
62
63 conversation_history[user_id].append({"role": "user", "content": message})
64
65 # Limitiere History
66 if len(conversation_history[user_id]) > 20:
67 conversation_history[user_id] = conversation_history[user_id][-20:]
68
69 try:
70 # Ollama API Format
71 messages = [
72 {"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."},
73 *conversation_history[user_id]
74 ]
75
76 payload = {
77 "model": OLLAMA_MODEL,
78 "messages": messages,
79 "stream": False,
80 "options": {
81 "temperature": 0.7,
82 "num_predict": 300  # Max Tokens
83 }
84 }
85
86 async with ClientSession() as session:
87 async with session.post(
88 f"{OLLAMA_URL}/api/chat",
89 json=payload,
90 timeout=60  # Ollama kann länger brauchen
91 ) as resp:
92 if resp.status == 200:
93 data = await resp.json()
94 answer = data["message"]["content"]
95
96 conversation_history[user_id].append({"role": "assistant", "content": answer})
97
98 logger.info(f"🧠 Ollama Antwort: {answer[:50]}...")
99 return answer
100 else:
101 text = await resp.text()
102 logger.error(f"Ollama Fehler {resp.status}: {text}")
103 return None
104
105 except asyncio.TimeoutError:
106 logger.error("Ollama Timeout - Modell lädt oder ist zu langsam")
107 return "⏱️ Entschuldige, ich brauche gerade etwas länger zum Nachdenken..."
108 except Exception as e:
109 logger.error(f"KI-Fehler: {e}")
110 return None
111
112 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
113 # Webhook-Handler (n8n → Bot)
114 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
115 async def webhook_handler(request):
116 """Empfängt Messages von n8n."""
117 try:
118 data = await request.json()
119
120 if data.get("secret") != WEBHOOK_SECRET:
121 logger.warning(f"❌ Unauthorized webhook: {request.remote}")
122 return web.json_response({"error": "Unauthorized"}, status=401)
123
124 message = data.get("message")
125 room_id = data.get("room_id", DEFAULT_ROOM_ID)
126
127 if not message:
128 return web.json_response({"error": "No message"}, status=400)
129
130 logger.info(f"📥 n8n → Bot: {message[:50]}...")
131
132 await client.room_send(
133 room_id,
134 "m.room.message",
135 {"msgtype": "m.text", "body": message}
136 )
137
138 logger.info(f"✓ Message an {room_id} gesendet")
139 return web.json_response({"status": "success", "room": room_id})
140
141 except json.JSONDecodeError:
142 return web.json_response({"error": "Invalid JSON"}, status=400)
143 except Exception as e:
144 logger.error(f"❌ Webhook Fehler: {e}")
145 return web.json_response({"error": str(e)}, status=500)
146
147 async def health_check(request):
148 """Health-Check Endpoint."""
149 return web.json_response({
150 "status": "online",
151 "bot": USER_ID,
152 "rooms": len(client.rooms) if client else 0,
153 "ai_enabled": AI_ENABLED,
154 "webhook_port": WEBHOOK_PORT,
155 "n8n_configured": N8N_WEBHOOK_URL != "https:~/~/n8n.rs-servertech.com/webhook/matrix-bot"
156 })
157
158 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
159 # n8n Trigger (Bot → n8n)
160 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
161 async def trigger_n8n(trigger_type: str, data: dict):
162 """Sendet Event an n8n."""
163 try:
164 payload = {
165 "trigger": trigger_type,
166 "data": data,
167 "timestamp": time.time()
168 }
169
170 async with ClientSession() as session:
171 async with session.post(N8N_WEBHOOK_URL, json=payload, timeout=10) as resp:
172 if resp.status == 200:
173 result = await resp.json()
174 logger.info(f"✓ n8n Trigger: {trigger_type}")
175 return result
176 else:
177 logger.warning(f"n8n returned {resp.status}")
178 return None
179 except Exception as e:
180 logger.error(f"n8n Fehler: {e}")
181 return None
182
183 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
184 # Command Handler
185 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
186 async def handle_command(room: MatrixRoom, event: RoomMessageText, message: str):
187 """Verarbeitet Befehle und Messages."""
188
189 response = None
190 parts = message.split()
191 command = parts[0].lower() if parts else ""
192 args = parts[1:] if len(parts) > 1 else []
193
194 # Befehle mit ! oder /
195 if message.startswith(BOT_PREFIX) or message.startswith("/"):
196
197 if command in ["!start", "!hilfe", "/start", "/hilfe"]:
198 response = f"""🤖 ~*~*Matrix Bot - Telegram Style~*~*
199
200 ~*~*Befehle:~*~*
201 • `/start` `/hilfe` - Diese Hilfe
202 • `/status` - Bot Status
203 • `/reset` - Chat löschen
204 • `/würfel` - Würfel 🎲
205 • `/münze` - Münze werfen
206 • `/rechne [...]` - Rechner
207 • `/zeit` - Uhrzeit
208 • `/witz` - Witz
209 • `/ping` - Test
210
211 ~*~*n8n:~*~*
212 • `/webhook` - Webhook Info
213 • `/n8n [cmd]` - n8n Befehl
214
215 ~*~*Raum:~*~* `{room.room_id}`
216
217 KI-Chat: {'✅ Aktiv' if AI_ENABLED else '❌ Inaktiv'}
218 Schreib mir einfach! 💬"""
219
220 elif command in ["!status", "/status"]:
221 ai = "✅ Aktiv" if AI_ENABLED else "❌ Inaktiv"
222 response = f"""📊 ~*~*Status~*~*
223
224 ~*~*Matrix:~*~* ✅ Online
225 • User: {USER_ID}
226 • Räume: {len(client.rooms)}
227
228 ~*~*Features:~*~*
229 • KI: {ai} (Ollama: {OLLAMA_MODEL})
230 • n8n: ✅ Konfiguriert
231 • Webhook: Port {WEBHOOK_PORT}
232
233 ~*~*Modus:~*~*
234 {'Antwortet auf alle' if RESPOND_TO_ALL else 'Nur Erwähnungen'}"""
235
236 elif command in ["!reset", "/reset"]:
237 if event.sender in conversation_history:
238 del conversation_history[event.sender]
239 response = "🔄 Chat-Verlauf gelöscht!"
240
241 elif command in ["!würfel", "/würfel"]:
242 result = random.randint(1, 6)
243 response = f"🎲 ~*~*{result}~*~*"
244
245 elif command in ["!münze", "/münze"]:
246 result = random.choice(["Kopf 🪙", "Zahl 🔢"])
247 response = f"{result}"
248
249 elif command in ["!zeit", "/zeit"]:
250 response = f"🕐 ~*~*{datetime.now().strftime('%H:%M:%S')}~*~* Uhr"
251
252 elif command in ["!ping", "/ping"]:
253 response = "🏓 Pong!"
254
255 elif command in ["!witz", "/witz"]:
256 witze = [
257 "Warum können Geister so schlecht lügen? Weil sie so leicht zu durchschauen sind! 👻",
258 "Was ist grün und steht vor der Tür? Ein Klopfsalat! 🥬",
259 "Warum summen Bienen? Weil sie den Text nicht kennen! 🐝",
260 "Was macht ein Clown im Büro? Faxen! 🤡",
261 "Welche Handwerker essen am meisten? Die Maurer! 🧱"
262 ]
263 response = random.choice(witze)
264
265 elif command in ["!rechne", "/rechne"]:
266 if not args:
267 response = "❌ Beispiel: `/rechne 5 + 3`"
268 else:
269 try:
270 expr = " ".join(args)
271 if all(c in "0123456789+-*/(). " for c in expr):
272 result = eval(expr)
273 response = f"🧮 `{expr}` = ~*~*{result}~*~*"
274 else:
275 response = "❌ Nur Zahlen und +-*/() erlaubt"
276 except:
277 response = "❌ Rechenfehler"
278
279 elif command in ["!webhook", "/webhook"]:
280 response = f"""📡 ~*~*Webhook Info~*~*
281
282 ~*~*n8n → Bot:~*~*
283 ```
284 POST http:~/~/YOUR_IP:{WEBHOOK_PORT}/webhook
285 ~{~{
286 "secret": "{WEBHOOK_SECRET}",
287 "message": "Text",
288 "room_id": "{room.room_id}"
289 }}
290 ```
291
292 ~*~*Health:~*~* http:~/~/YOUR_IP:{WEBHOOK_PORT}/health
293 ~*~*Raum-ID:~*~* `{room.room_id}`"""
294
295 elif command in ["!n8n", "/n8n"]:
296 if not args:
297 response = "❌ Beispiel: `/n8n status`"
298 else:
299 cmd = " ".join(args)
300 result = await trigger_n8n("command", {
301 "command": cmd,
302 "room_id": room.room_id,
303 "sender": event.sender
304 })
305
306 if result and "response" in result:
307 response = f"✅ n8n:\n{result['response']}"
308 elif result:
309 response = f"✅ Befehl gesendet: `{cmd}`"
310 else:
311 response = "❌ n8n nicht erreichbar"
312
313 # Normale Nachricht (kein Befehl)
314 else:
315 # Prüfe ob Bot erwähnt wurde
316 bot_mentioned = (
317 f"@{USER_ID.split(':')[0][1:]}" in message.lower() or 
318 USER_ID in message or 
319 "@terry" in message.lower()
320 )
321
322 # Ist es ein Direktchat (1-zu-1)?
323 is_direct_chat = False
324 if room.room_id in client.rooms:
325 room_obj = client.rooms[room.room_id]
326 is_direct_chat = len(room_obj.users) <= 2
327
328 # Bot antwortet nur wenn:
329 # - Erwähnt (@terry)
330 # - In Direktnachrichten
331 # - Oder RESPOND_TO_ALL aktiv
332 should_respond = (
333 RESPOND_TO_ALL or 
334 (RESPOND_TO_MENTIONS and bot_mentioned) or
335 (RESPOND_TO_DM and is_direct_chat)
336 )
337
338 if should_respond:
339 logger.info(f"💬 Verarbeite Nachricht (Mentioned: {bot_mentioned}, DM: {is_direct_chat})")
340
341 # Versuche KI
342 if AI_ENABLED:
343 logger.info("🧠 Sende an Ollama...")
344 ai_response = await ask_ai(message, event.sender)
345 if ai_response:
346 response = ai_response
347 else:
348 response = "🤖 Ollama antwortet nicht. Prüfe `/status`"
349 else:
350 # Einfache Antworten (OHNE Grüße die sich selbst triggern)
351 msg_lower = message.lower()
352
353 # NUR wenn Message MIT Gruß BEGINNT (nicht enthält)
354 if any(message.lower().startswith(w) for w in ["hallo", "hi", "hey", "moin", "guten tag", "servus"]):
355 username = event.sender.split(':')[0][1:]
356 response = f"Hey {username}! 👋 Tippe `/hilfe` für alle Befehle."
357
358 elif any(w in msg_lower for w in ["danke", "thx", "thanks"]):
359 response = "Gerne! 😊"
360
361 elif any(w in msg_lower for w in ["wie geht", "wie gehts"]):
362 response = "Mir geht's super! 🤖 Wie kann ich helfen?"
363
364 elif "?" in message:
365 response = "🤔 Gute Frage! Tippe `/hilfe` für verfügbare Befehle."
366
367 # Antwort senden
368 if response:
369 try:
370 await client.room_send(
371 room.room_id,
372 "m.room.message",
373 {"msgtype": "m.text", "body": response}
374 )
375 logger.info(f"✓ Antwort → {event.sender.split(':')[0]}")
376 except Exception as e:
377 logger.error(f"❌ Senden fehlgeschlagen: {e}")
378
379 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
380 # Message Callback
381 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
382 async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None:
383 """Reagiert auf Messages."""
384
385 # WICHTIG: Ignoriere eigene Messages
386 if event.sender == client.user_id:
387 logger.debug(f"Ignoriere eigene Nachricht von {event.sender}")
388 return
389
390 # Ignoriere alte Messages beim Start
391 if hasattr(event, 'server_timestamp'):
392 current_time = time.time() * 1000
393 age = current_time - event.server_timestamp
394 if age > 5000:  # Älter als 5 Sekunden
395 return
396
397 message = event.body.strip()
398 logger.info(f"📨 {event.sender.split(':')[0]}: {message[:50]}...")
399
400 await handle_command(room, event, message)
401
402 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
403 # Webserver
404 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
405 async def start_webserver():
406 """Startet Webhook-Server."""
407 app.router.add_post('/webhook', webhook_handler)
408 app.router.add_get('/health', health_check)
409
410 runner = web.AppRunner(app)
411 await runner.setup()
412 site = web.TCPSite(runner, '0.0.0.0', WEBHOOK_PORT)
413 await site.start()
414
415 logger.info(f"🌐 Webhook: Port {WEBHOOK_PORT}")
416
417 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
418 # Matrix Client
419 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
420 async def start_matrix_client():
421 """Startet Matrix Client."""
422 global client
423
424 client = AsyncClient(HOMESERVER, USER_ID, device_id=DEVICE_ID)
425 client.access_token = ACCESS_TOKEN
426 client.add_event_callback(message_callback, RoomMessageText)
427
428 await client.sync(timeout=30000, full_state=True)
429
430 logger.info(f"✓ In {len(client.rooms)} Räumen:")
431 for room_id, room in client.rooms.items():
432 logger.info(f"  • {room.display_name or room.room_id}")
433
434 await client.sync_forever(timeout=30000)
435
436 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
437 # Main
438 # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~--
439 async def main():
440 """Hauptfunktion."""
441 logger.info("=" * 70)
442 logger.info("🤖 Matrix Bot - Telegram Style v3.0")
443 logger.info("=" * 70)
444 logger.info(f"🔗 {HOMESERVER}")
445 logger.info(f"👤 {USER_ID}")
446 logger.info(f"🧠 KI: {'✅' if AI_ENABLED else '❌'}")
447 logger.info(f"🌐 Webhook: Port {WEBHOOK_PORT}")
448 logger.info(f"🎯 n8n: https:~/~/n8n.rs-servertech.com")
449 logger.info(f"📍 Raum: {DEFAULT_ROOM_ID}")
450 logger.info("=" * 70)
451
452 try:
453 await asyncio.gather(
454 start_webserver(),
455 start_matrix_client()
456 )
457 except KeyboardInterrupt:
458 logger.info("\n⏹️  Bot beendet")
459 finally:
460 if client:
461 await client.close()
462
463 if ~_~_name~_~_ == "~_~_main~_~_":
464 try:
465 asyncio.run(main())
466 except KeyboardInterrupt:
467 pass

Anwendungen

Benötigen Sie Hilfe?

Wenn Sie Hilfe mit XWiki benötigen, wenden Sie sich an: