feat: send personal player notifications
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Dapper;
|
||||
using GmRelay.Bot.Features.Notifications;
|
||||
using GmRelay.Shared.Domain;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
@@ -12,7 +13,8 @@ internal sealed record JoinLinkSession(
|
||||
string Title,
|
||||
string JoinLink,
|
||||
DateTime ScheduledAt,
|
||||
long TelegramChatId);
|
||||
long TelegramChatId,
|
||||
string NotificationMode);
|
||||
|
||||
internal sealed record ConfirmedPlayer(
|
||||
long TelegramId,
|
||||
@@ -28,6 +30,7 @@ internal sealed record ConfirmedPlayer(
|
||||
public sealed class SendJoinLinkHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
DirectSessionNotificationSender directSender,
|
||||
ILogger<SendJoinLinkHandler> logger)
|
||||
{
|
||||
public async Task HandleAsync(Guid sessionId, CancellationToken ct)
|
||||
@@ -38,7 +41,8 @@ public sealed class SendJoinLinkHandler(
|
||||
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
|
||||
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
|
||||
@@ -102,6 +106,24 @@ public sealed class SendJoinLinkHandler(
|
||||
""",
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Dapper;
|
||||
using GmRelay.Bot.Features.Notifications;
|
||||
using GmRelay.Shared.Domain;
|
||||
using Npgsql;
|
||||
|
||||
namespace GmRelay.Bot.Features.Reminders.SendOneHourReminder;
|
||||
|
||||
internal sealed record OneHourReminderSession(
|
||||
Guid Id,
|
||||
string Title,
|
||||
string JoinLink,
|
||||
DateTime ScheduledAt,
|
||||
string NotificationMode);
|
||||
|
||||
public sealed class SendOneHourReminderHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
DirectSessionNotificationSender directSender,
|
||||
ILogger<SendOneHourReminderHandler> logger)
|
||||
{
|
||||
public async Task HandleAsync(Guid sessionId, CancellationToken ct)
|
||||
{
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var session = await connection.QuerySingleOrDefaultAsync<OneHourReminderSession>(
|
||||
"""
|
||||
SELECT id,
|
||||
title,
|
||||
join_link AS JoinLink,
|
||||
scheduled_at AS ScheduledAt,
|
||||
notification_mode AS NotificationMode
|
||||
FROM sessions
|
||||
WHERE id = @SessionId
|
||||
AND status IN (@Confirmed, @ConfirmationSent)
|
||||
AND one_hour_reminder_processed_at IS NULL
|
||||
""",
|
||||
new
|
||||
{
|
||||
SessionId = sessionId,
|
||||
Confirmed = SessionStatus.Confirmed,
|
||||
ConfirmationSent = SessionStatus.ConfirmationSent
|
||||
});
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
logger.LogWarning("Session {SessionId} not eligible for one-hour reminder", sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
var recipients = (await connection.QueryAsync<DirectNotificationRecipient>(
|
||||
"""
|
||||
SELECT p.telegram_id AS TelegramId,
|
||||
p.display_name AS DisplayName
|
||||
FROM session_participants sp
|
||||
JOIN players p ON p.id = sp.player_id
|
||||
WHERE sp.session_id = @SessionId
|
||||
AND sp.is_gm = false
|
||||
AND sp.registration_status = @Active
|
||||
AND sp.rsvp_status != @Declined
|
||||
""",
|
||||
new
|
||||
{
|
||||
SessionId = sessionId,
|
||||
Active = ParticipantRegistrationStatus.Active,
|
||||
Declined = RsvpStatus.Declined
|
||||
})).ToList();
|
||||
|
||||
var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
|
||||
if (mode.ShouldSendDirectMessages() && recipients.Count > 0)
|
||||
{
|
||||
var text = $"""
|
||||
⏰ <b>Игра начнётся примерно через 1 час</b>
|
||||
|
||||
📌 <b>{System.Net.WebUtility.HtmlEncode(session.Title)}</b>
|
||||
📅 {session.ScheduledAt.FormatMoscow()} (МСК)
|
||||
🔗 {System.Net.WebUtility.HtmlEncode(session.JoinLink)}
|
||||
""";
|
||||
|
||||
await directSender.SendAsync(recipients, text, "one-hour-reminder", session.Id, ct);
|
||||
}
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
"""
|
||||
UPDATE sessions
|
||||
SET one_hour_reminder_processed_at = now(),
|
||||
updated_at = now()
|
||||
WHERE id = @SessionId
|
||||
AND one_hour_reminder_processed_at IS NULL
|
||||
""",
|
||||
new { SessionId = sessionId });
|
||||
|
||||
logger.LogInformation(
|
||||
"One-hour reminder processed for session {SessionId} ({Title}) with mode {NotificationMode}",
|
||||
sessionId,
|
||||
session.Title,
|
||||
session.NotificationMode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user