132 lines
4.7 KiB
C#
132 lines
4.7 KiB
C#
using Dapper;
|
|
using GmRelay.Bot.Features.Notifications;
|
|
using GmRelay.Shared.Domain;
|
|
using Npgsql;
|
|
using Telegram.Bot;
|
|
|
|
namespace GmRelay.Bot.Features.Reminders.SendJoinLink;
|
|
|
|
// ── DTOs ─────────────────────────────────────────────────────────────
|
|
|
|
internal sealed record JoinLinkSession(
|
|
Guid Id,
|
|
string Title,
|
|
string JoinLink,
|
|
DateTime ScheduledAt,
|
|
long TelegramChatId,
|
|
string NotificationMode);
|
|
|
|
internal sealed record ConfirmedPlayer(
|
|
long TelegramId,
|
|
string DisplayName,
|
|
string? TelegramUsername);
|
|
|
|
// ── Handler ──────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Sends the join link to the group chat at T-5min, tagging all confirmed players.
|
|
/// Called by SessionSchedulerService.
|
|
/// </summary>
|
|
public sealed class SendJoinLinkHandler(
|
|
NpgsqlDataSource dataSource,
|
|
ITelegramBotClient bot,
|
|
DirectSessionNotificationSender directSender,
|
|
ILogger<SendJoinLinkHandler> logger)
|
|
{
|
|
public async Task HandleAsync(Guid sessionId, CancellationToken ct)
|
|
{
|
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
|
|
|
// 1. Load session
|
|
var session = await connection.QuerySingleOrDefaultAsync<JoinLinkSession>(
|
|
"""
|
|
SELECT s.id, s.title, s.join_link AS JoinLink, s.scheduled_at AS ScheduledAt,
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
s.notification_mode AS NotificationMode
|
|
FROM sessions s
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
WHERE s.id = @SessionId
|
|
AND s.status = @Confirmed
|
|
AND s.link_message_id IS NULL
|
|
""",
|
|
new { SessionId = sessionId, Confirmed = SessionStatus.Confirmed });
|
|
|
|
if (session is null)
|
|
{
|
|
logger.LogWarning("Session {SessionId} not eligible for join link", sessionId);
|
|
return;
|
|
}
|
|
|
|
// 2. Load confirmed players
|
|
var players = (await connection.QueryAsync<ConfirmedPlayer>(
|
|
"""
|
|
SELECT p.telegram_id AS TelegramId,
|
|
p.display_name AS DisplayName,
|
|
p.telegram_username AS TelegramUsername
|
|
FROM session_participants sp
|
|
JOIN players p ON p.id = sp.player_id
|
|
WHERE sp.session_id = @SessionId
|
|
AND sp.rsvp_status = @Confirmed
|
|
AND sp.registration_status = @Active
|
|
""",
|
|
new
|
|
{
|
|
SessionId = sessionId,
|
|
Confirmed = RsvpStatus.Confirmed,
|
|
Active = ParticipantRegistrationStatus.Active
|
|
})).ToList();
|
|
|
|
// 3. Build message with player mentions
|
|
var mentions = string.Join(", ", players.Select(p =>
|
|
p.TelegramUsername is not null ? $"@{p.TelegramUsername}" : p.DisplayName));
|
|
|
|
var text = $"""
|
|
🎮 Игра «{session.Title}» начинается через 5 минут!
|
|
|
|
🔗 Ссылка на подключение:
|
|
{session.JoinLink}
|
|
|
|
Участники: {mentions}
|
|
|
|
Хорошей игры! 🎲
|
|
""";
|
|
|
|
// 4. Send
|
|
var message = await bot.SendMessage(
|
|
chatId: session.TelegramChatId,
|
|
text: text,
|
|
cancellationToken: ct);
|
|
|
|
// 5. Mark as sent (idempotent — link_message_id IS NULL guard in query)
|
|
await connection.ExecuteAsync(
|
|
"""
|
|
UPDATE sessions
|
|
SET link_message_id = @MessageId, updated_at = now()
|
|
WHERE id = @SessionId AND link_message_id IS NULL
|
|
""",
|
|
new { SessionId = sessionId, MessageId = message.MessageId });
|
|
|
|
var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
|
|
if (mode.ShouldSendDirectMessages())
|
|
{
|
|
var directText = $"""
|
|
🎮 <b>Игра начинается через 5 минут</b>
|
|
|
|
📌 <b>{System.Net.WebUtility.HtmlEncode(session.Title)}</b>
|
|
🔗 {System.Net.WebUtility.HtmlEncode(session.JoinLink)}
|
|
""";
|
|
|
|
await directSender.SendAsync(
|
|
players.Select(p => new DirectNotificationRecipient(p.TelegramId, p.DisplayName)),
|
|
directText,
|
|
"join-link",
|
|
sessionId,
|
|
ct);
|
|
}
|
|
|
|
logger.LogInformation(
|
|
"Join link sent for session {SessionId} ({Title}), message_id={MessageId}",
|
|
sessionId, session.Title, message.MessageId);
|
|
}
|
|
}
|