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

+49 -132
View File
@@ -30,21 +30,9 @@ public class TelegramService
_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)
public async Task HandleUpdate(JsonElement update, TelegramBotRecord bot)
{
if (!update.TryGetProperty("message", out var message)) return;
if (!message.TryGetProperty("chat", out var chat)) return;
@@ -55,77 +43,54 @@ public class TelegramService
if (string.IsNullOrEmpty(text)) return;
var botToken = session.TelegramBotToken;
if (string.IsNullOrEmpty(botToken)) return;
// Session für diesen Chat finden (1 Session = max 1 Chat)
var session = await _db.FindSessionForTelegramChat(bot.BotId, chatId);
if (text.StartsWith("/start"))
{
if (!IsChatIdAllowed(session, chatId))
{
// Telegram-Username aus dem Update extrahieren
var username = "";
if (message.TryGetProperty("from", out var from))
{
if (from.TryGetProperty("username", out var uname))
username = uname.GetString() ?? "";
else if (from.TryGetProperty("first_name", out var fname))
username = fname.GetString() ?? "";
}
await SendMessage(chatId, botToken,
"🔒 Zugriff verweigert.\n\n" +
"Du bist nicht für diesen Bot freigegeben. " +
"Bitte kontaktiere den Inhaber und teile deine Chat-ID mit:\n\n" +
$"📋 Deine Chat-ID: `{chatId}`",
parseMode: "Markdown");
// Owner benachrichtigen (wenn chatId gesetzt)
if (!string.IsNullOrEmpty(session.TelegramChatId))
{
var ownerChatId = long.Parse(session.TelegramChatId);
var requesterInfo = string.IsNullOrEmpty(username) ? chatId.ToString() : $"@{username} ({chatId})";
await SendMessage(ownerChatId, botToken,
$"🔔 *Zugriffsanfrage*\n\n" +
$"Benutzer {EscapeMd(requesterInfo)} möchte diesen Bot nutzen\\.\n\n" +
$"Chat\\-ID: `{chatId}`\n\n" +
"Zum Freigeben: Chat\\-ID in den Session\\-Einstellungen unter _Erlaubte Chat\\-IDs_ hinzufügen\\.",
parseMode: "MarkdownV2");
}
return;
}
// Erste Verbindung: chatId als Owner setzen (wenn noch keine gesetzt)
if (string.IsNullOrEmpty(session.TelegramChatId))
{
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");
await HandleStart(chatId, bot, session);
return;
}
// Alle anderen Befehle/Nachrichten: Zugriff prüfen
if (!IsChatIdAllowed(session, chatId))
if (session == null)
{
await SendMessage(chatId, botToken, "🔒 Du bist nicht für diesen Bot freigegeben. Sende /start für mehr Informationen.");
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, botToken, session);
await HandleCommand(chatId, text, bot.BotToken, session);
return;
}
await HandleMessage(chatId, text, botToken, session);
await HandleMessage(chatId, text, bot.BotToken, 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 – 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 ──
@@ -153,11 +118,6 @@ public class TelegramService
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;
@@ -168,13 +128,6 @@ public class TelegramService
private async Task HandleMessage(long chatId, string userText, string botToken, SessionRecord session)
{
if (!IsChatIdAllowed(session, chatId))
{
await SendMessage(chatId, botToken, "🔒 Du bist nicht für diesen Bot freigegeben. Sende /start für mehr Informationen.");
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)
{
@@ -186,7 +139,6 @@ public class TelegramService
try
{
// History aus DB laden + User-Nachricht anhängen
var history = await _chatService.BuildHistoryFromDb(session.SessionId);
history.Add(new Dictionary<string, object>
{
@@ -194,7 +146,6 @@ public class TelegramService
["content"] = userText
});
// User-Nachricht in DB speichern
await _db.SaveMessage(new MessageRecord
{
ChainId = ctx.ChainId,
@@ -204,7 +155,6 @@ public class TelegramService
Content = userText
});
// AnthropicService für Non-Streaming-Chat nutzen
var reply = await _anthropic.ChatNonStreaming(ctx, history, onIteration: async (iteration) =>
{
if (iteration % 3 == 0)
@@ -224,11 +174,14 @@ public class TelegramService
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))
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, session.TelegramBotToken, text);
await SendMessage(chatId, bot.BotToken, text);
}
// ── Telegram Bot API ──
@@ -274,15 +227,11 @@ public class TelegramService
new StringContent(JsonSerializer.Serialize(new { chat_id = chatId, action }), Encoding.UTF8, "application/json"));
}
// ── Webhook-Verwaltung (pro Session) ──
// ── Webhook-Verwaltung (pro Bot) ──
public async Task<string> RegisterWebhook(string baseUrl, string sessionId)
public async Task<string> RegisterWebhook(string baseUrl, TelegramBotRecord bot)
{
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 webhookUrl = $"{baseUrl.TrimEnd('/')}/api/telegram/webhook/{bot.BotId}";
var client = _httpFactory.CreateClient();
var payload = new Dictionary<string, object>
{
@@ -290,66 +239,34 @@ public class TelegramService
["allowed_updates"] = new[] { "message" }
};
if (!string.IsNullOrEmpty(session.TelegramWebhookSecret))
payload["secret_token"] = session.TelegramWebhookSecret;
if (!string.IsNullOrEmpty(bot.WebhookSecret))
payload["secret_token"] = bot.WebhookSecret;
var resp = await client.PostAsync(
$"https://api.telegram.org/bot{session.TelegramBotToken}/setWebhook",
$"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<string> GetWebhookInfo(string sessionId)
public async Task<string> GetWebhookInfo(TelegramBotRecord bot)
{
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");
var resp = await client.GetAsync($"https://api.telegram.org/bot{bot.BotToken}/getWebhookInfo");
return await resp.Content.ReadAsStringAsync();
}
public async Task<string> DeleteWebhook(string sessionId)
public async Task<string> DeleteWebhook(TelegramBotRecord bot)
{
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",
$"https://api.telegram.org/bot{bot.BotToken}/deleteWebhook",
new StringContent("{}", Encoding.UTF8, "application/json"));
return await resp.Content.ReadAsStringAsync();
}
// ── Helper ──
private static bool IsChatIdAllowed(SessionRecord session, long chatId)
{
var chatIdStr = chatId.ToString();
// Owner (die primäre verknüpfte chatId) ist immer erlaubt
if (session.TelegramChatId == chatIdStr) return true;
// Wenn noch kein Owner gesetzt und keine Whitelist → erster User wird Owner
if (string.IsNullOrEmpty(session.TelegramChatId) && string.IsNullOrEmpty(session.TelegramAllowedChatIds))
return true;
// Whitelist prüfen
if (!string.IsNullOrEmpty(session.TelegramAllowedChatIds))
{
var allowed = session.TelegramAllowedChatIds.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var id in allowed)
{
if (id == chatIdStr) return true;
}
}
return false;
}
private static string EscapeMd(string text)
{
if (string.IsNullOrEmpty(text)) return "";