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

+159 -17
View File
@@ -167,6 +167,7 @@ public class DatabaseService
TryAddColumn(conn, "sessions", "telegram_webhook_secret", "TEXT");
TryAddColumn(conn, "sessions", "telegram_chat_id", "TEXT");
TryAddColumn(conn, "sessions", "telegram_allowed_chat_ids", "TEXT");
TryAddColumn(conn, "sessions", "telegram_bot_id", "TEXT");
TryAddColumn(conn, "agents", "user_id", "TEXT");
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
TryAddColumn(conn, "users", "persona", "TEXT");
@@ -227,6 +228,25 @@ public class DatabaseService
CREATE INDEX IF NOT EXISTS idx_telegram_links_code ON telegram_links(link_code);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS telegram_bots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bot_id TEXT NOT NULL UNIQUE,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
bot_token TEXT NOT NULL,
webhook_secret TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_telegram_bots_user ON telegram_bots(user_id);
");
TryAddColumn(conn, "telegram_bots", "is_public", "INTEGER NOT NULL DEFAULT 0");
// Migration: Bestehende Session-Telegram-Daten in telegram_bots überführen
MigrateTelegramBotsFromSessions(conn);
conn.Execute(@"
CREATE TABLE IF NOT EXISTS cronjobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -578,7 +598,7 @@ public class DatabaseService
MessageCount = activeCount,
ArchiveMessageCount = totalCount - activeCount,
TotalMessageCount = totalCount,
TelegramConfigured = !string.IsNullOrEmpty(s.TelegramBotToken),
TelegramConfigured = !string.IsNullOrEmpty(s.TelegramBotId),
TelegramLinked = !string.IsNullOrEmpty(s.TelegramChatId)
});
}
@@ -1147,37 +1167,140 @@ public class DatabaseService
await conn.ExecuteAsync("DELETE FROM users WHERE user_id = @userId", new { userId });
}
// ── Telegram (Session-basiert) ──
// ── Telegram Bots ──
public async Task<SessionRecord> GetSessionByTelegramChatId(long chatId)
private void MigrateTelegramBotsFromSessions(SqliteConnection conn)
{
using var conn = CreateConnection();
return await conn.QuerySingleOrDefaultAsync<SessionRecord>(
"SELECT * FROM sessions WHERE telegram_chat_id = @chatId AND is_deleted = 0",
new { chatId = chatId.ToString() });
// Prüfe ob es Sessions mit bot_token gibt, die noch kein telegram_bot_id haben
var sessions = conn.Query<dynamic>(
"SELECT session_id, user_id, name, telegram_bot_token, telegram_webhook_secret FROM sessions WHERE telegram_bot_token IS NOT NULL AND telegram_bot_token != '' AND (telegram_bot_id IS NULL OR telegram_bot_id = '')").ToList();
if (sessions.Count == 0) return;
var now = DateTime.UtcNow.ToString("o");
foreach (var s in sessions)
{
string token = s.telegram_bot_token;
string userId = s.user_id ?? "";
string sessionName = s.name ?? "Unbenannt";
string secret = s.telegram_webhook_secret ?? Guid.NewGuid().ToString("N");
// Prüfe ob für diesen Token bereits ein Bot existiert
var existing = conn.QuerySingleOrDefault<dynamic>(
"SELECT bot_id FROM telegram_bots WHERE bot_token = @token", new { token });
string botId;
if (existing != null)
{
botId = existing.bot_id;
}
else
{
botId = Guid.NewGuid().ToString();
conn.Execute(@"
INSERT INTO telegram_bots (bot_id, user_id, name, bot_token, webhook_secret, created_at, updated_at)
VALUES (@botId, @userId, @name, @token, @secret, @now, @now)",
new { botId, userId, name = $"Bot von {sessionName}", token, secret, now });
}
conn.Execute(
"UPDATE sessions SET telegram_bot_id = @botId, updated_at = @now WHERE session_id = @sessionId",
new { botId, sessionId = (string)s.session_id, now });
}
}
public async Task UpdateSessionTelegram(string sessionId, string botToken, string webhookSecret, string chatId, string allowedChatIds = null)
public async Task<IEnumerable<TelegramBotRecord>> GetTelegramBots(string userId)
{
using var conn = CreateConnection();
return await conn.QueryAsync<TelegramBotRecord>(
"SELECT * FROM telegram_bots WHERE user_id = @userId ORDER BY created_at DESC",
new { userId });
}
public async Task<IEnumerable<TelegramBotRecord>> GetAvailableTelegramBots(string userId)
{
using var conn = CreateConnection();
return await conn.QueryAsync<TelegramBotRecord>(
"SELECT * FROM telegram_bots WHERE user_id = @userId OR is_public = 1 ORDER BY created_at DESC",
new { userId });
}
public async Task<TelegramBotRecord> GetTelegramBot(string botId)
{
using var conn = CreateConnection();
return await conn.QuerySingleOrDefaultAsync<TelegramBotRecord>(
"SELECT * FROM telegram_bots WHERE bot_id = @botId",
new { botId });
}
public async Task<TelegramBotRecord> CreateTelegramBot(string userId, string name, string botToken)
{
using var conn = CreateConnection();
var botId = Guid.NewGuid().ToString();
var secret = Guid.NewGuid().ToString("N");
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
UPDATE sessions SET telegram_bot_token = @botToken, telegram_webhook_secret = @webhookSecret,
telegram_chat_id = @chatId, telegram_allowed_chat_ids = @allowedChatIds, updated_at = @now
INSERT INTO telegram_bots (bot_id, user_id, name, bot_token, webhook_secret, created_at, updated_at)
VALUES (@botId, @userId, @name, @botToken, @secret, @now, @now)",
new { botId, userId, name, botToken, secret, now });
return await GetTelegramBot(botId);
}
public async Task UpdateTelegramBot(string botId, string name, string botToken, bool isPublic)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
UPDATE telegram_bots SET name = @name, bot_token = @botToken, is_public = @isPublic, updated_at = @now
WHERE bot_id = @botId",
new { botId, name, botToken, isPublic, now });
}
public async Task DeleteTelegramBot(string botId)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
// Erst alle Session-Verknüpfungen entfernen
await conn.ExecuteAsync(
"UPDATE sessions SET telegram_bot_id = NULL, updated_at = @now WHERE telegram_bot_id = @botId",
new { botId, now });
await conn.ExecuteAsync("DELETE FROM telegram_bots WHERE bot_id = @botId", new { botId });
}
public async Task<int> GetTelegramBotSessionCount(string botId)
{
using var conn = CreateConnection();
return await conn.ExecuteScalarAsync<int>(
"SELECT COUNT(*) FROM sessions WHERE telegram_bot_id = @botId AND is_deleted = 0",
new { botId });
}
public async Task SetSessionTelegramBot(string sessionId, string botId)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(
"UPDATE sessions SET telegram_bot_id = @botId, updated_at = @now WHERE session_id = @sessionId",
new { sessionId, botId = (object)botId ?? DBNull.Value, now });
}
public async Task UpdateSessionTelegramChat(string sessionId, string chatId)
{
using var conn = CreateConnection();
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(@"
UPDATE sessions SET telegram_chat_id = @chatId, updated_at = @now
WHERE session_id = @sessionId",
new { sessionId, botToken, webhookSecret, chatId, allowedChatIds, now = DateTime.UtcNow.ToString("o") });
new { sessionId, chatId, now });
}
public async Task SetSessionTelegramChatId(string sessionId, string chatId)
{
using var conn = CreateConnection();
// Erst alte Verknüpfung mit dieser chatId entfernen (ein Telegram-Chat = eine Session)
await conn.ExecuteAsync(
"UPDATE sessions SET telegram_chat_id = NULL, updated_at = @now WHERE telegram_chat_id = @chatId",
new { chatId, now = DateTime.UtcNow.ToString("o") });
// Dann diese Session verknüpfen
var now = DateTime.UtcNow.ToString("o");
await conn.ExecuteAsync(
"UPDATE sessions SET telegram_chat_id = @chatId, updated_at = @now WHERE session_id = @sessionId",
new { sessionId, chatId, now = DateTime.UtcNow.ToString("o") });
new { sessionId, chatId, now });
}
public async Task ClearSessionTelegramChatId(string sessionId)
@@ -1188,6 +1311,25 @@ public class DatabaseService
new { sessionId, now = DateTime.UtcNow.ToString("o") });
}
public async Task<IEnumerable<SessionRecord>> GetSessionsByTelegramBot(string botId)
{
using var conn = CreateConnection();
return await conn.QueryAsync<SessionRecord>(
"SELECT * FROM sessions WHERE telegram_bot_id = @botId AND is_deleted = 0",
new { botId });
}
public async Task<SessionRecord> FindSessionForTelegramChat(string botId, long chatId)
{
using var conn = CreateConnection();
var chatIdStr = chatId.ToString();
// Erst exakte Chat-ID-Verknüpfung suchen
var session = await conn.QuerySingleOrDefaultAsync<SessionRecord>(
"SELECT * FROM sessions WHERE telegram_bot_id = @botId AND telegram_chat_id = @chatIdStr AND is_deleted = 0",
new { botId, chatIdStr });
return session;
}
public async Task<IEnumerable<SessionRecord>> GetUserSessions(string userId)
{
using var conn = CreateConnection();