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:
pdyde 2026-02-21 13:17:04 +01:00
parent 4c123d5f0f
commit 73c36785e2
22 changed files with 8324 additions and 61 deletions

215
app.py
View file

@ -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)