Files
GmRelayBot/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs
T
Toutsu 41f2ea6e90
Deploy Telegram Bot / build-and-push (push) Successful in 23s
Deploy Telegram Bot / deploy (push) Successful in 10s
feat: add telegram mini app dashboard
2026-04-28 14:56:55 +03:00

259 lines
9.9 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.Shared.Domain;
using GmRelay.Shared.Rendering;
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;
using Telegram.Bot.Types.ReplyMarkups;
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,
LeaveSessionHandler leaveSessionHandler,
PromoteWaitlistedPlayerHandler promoteWaitlistedPlayerHandler,
CancelSessionHandler cancelSessionHandler,
DeleteSessionHandler deleteSessionHandler,
ListSessionsHandler listSessionsHandler,
ExportCalendarHandler exportCalendarHandler,
InitiateRescheduleHandler initiateRescheduleHandler,
HandleRescheduleTimeInputHandler rescheduleTimeInputHandler,
HandleRescheduleVoteHandler rescheduleVoteHandler,
ITelegramBotClient bot,
IConfiguration configuration,
ILogger<UpdateRouter> logger) : ITelegramUpdateHandler
{
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 == "leave_session" && parts.Length >= 2 && Guid.TryParse(parts[1], out var leaveSessionId))
{
var command = new LeaveSessionCommand(
SessionId: leaveSessionId,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await leaveSessionHandler.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 == "promote_waitlist" && parts.Length >= 2 && Guid.TryParse(parts[1], out var promoteSessionId))
{
var command = new PromoteWaitlistedPlayerCommand(
SessionId: promoteSessionId,
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
await promoteWaitlistedPlayerHandler.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 >= 2 && Guid.TryParse(parts[1], out var optionId))
{
var command = new HandleRescheduleVoteCommand(
OptionId: optionId,
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" => RsvpStatus.Confirmed,
"decline" => 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 SendStartMessageAsync(message, 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
Мест: 4
Ссылка: https://link
Для регулярного расписания можно указать одну дату:
Игр: 4
Интервал: 7
/listsessions список предстоящих сессий
Игроки могут записаться кнопкой «На дату» и сняться кнопкой «Выйти».
Owner и co-GM могут переносить сессии кнопкой «Перенести»: бот попросит 2-3 варианта времени и дедлайн голосования.
/help эта справка
""",
cancellationToken: ct);
break;
// TODO: /listsessions — will be implemented as features
default:
logger.LogDebug("Unknown command: {Command}", command);
break;
}
}
private async Task SendStartMessageAsync(Message message, CancellationToken ct)
{
var miniAppUrl = configuration["Telegram:MiniAppUrl"];
if (string.IsNullOrWhiteSpace(miniAppUrl))
{
await bot.SendMessage(
chatId: message.Chat.Id,
text: "GM-Relay Bot ready. Use /help for commands.",
cancellationToken: ct);
return;
}
await bot.SendMessage(
chatId: message.Chat.Id,
text: "GM-Relay Bot ready. Откройте dashboard внутри Telegram или используйте /help для команд.",
replyMarkup: new InlineKeyboardMarkup(
InlineKeyboardButton.WithWebApp("Открыть dashboard", new WebAppInfo(miniAppUrl))),
cancellationToken: ct);
}
}