Telegram Bots als eigenständige Entität mit eigener Page

- Neue Tabelle 'telegram_bots' (bot_id, name, bot_token, webhook_secret, is_public)
- Automatische Migration bestehender Session-Telegram-Configs in telegram_bots
- Sessions verweisen via telegram_bot_id auf Bot (mehrere Sessions pro Bot möglich)
- Webhook-URL: /api/telegram/webhook/{botId} statt {sessionId}
- Eigene Telegram-Bot-Page (wie Agent/Cronjob/Session) statt Modal
- Bot-Page: Name, Token, Secret, Webhook-Verwaltung (registrieren/status/entfernen), Löschen
- Dashboard: Telegram Bots Section mit Cards (Name, maskierter Token, Session-Count)
- Session-Page: Bot-Picker (wie Agent-Picker) statt Token-Input, Chat-ID readonly + Unlink-Button
- Public-Flag: Bot kann von anderen Usern in ihren Sessions ausgewählt werden
- Vereinfacht: 1 Session = max. 1 Telegram Chat, allowed_chat_ids entfällt
- /start zeigt Chat-ID an, /unlink entfernt (Unlink nur über Web-UI)
- Nachrichten-Routing: Bot + Chat-ID → Session Lookup
This commit is contained in:
Meleki committed 2026-03-08 19:27:05 +01:00
1 parent 3b7bdb7a20
commit ec73ccd71e
13 files changed
+907 -371

No files matched your search

+221 -71
View File
@@ -13,29 +13,33 @@ public static class TelegramEndpoint
{
public static void MapTelegramEndpoints(this IEndpointRouteBuilder app)
{
// Webhook pro Session (von Telegram aufgerufen – keine Cookie-Auth!)
app.MapPost("/api/telegram/webhook/{sessionId}", HandleWebhook);
// Webhook pro Bot (von Telegram aufgerufen – keine Cookie-Auth!)
app.MapPost("/api/telegram/webhook/{botId}", HandleWebhook);
// Session Telegram-Config speichern
app.MapPut("/api/sessions/{sessionId}/telegram", HandleSaveTelegram);
// ── Telegram Bot CRUD ──
app.MapGet("/api/telegram/bots", HandleGetBots);
app.MapGet("/api/telegram/bots/available", HandleGetAvailableBots);
app.MapPost("/api/telegram/bots", HandleCreateBot);
app.MapGet("/api/telegram/bots/{botId}", HandleGetBot);
app.MapPut("/api/telegram/bots/{botId}", HandleUpdateBot);
app.MapDelete("/api/telegram/bots/{botId}", HandleDeleteBot);
// Session Telegram-Config abrufen
app.MapGet("/api/sessions/{sessionId}/telegram", HandleGetTelegram);
// ── Webhook-Verwaltung (pro Bot) ──
app.MapPost("/api/telegram/bots/{botId}/register-webhook", HandleRegisterWebhook);
app.MapGet("/api/telegram/bots/{botId}/webhook-info", HandleWebhookInfo);
app.MapDelete("/api/telegram/bots/{botId}/webhook", HandleDeleteWebhook);
// Webhook registrieren (pro Session)
app.MapPost("/api/sessions/{sessionId}/telegram/register-webhook", HandleRegisterWebhook);
// Webhook-Info (pro Session)
app.MapGet("/api/sessions/{sessionId}/telegram/webhook-info", HandleWebhookInfo);
// Webhook löschen (pro Session)
app.MapDelete("/api/sessions/{sessionId}/telegram/webhook", HandleDeleteWebhook);
// ── Session Telegram-Config ──
app.MapGet("/api/sessions/{sessionId}/telegram", HandleGetSessionTelegram);
app.MapPut("/api/sessions/{sessionId}/telegram", HandleSaveSessionTelegram);
}
private static async Task HandleWebhook(string sessionId, HttpRequest req, HttpResponse res, TelegramService telegram, DatabaseService db)
// ── Webhook Handler ──
private static async Task HandleWebhook(string botId, HttpRequest req, HttpResponse res, TelegramService telegram, DatabaseService db)
{
var session = await db.GetSession(sessionId);
if (session == null || string.IsNullOrEmpty(session.TelegramBotToken))
var bot = await db.GetTelegramBot(botId);
if (bot == null)
{
res.StatusCode = 404;
return;
@@ -43,7 +47,7 @@ public static class TelegramEndpoint
// Secret-Token prüfen
var secret = req.Headers["X-Telegram-Bot-Api-Secret-Token"].ToString();
if (!string.IsNullOrEmpty(session.TelegramWebhookSecret) && secret != session.TelegramWebhookSecret)
if (!string.IsNullOrEmpty(bot.WebhookSecret) && secret != bot.WebhookSecret)
{
res.StatusCode = 403;
return;
@@ -52,11 +56,10 @@ public static class TelegramEndpoint
try
{
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
// Fire-and-forget – Telegram erwartet schnelle 200 OK Antwort
_ = Task.Run(async () =>
{
try { await telegram.HandleUpdate(body, session); }
catch (Exception ex) { Console.Error.WriteLine($"[Telegram] Fehler für Session {sessionId}: {ex.Message}"); }
try { await telegram.HandleUpdate(body, bot); }
catch (Exception ex) { Console.Error.WriteLine($"[Telegram] Fehler für Bot {bot.Name}: {ex.Message}"); }
});
}
catch
@@ -67,27 +70,203 @@ public static class TelegramEndpoint
res.StatusCode = 200;
}
private static async Task HandleSaveTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
// ── Bot CRUD ──
private static async Task HandleGetBots(HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
var bots = await db.GetTelegramBots(userId);
var result = new System.Collections.Generic.List<object>();
foreach (var bot in bots)
{
var sessionCount = await db.GetTelegramBotSessionCount(bot.BotId);
result.Add(new
{
botId = bot.BotId,
name = bot.Name,
botToken = bot.BotToken,
webhookSecret = bot.WebhookSecret,
isPublic = bot.IsPublic,
sessionCount,
createdAt = bot.CreatedAt,
updatedAt = bot.UpdatedAt
});
}
await res.WriteAsJsonAsync(result);
}
private static async Task HandleGetBot(string botId, HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Session nicht gefunden" });
await res.WriteAsJsonAsync(new { error = "Bot nicht gefunden" });
return;
}
var sessionCount = await db.GetTelegramBotSessionCount(bot.BotId);
await res.WriteAsJsonAsync(new
{
botId = bot.BotId,
name = bot.Name,
botToken = bot.BotToken,
webhookSecret = bot.WebhookSecret,
isPublic = bot.IsPublic,
sessionCount,
createdAt = bot.CreatedAt,
updatedAt = bot.UpdatedAt
});
}
private static async Task HandleCreateBot(HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
var name = body.TryGetProperty("name", out var n) ? n.GetString()?.Trim() : null;
var botToken = body.TryGetProperty("botToken", out var t) ? t.GetString()?.Trim() : null;
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(botToken))
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "Name und Bot-Token sind erforderlich" });
return;
}
var bot = await db.CreateTelegramBot(userId, name, botToken);
await res.WriteAsJsonAsync(new
{
botId = bot.BotId,
name = bot.Name,
botToken = bot.BotToken,
webhookSecret = bot.WebhookSecret,
createdAt = bot.CreatedAt
});
}
private static async Task HandleUpdateBot(string botId, HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Bot nicht gefunden" });
return;
}
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;
var allowedChatIds = body.TryGetProperty("allowedChatIds", out var a) ? a.GetString() : session.TelegramAllowedChatIds;
var name = body.TryGetProperty("name", out var n) ? n.GetString()?.Trim() : bot.Name;
var botToken = body.TryGetProperty("botToken", out var t) ? t.GetString()?.Trim() : bot.BotToken;
var isPublic = body.TryGetProperty("isPublic", out var p) ? p.GetBoolean() : bot.IsPublic;
await db.UpdateSessionTelegram(sessionId, botToken, webhookSecret, session.TelegramChatId, allowedChatIds);
await db.UpdateTelegramBot(botId, name, botToken, isPublic);
await res.WriteAsJsonAsync(new { ok = true });
}
private static async Task HandleGetTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
private static async Task HandleDeleteBot(string botId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Bot nicht gefunden" });
return;
}
// Webhook bei Telegram löschen
try { await telegram.DeleteWebhook(bot); } catch { /* ignore */ }
await db.DeleteTelegramBot(botId);
await res.WriteAsJsonAsync(new { ok = true });
}
// ── Webhook-Verwaltung ──
private static async Task HandleRegisterWebhook(string botId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
await res.WriteAsJsonAsync(new { error = "Bot nicht gefunden" });
return;
}
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
if (!body.TryGetProperty("baseUrl", out var urlEl))
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "baseUrl required" });
return;
}
var result = await telegram.RegisterWebhook(urlEl.GetString(), bot);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
private static async Task HandleWebhookInfo(string botId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
return;
}
var result = await telegram.GetWebhookInfo(bot);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
private static async Task HandleDeleteWebhook(string botId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
{
var userId = req.HttpContext.GetUserId();
var bot = await db.GetTelegramBot(botId);
if (bot == null || bot.UserId != userId)
{
res.StatusCode = 404;
return;
}
var result = await telegram.DeleteWebhook(bot);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
// ── Verfügbare Bots (eigene + public) ──
private static async Task HandleGetAvailableBots(HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var bots = await db.GetAvailableTelegramBots(userId);
var result = new System.Collections.Generic.List<object>();
foreach (var bot in bots)
{
var isOwner = bot.UserId == userId;
result.Add(new
{
botId = bot.BotId,
name = bot.Name,
isOwner,
isPublic = bot.IsPublic,
createdAt = bot.CreatedAt,
// Token nur für eigene Bots anzeigen
botToken = isOwner ? bot.BotToken : null
});
}
await res.WriteAsJsonAsync(result);
}
// ── Session Telegram-Config ──
private static async Task HandleGetSessionTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
@@ -100,15 +279,13 @@ public static class TelegramEndpoint
await res.WriteAsJsonAsync(new
{
botToken = session.TelegramBotToken ?? "",
webhookSecret = session.TelegramWebhookSecret ?? "",
telegramBotId = session.TelegramBotId ?? "",
chatId = session.TelegramChatId ?? "",
allowedChatIds = session.TelegramAllowedChatIds ?? "",
configured = !string.IsNullOrEmpty(session.TelegramBotToken)
configured = !string.IsNullOrEmpty(session.TelegramBotId)
});
}
private static async Task HandleRegisterWebhook(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db, TelegramService telegram)
private static async Task HandleSaveSessionTelegram(string sessionId, HttpRequest req, HttpResponse res, DatabaseService db)
{
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
@@ -120,45 +297,18 @@ public static class TelegramEndpoint
}
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
if (!body.TryGetProperty("baseUrl", out var urlEl))
var botId = body.TryGetProperty("telegramBotId", out var b) ? b.GetString() : null;
var chatId = body.TryGetProperty("chatId", out var c) ? c.GetString() : session.TelegramChatId;
// Bot-Zuordnung setzen
if (botId != null)
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "baseUrl required" });
return;
await db.SetSessionTelegramBot(sessionId, string.IsNullOrEmpty(botId) ? null : botId);
}
var result = await telegram.RegisterWebhook(urlEl.GetString(), sessionId);
res.ContentType = "application/json";
await res.WriteAsync(result);
}
// Chat-ID aktualisieren (leerer String → NULL)
await db.UpdateSessionTelegramChat(sessionId, string.IsNullOrEmpty(chatId) ? null : chatId);
private static async Task HandleWebhookInfo(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.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);
await res.WriteAsJsonAsync(new { ok = true });
}
}