refactor: add platform messenger contracts
PR Checks / test-and-build (pull_request) Successful in 12m35s
PR Checks / test-and-build (pull_request) Successful in 12m35s
Introduce platform-neutral PlatformKind, PlatformUser, PlatformGroup, and IPlatformMessenger contracts in GmRelay.Shared. Route Telegram session schedule updates, direct notifications, interaction replies, and calendar export through TelegramPlatformMessenger while preserving existing Telegram behavior. Bump version -> 2.0.1
This commit is contained in:
+15
-19
@@ -1,6 +1,7 @@
|
||||
using Dapper;
|
||||
using GmRelay.Bot.Features.Notifications;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Platform;
|
||||
using GmRelay.Shared.Rendering;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
@@ -33,6 +34,7 @@ internal sealed record VoteParticipantDto(
|
||||
public sealed class HandleRescheduleTimeInputHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
IPlatformMessenger messenger,
|
||||
DirectSessionNotificationSender directSender,
|
||||
ILogger<HandleRescheduleTimeInputHandler> logger)
|
||||
{
|
||||
@@ -83,12 +85,10 @@ public sealed class HandleRescheduleTimeInputHandler(
|
||||
// 2. Parse voting input
|
||||
if (!RescheduleVotingInput.TryParse(text, DateTimeOffset.UtcNow, out var votingInput, out var parseError))
|
||||
{
|
||||
await bot.SendMessage(
|
||||
chatId: chatId,
|
||||
messageThreadId: proposal.ThreadId,
|
||||
text: $"⚠️ {parseError}\n\nИспользуйте формат:\n<code>25.04.2026 19:30\n26.04.2026 18:00\nДедлайн: 25.04.2026 12:00</code>",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
await messenger.SendGroupMessageAsync(
|
||||
TelegramPlatformIds.Group(chatId, proposal.ThreadId),
|
||||
$"⚠️ {parseError}\n\nИспользуйте формат:\n<code>25.04.2026 19:30\n26.04.2026 18:00\nДедлайн: 25.04.2026 12:00</code>",
|
||||
ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -243,12 +243,10 @@ public sealed class HandleRescheduleTimeInputHandler(
|
||||
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
await bot.SendMessage(
|
||||
chatId: chatId,
|
||||
messageThreadId: proposal.ThreadId,
|
||||
text: $"✅ Сессия «{proposal.Title}» перенесена!\n\n📅 Новое время: <b>{newTime.ToOffset(TimeSpan.FromHours(3)).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"))}</b> (МСК)\n\n<i>Участников нет — голосование не требуется.</i>",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
await messenger.SendGroupMessageAsync(
|
||||
TelegramPlatformIds.Group(chatId, proposal.ThreadId),
|
||||
$"✅ Сессия «{proposal.Title}» перенесена!\n\n📅 Новое время: <b>{newTime.ToOffset(TimeSpan.FromHours(3)).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"))}</b> (МСК)\n\n<i>Участников нет — голосование не требуется.</i>",
|
||||
ct);
|
||||
|
||||
// Re-render batch message with updated time
|
||||
await TryUpdateBatchMessage(proposal, ct);
|
||||
@@ -383,14 +381,12 @@ public sealed class HandleRescheduleTimeInputHandler(
|
||||
if (proposal.BatchMessageId.HasValue)
|
||||
{
|
||||
var view = SessionBatchViewBuilder.Build(proposal.Title, batchSessions, batchParticipants);
|
||||
var renderResult = TelegramSessionBatchRenderer.Render(view);
|
||||
|
||||
await BatchMessageEditor.EditBatchMessageAsync(
|
||||
bot,
|
||||
chatId: proposal.TelegramChatId,
|
||||
messageId: proposal.BatchMessageId.Value,
|
||||
text: renderResult.Text,
|
||||
replyMarkup: renderResult.Markup,
|
||||
await messenger.UpdateScheduleAsync(
|
||||
new PlatformScheduleMessage(
|
||||
TelegramPlatformIds.Group(proposal.TelegramChatId, proposal.ThreadId),
|
||||
view,
|
||||
TelegramPlatformIds.Message(proposal.TelegramChatId, proposal.ThreadId, proposal.BatchMessageId.Value)),
|
||||
ct);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Dapper;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Platform;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
|
||||
@@ -22,6 +23,7 @@ internal sealed record VoteProposalDto(
|
||||
public sealed class HandleRescheduleVoteHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
IPlatformMessenger messenger,
|
||||
ILogger<HandleRescheduleVoteHandler> logger)
|
||||
{
|
||||
public async Task HandleAsync(HandleRescheduleVoteCommand command, CancellationToken ct)
|
||||
@@ -46,20 +48,13 @@ public sealed class HandleRescheduleVoteHandler(
|
||||
|
||||
if (proposal is null)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(
|
||||
command.CallbackQueryId,
|
||||
"Голосование уже завершено или не найдено.",
|
||||
cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Голосование уже завершено или не найдено.", ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (proposal.VotingDeadlineAt <= DateTimeOffset.UtcNow)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(
|
||||
command.CallbackQueryId,
|
||||
"Дедлайн уже прошёл. Результаты скоро будут применены.",
|
||||
showAlert: true,
|
||||
cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Дедлайн уже прошёл. Результаты скоро будут применены.", ct, showAlert: true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,10 +73,7 @@ public sealed class HandleRescheduleVoteHandler(
|
||||
|
||||
if (playerId is null)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(
|
||||
command.CallbackQueryId,
|
||||
"Вы не являетесь участником этой сессии.",
|
||||
cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Вы не являетесь участником этой сессии.", ct);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,9 +161,9 @@ public sealed class HandleRescheduleVoteHandler(
|
||||
logger.LogWarning(ex, "Failed to update reschedule vote message for proposal {ProposalId}", proposal.Id);
|
||||
}
|
||||
|
||||
await bot.AnswerCallbackQuery(
|
||||
command.CallbackQueryId,
|
||||
"Ваш голос учтён. До дедлайна его можно изменить.",
|
||||
cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Ваш голос учтён. До дедлайна его можно изменить.", ct);
|
||||
}
|
||||
|
||||
private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) =>
|
||||
messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Dapper;
|
||||
using GmRelay.Bot.Infrastructure.Telegram;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Platform;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
|
||||
namespace GmRelay.Bot.Features.Sessions.RescheduleSession;
|
||||
|
||||
@@ -28,7 +29,7 @@ internal sealed record RescheduleSessionInfoDto(string Title, bool CanManage);
|
||||
/// </summary>
|
||||
public sealed class InitiateRescheduleHandler(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
IPlatformMessenger messenger,
|
||||
ILogger<InitiateRescheduleHandler> logger)
|
||||
{
|
||||
public async Task HandleAsync(InitiateRescheduleCommand command, CancellationToken ct)
|
||||
@@ -53,14 +54,13 @@ public sealed class InitiateRescheduleHandler(
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия не найдена.", cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Сессия не найдена.", ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.CanManage)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(command.CallbackQueryId,
|
||||
"Только owner или co-GM может переносить сессию.", showAlert: true, cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Только owner или co-GM может переносить сессию.", ct, showAlert: true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ public sealed class InitiateRescheduleHandler(
|
||||
|
||||
if (hasActive)
|
||||
{
|
||||
await bot.AnswerCallbackQuery(command.CallbackQueryId,
|
||||
"Уже есть активный запрос на перенос этой сессии.", showAlert: true, cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Уже есть активный запрос на перенос этой сессии.", ct, showAlert: true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,23 +91,23 @@ public sealed class InitiateRescheduleHandler(
|
||||
logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId);
|
||||
|
||||
// 4. Prompt GM in chat
|
||||
await bot.AnswerCallbackQuery(command.CallbackQueryId,
|
||||
"Введите 2-3 варианта времени и дедлайн голосования.", cancellationToken: ct);
|
||||
await AnswerAsync(command.CallbackQueryId, "Введите 2-3 варианта времени и дедлайн голосования.", ct);
|
||||
|
||||
await bot.SendMessage(
|
||||
chatId: command.ChatId,
|
||||
messageThreadId: command.MessageThreadId,
|
||||
text: $"""
|
||||
⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.
|
||||
|
||||
Формат:
|
||||
<code>25.04.2026 19:30
|
||||
26.04.2026 18:00
|
||||
Дедлайн: 25.04.2026 12:00</code>
|
||||
|
||||
Дедлайн должен быть в будущем и раньше первого предложенного времени.
|
||||
""",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
await messenger.SendGroupMessageAsync(
|
||||
TelegramPlatformIds.Group(command.ChatId, command.MessageThreadId),
|
||||
$"""
|
||||
⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.
|
||||
|
||||
Формат:
|
||||
<code>25.04.2026 19:30
|
||||
26.04.2026 18:00
|
||||
Дедлайн: 25.04.2026 12:00</code>
|
||||
|
||||
Дедлайн должен быть в будущем и раньше первого предложенного времени.
|
||||
""",
|
||||
ct);
|
||||
}
|
||||
|
||||
private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) =>
|
||||
messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct);
|
||||
}
|
||||
|
||||
+11
-13
@@ -2,6 +2,7 @@ using Dapper;
|
||||
using GmRelay.Bot.Features.Notifications;
|
||||
using GmRelay.Bot.Infrastructure.Scheduling;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Platform;
|
||||
using GmRelay.Shared.Rendering;
|
||||
using Npgsql;
|
||||
using Telegram.Bot;
|
||||
@@ -26,6 +27,7 @@ internal sealed record DueRescheduleProposalDto(
|
||||
public sealed class RescheduleVotingDeadlineService(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
IPlatformMessenger messenger,
|
||||
DirectSessionNotificationSender directSender,
|
||||
ISystemClock clock,
|
||||
ILogger<RescheduleVotingDeadlineService> logger) : BackgroundService
|
||||
@@ -312,24 +314,20 @@ public sealed class RescheduleVotingDeadlineService(
|
||||
if (proposal.BatchMessageId.HasValue)
|
||||
{
|
||||
var view = SessionBatchViewBuilder.Build(proposal.Title, batchSessions, batchParticipants);
|
||||
var renderResult = TelegramSessionBatchRenderer.Render(view);
|
||||
|
||||
await BatchMessageEditor.EditBatchMessageAsync(
|
||||
bot,
|
||||
chatId: proposal.TelegramChatId,
|
||||
messageId: proposal.BatchMessageId.Value,
|
||||
text: renderResult.Text,
|
||||
replyMarkup: renderResult.Markup,
|
||||
await messenger.UpdateScheduleAsync(
|
||||
new PlatformScheduleMessage(
|
||||
TelegramPlatformIds.Group(proposal.TelegramChatId, proposal.ThreadId),
|
||||
view,
|
||||
TelegramPlatformIds.Message(proposal.TelegramChatId, proposal.ThreadId, proposal.BatchMessageId.Value)),
|
||||
ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await bot.SendMessage(
|
||||
chatId: proposal.TelegramChatId,
|
||||
messageThreadId: proposal.ThreadId,
|
||||
text: $"📣 Расписание обновлено после голосования за перенос сессии «{System.Net.WebUtility.HtmlEncode(proposal.Title)}».",
|
||||
parseMode: ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
await messenger.SendGroupMessageAsync(
|
||||
TelegramPlatformIds.Group(proposal.TelegramChatId, proposal.ThreadId),
|
||||
$"📣 Расписание обновлено после голосования за перенос сессии «{System.Net.WebUtility.HtmlEncode(proposal.Title)}».",
|
||||
ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user