Wiki-Quellcode von Version 2.0

Zuletzt geändert von René Schmidt am 2025/10/11 20:07

Verstecke letzte Bearbeiter
René Schmidt 1.1 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

Anwendungen

Benötigen Sie Hilfe?

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