using Dapper; using Npgsql; using Telegram.Bot; namespace GmRelay.Bot.Features.Sessions.RescheduleSession; // ── Command ────────────────────────────────────────────────────────── public sealed record InitiateRescheduleCommand( Guid SessionId, long TelegramUserId, string CallbackQueryId, long ChatId, int MessageId); // ── DTOs ───────────────────────────────────────────────────────────── internal sealed record RescheduleSessionInfoDto(string Title, long GmId); // ── Handler ────────────────────────────────────────────────────────── /// /// Handles the "⏰ Перенести" button press from the batch message. /// Creates a reschedule proposal in AwaitingTime status and prompts /// the GM to enter the new time via a regular text message. /// public sealed class InitiateRescheduleHandler( NpgsqlDataSource dataSource, ITelegramBotClient bot, ILogger logger) { public async Task HandleAsync(InitiateRescheduleCommand command, CancellationToken ct) { await using var connection = await dataSource.OpenConnectionAsync(ct); // 1. Verify GM ownership var session = await connection.QuerySingleOrDefaultAsync( """ SELECT s.title AS Title, g.gm_telegram_id AS GmId FROM sessions s JOIN game_groups g ON s.group_id = g.id WHERE s.id = @SessionId AND s.status != 'Cancelled' """, new { command.SessionId }); if (session is null) { await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия не найдена.", cancellationToken: ct); return; } if (session.GmId != command.TelegramUserId) { await bot.AnswerCallbackQuery(command.CallbackQueryId, "Только Мастер Игры (GM) может переносить сессию.", showAlert: true, cancellationToken: ct); return; } // 2. Check no active proposal exists var hasActive = await connection.ExecuteScalarAsync( """ SELECT EXISTS ( SELECT 1 FROM reschedule_proposals WHERE session_id = @SessionId AND status IN ('AwaitingTime', 'Voting') ) """, new { command.SessionId }); if (hasActive) { await bot.AnswerCallbackQuery(command.CallbackQueryId, "Уже есть активный запрос на перенос этой сессии.", showAlert: true, cancellationToken: ct); return; } // 3. Create proposal in AwaitingTime status await connection.ExecuteAsync( """ INSERT INTO reschedule_proposals (session_id, proposed_by, status) VALUES (@SessionId, @GmId, 'AwaitingTime') """, new { command.SessionId, GmId = command.TelegramUserId }); logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId); // 4. Prompt GM in chat await bot.AnswerCallbackQuery(command.CallbackQueryId, "Введите новое время в чат (формат: ДД.ММ.ГГГГ ЧЧ:ММ)", cancellationToken: ct); await bot.SendMessage( chatId: command.ChatId, text: $"⏰ Укажите новое время для сессии «{session.Title}» в формате:\nДД.ММ.ГГГГ ЧЧ:ММ\n\nНапример: 25.04.2026 19:30", parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, cancellationToken: ct); } }