Initial commit: GM-Relay Telegram Bot
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Dapper;
|
||||
using GmRelay.Bot.Domain;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.ReplyMarkups;
|
||||
|
||||
namespace GmRelay.Bot.Features.Sessions.CreateSession;
|
||||
|
||||
public sealed class CreateSessionHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient botClient,
|
||||
ILogger<CreateSessionHandler> logger)
|
||||
{
|
||||
public async Task HandleAsync(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
var text = message.Text ?? "";
|
||||
|
||||
string? title = null;
|
||||
string? link = null;
|
||||
var scheduledTimes = new List<DateTimeOffset>();
|
||||
|
||||
foreach (var line in text.Split('\n'))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.StartsWith("Название:", StringComparison.OrdinalIgnoreCase))
|
||||
title = trimmed["Название:".Length..].Trim();
|
||||
else if (trimmed.StartsWith("Ссылка:", StringComparison.OrdinalIgnoreCase))
|
||||
link = trimmed["Ссылка:".Length..].Trim();
|
||||
else if (trimmed.StartsWith("Время:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var timeStr = trimmed["Время:".Length..].Trim();
|
||||
if (MoscowTime.TryParseMoscow(timeStr, out var scheduledAt))
|
||||
{
|
||||
if (scheduledAt > DateTimeOffset.UtcNow)
|
||||
scheduledTimes.Add(scheduledAt);
|
||||
else
|
||||
await botClient.SendMessage(message.Chat.Id, $"⚠️ Предупреждение: Дата {timeStr} находится в прошлом и будет пропущена.", cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await botClient.SendMessage(message.Chat.Id, $"⚠️ Предупреждение: Некорректный формат времени '{timeStr}'. Пропущено.", cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(link) || scheduledTimes.Count == 0)
|
||||
{
|
||||
await botClient.SendMessage(
|
||||
chatId: message.Chat.Id,
|
||||
text: "❌ Не удалось распознать формат. Пожалуйста, используйте шаблон:\n\n/newsession\nНазвание: My Game\nВремя: 15.05.2026 19:30\nВремя: 22.05.2026 19:30\nСсылка: https://link",
|
||||
cancellationToken: cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var gmId = message.From!.Id;
|
||||
var gmName = message.From.FirstName + (string.IsNullOrEmpty(message.From.LastName) ? "" : $" {message.From.LastName}");
|
||||
var gmUsername = message.From.Username;
|
||||
|
||||
var chatId = message.Chat.Id;
|
||||
var chatTitle = message.Chat.Title ?? "Private Chat";
|
||||
|
||||
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
|
||||
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Убеждаемся, что GM зарегистрирован
|
||||
await connection.ExecuteAsync(
|
||||
@"INSERT INTO players (telegram_id, display_name, telegram_username)
|
||||
VALUES (@TgId, @Name, @Username)
|
||||
ON CONFLICT (telegram_id) DO UPDATE SET display_name = EXCLUDED.display_name, telegram_username = EXCLUDED.telegram_username;",
|
||||
new { TgId = gmId, Name = gmName, Username = gmUsername },
|
||||
transaction);
|
||||
|
||||
// 2. Убеждаемся, что Группа зарегистрирована
|
||||
var groupId = await connection.ExecuteScalarAsync<Guid>(
|
||||
@"INSERT INTO game_groups (telegram_chat_id, name, gm_telegram_id)
|
||||
VALUES (@ChatId, @ChatName, @GmId)
|
||||
ON CONFLICT (telegram_chat_id) DO UPDATE SET name = EXCLUDED.name
|
||||
RETURNING id;",
|
||||
new { ChatId = chatId, ChatName = chatTitle, GmId = gmId },
|
||||
transaction);
|
||||
|
||||
int? messageThreadId = null;
|
||||
if (message.Chat.IsForum)
|
||||
{
|
||||
var topic = await botClient.CreateForumTopic(
|
||||
chatId: chatId,
|
||||
name: $"🎲 Игры: {title}",
|
||||
cancellationToken: cancellationToken);
|
||||
messageThreadId = topic.MessageThreadId;
|
||||
}
|
||||
|
||||
// 3. Создаем сессии в цикле с общим batch_id
|
||||
var batchId = Guid.NewGuid();
|
||||
var sessions = new List<SessionBatchDto>();
|
||||
|
||||
foreach (var dt in scheduledTimes.OrderBy(d => d))
|
||||
{
|
||||
var sessionId = await connection.ExecuteScalarAsync<Guid>(
|
||||
@"INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id)
|
||||
VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, 'Planned', @ThreadId)
|
||||
RETURNING id;",
|
||||
new { BatchId = batchId, GroupId = groupId, Title = title, Link = link, ScheduledAt = dt, ThreadId = messageThreadId },
|
||||
transaction);
|
||||
|
||||
sessions.Add(new SessionBatchDto(sessionId, dt.UtcDateTime, "Planned"));
|
||||
}
|
||||
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
logger.LogInformation("Создан батч {BatchId} с {Count} сессиями в группе {GroupId}", batchId, sessions.Count, groupId);
|
||||
|
||||
// 4. Отправляем сообщение в чат
|
||||
var renderResult = SessionBatchRenderer.Render(title, sessions, Array.Empty<ParticipantBatchDto>());
|
||||
|
||||
|
||||
var batchMessage = await botClient.SendMessage(
|
||||
chatId: chatId,
|
||||
messageThreadId: messageThreadId,
|
||||
text: renderResult.Text,
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
||||
replyMarkup: renderResult.Markup,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
// 4b. Сохраняем message_id батч-сообщения для дальнейшего редактирования
|
||||
await connection.ExecuteAsync(
|
||||
"UPDATE sessions SET batch_message_id = @MsgId WHERE batch_id = @BatchId",
|
||||
new { MsgId = batchMessage.MessageId, BatchId = batchId });
|
||||
|
||||
// 5. Удаляем исходное сообщение с командой /newsession, чтобы не спамить
|
||||
try
|
||||
{
|
||||
await botClient.DeleteMessage(
|
||||
chatId: chatId,
|
||||
messageId: message.MessageId,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Не удалось удалить исходное сообщение {MessageId} в чате {ChatId}", message.MessageId, chatId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Ошибка при создании сессии");
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
await botClient.SendMessage(chatId, "💥 Произошла ошибка базы данных при создании сессии.", cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user