Skip to content

Commit

Permalink
Merge pull request #1039 from discord-csharp/start-moderation-refactor
Browse files Browse the repository at this point in the history
V3: Start moderation refactor, add scoped session concept
  • Loading branch information
patrickklaeren authored Nov 14, 2024
2 parents 063417e + 55a6b3e commit fcb4b8c
Show file tree
Hide file tree
Showing 34 changed files with 973 additions and 1,021 deletions.
6 changes: 3 additions & 3 deletions src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public ModerationLoggingBehavior(
DesignatedChannelService = designatedChannelService;
Config = config.Value;

_lazyModerationService = new Lazy<IModerationService>(() => serviceProvider.GetRequiredService<IModerationService>());
_lazyModerationService = new Lazy<ModerationService>(() => serviceProvider.GetRequiredService<ModerationService>());
}

/// <inheritdoc />
Expand Down Expand Up @@ -95,9 +95,9 @@ public async Task OnModerationActionCreatedAsync(long moderationActionId, Modera
/// <summary>
/// An <see cref="IModerationService"/> for performing moderation actions.
/// </summary>
internal protected IModerationService ModerationService
internal protected ModerationService ModerationService
=> _lazyModerationService.Value;
private readonly Lazy<IModerationService> _lazyModerationService;
private readonly Lazy<ModerationService> _lazyModerationService;

internal protected static ModixConfig Config { get; private set; }

Expand Down
40 changes: 40 additions & 0 deletions src/Modix.Bot/DiscordBotSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using Discord.WebSocket;
using Modix.Data.Models.Core;
using Modix.Services;

namespace Modix.Bot;

public class DiscordBotSession(DiscordSocketClient discordSocketClient,
AuthorizationClaimService authorizationClaimService) : IScopedSession
{
public ulong SelfUserId { get; } = discordSocketClient.CurrentUser.Id;

private ulong _executingUserId;

public ulong ExecutingUserId =>
_executingUserId == default
? SelfUserId
: _executingUserId;

private IReadOnlyCollection<AuthorizationClaim> _authorizationClaims;

public void ApplyCommandContext(ICommandContext context)
{
_executingUserId = context.User.Id;
}

private async Task<IReadOnlyCollection<AuthorizationClaim>> GetClaims()
{
return _authorizationClaims ??= await authorizationClaimService.GetClaimsForUser(ExecutingUserId);
}

public async Task<bool> HasClaim(params AuthorizationClaim[] claims)
{
var ownedClaims = await GetClaims();
return claims.All(claim => ownedClaims.Contains(claim));
}
}
97 changes: 63 additions & 34 deletions src/Modix.Bot/ModixBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Microsoft.Extensions.Options;
using Modix.Bot.Notifications;
using Modix.Data.Models.Core;
using Modix.Services;

namespace Modix.Bot
{
Expand Down Expand Up @@ -61,6 +62,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
discordSocketClient.MessageDeleted += OnMessageDeleted;
discordSocketClient.ReactionAdded += OnReactionAdded;
discordSocketClient.ReactionRemoved += OnReactionRemoved;
discordSocketClient.UserJoined += OnUserJoined;
discordSocketClient.AuditLogCreated += OnAuditLogCreated;
discordSocketClient.GuildAvailable += OnGuildAvailable;
discordSocketClient.ChannelCreated += OnChannelCreated;
discordSocketClient.ChannelUpdated += OnChannelUpdated;
discordSocketClient.JoinedGuild += OnJoinedGuild;

discordRestClient.Log += discordSerilogAdapter.HandleLog;
commandService.Log += discordSerilogAdapter.HandleLog;
Expand All @@ -77,7 +84,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
await commandService.AddModulesAsync(typeof(ModixBot).Assembly, _scope.ServiceProvider);

logger.LogInformation("{Modules} modules loaded, containing {Commands} commands",
commandService.Modules.Count(), commandService.Modules.SelectMany(d=>d.Commands).Count());
commandService.Modules.Count(), commandService.Modules.SelectMany(d => d.Commands).Count());

logger.LogInformation("Logging into Discord and starting the client");

Expand All @@ -87,18 +94,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

logger.LogInformation("Loading interaction modules...");

var modules = (await interactionService.AddModulesAsync(typeof(ModixBot).Assembly, _scope.ServiceProvider)).ToArray();
var modules =
(await interactionService.AddModulesAsync(typeof(ModixBot).Assembly, _scope.ServiceProvider))
.ToArray();

foreach (var guild in discordSocketClient.Guilds)
{
var commands = await interactionService.AddModulesToGuildAsync(guild, deleteMissing: true, modules);
}

logger.LogInformation("{Modules} interaction modules loaded", modules.Length);
logger.LogInformation("Loaded {SlashCommands} slash commands", modules.SelectMany(x => x.SlashCommands).Count());
logger.LogInformation("Loaded {ContextCommands} context commands", modules.SelectMany(x => x.ContextCommands).Count());
logger.LogInformation("Loaded {ModalCommands} modal commands", modules.SelectMany(x => x.ModalCommands).Count());
logger.LogInformation("Loaded {ComponentCommands} component commands", modules.SelectMany(x => x.ComponentCommands).Count());
logger.LogInformation("Loaded {SlashCommands} slash commands",
modules.SelectMany(x => x.SlashCommands).Count());
logger.LogInformation("Loaded {ContextCommands} context commands",
modules.SelectMany(x => x.ContextCommands).Count());
logger.LogInformation("Loaded {ModalCommands} modal commands",
modules.SelectMany(x => x.ModalCommands).Count());
logger.LogInformation("Loaded {ComponentCommands} component commands",
modules.SelectMany(x => x.ComponentCommands).Count());

await Task.Delay(-1, stoppingToken);
}
Expand Down Expand Up @@ -154,7 +167,7 @@ private Task OnDisconnect(Exception ex)
{
// Reconnections are handled by Discord.NET, we
// don't need to worry about handling this ourselves
if(ex is GatewayReconnectException)
if (ex is GatewayReconnectException)
{
logger.LogInformation("Received gateway reconnect");
return Task.CompletedTask;
Expand All @@ -171,7 +184,6 @@ private async Task StartClient(CancellationToken cancellationToken)

try
{

cancellationToken.ThrowIfCancellationRequested();

await discordSocketClient.LoginAsync(TokenType.Bot, modixConfig.Value.DiscordToken);
Expand All @@ -193,14 +205,18 @@ private void UnregisterClientHandlers()
discordSocketClient.LatencyUpdated -= OnLatencyUpdated;
discordSocketClient.Disconnected -= OnDisconnect;
discordSocketClient.Log -= discordSerilogAdapter.HandleLog;

discordSocketClient.Ready -= OnClientReady;

discordSocketClient.MessageReceived -= OnMessageReceived;
discordSocketClient.MessageUpdated -= OnMessageUpdated;
discordSocketClient.MessageDeleted -= OnMessageDeleted;
discordSocketClient.ReactionAdded -= OnReactionAdded;
discordSocketClient.ReactionRemoved -= OnReactionRemoved;
discordSocketClient.UserJoined -= OnUserJoined;
discordSocketClient.AuditLogCreated -= OnAuditLogCreated;
discordSocketClient.GuildAvailable -= OnGuildAvailable;
discordSocketClient.ChannelCreated -= OnChannelCreated;
discordSocketClient.ChannelUpdated -= OnChannelUpdated;
discordSocketClient.JoinedGuild -= OnJoinedGuild;
}

private async Task OnClientReady()
Expand All @@ -209,40 +225,53 @@ private async Task OnClientReady()
_whenReadySource.SetResult(null);
}

private async Task OnMessageReceived(SocketMessage arg)
private async Task PublishMessage<T>(T message) where T : INotification
{
using var scope = serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new MessageReceivedNotificationV3(arg));
await PublishMessage(scope, message);
}

private async Task OnMessageUpdated(Cacheable<IMessage, ulong> cachedMessage, SocketMessage newMessage, ISocketMessageChannel channel)
private async Task PublishMessage<T>(IServiceScope scope, T message) where T : INotification
{
using var scope = serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new MessageUpdatedNotificationV3(cachedMessage, newMessage, channel));
await mediator.Publish(message);
}

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 Task OnMessageReceived(SocketMessage message) =>
PublishMessage(new MessageReceivedNotificationV3(message));

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 Task OnMessageUpdated(Cacheable<IMessage, ulong> cachedMessage, SocketMessage newMessage,
ISocketMessageChannel channel) =>
PublishMessage(new MessageUpdatedNotificationV3(cachedMessage, newMessage, channel));

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));
}
private Task OnMessageDeleted(Cacheable<IMessage, ulong> message,
Cacheable<IMessageChannel, ulong> channel) =>
PublishMessage(new MessageDeletedNotificationV3(message, channel));

private Task OnReactionAdded(Cacheable<IUserMessage, ulong> message,
Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction) =>
PublishMessage(new ReactionAddedNotificationV3(message, channel, reaction));

private Task OnReactionRemoved(Cacheable<IUserMessage, ulong> message,
Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction) =>
PublishMessage(new ReactionRemovedNotificationV3(message, channel, reaction));

private Task OnUserJoined(SocketGuildUser guildUser) =>
PublishMessage(new UserJoinedNotificationV3(guildUser));

private Task OnAuditLogCreated(SocketAuditLogEntry entry, SocketGuild guild) =>
PublishMessage(new AuditLogCreatedNotificationV3(entry, guild));

private Task OnGuildAvailable(SocketGuild guild)
=> PublishMessage(new GuildAvailableNotificationV3(guild));

private Task OnChannelCreated(SocketChannel channel) =>
PublishMessage(new ChannelCreatedNotificationV3(channel));

private Task OnChannelUpdated(SocketChannel oldChannel, SocketChannel newChannel) =>
PublishMessage(new ChannelUpdatedNotificationV3(oldChannel, newChannel));

private Task OnJoinedGuild(SocketGuild guild) => PublishMessage(new JoinedGuildNotificationV3(guild));

public override void Dispose()
{
Expand Down
4 changes: 2 additions & 2 deletions src/Modix.Bot/Modules/InfractionModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ namespace Modix.Modules
[ModuleHelp("Infractions", "Provides commands for working with infractions.")]
public class InfractionModule : InteractionModuleBase
{
private readonly IModerationService _moderationService;
private readonly ModerationService _moderationService;
private readonly ModixConfig _config;

public InfractionModule(IModerationService moderationService, IOptions<ModixConfig> config)
public InfractionModule(ModerationService moderationService, IOptions<ModixConfig> config)
{
_moderationService = moderationService;
_config = config.Value;
Expand Down
4 changes: 2 additions & 2 deletions src/Modix.Bot/Modules/ModerationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace Modix.Modules
public class ModerationModule : ModuleBase
{
public ModerationModule(
IModerationService moderationService,
ModerationService moderationService,
IUserService userService,
IOptions<ModixConfig> config)
{
Expand Down Expand Up @@ -306,7 +306,7 @@ private async ValueTask<bool> GetConfirmationIfRequiredAsync(DiscordUserOrMessag
+ $"{Format.Bold(author.GetDisplayName())} ({userOrAuthor.UserId}), the message's author?");
}

internal protected IModerationService ModerationService { get; }
internal protected ModerationService ModerationService { get; }

internal protected IUserService UserService { get; }

Expand Down
4 changes: 2 additions & 2 deletions src/Modix.Bot/Modules/UserInfoModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class UserInfoModule : InteractionModuleBase
{
private readonly ILogger<UserInfoModule> _log;
private readonly IUserService _userService;
private readonly IModerationService _moderationService;
private readonly ModerationService _moderationService;
private readonly IAuthorizationService _authorizationService;
private readonly IMessageRepository _messageRepository;
private readonly IEmojiRepository _emojiRepository;
Expand All @@ -51,7 +51,7 @@ public class UserInfoModule : InteractionModuleBase
public UserInfoModule(
ILogger<UserInfoModule> logger,
IUserService userService,
IModerationService moderationService,
ModerationService moderationService,
IAuthorizationService authorizationService,
IMessageRepository messageRepository,
IEmojiRepository emojiRepository,
Expand Down
10 changes: 10 additions & 0 deletions src/Modix.Bot/Notifications/AuditLogCreatedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class AuditLogCreatedNotificationV3(SocketAuditLogEntry entry, SocketGuild guild) : INotification
{
public SocketAuditLogEntry Entry { get; } = entry;
public SocketGuild Guild { get; } = guild;
}
9 changes: 9 additions & 0 deletions src/Modix.Bot/Notifications/ChannelCreatedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class ChannelCreatedNotificationV3(SocketChannel channel) : INotification
{
public SocketChannel Channel { get; } = channel;
}
10 changes: 10 additions & 0 deletions src/Modix.Bot/Notifications/ChannelUpdatedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class ChannelUpdatedNotificationV3(SocketChannel oldChannel, SocketChannel newChannel) : INotification
{
public SocketChannel OldChannel { get; } = oldChannel;
public SocketChannel NewChannel { get; } = newChannel;
}
9 changes: 9 additions & 0 deletions src/Modix.Bot/Notifications/GuildAvailableNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class GuildAvailableNotificationV3(SocketGuild guild) : INotification
{
public SocketGuild Guild { get; } = guild;
}
9 changes: 9 additions & 0 deletions src/Modix.Bot/Notifications/JoinedGuildNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class JoinedGuildNotificationV3(SocketGuild guild) : INotification
{
public SocketGuild Guild { get; } = guild;
}
9 changes: 9 additions & 0 deletions src/Modix.Bot/Notifications/UserJoinedNotificationV3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Discord.WebSocket;
using MediatR;

namespace Modix.Bot.Notifications;

public class UserJoinedNotificationV3(SocketGuildUser guildUser) : INotification
{
public SocketGuildUser GuildUser { get; } = guildUser;
}
Loading

0 comments on commit fcb4b8c

Please sign in to comment.