feat: Telegram Bot Integration mit QR-Code
Features: - Telegram Bot mit python-telegram-bot Library - Bidirektionale Kommunikation (Anfragen → Tasks → Antworten) - QR-Code auf Settings-Seite für einfache Bot-Verbindung - User-ID Whitelist für Sicherheit - Automatische Task-Erstellung aus Telegram-Nachrichten - Agent-Antworten werden zurück zu Telegram gesendet Implementation: - Neue Telegram-Handler in app.py (start, message) - QR-Code Generator mit qrcode Library - Settings-Seite erweitert mit Telegram-Konfiguration - .env.example mit Telegram-Setup-Anleitung - Background Thread für Telegram Polling - Integration mit bestehendem Task-System Configuration: - TELEGRAM_BOT_TOKEN: Bot Token von @BotFather - TELEGRAM_BOT_USERNAME: Bot Username für QR-Code - TELEGRAM_ALLOWED_USERS: Komma-getrennte User-IDs Usage: 1. Bot via @BotFather erstellen 2. Token + User-IDs in .env eintragen 3. App starten 4. QR-Code auf /settings scannen 5. /start im Bot senden
This commit is contained in:
parent
4c123d5f0f
commit
73c36785e2
22 changed files with 8324 additions and 61 deletions
215
app.py
215
app.py
|
|
@ -9,12 +9,16 @@ import email
|
|||
import threading
|
||||
import time
|
||||
import logging
|
||||
import io
|
||||
import qrcode
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.header import decode_header
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, flash, Response, send_from_directory, jsonify
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
|
@ -340,6 +344,20 @@ EMAIL_WHITELIST = [
|
|||
]
|
||||
EMAIL_WHITELIST_DOMAINS = ['diversityball.at']
|
||||
|
||||
# ── Telegram Bot Configuration ────────────────────────────────────────────────
|
||||
TELEGRAM_CONFIG = {
|
||||
'bot_token': os.getenv('TELEGRAM_BOT_TOKEN', ''),
|
||||
'bot_username': os.getenv('TELEGRAM_BOT_USERNAME', 'frankenbot'),
|
||||
'allowed_users': [
|
||||
int(uid.strip()) for uid in os.getenv('TELEGRAM_ALLOWED_USERS', '').split(',')
|
||||
if uid.strip().isdigit()
|
||||
]
|
||||
}
|
||||
|
||||
# Telegram Bot Application (globale Instanz)
|
||||
telegram_app = None
|
||||
telegram_thread = None
|
||||
|
||||
# ── Poller-Einstellungen (zur Laufzeit änderbar via /settings) ───────────────
|
||||
# POLLER_INTERVAL: Wie oft der IMAP-Poller läuft (Sekunden)
|
||||
# FAILSAFE_WINDOW: Wie lange ein Task laufen darf bevor Failsafe anschlägt (Sekunden)
|
||||
|
|
@ -984,6 +1002,159 @@ def add_to_email_log(entry):
|
|||
email_log.pop(0)
|
||||
|
||||
|
||||
# ── Telegram Bot Functions ────────────────────────────────────────────────────
|
||||
|
||||
async def telegram_start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handler für /start Kommando."""
|
||||
user_id = update.effective_user.id
|
||||
username = update.effective_user.username or update.effective_user.first_name
|
||||
|
||||
# Whitelist-Check
|
||||
if TELEGRAM_CONFIG['allowed_users'] and user_id not in TELEGRAM_CONFIG['allowed_users']:
|
||||
await update.message.reply_text(
|
||||
f"⛔ Zugriff verweigert.\n\n"
|
||||
f"Deine User-ID: {user_id}\n"
|
||||
f"Bitte füge diese ID zu TELEGRAM_ALLOWED_USERS in der .env Datei hinzu."
|
||||
)
|
||||
logging.warning(f"[Telegram] Unauthorized access attempt from {username} (ID: {user_id})")
|
||||
return
|
||||
|
||||
await update.message.reply_text(
|
||||
f"👋 Willkommen bei Frankenbot, {username}!\n\n"
|
||||
f"🤖 Ich bin dein Multi-Agent Event-Management-Assistent.\n\n"
|
||||
f"Sende mir einfach eine Nachricht und ich erstelle einen Task, "
|
||||
f"der von unseren spezialisierten Agents bearbeitet wird:\n\n"
|
||||
f"• 💰 Budget Manager\n"
|
||||
f"• 🍽️ Catering Manager\n"
|
||||
f"• 📍 Location Manager\n"
|
||||
f"• 📅 Program Manager\n"
|
||||
f"• 🔍 Researcher\n"
|
||||
f"• und mehr!\n\n"
|
||||
f"Der Orchestrator wählt automatisch den besten Agent für deine Anfrage."
|
||||
)
|
||||
logging.info(f"[Telegram] User {username} (ID: {user_id}) started bot")
|
||||
|
||||
|
||||
async def telegram_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handler für eingehende Nachrichten."""
|
||||
user_id = update.effective_user.id
|
||||
username = update.effective_user.username or update.effective_user.first_name
|
||||
message_text = update.message.text
|
||||
|
||||
# Whitelist-Check
|
||||
if TELEGRAM_CONFIG['allowed_users'] and user_id not in TELEGRAM_CONFIG['allowed_users']:
|
||||
await update.message.reply_text("⛔ Zugriff verweigert. Verwende /start für Details.")
|
||||
return
|
||||
|
||||
# Task erstellen
|
||||
task_id = len(tasks) + 1
|
||||
while any(t['id'] == task_id for t in tasks):
|
||||
task_id += 1
|
||||
|
||||
new_task = {
|
||||
'id': task_id,
|
||||
'title': f"Telegram: {message_text[:50]}{'...' if len(message_text) > 50 else ''}",
|
||||
'description': message_text,
|
||||
'assigned_agent': 'Orchestrator',
|
||||
'agent_key': 'orchestrator',
|
||||
'status': 'pending',
|
||||
'created': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'type': 'telegram',
|
||||
'created_by': f'telegram_user_{user_id}',
|
||||
'telegram_chat_id': update.effective_chat.id,
|
||||
'telegram_user': username
|
||||
}
|
||||
tasks.append(new_task)
|
||||
|
||||
await update.message.reply_text(
|
||||
f"✅ Task #{task_id} erstellt!\n\n"
|
||||
f"📝 {message_text[:100]}{'...' if len(message_text) > 100 else ''}\n\n"
|
||||
f"⏳ Der Orchestrator wird deine Anfrage verarbeiten.\n"
|
||||
f"Ich benachrichtige dich, sobald die Antwort bereit ist."
|
||||
)
|
||||
|
||||
logging.info(f"[Telegram] Task #{task_id} created by {username} (ID: {user_id})")
|
||||
|
||||
|
||||
def send_telegram_message(chat_id: int, message: str):
|
||||
"""Sendet eine Nachricht an einen Telegram-Chat."""
|
||||
global telegram_app
|
||||
|
||||
if not telegram_app or not TELEGRAM_CONFIG['bot_token']:
|
||||
logging.warning("[Telegram] Cannot send message: Bot not configured")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Async-Funktion in sync Context ausführen
|
||||
import asyncio
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(telegram_app.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=message
|
||||
))
|
||||
loop.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"[Telegram] Error sending message: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def init_telegram_bot():
|
||||
"""Initialisiert den Telegram Bot."""
|
||||
global telegram_app
|
||||
|
||||
if not TELEGRAM_CONFIG['bot_token']:
|
||||
logging.info("[Telegram] Bot token not configured, skipping initialization")
|
||||
return
|
||||
|
||||
try:
|
||||
# Application erstellen
|
||||
telegram_app = Application.builder().token(TELEGRAM_CONFIG['bot_token']).build()
|
||||
|
||||
# Handler registrieren
|
||||
telegram_app.add_handler(CommandHandler("start", telegram_start_command))
|
||||
telegram_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, telegram_message_handler))
|
||||
|
||||
logging.info("[Telegram] Bot initialized successfully")
|
||||
logging.info(f"[Telegram] Allowed users: {TELEGRAM_CONFIG['allowed_users']}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[Telegram] Failed to initialize bot: {e}")
|
||||
telegram_app = None
|
||||
|
||||
|
||||
def run_telegram_bot():
|
||||
"""Startet den Telegram Bot in einem separaten Thread."""
|
||||
global telegram_app
|
||||
|
||||
if not telegram_app:
|
||||
logging.warning("[Telegram] Bot not initialized, cannot start")
|
||||
return
|
||||
|
||||
try:
|
||||
logging.info("[Telegram] Starting bot polling...")
|
||||
telegram_app.run_polling(drop_pending_updates=True)
|
||||
except Exception as e:
|
||||
logging.error(f"[Telegram] Bot polling error: {e}")
|
||||
|
||||
|
||||
def start_telegram_thread():
|
||||
"""Startet den Telegram Bot im Hintergrund."""
|
||||
global telegram_thread
|
||||
|
||||
if not TELEGRAM_CONFIG['bot_token']:
|
||||
logging.info("[Telegram] No bot token configured, skipping bot start")
|
||||
return
|
||||
|
||||
init_telegram_bot()
|
||||
|
||||
if telegram_app:
|
||||
telegram_thread = threading.Thread(target=run_telegram_bot, daemon=True, name="TelegramBot")
|
||||
telegram_thread.start()
|
||||
logging.info("[Telegram] Background thread started")
|
||||
|
||||
|
||||
def poll_emails():
|
||||
"""
|
||||
Hintergrund-Thread: Checkt alle 2 Minuten den IMAP-Posteingang.
|
||||
|
|
@ -1378,6 +1549,19 @@ Arbeite diesen Teil ab und liefere ein vollständiges Ergebnis.""",
|
|||
|
||||
task['status'] = 'completed'
|
||||
logger.info("[TaskBeat] Task #%d abgeschlossen.", task['id'])
|
||||
|
||||
# Telegram-Benachrichtigung wenn Task von Telegram kam
|
||||
if task.get('type') == 'telegram' and task.get('telegram_chat_id'):
|
||||
try:
|
||||
telegram_msg = (
|
||||
f"✅ Task #{task['id']} abgeschlossen!\n\n"
|
||||
f"📝 Anfrage: {task.get('title', 'N/A')}\n\n"
|
||||
f"💬 Antwort:\n{response[:4000]}" # Telegram limit: 4096 chars
|
||||
)
|
||||
send_telegram_message(task['telegram_chat_id'], telegram_msg)
|
||||
logger.info("[TaskBeat] Telegram-Antwort gesendet für Task #%d", task['id'])
|
||||
except Exception as e:
|
||||
logger.error("[TaskBeat] Fehler beim Senden der Telegram-Antwort: %s", str(e))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("[TaskBeat] Fehler: %s", str(e))
|
||||
|
|
@ -1925,7 +2109,8 @@ def settings():
|
|||
return render_template('settings.html',
|
||||
agents=AGENTS,
|
||||
poller_settings=poller_settings,
|
||||
journal_stats=journal_stats)
|
||||
journal_stats=journal_stats,
|
||||
telegram_config=TELEGRAM_CONFIG)
|
||||
|
||||
|
||||
@app.route('/settings/journal-clear', methods=['POST'])
|
||||
|
|
@ -1941,6 +2126,30 @@ def journal_clear():
|
|||
return redirect(url_for('settings'))
|
||||
|
||||
|
||||
@app.route('/api/telegram-qr')
|
||||
def telegram_qr():
|
||||
"""Generiert QR-Code für Telegram Bot."""
|
||||
if not TELEGRAM_CONFIG['bot_token'] or not TELEGRAM_CONFIG['bot_username']:
|
||||
return "Telegram Bot nicht konfiguriert", 404
|
||||
|
||||
# Bot-Link erstellen
|
||||
bot_link = f"https://t.me/{TELEGRAM_CONFIG['bot_username']}?start=connect"
|
||||
|
||||
# QR-Code generieren
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=2)
|
||||
qr.add_data(bot_link)
|
||||
qr.make(fit=True)
|
||||
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# In BytesIO speichern
|
||||
img_io = io.BytesIO()
|
||||
img.save(img_io, 'PNG')
|
||||
img_io.seek(0)
|
||||
|
||||
return Response(img_io.getvalue(), mimetype='image/png')
|
||||
|
||||
|
||||
# ── Task API ────────────────────────────────────────────────────────────────
|
||||
@app.route('/api/tasks', methods=['GET', 'POST'])
|
||||
def api_tasks():
|
||||
|
|
@ -2126,4 +2335,8 @@ def distribute_tasks():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Telegram Bot starten
|
||||
start_telegram_thread()
|
||||
|
||||
# Flask App starten
|
||||
app.run(debug=False, host='0.0.0.0', port=5000, threaded=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue