using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Gott.Models; namespace Gott.Services; public class TelegramService { private readonly IHttpClientFactory _httpFactory; private readonly DatabaseService _db; private readonly ChatService _chatService; private readonly AnthropicService _anthropic; private readonly int _maxIterations; public TelegramService( IHttpClientFactory httpFactory, DatabaseService db, ChatService chatService, AnthropicService anthropic, int maxIterations = 100) { _httpFactory = httpFactory; _db = db; _chatService = chatService; _anthropic = anthropic; _maxIterations = maxIterations; } // ── Bot-Token / Secret aus Session laden ── public string GetBotTokenForSession(SessionRecord session) { return session?.TelegramBotToken; } public string GetWebhookSecretForSession(SessionRecord session) { return session?.TelegramWebhookSecret; } // ── Webhook-Update verarbeiten ── public async Task HandleUpdate(JsonElement update, SessionRecord session) { if (!update.TryGetProperty("message", out var message)) return; if (!message.TryGetProperty("chat", out var chat)) return; if (!message.TryGetProperty("text", out var textEl)) return; var chatId = chat.GetProperty("id").GetInt64(); var text = textEl.GetString()?.Trim() ?? ""; if (string.IsNullOrEmpty(text)) return; var botToken = session.TelegramBotToken; if (string.IsNullOrEmpty(botToken)) return; if (text.StartsWith("/start")) { await _db.SetSessionTelegramChatId(session.SessionId, chatId.ToString()); var sessionName = session.Name ?? session.SessionId; await SendMessage(chatId, botToken, $"✅ *Verbunden mit Session: {EscapeMd(sessionName)}*\n\n" + "Du kannst jetzt einfach drauflos schreiben\\!\n\n" + "Befehle:\n" + "/clear – History archivieren\n" + "/status – Aktuelle Session\\-Info\n" + "/unlink – Verknüpfung aufheben", parseMode: "MarkdownV2"); return; } if (text.StartsWith("/")) { await HandleCommand(chatId, text, botToken, session); return; } await HandleMessage(chatId, text, botToken, session); } // ── Commands ── private async Task HandleCommand(long chatId, string text, string botToken, SessionRecord session) { var parts = text.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); var cmd = parts[0].ToLowerInvariant().Split('@')[0]; switch (cmd) { case "/clear": var archive = await _db.CreateArchive(session.SessionId); await SendMessage(chatId, botToken, archive == null ? "History ist bereits leer." : $"✅ History archiviert ({archive.MessageCount} Nachrichten)."); break; case "/status": var agent = !string.IsNullOrEmpty(session.AgentId) ? await _db.GetAgent(session.AgentId) : null; var sb = new StringBuilder("📊 *Status:*\n\n"); sb.AppendLine($"Session: *{EscapeMd(session.Name ?? session.SessionId)}*"); if (agent != null) sb.AppendLine($"Agent: *{EscapeMd(agent.Name)}* \\({EscapeMd(agent.Model)}\\)"); await SendMessage(chatId, botToken, sb.ToString(), parseMode: "MarkdownV2"); break; case "/unlink": await _db.ClearSessionTelegramChatId(session.SessionId); await SendMessage(chatId, botToken, "✅ Verknüpfung aufgehoben. Sende /start um erneut zu verbinden."); break; default: await SendMessage(chatId, botToken, "❓ Unbekannter Befehl. Sende /start für Hilfe."); break; } } // ── Nachricht an Claude weiterleiten ── private async Task HandleMessage(long chatId, string userText, string botToken, SessionRecord session) { if (session.TelegramChatId != chatId.ToString()) { await SendMessage(chatId, botToken, "❌ Bitte sende zuerst /start um dich mit dieser Session zu verbinden."); return; } // ChatService für Setup nutzen (userId = null → kein Auth-Check, Session ist bereits authentifiziert via Webhook-Secret) var ctx = await _chatService.PrepareContext(session.SessionId, null, _maxIterations); if (ctx == null) { await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu."); return; } await SendChatAction(chatId, botToken, "typing"); try { // History aus DB laden + User-Nachricht anhängen var history = await _chatService.BuildHistoryFromDb(session.SessionId); history.Add(new Dictionary { ["role"] = "user", ["content"] = userText }); // User-Nachricht in DB speichern await _db.SaveMessage(new MessageRecord { ChainId = ctx.ChainId, Iteration = 0, Role = "user", Type = "text", Content = userText }); // AnthropicService für Non-Streaming-Chat nutzen var reply = await _anthropic.ChatNonStreaming(ctx, history, onIteration: async (iteration) => { if (iteration % 3 == 0) await SendChatAction(chatId, botToken, "typing"); }); await SendLongMessage(chatId, botToken, reply); } catch (Exception ex) { await SendMessage(chatId, botToken, $"❌ Fehler: {ex.Message}"); } } // ── Notification senden (für Cronjobs) ── public async Task SendNotification(string sessionId, string text) { var session = await _db.GetSession(sessionId); if (session == null || string.IsNullOrEmpty(session.TelegramBotToken) || string.IsNullOrEmpty(session.TelegramChatId)) return; var chatId = long.Parse(session.TelegramChatId); await SendMessage(chatId, session.TelegramBotToken, text); } // ── Telegram Bot API ── public async Task SendMessage(long chatId, string botToken, string text, string parseMode = null) { var client = _httpFactory.CreateClient(); var payload = new Dictionary { ["chat_id"] = chatId, ["text"] = text }; if (!string.IsNullOrEmpty(parseMode)) payload["parse_mode"] = parseMode; await client.PostAsync( $"https://api.telegram.org/bot{botToken}/sendMessage", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); } public async Task SendLongMessage(long chatId, string botToken, string text) { const int maxLen = 4096; if (text.Length <= maxLen) { await SendMessage(chatId, botToken, text); return; } for (var i = 0; i < text.Length; i += maxLen) { var chunk = text.Substring(i, Math.Min(maxLen, text.Length - i)); await SendMessage(chatId, botToken, chunk); await Task.Delay(100); } } public async Task SendChatAction(long chatId, string botToken, string action) { var client = _httpFactory.CreateClient(); await client.PostAsync( $"https://api.telegram.org/bot{botToken}/sendChatAction", new StringContent(JsonSerializer.Serialize(new { chat_id = chatId, action }), Encoding.UTF8, "application/json")); } // ── Webhook-Verwaltung (pro Session) ── public async Task RegisterWebhook(string baseUrl, string sessionId) { var session = await _db.GetSession(sessionId); if (session == null || string.IsNullOrEmpty(session.TelegramBotToken)) return JsonSerializer.Serialize(new { ok = false, error = "Kein Bot-Token für diese Session konfiguriert" }); var webhookUrl = $"{baseUrl.TrimEnd('/')}/api/telegram/webhook/{sessionId}"; var client = _httpFactory.CreateClient(); var payload = new Dictionary { ["url"] = webhookUrl, ["allowed_updates"] = new[] { "message" } }; if (!string.IsNullOrEmpty(session.TelegramWebhookSecret)) payload["secret_token"] = session.TelegramWebhookSecret; var resp = await client.PostAsync( $"https://api.telegram.org/bot{session.TelegramBotToken}/setWebhook", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); return await resp.Content.ReadAsStringAsync(); } public async Task GetWebhookInfo(string sessionId) { var session = await _db.GetSession(sessionId); if (session == null || string.IsNullOrEmpty(session.TelegramBotToken)) return JsonSerializer.Serialize(new { ok = false, error = "Kein Bot-Token konfiguriert" }); var client = _httpFactory.CreateClient(); var resp = await client.GetAsync($"https://api.telegram.org/bot{session.TelegramBotToken}/getWebhookInfo"); return await resp.Content.ReadAsStringAsync(); } public async Task DeleteWebhook(string sessionId) { var session = await _db.GetSession(sessionId); if (session == null || string.IsNullOrEmpty(session.TelegramBotToken)) return JsonSerializer.Serialize(new { ok = false, error = "Kein Bot-Token konfiguriert" }); var client = _httpFactory.CreateClient(); var resp = await client.PostAsync( $"https://api.telegram.org/bot{session.TelegramBotToken}/deleteWebhook", new StringContent("{}", Encoding.UTF8, "application/json")); return await resp.Content.ReadAsStringAsync(); } // ── Helper ── private static string EscapeMd(string text) { if (string.IsNullOrEmpty(text)) return ""; foreach (var c in new[] { '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!' }) text = text.Replace(c.ToString(), "\\" + c); return text; } }