Telegram: Bot=Session statt User-Level, Chat-Code-Refactoring

- Telegram-Config (Bot-Token, Webhook-Secret, ChatId) von User-Settings
  in sessions-Tabelle verschoben → jede Session hat ihren eigenen Bot
- Link-Code-System komplett entfernt (TelegramLinkRecord gelöscht)
- /start verknüpft automatisch, kein /link mehr nötig
- Alle Endpoints session-basiert: /api/sessions/{id}/telegram/*
- Webhook-URL pro Session: /api/telegram/webhook/{sessionId}

Refactoring: Doppelten Chat-Code eliminiert
- Neuer ChatService: PrepareContext, BuildHistoryFromDb, ResolveModelId
- Neues ChatContext-Model als gemeinsame Datenstruktur
- AnthropicService.ChatNonStreaming() für Telegram (statt eigenem Loop)
- TelegramService um ~180 Zeilen reduziert, kein eigener Claude-Loop mehr
- ChatEndpoint nutzt ChatService.PrepareContext() statt eigener Setup-Logik
- Model-Mapping nur noch an einer Stelle (3x → 1x)

Frontend
- Telegram-Section von Settings-Page in Session-Bearbeitungsseite verschoben
- Webhook-Secret readonly, wird einmalig generiert und persistent gespeichert
- Status wird automatisch geladen, Register/Delete je nach Webhook-Status
- Toast-Notification-System (showToast) in ui.js
- Link-Code-Modal und Telegram-Menüpunkt im Dashboard entfernt
- Telegram-Badge auf Session-Cards (konfiguriert/verbunden)

Bugfix: Bearer statt x-api-key für OAuth-Tokens im TelegramService
This commit is contained in:
Meleki committed 2026-03-03 21:46:59 +01:00
1 parent a0752ea492
commit 0120bb8f73
21 files changed
+994 -1272

No files matched your search

+72 -66
View File
@@ -13,28 +13,29 @@ public static class TelegramEndpoint
{
public static void MapTelegramEndpoints(this IEndpointRouteBuilder app)
{
// Webhook (von Telegram aufgerufen – keine Cookie-Auth!)
app.MapPost("/api/telegram/webhook", HandleWebhook);
// Webhook pro Session (von Telegram aufgerufen – keine Cookie-Auth!)
app.MapPost("/api/telegram/webhook/{sessionId}", HandleWebhook);
// Link-Code generieren (aus Web-UI, braucht Auth)
app.MapPost("/api/telegram/link", HandleCreateLink);
// Session Telegram-Config speichern
app.MapPut("/api/sessions/{sessionId}/telegram", HandleSaveTelegram);
// Webhook registrieren (Admin)
app.MapPost("/api/telegram/register-webhook", HandleRegisterWebhook);
// Session Telegram-Config abrufen
app.MapGet("/api/sessions/{sessionId}/telegram", HandleGetTelegram);
// Telegram-Status (ob konfiguriert – für Frontend)
app.MapGet("/api/telegram/status", HandleStatus);
// Webhook registrieren (pro Session)
app.MapPost("/api/sessions/{sessionId}/telegram/register-webhook", HandleRegisterWebhook);
// Webhook-Info (Admin)
app.MapGet("/api/telegram/webhook-info", HandleWebhookInfo);
// Webhook-Info (pro Session)
app.MapGet("/api/sessions/{sessionId}/telegram/webhook-info", HandleWebhookInfo);
// Webhook löschen (Admin)
app.MapDelete("/api/telegram/webhook", HandleDeleteWebhook);
// Webhook löschen (pro Session)
app.MapDelete("/api/sessions/{sessionId}/telegram/webhook", HandleDeleteWebhook);
}
private static async Task HandleWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
private static async Task HandleWebhook(string sessionId, HttpRequest req, HttpResponse res, TelegramService telegram, DatabaseService db)
{
if (!telegram.IsConfigured)
var session = await db.GetSession(sessionId);
if (session == null || string.IsNullOrEmpty(session.TelegramBotToken))
{
res.StatusCode = 404;
return;
@@ -42,7 +43,7 @@ public static class TelegramEndpoint
// Secret-Token prüfen
var secret = req.Headers["X-Telegram-Bot-Api-Secret-Token"].ToString();
if (secret != telegram.WebhookSecret)
if (!string.IsNullOrEmpty(session.TelegramWebhookSecret) && secret != session.TelegramWebhookSecret)
{
res.StatusCode = 403;
return;
@@ -54,8 +55,8 @@ public static class TelegramEndpoint
// Fire-and-forget – Telegram erwartet schnelle 200 OK Antwort
_ = Task.Run(async () =>
{
try { await telegram.HandleUpdate(body); }
catch (Exception ex) { Console.Error.WriteLine($"[Telegram] Fehler: {ex.Message}"); }
try { await telegram.HandleUpdate(body, session); }
catch (Exception ex) { Console.Error.WriteLine($"[Telegram] Fehler für Session {sessionId}: {ex.Message}"); }
});
}
catch
@@ -66,26 +67,9 @@ public static class TelegramEndpoint
res.StatusCode = 200;
}
private static async Task HandleCreateLink(HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
private static async Task HandleSaveTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
{
if (!telegram.IsConfigured)
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "Telegram Bot nicht konfiguriert" });
return;
}
var userId = req.HttpContext.GetUserId();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
if (!body.TryGetProperty("sessionId", out var sidEl))
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "sessionId required" });
return;
}
var sessionId = sidEl.GetString();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
@@ -94,18 +78,42 @@ public static class TelegramEndpoint
return;
}
var agentId = session.AgentId;
var code = await db.CreateTelegramLinkCode(userId, sessionId, agentId);
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
var botToken = body.TryGetProperty("botToken", out var t) ? t.GetString() : null;
var webhookSecret = body.TryGetProperty("webhookSecret", out var s) ? s.GetString() : null;
await res.WriteAsJsonAsync(new { code, expiresInSeconds = 300 });
await db.UpdateSessionTelegram(sessionId, botToken, webhookSecret, session.TelegramChatId);
await res.WriteAsJsonAsync(new { ok = true });
}
private static async Task HandleRegisterWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
private static async Task HandleGetTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
{
if (!req.HttpContext.IsAdmin())
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
res.StatusCode = 403;
await res.WriteAsJsonAsync(new { error = "Admin required" });
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Session nicht gefunden" });
return;
}
await res.WriteAsJsonAsync(new
{
botToken = session.TelegramBotToken ?? "",
webhookSecret = session.TelegramWebhookSecret ?? "",
chatId = session.TelegramChatId ?? "",
configured = !string.IsNullOrEmpty(session.TelegramBotToken)
});
}
private static async Task HandleRegisterWebhook(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Session nicht gefunden" });
return;
}
@@ -117,39 +125,37 @@ public static class TelegramEndpoint
return;
}
var result = await telegram.RegisterWebhook(urlEl.GetString());
await res.WriteAsJsonAsync(new { result });
}
private static async Task HandleStatus(HttpResponse res, TelegramService telegram)
{
await res.WriteAsJsonAsync(new { configured = telegram.IsConfigured });
}
private static async Task HandleWebhookInfo(HttpRequest req, HttpResponse res, TelegramService telegram)
{
if (!req.HttpContext.IsAdmin())
{
res.StatusCode = 403;
await res.WriteAsJsonAsync(new { error = "Admin required" });
return;
}
var result = await telegram.GetWebhookInfo();
var result = await telegram.RegisterWebhook(urlEl.GetString(), sessionId);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
private static async Task HandleDeleteWebhook(HttpRequest req, HttpResponse res, TelegramService telegram)
private static async Task HandleWebhookInfo(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
if (!req.HttpContext.IsAdmin())
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
res.StatusCode = 403;
await res.WriteAsJsonAsync(new { error = "Admin required" });
res.StatusCode = 404;
return;
}
var result = await telegram.DeleteWebhook();
var result = await telegram.GetWebhookInfo(sessionId);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
private static async Task HandleDeleteWebhook(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
res.StatusCode = 404;
return;
}
var result = await telegram.DeleteWebhook(sessionId);
res.ContentType = "application/json";
await res.WriteAsync(result);
}