Files
GmRelayBot/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs
T

208 lines
7.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ... UpdateRouter will have CancelSessionHandler and cancel_session route instead of close_recruitment
using GmRelay.Bot.Features.Confirmation.HandleRsvp;
using GmRelay.Bot.Features.Sessions.CreateSession;
using GmRelay.Bot.Features.Sessions.ListSessions;
using GmRelay.Bot.Features.Sessions.ExportCalendar;
using GmRelay.Bot.Features.Sessions.RescheduleSession;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace GmRelay.Bot.Infrastructure.Telegram;
/// <summary>
/// Routes incoming Telegram updates to the appropriate feature handler.
/// No reflection — all routing is explicit (AOT-safe).
/// </summary>
public sealed class UpdateRouter(
HandleRsvpHandler rsvpHandler,
CreateSessionHandler createSessionHandler,
JoinSessionHandler joinSessionHandler,
CancelSessionHandler cancelSessionHandler,
DeleteSessionHandler deleteSessionHandler,
ListSessionsHandler listSessionsHandler,
ExportCalendarHandler exportCalendarHandler,
InitiateRescheduleHandler initiateRescheduleHandler,
HandleRescheduleTimeInputHandler rescheduleTimeInputHandler,
HandleRescheduleVoteHandler rescheduleVoteHandler,
ITelegramBotClient bot,
ILogger<UpdateRouter> logger)
{
public async Task RouteAsync(Update update, CancellationToken ct)
{
switch (update)
{
case { CallbackQuery: { } query }:
await HandleCallbackQueryAsync(query, ct);
break;
case { Message: { Text: { } text } message } when text.StartsWith('/'):
await HandleCommandAsync(message, text, ct);
break;
// Non-command text messages — check for reschedule time input
case { Message: { Text: { } } message } when !message.Text!.StartsWith('/'):
await rescheduleTimeInputHandler.TryHandleAsync(message, ct);
break;
}
}
private async Task HandleCallbackQueryAsync(CallbackQuery query, CancellationToken ct)
{
if (query.Data is not { } data || query.Message is not { } message)
return;
var parts = data.Split(':', 3);
var action = parts[0];
if (action == "join_session" && parts.Length >= 2 && Guid.TryParse(parts[1], out var joinSessionId))
{
var command = new JoinSessionCommand(
SessionId: joinSessionId,
TelegramUserId: query.From.Id,
DisplayName: query.From.FirstName + (string.IsNullOrEmpty(query.From.LastName) ? "" : $" {query.From.LastName}"),
TelegramUsername: query.From.Username,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await joinSessionHandler.HandleAsync(command, ct);
return;
}
if (action == "cancel_session" && parts.Length >= 2 && Guid.TryParse(parts[1], out var cancelSessionId))
{
var command = new CancelSessionCommand(
SessionId: cancelSessionId,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await cancelSessionHandler.HandleAsync(command, ct);
return;
}
if (action == "delete_session" && parts.Length >= 2 && Guid.TryParse(parts[1], out var deleteSessionId))
{
var command = new DeleteSessionCommand(
SessionId: deleteSessionId,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await deleteSessionHandler.HandleAsync(command, ct);
return;
}
if (action == "reschedule_session" && parts.Length >= 2 && Guid.TryParse(parts[1], out var rescheduleSessionId))
{
var command = new InitiateRescheduleCommand(
SessionId: rescheduleSessionId,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await initiateRescheduleHandler.HandleAsync(command, ct);
return;
}
if (action == "reschedule_vote" && parts.Length >= 3 && Guid.TryParse(parts[2], out var proposalId))
{
var vote = parts[1]; // "yes" or "no"
if (vote is not ("yes" or "no"))
return;
var command = new HandleRescheduleVoteCommand(
ProposalId: proposalId,
Vote: vote,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await rescheduleVoteHandler.HandleAsync(command, ct);
return;
}
if (action == "rsvp")
{
if (parts.Length < 3 || !Guid.TryParse(parts[2], out var sessionId))
return;
var status = parts[1] switch
{
"confirm" => Domain.RsvpStatus.Confirmed,
"decline" => Domain.RsvpStatus.Declined,
_ => (string?)null
};
if (status is null)
return;
var command = new HandleRsvpCommand(
SessionId: sessionId,
TelegramUserId: query.From.Id,
Status: status,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await rsvpHandler.HandleAsync(command, ct);
}
}
private async Task HandleCommandAsync(Message message, string text, CancellationToken ct)
{
// Извлекаем команду: берём первую строку, первое слово, убираем @BotUsername
var firstLine = text.Split('\n')[0].Trim();
var command = firstLine.Split(' ')[0].Split('@')[0].ToLowerInvariant();
switch (command)
{
case "/start":
await bot.SendMessage(
chatId: message.Chat.Id,
text: "GM-Relay Bot ready. Use /help for commands.",
cancellationToken: ct);
break;
case "/newsession":
await createSessionHandler.HandleAsync(message, ct);
break;
case "/listsessions":
await listSessionsHandler.HandleAsync(message, ct);
break;
case "/exportcalendar":
await exportCalendarHandler.HandleAsync(message, ct);
break;
case "/help":
await bot.SendMessage(
chatId: message.Chat.Id,
text: """
GM-Relay бот для управления игровыми сессиями.
/newsession
Название: My Game
Время: 15.05.2026 19:30
Ссылка: https://link
/listsessions список предстоящих сессий
/help эта справка
""",
cancellationToken: ct);
break;
// TODO: /listsessions — will be implemented as features
default:
logger.LogDebug("Unknown command: {Command}", command);
break;
}
}
}