Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions DiplomaticMailBot.Repositories/RegisteredChatRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,12 @@ public async Task<Either<RegisteredChatCreateOrUpdateResultSm, Error>> CreateOrU
};
}

public async Task<Either<bool, Error>> DeleteAsync(long chatId, string chatAlias, CancellationToken cancellationToken = default)
public async Task<Either<bool, Error>> DeleteAsync(long chatId, CancellationToken cancellationToken = default)
{
return await DeleteAsync(chatId, string.Empty, checkAlias: false, cancellationToken);
}

public async Task<Either<bool, Error>> DeleteAsync(long chatId, string chatAlias, bool checkAlias = true, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Deleting registered chat {ChatId}", chatId);

Expand All @@ -179,7 +184,7 @@ public async Task<Either<bool, Error>> DeleteAsync(long chatId, string chatAlias
return new DomainError(EventCode.RegisteredChatNotFound.ToInt(), "Registered chat not found");
}

if (!registeredChat.ChatAlias.EqualsIgnoreCase(chatAlias))
if (checkAlias && !registeredChat.ChatAlias.EqualsIgnoreCase(chatAlias))
{
_logger.LogInformation("Registered chat {ChatId} alias mismatch; expected: {ExpectedAlias}, actual: {ActualAlias}. Won't delete",
chatId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@
using DiplomaticMailBot.ServiceModels.RegisteredChat;
using DiplomaticMailBot.TelegramInterop.Extensions;
using DiplomaticMailBot.TelegramInterop.Services;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
using Telegram.Bot.Types;

namespace DiplomaticMailBot.Services.CommandHandlers;

public sealed partial class RegisterChatHandler
{
private readonly ILogger<RegisterChatHandler> _logger;
private readonly ITelegramBotClient _telegramBotClient;
private readonly TelegramInfoService _telegramInfoService;
private readonly RegisteredChatRepository _registeredChatRepository;
private readonly PreviewGenerator _previewGenerator;

public RegisterChatHandler(
ILogger<RegisterChatHandler> logger,
ITelegramBotClient telegramBotClient,
TelegramInfoService telegramInfoService,
RegisteredChatRepository registeredChatRepository,
PreviewGenerator previewGenerator)
{
_logger = logger;
_telegramBotClient = telegramBotClient;
_telegramInfoService = telegramInfoService;
_registeredChatRepository = registeredChatRepository;
Expand Down Expand Up @@ -111,6 +115,19 @@ await createOrUpdateResult.MatchAsync(
}
}

public async Task HandleDeregisterExitedChatAsync(User bot, Chat chat, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(chat);

_logger.LogDebug("Deregistering chat {ChatId} ({ChatType}, {ChatTitle}) because the bot was kicked or left or restricted",
chat.Id,
chat.Type,
chat.Title);

await _registeredChatRepository.DeleteAsync(chat.Id, cancellationToken);
}

public async Task HandleDeregisterChatAsync(User bot, Message userCommand, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(bot);
Expand All @@ -125,7 +142,7 @@ public async Task HandleDeregisterChatAsync(User bot, Message userCommand, Cance
{
var deregisteredChatAlias = match.Groups["alias"].Value.ToLowerInvariant();

var deleteResult = await _registeredChatRepository.DeleteAsync(userCommand.Chat.Id, deregisteredChatAlias, cancellationToken);
var deleteResult = await _registeredChatRepository.DeleteAsync(userCommand.Chat.Id, deregisteredChatAlias, cancellationToken: cancellationToken);

await deleteResult.MatchAsync(
async err =>
Expand Down
125 changes: 83 additions & 42 deletions DiplomaticMailBot.Services/TelegramBotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed partial class TelegramBotService
{
private static readonly ReceiverOptions ReceiverOptions = new()
{
AllowedUpdates = [UpdateType.Message],
AllowedUpdates = [UpdateType.Message, UpdateType.MyChatMember],
};

private readonly ILogger<TelegramBotService> _logger;
Expand Down Expand Up @@ -45,6 +45,78 @@ public TelegramBotService(
_withdrawMessageHandler = withdrawMessageHandler;
}

private async Task HandleMyChatMemberUpdateAsync(
User me,
Update update,
CancellationToken cancellationToken = default)
{
var myChatMember = update.MyChatMember;
if (myChatMember is null)
{
_logger.LogTrace("Ignoring update without my_chat_member");
return;
}

var chat = myChatMember.Chat;

_logger.LogDebug("Handling my_chat_member update for chat {ChatId} ({ChatType}, {ChatTitle})", chat.Id, chat.Type, chat.Title);

if (update.MyChatMember?.NewChatMember.Status is ChatMemberStatus.Kicked or ChatMemberStatus.Left or ChatMemberStatus.Restricted)
{
await _registerChatHandler.HandleDeregisterExitedChatAsync(me, chat, cancellationToken);
}
}

private async Task HandleMessageUpdateAsync(
User me,
Update update,
CancellationToken cancellationToken = default)
{
var message = update.Message;
if (message is null)
{
_logger.LogTrace("Ignoring update without message");
return;
}

var messageText = message.Text;
if (string.IsNullOrWhiteSpace(messageText))
{
_logger.LogTrace("Ignoring update with empty message text");
return;
}

var match = CommandRegex().Match(messageText);
if (!match.Success)
{
return;
}

var commandBotUsername = match.Groups["botname"].Value;

if (!string.IsNullOrWhiteSpace(commandBotUsername)
&& !string.IsNullOrWhiteSpace(me.Username)
&& !commandBotUsername.Equals(me.Username, StringComparison.OrdinalIgnoreCase))
{
return;
}

_logger.LogDebug("Handling command {MessageText}", messageText);

var handlerTask = messageText switch
{
_ when messageText.StartsWith(BotCommands.ListChats, StringComparison.Ordinal) => _registerChatHandler.HandleListChatsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.RegisterChat, StringComparison.Ordinal) => _registerChatHandler.HandleRegisterChatAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.DeregisterChat, StringComparison.Ordinal) => _registerChatHandler.HandleDeregisterChatAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.EstablishRelations, StringComparison.Ordinal) => _establishRelationsHandler.HandleEstablishRelationsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.BreakOffRelations, StringComparison.Ordinal) => _breakOffRelationsHandler.HandleBreakOffRelationsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.PutMessage, StringComparison.Ordinal) => _putMessageHandler.HandlePutMessageAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.WithdrawMessage, StringComparison.Ordinal) => _withdrawMessageHandler.HandleWithdrawMessageAsync(me, message, cancellationToken),
_ => Task.CompletedTask,
};
await handlerTask;
}

private async Task HandleUpdateAsync(
ITelegramBotClient botClient,
Update update,
Expand All @@ -59,49 +131,18 @@ private async Task HandleUpdateAsync(
return;
}

var message = update.Message;
if (message is null)
{
_logger.LogTrace("Ignoring update without message");
return;
}

var messageText = message.Text;
if (string.IsNullOrWhiteSpace(messageText))
{
_logger.LogTrace("Ignoring update with empty message text");
return;
}

var match = CommandRegex().Match(messageText);
if (!match.Success)
{
return;
}

var commandBotUsername = match.Groups["botname"].Value;

if (!string.IsNullOrWhiteSpace(commandBotUsername)
&& !string.IsNullOrWhiteSpace(me.Username)
&& !commandBotUsername.Equals(me.Username, StringComparison.OrdinalIgnoreCase))
switch (update.Type)
{
return;
case UpdateType.MyChatMember:
await HandleMyChatMemberUpdateAsync(me, update, cancellationToken);
break;
case UpdateType.Message:
await HandleMessageUpdateAsync(me, update, cancellationToken);
break;
default:
_logger.LogDebug("Ignoring update with unknown type: {UpdateType}", update.Type);
break;
}

_logger.LogDebug("Handling command {MessageText}", messageText);

var handlerTask = messageText switch
{
_ when messageText.StartsWith(BotCommands.ListChats, StringComparison.Ordinal) => _registerChatHandler.HandleListChatsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.RegisterChat, StringComparison.Ordinal) => _registerChatHandler.HandleRegisterChatAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.DeregisterChat, StringComparison.Ordinal) => _registerChatHandler.HandleDeregisterChatAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.EstablishRelations, StringComparison.Ordinal) => _establishRelationsHandler.HandleEstablishRelationsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.BreakOffRelations, StringComparison.Ordinal) => _breakOffRelationsHandler.HandleBreakOffRelationsAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.PutMessage, StringComparison.Ordinal) => _putMessageHandler.HandlePutMessageAsync(me, message, cancellationToken),
_ when messageText.StartsWith(BotCommands.WithdrawMessage, StringComparison.Ordinal) => _withdrawMessageHandler.HandleWithdrawMessageAsync(me, message, cancellationToken),
_ => Task.CompletedTask,
};
await handlerTask;
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public async Task DeleteAsync_WhenValidInput_DeletesChat(CancellationToken cance
timeProvider);

// Act
var result = await repository.DeleteAsync(chat.ChatId, chat.ChatAlias, cancellationToken);
var result = await repository.DeleteAsync(chat.ChatId, chat.ChatAlias, cancellationToken: cancellationToken);

// Assert
Assert.That(result.IsRight, Is.False);
Expand Down Expand Up @@ -278,7 +278,7 @@ public async Task DeleteAsync_WhenChatNotFound_ReturnsError(CancellationToken ca
timeProvider);

// Act
var result = await repository.DeleteAsync(123, "test", cancellationToken);
var result = await repository.DeleteAsync(123, "test", cancellationToken: cancellationToken);

// Assert
Assert.That(result.IsRight, Is.True);
Expand Down Expand Up @@ -313,7 +313,7 @@ public async Task DeleteAsync_WhenAliasMismatch_ReturnsError(CancellationToken c
timeProvider);

// Act
var result = await repository.DeleteAsync(chat.ChatId, "wrong_alias", cancellationToken);
var result = await repository.DeleteAsync(chat.ChatId, "wrong_alias", cancellationToken: cancellationToken);

// Assert
Assert.That(result.IsRight, Is.True);
Expand Down
12 changes: 12 additions & 0 deletions DiplomaticMailBot.Tests.Unit/DomainServices/SlotDateUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ public void GetNextAvailableSlotDate_WhenCurrentTimeAfterVoteStart2_ReturnsTomor
Assert.That(result, Is.EqualTo(DateOnly.FromDateTime(currentTime.AddDays(1))));
}

[Test]
public void IsVoteGoingOn_WhenNotUtcKind_ThrowsException()
{
// Arrange
var currentTime = new DateTime(2025, 2, 23, 11, 30, 0, DateTimeKind.Local);
var voteStartsAt = new TimeOnly(11, 0); // 11:00
var voteEndsAt = new TimeOnly(12, 0); // 12:00

// Act & Assert
Assert.Throws<ArgumentException>(() => SlotDateUtils.IsVoteGoingOn(currentTime, voteStartsAt, voteEndsAt));
}

[Test]
public void IsVoteGoingOn_WhenCurrentTimeWithinVotingPeriod_ReturnsTrue()
{
Expand Down