using System; using System.Collections.Generic; using System.Linq; 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 ServerSettingsService _settings; public TelegramService( IHttpClientFactory httpFactory, DatabaseService db, ChatService chatService, AnthropicService anthropic, ServerSettingsService settings) { _httpFactory = httpFactory; _db = db; _chatService = chatService; _anthropic = anthropic; _settings = settings; } // ── Webhook-Update verarbeiten ── public async Task HandleUpdate(JsonElement update, TelegramBotRecord bot) { 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; // Session für diesen Chat finden (1 Session = max 1 Chat) var session = await _db.FindSessionForTelegramChat(bot.BotId, chatId); if (text.StartsWith("/start")) { await HandleStart(chatId, bot, session); return; } if (session == null) { // Gibt es genau 1 unverknüpfte Session für diesen Bot? → Auto-Link var unlinked = (await _db.FindUnlinkedSessionsForBot(bot.BotId)).ToList(); if (unlinked.Count == 1) { session = unlinked[0]; await _db.UpdateSessionTelegramChat(session.SessionId, chatId.ToString()); var sessionName = session.Name ?? session.SessionId; await SendMessage(chatId, bot.BotToken, $"✅ Automatisch verbunden mit Session: {sessionName}"); } else { await SendMessage(chatId, bot.BotToken, $"🔒 Keine Session mit diesem Chat verknüpft.\n\nDeine Chat-ID: `{chatId}`\nBitte weise diese Chat-ID im Web-UI einer Session zu.", parseMode: "Markdown"); return; } } if (text.StartsWith("/")) { await HandleCommand(chatId, text, bot.BotToken, session); return; } await HandleMessage(chatId, text, bot.BotToken, bot.UserId, session); } private async Task HandleStart(long chatId, TelegramBotRecord bot, SessionRecord session) { if (session != null) { // Bereits verknüpft – Willkommensinfo var sessionName = session.Name ?? session.SessionId; await SendMessage(chatId, bot.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", parseMode: "MarkdownV2"); return; } // Nicht verknüpft – gibt es Sessions mit diesem Bot ohne Chat-ID? var unlinked = (await _db.FindUnlinkedSessionsForBot(bot.BotId)).ToList(); if (unlinked.Count == 1) { // Genau 1 Session ohne Chat-ID → automatisch zuweisen var target = unlinked[0]; await _db.UpdateSessionTelegramChat(target.SessionId, chatId.ToString()); var sessionName = target.Name ?? target.SessionId; await SendMessage(chatId, bot.BotToken, $"✅ *Automatisch 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", parseMode: "MarkdownV2"); return; } // Keine oder mehrere unverknüpfte Sessions → Chat-ID anzeigen await SendMessage(chatId, bot.BotToken, $"📋 Deine Chat-ID: `{chatId}`\n\n" + "Bitte weise diese Chat-ID im Web-UI einer Session zu, " + "die diesen Bot verwendet.", parseMode: "Markdown"); } // ── 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; 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, string botUserId, SessionRecord session) { var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _settings.GetInt("MaxIterations", 100)); if (ctx == null) { await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu."); return; } // API-Key des Bot-Owners für diesen Agent auflösen var userApiKey = await _db.GetAgentApiKeyRecord(botUserId, ctx.AgentId); if (userApiKey == null) { await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen. Der Bot-Besitzer muss im Web-UI einen Key zuweisen."); return; } ctx.ApiToken = userApiKey.ApiKey; await SendChatAction(chatId, botToken, "typing"); try { var history = await _chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: ctx.PromptBlocks); history.Add(new Dictionary { ["role"] = "user", ["content"] = userText }); await _db.SaveMessage(new MessageRecord { ChainId = ctx.ChainId, Iteration = 0, Role = "user", Type = "text", Content = userText }); 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.TelegramBotId) || string.IsNullOrEmpty(session.TelegramChatId)) return; var bot = await _db.GetTelegramBot(session.TelegramBotId); if (bot == null || string.IsNullOrEmpty(bot.BotToken)) return; var chatId = long.Parse(session.TelegramChatId); await SendMessage(chatId, bot.BotToken, 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 Bot) ── public async Task RegisterWebhook(string baseUrl, TelegramBotRecord bot) { var webhookUrl = $"{baseUrl.TrimEnd('/')}/api/telegram/webhook/{bot.BotId}"; var client = _httpFactory.CreateClient(); var payload = new Dictionary { ["url"] = webhookUrl, ["allowed_updates"] = new[] { "message" } }; if (!string.IsNullOrEmpty(bot.WebhookSecret)) payload["secret_token"] = bot.WebhookSecret; var resp = await client.PostAsync( $"https://api.telegram.org/bot{bot.BotToken}/setWebhook", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); return await resp.Content.ReadAsStringAsync(); } public async Task GetWebhookInfo(TelegramBotRecord bot) { var client = _httpFactory.CreateClient(); var resp = await client.GetAsync($"https://api.telegram.org/bot{bot.BotToken}/getWebhookInfo"); return await resp.Content.ReadAsStringAsync(); } public async Task DeleteWebhook(TelegramBotRecord bot) { var client = _httpFactory.CreateClient(); var resp = await client.PostAsync( $"https://api.telegram.org/bot{bot.BotToken}/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; } }