Skip to content

Commit

Permalink
V3: Refactor auto remove, add new message notifications, add reaction…
Browse files Browse the repository at this point in the history
… notifications
  • Loading branch information
patrickklaeren committed Nov 13, 2024
1 parent 20ad348 commit 6f0a3ea
Show file tree
Hide file tree
Showing 34 changed files with 457 additions and 641 deletions.
147 changes: 54 additions & 93 deletions src/Modix.Bot/Behaviors/CommandListeningBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,121 +1,82 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Discord;
using Discord.Commands;

using Modix.Common.Messaging;
using Modix.Services.CommandHelp;
using MediatR;
using Modix.Bot.Notifications;
using Modix.Bot.Responders.CommandErrors;
using Modix.Services.Core;
using Modix.Services.Utilities;

using Serilog;

using Stopwatch = System.Diagnostics.Stopwatch;

namespace Modix.Bot.Behaviors
namespace Modix.Bot.Behaviors;

public class CommandListeningBehavior(
ICommandPrefixParser commandPrefixParser,
IServiceProvider serviceProvider,
CommandService commandService,
CommandErrorService commandErrorService,
IDiscordClient discordClient,
IAuthorizationService authorizationService) : INotificationHandler<MessageReceivedNotificationV3>
{
/// <summary>
/// Listens for user commands within messages received from Discord, and executes them, if found.
/// </summary>
public class CommandListeningBehavior : INotificationHandler<MessageReceivedNotification>
public async Task Handle(MessageReceivedNotificationV3 notification, CancellationToken cancellationToken = default)
{
/// <summary>
/// Constructs a new <see cref="CommandListeningBehavior"/>, with the given dependencies.
/// </summary>
public CommandListeningBehavior(
ICommandPrefixParser commandPrefixParser,
IServiceProvider serviceProvider,
CommandService commandService,
CommandErrorHandler commandErrorHandler,
IDiscordClient discordClient,
IAuthorizationService authorizationService)
{
_commandPrefixParser = commandPrefixParser;
ServiceProvider = serviceProvider;
CommandService = commandService;
CommandErrorHandler = commandErrorHandler;
DiscordClient = discordClient;
AuthorizationService = authorizationService;
}
var stopwatch = new Stopwatch();
stopwatch.Start();

/// <inheritdoc />
public async Task HandleNotificationAsync(MessageReceivedNotification notification, CancellationToken cancellationToken = default)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
if (!(notification.Message is IUserMessage userMessage)
|| (userMessage.Author is null))
return;

if (!(notification.Message is IUserMessage userMessage)
|| (userMessage.Author is null))
return;
if (!(userMessage.Author is IGuildUser author)
|| (author.Guild is null)
|| author.IsBot
|| author.IsWebhook)
return;

if (!(userMessage.Author is IGuildUser author)
|| (author.Guild is null)
|| author.IsBot
|| author.IsWebhook)
return;
if (userMessage.Content.Length <= 1)
return;

if (userMessage.Content.Length <= 1)
return;
var argPos = await commandPrefixParser.TryFindCommandArgPosAsync(userMessage, cancellationToken);

var argPos = await _commandPrefixParser.TryFindCommandArgPosAsync(userMessage, cancellationToken);
if (argPos is null)
return;
if (argPos is null)
return;

var commandContext = new CommandContext(DiscordClient, userMessage);
var commandContext = new CommandContext(discordClient, userMessage);

await AuthorizationService.OnAuthenticatedAsync(author.Id, author.Guild.Id, author.RoleIds.ToList());
await authorizationService.OnAuthenticatedAsync(author.Id, author.Guild.Id, author.RoleIds.ToList());

var commandResult = await CommandService.ExecuteAsync(commandContext, argPos.Value, ServiceProvider);
var commandResult = await commandService.ExecuteAsync(commandContext, argPos.Value, serviceProvider);

if(!commandResult.IsSuccess)
{
var error = $"{commandResult.Error}: {commandResult.ErrorReason}";

if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase))
Log.Error(error);
else
Log.Warning(error);
if(!commandResult.IsSuccess)
{
var error = $"{commandResult.Error}: {commandResult.ErrorReason}";

if (commandResult.Error == CommandError.Exception)
await commandContext.Channel.SendMessageAsync($"Error: {commandResult.ErrorReason}", allowedMentions: AllowedMentions.None);
else
await CommandErrorHandler.AssociateErrorAsync(userMessage, error);
if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase))
{
Log.Error(error);
}
else
{
Log.Warning(error);
}

stopwatch.Stop();
Log.Information($"Command took {stopwatch.ElapsedMilliseconds}ms to process: {commandContext.Message}");
if (commandResult.Error == CommandError.Exception)
{
await commandContext.Channel.SendMessageAsync($"Error: {commandResult.ErrorReason}", allowedMentions: AllowedMentions.None);
}
else
{
await commandErrorService.SignalError(userMessage, error);
}
}

/// <summary>
/// The <see cref="IServiceProvider"/> for the current service scope.
/// </summary>
internal protected IServiceProvider ServiceProvider { get; }

/// <summary>
/// A <see cref="Discord.Commands.CommandService"/> used to parse and execute commands.
/// </summary>
internal protected CommandService CommandService { get; }

/// <summary>
/// A <see cref="Services.CommandHelp.CommandErrorHandler"/> used to report and track command errors, in the Discord UI.
/// </summary>
internal protected CommandErrorHandler CommandErrorHandler { get; }

/// <summary>
/// An <see cref="IDiscordClient"/> used to interact with the Discord API.
/// </summary>
internal protected IDiscordClient DiscordClient { get; }

/// <summary>
/// An <see cref="IAuthorizationService"/> used to interact with the application authorization system.
/// </summary>
internal protected IAuthorizationService AuthorizationService { get; }
stopwatch.Stop();

private readonly ICommandPrefixParser _commandPrefixParser;
}
Log.Information("Command took {StopwatchElapsedMilliseconds}ms to process: {CommandContextMessage}",
stopwatch.ElapsedMilliseconds,
commandContext.Message);
}
}
27 changes: 27 additions & 0 deletions src/Modix.Bot/ModixBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
discordSocketClient.Ready += OnClientReady;
discordSocketClient.MessageReceived += OnMessageReceived;
discordSocketClient.MessageUpdated += OnMessageUpdated;
discordSocketClient.MessageDeleted += OnMessageDeleted;
discordSocketClient.ReactionAdded += OnReactionAdded;
discordSocketClient.ReactionRemoved += OnReactionRemoved;

discordRestClient.Log += discordSerilogAdapter.HandleLog;
commandService.Log += discordSerilogAdapter.HandleLog;
Expand Down Expand Up @@ -195,6 +198,9 @@ private void UnregisterClientHandlers()

discordSocketClient.MessageReceived -= OnMessageReceived;
discordSocketClient.MessageUpdated -= OnMessageUpdated;
discordSocketClient.MessageDeleted -= OnMessageDeleted;
discordSocketClient.ReactionAdded -= OnReactionAdded;
discordSocketClient.ReactionRemoved -= OnReactionRemoved;
}

private async Task OnClientReady()
Expand All @@ -217,6 +223,27 @@ private async Task OnMessageUpdated(Cacheable<IMessage, ulong> cachedMessage, So
await mediator.Publish(new MessageUpdatedNotificationV3(cachedMessage, newMessage, channel));
}

private async Task OnMessageDeleted(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel)
{
using var scope = serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new MessageDeletedNotificationV3(message, channel));
}

private async Task OnReactionAdded(Cacheable<IUserMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction)
{
using var scope = serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new ReactionAddedNotificationV3(message, channel, reaction));
}

private async Task OnReactionRemoved(Cacheable<IUserMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction)
{
using var scope = serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new ReactionRemovedNotificationV3(message, channel, reaction));
}

public override void Dispose()
{
try
Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/DocumentationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Discord;
using Discord.Interactions;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Services.AutoRemoveMessage;
using Modix.Services.CommandHelp;
using Modix.Services.Csharp;
Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/IlModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Discord;
using Discord.Commands;
using Microsoft.Extensions.Options;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Data.Models.Core;
using Modix.Services;
using Modix.Services.AutoRemoveMessage;
Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/LegacyLinkModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Discord;
using Discord.Commands;
using LZStringCSharp;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Services.AutoRemoveMessage;
using Modix.Services.Utilities;

Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/ReplModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Discord;
using Discord.Commands;
using Microsoft.Extensions.Options;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Data.Models.Core;
using Modix.Services;
using Modix.Services.AutoRemoveMessage;
Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/SharpLabModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Discord;
using Discord.Interactions;
using LZStringCSharp;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Services.AutoRemoveMessage;
using Modix.Services.CommandHelp;
using Modix.Services.Utilities;
Expand Down
1 change: 1 addition & 0 deletions src/Modix.Bot/Modules/UserInfoModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Modix.Bot.Extensions;
using Modix.Bot.Responders.AutoRemoveMessages;
using Modix.Common.Extensions;
using Modix.Data.Models;
using Modix.Data.Models.Core;
Expand Down
11 changes: 11 additions & 0 deletions src/Modix.Bot/Notifications/MessageDeletedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Discord;
using MediatR;

namespace Modix.Bot.Notifications;

public class MessageDeletedNotificationV3(Cacheable<IMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel)
: INotification
{
public Cacheable<IMessage, ulong> Message { get; } = message;
public Cacheable<IMessageChannel, ulong> Channel { get; } = channel;
}
9 changes: 6 additions & 3 deletions src/Modix.Bot/Notifications/MessageUpdatedNotificationV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

namespace Modix.Bot.Notifications;

public class MessageUpdatedNotificationV3(Cacheable<IMessage, ulong> cachedMessage, SocketMessage newMessage, ISocketMessageChannel channel) : INotification
public class MessageUpdatedNotificationV3(
Cacheable<IMessage, ulong> oldMessage,
SocketMessage newMessage,
ISocketMessageChannel channel) : INotification
{
public Cacheable<IMessage, ulong> Cached { get; } = cachedMessage;
public SocketMessage Message { get; } = newMessage;
public Cacheable<IMessage, ulong> OldMessage { get; } = oldMessage;
public SocketMessage NewMessage { get; } = newMessage;
public ISocketMessageChannel Channel { get; } = channel;
}
15 changes: 15 additions & 0 deletions src/Modix.Bot/Notifications/ReactionAddedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Discord;
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class ReactionAddedNotificationV3(
Cacheable<IUserMessage, ulong> message,
Cacheable<IMessageChannel, ulong> channel,
SocketReaction reaction) : INotification
{
public Cacheable<IUserMessage, ulong> Message { get; } = message;
public Cacheable<IMessageChannel, ulong> Channel { get; } = channel;
public SocketReaction Reaction { get; } = reaction;
}
15 changes: 15 additions & 0 deletions src/Modix.Bot/Notifications/ReactionRemovedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Discord;
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class ReactionRemovedNotificationV3(
Cacheable<IUserMessage, ulong> message,
Cacheable<IMessageChannel, ulong> channel,
SocketReaction reaction): INotification
{
public Cacheable<IUserMessage, ulong> Message { get; } = message;
public Cacheable<IMessageChannel, ulong> Channel { get; } = channel;
public SocketReaction Reaction { get; } = reaction;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Modix.Bot.Notifications;

namespace Modix.Bot.Responders.AutoRemoveMessages;

public class AutoRemoveMessageResponder(AutoRemoveMessageService service) :
INotificationHandler<ReactionAddedNotificationV3>
{
public async Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested
|| notification.Reaction.Emote.Name != "❌"
|| !service.IsKnownRemovableMessage(notification.Message.Id, out var cachedMessage)
|| !cachedMessage.Users.Any(user => user.Id == notification.Reaction.UserId))
{
return;
}

await cachedMessage.Message.DeleteAsync();

service.UnregisterRemovableMessage(cachedMessage.Message);
}
}
Loading

0 comments on commit 6f0a3ea

Please sign in to comment.