From e2bf083189ac6584056749b128899b1da0b27ac6 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 20 Jul 2023 02:08:44 +0500 Subject: [PATCH] Reduce method complexity in /ban, /unban and some other commands (#50) This PR does numerous things to reduce method complexity: - Created an extension method `FeedbackService#SendContextualEmbedResultAsync`, which directly takes a `Result` and checks it once in the extension method instead of multiple times in all commands; - Split the command methods for `/ban` and `/unban` into 2 parts: `Execute(Un)Ban` and `(Un)BanUserAsync`. The former will check all the needed results and will pass the outputs into the latter; - Extracted the method for logging an action into Private- and PublicFeedbackChannels. It now resides in UtilityService. Right now, only `/ban` and `/unban` make use of that method; - Created an extension method `Snowflake#EmptyOrEqualTo`, that combines the task of checking if a Snowflake is empty and checking if that Snowflake is equal to another Snowflake. Similar changes will be made to other command groups in future PRs. This is not done here to make the reviewing process easier. Signed-off-by: Octol1ttle --- src/Commands/AboutCommandGroup.cs | 3 +- src/Commands/BanCommandGroup.cs | 229 ++++++++++----------------- src/Commands/ClearCommandGroup.cs | 3 +- src/Commands/KickCommandGroup.cs | 10 +- src/Commands/MuteCommandGroup.cs | 20 +-- src/Commands/PingCommandGroup.cs | 3 +- src/Commands/RemindCommandGroup.cs | 5 +- src/Commands/SettingsCommandGroup.cs | 9 +- src/Extensions.cs | 35 +++- src/Services/UtilityService.cs | 55 ++++++- 10 files changed, 186 insertions(+), 186 deletions(-) diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index 70f4bcf..760d96b 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -72,8 +72,7 @@ public class AboutCommandGroup : CommandGroup { .WithColour(ColorsList.Cyan) .WithImageUrl("https://cdn.upload.systems/uploads/JFAaX5vr.png") .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index d2c1c76..902c753 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -7,13 +7,13 @@ using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.API.Objects; using Remora.Discord.Commands.Attributes; using Remora.Discord.Commands.Conditions; using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; +using Remora.Rest.Core; using Remora.Results; namespace Boyfriend.Commands; @@ -57,7 +57,7 @@ public class BanCommandGroup : CommandGroup { /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user /// was banned and vice-versa. /// - /// + /// [Command("ban", "бан")] [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)] [DiscordDefaultDMPermission(false)] @@ -66,122 +66,95 @@ public class BanCommandGroup : CommandGroup { [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] [Description("Ban user")] [UsedImplicitly] - public async Task BanUserAsync( + public async Task ExecuteBan( [Description("User to ban")] IUser target, [Description("Ban reason")] string reason, [Description("Ban duration")] TimeSpan? duration = null) { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) return Result.FromError( new ArgumentNullError(nameof(_context), "Unable to retrieve necessary IDs from command context")); - // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); + var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken); + if (!userResult.IsDefined(out var user)) + return Result.FromError(userResult); + var guildResult = await _guildApi.GetGuildAsync(guildId.Value, ct: CancellationToken); + if (!guildResult.IsDefined(out var guild)) + return Result.FromError(guildResult); - var data = await _dataService.GetData(guildId.Value, CancellationToken); + return await BanUserAsync(target, reason, duration, guild, channelId.Value, user, currentUser); + } + + private async Task BanUserAsync( + IUser target, string reason, TimeSpan? duration, IGuild guild, Snowflake channelId, + IUser user, IUser currentUser) { + var data = await _dataService.GetData(guild.ID, CancellationToken); var cfg = data.Settings; Messages.Culture = GuildSettings.Language.Get(cfg); - var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken); + var existingBanResult = await _guildApi.GetGuildBanAsync(guild.ID, target.ID, CancellationToken); if (existingBanResult.IsDefined()) { - var embed = new EmbedBuilder().WithSmallTitle(Messages.UserAlreadyBanned, currentUser) + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserAlreadyBanned, currentUser) .WithColour(ColorsList.Red).Build(); - if (!embed.IsDefined(out var alreadyBuilt)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, CancellationToken); } var interactionResult - = await _utility.CheckInteractionsAsync(guildId.Value, userId.Value, target.ID, "Ban", CancellationToken); + = await _utility.CheckInteractionsAsync(guild.ID, user.ID, target.ID, "Ban", CancellationToken); if (!interactionResult.IsSuccess) return Result.FromError(interactionResult); - Result responseEmbed; if (interactionResult.Entity is not null) { - responseEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) + var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) .WithColour(ColorsList.Red).Build(); - } else { - var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken); - if (!userResult.IsDefined(out var user)) - return Result.FromError(userResult); - var builder = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason)); - if (duration is not null) - builder.Append( - string.Format( - Messages.DescriptionActionExpiresAt, - Markdown.Timestamp(DateTimeOffset.UtcNow.Add(duration.Value)))); - var description = builder.ToString(); - - var dmChannelResult = await _userApi.CreateDMAsync(target.ID, CancellationToken); - if (dmChannelResult.IsDefined(out var dmChannel)) { - var guildResult = await _guildApi.GetGuildAsync(guildId.Value, ct: CancellationToken); - if (!guildResult.IsDefined(out var guild)) - return Result.FromError(guildResult); - - var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) - .WithTitle(Messages.YouWereBanned) - .WithDescription(description) - .WithActionFooter(user) - .WithCurrentTimestamp() - .WithColour(ColorsList.Red) - .Build(); - - if (!dmEmbed.IsDefined(out var dmBuilt)) - return Result.FromError(dmEmbed); - await _channelApi.CreateMessageAsync(dmChannel.ID, embeds: new[] { dmBuilt }, ct: CancellationToken); - } - - var banResult = await _guildApi.CreateGuildBanAsync( - guildId.Value, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), - ct: CancellationToken); - if (!banResult.IsSuccess) - return Result.FromError(banResult.Error); - var memberData = data.GetMemberData(target.ID); - memberData.BannedUntil - = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; - memberData.Roles.Clear(); - - responseEmbed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserBanned, target.GetTag()), target) - .WithColour(ColorsList.Green).Build(); - - if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty() - && GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value) - || (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty() - && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) { - var logEmbed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserBanned, target.GetTag()), target) - .WithDescription(description) - .WithActionFooter(user) - .WithCurrentTimestamp() - .WithColour(ColorsList.Red) - .Build(); - - if (!logEmbed.IsDefined(out var logBuilt)) - return Result.FromError(logEmbed); - - var builtArray = new[] { logBuilt }; - // Not awaiting to reduce response time - if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value) - _ = _channelApi.CreateMessageAsync( - GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray, - ct: CancellationToken); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg) - && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value) - _ = _channelApi.CreateMessageAsync( - GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray, - ct: CancellationToken); - } + return await _feedbackService.SendContextualEmbedResultAsync(errorEmbed, CancellationToken); } - if (!responseEmbed.IsDefined(out var built)) - return Result.FromError(responseEmbed); + var builder = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason)); + if (duration is not null) + builder.Append( + string.Format( + Messages.DescriptionActionExpiresAt, + Markdown.Timestamp(DateTimeOffset.UtcNow.Add(duration.Value)))); + var title = string.Format(Messages.UserBanned, target.GetTag()); + var description = builder.ToString(); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + var dmChannelResult = await _userApi.CreateDMAsync(target.ID, CancellationToken); + if (dmChannelResult.IsDefined(out var dmChannel)) { + var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) + .WithTitle(Messages.YouWereBanned) + .WithDescription(description) + .WithActionFooter(user) + .WithCurrentTimestamp() + .WithColour(ColorsList.Red) + .Build(); + + if (!dmEmbed.IsDefined(out var dmBuilt)) + return Result.FromError(dmEmbed); + await _channelApi.CreateMessageAsync(dmChannel.ID, embeds: new[] { dmBuilt }, ct: CancellationToken); + } + + var banResult = await _guildApi.CreateGuildBanAsync( + guild.ID, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), + ct: CancellationToken); + if (!banResult.IsSuccess) + return Result.FromError(banResult.Error); + var memberData = data.GetMemberData(target.ID); + memberData.BannedUntil + = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; + memberData.Roles.Clear(); + + var embed = new EmbedBuilder().WithSmallTitle( + title, target) + .WithColour(ColorsList.Green).Build(); + + _utility.LogActionAsync(cfg, channelId, title, target, description, user, CancellationToken); + + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } /// @@ -196,7 +169,7 @@ public class BanCommandGroup : CommandGroup { /// A feedback sending result which may or may not have succeeded. A successful result does not mean that the user /// was unbanned and vice-versa. /// - /// + /// /// [Command("unban")] [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)] @@ -206,79 +179,53 @@ public class BanCommandGroup : CommandGroup { [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] [Description("Unban user")] [UsedImplicitly] - public async Task UnbanUserAsync( + public async Task ExecuteUnban( [Description("User to unban")] IUser target, [Description("Unban reason")] string reason) { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) return Result.FromError( new ArgumentNullError(nameof(_context), "Unable to retrieve necessary IDs from command context")); - // The current user's avatar is used when sending error messages var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); - - var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(cfg); - - var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken); - if (!existingBanResult.IsDefined()) { - var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotBanned, currentUser) - .WithColour(ColorsList.Red).Build(); - - if (!embed.IsDefined(out var alreadyBuilt)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken); - } - // Needed to get the tag and avatar var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken); if (!userResult.IsDefined(out var user)) return Result.FromError(userResult); + return await UnbanUserAsync(target, reason, guildId.Value, channelId.Value, user, currentUser); + } + + private async Task UnbanUserAsync( + IUser target, string reason, Snowflake guildId, Snowflake channelId, IUser user, IUser currentUser) { + var cfg = await _dataService.GetSettings(guildId, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(cfg); + + var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, target.ID, CancellationToken); + if (!existingBanResult.IsDefined()) { + var errorEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserNotBanned, currentUser) + .WithColour(ColorsList.Red).Build(); + + return await _feedbackService.SendContextualEmbedResultAsync(errorEmbed, CancellationToken); + } + var unbanResult = await _guildApi.RemoveGuildBanAsync( - guildId.Value, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), + guildId, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), ct: CancellationToken); if (!unbanResult.IsSuccess) return Result.FromError(unbanResult.Error); - var responseEmbed = new EmbedBuilder().WithSmallTitle( + var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.UserUnbanned, target.GetTag()), target) .WithColour(ColorsList.Green).Build(); - if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty() - && GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value) - || (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty() - && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) { - var logEmbed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.UserUnbanned, target.GetTag()), target) - .WithDescription(string.Format(Messages.DescriptionActionReason, reason)) - .WithActionFooter(user) - .WithCurrentTimestamp() - .WithColour(ColorsList.Green) - .Build(); + var title = string.Format(Messages.UserUnbanned, target.GetTag()); + var description = string.Format(Messages.DescriptionActionReason, reason); + var logResult = _utility.LogActionAsync(cfg, channelId, title, target, description, user, CancellationToken); + if (!logResult.IsSuccess) + return Result.FromError(logResult.Error); - if (!logEmbed.IsDefined(out var logBuilt)) - return Result.FromError(logEmbed); - - var builtArray = new[] { logBuilt }; - - // Not awaiting to reduce response time - if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value) - _ = _channelApi.CreateMessageAsync( - GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray, - ct: CancellationToken); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg) - && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value) - _ = _channelApi.CreateMessageAsync( - GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray, - ct: CancellationToken); - } - - if (!responseEmbed.IsDefined(out var built)) - return Result.FromError(responseEmbed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs index ede4d0b..306ab86 100644 --- a/src/Commands/ClearCommandGroup.cs +++ b/src/Commands/ClearCommandGroup.cs @@ -117,8 +117,7 @@ public class ClearCommandGroup : CommandGroup { var embed = new EmbedBuilder().WithSmallTitle(title, currentUser) .WithColour(ColorsList.Green).Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs index 5809677..56154bd 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/src/Commands/KickCommandGroup.cs @@ -83,10 +83,7 @@ public class KickCommandGroup : CommandGroup { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - if (!embed.IsDefined(out var alreadyBuilt)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } var interactionResult @@ -162,9 +159,6 @@ public class KickCommandGroup : CommandGroup { } } - if (!responseEmbed.IsDefined(out var built)) - return Result.FromError(responseEmbed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(responseEmbed, CancellationToken); } } diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index 4338d09..eff7029 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -84,10 +84,7 @@ public class MuteCommandGroup : CommandGroup { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - if (!embed.IsDefined(out var alreadyBuilt)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } var interactionResult @@ -154,10 +151,7 @@ public class MuteCommandGroup : CommandGroup { } } - if (!responseEmbed.IsDefined(out var built)) - return Result.FromError(responseEmbed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(responseEmbed, CancellationToken); } /// @@ -202,10 +196,7 @@ public class MuteCommandGroup : CommandGroup { var embed = new EmbedBuilder().WithSmallTitle(Messages.UserNotFoundShort, currentUser) .WithColour(ColorsList.Red).Build(); - if (!embed.IsDefined(out var alreadyBuilt)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(alreadyBuilt, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } var interactionResult @@ -258,9 +249,6 @@ public class MuteCommandGroup : CommandGroup { ct: CancellationToken); } - if (!responseEmbed.IsDefined(out var built)) - return Result.FromError(responseEmbed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(responseEmbed, CancellationToken); } } diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs index ad05451..5819336 100644 --- a/src/Commands/PingCommandGroup.cs +++ b/src/Commands/PingCommandGroup.cs @@ -77,8 +77,7 @@ public class PingCommandGroup : CommandGroup { .WithColour(latency < 250 ? ColorsList.Green : latency < 500 ? ColorsList.Yellow : ColorsList.Red) .WithCurrentTimestamp() .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs index 28fc0ed..954e84a 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/src/Commands/RemindCommandGroup.cs @@ -71,9 +71,6 @@ public class RemindCommandGroup : CommandGroup { .WithColour(ColorsList.Green) .Build(); - if (!embed.IsDefined(out var built)) - return Result.FromError(embed); - - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index 5c4a505..d2d28be 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -90,9 +90,8 @@ public class SettingsCommandGroup : CommandGroup { .WithDescription(builder.ToString()) .WithColour(ColorsList.Default) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } /// @@ -132,9 +131,8 @@ public class SettingsCommandGroup : CommandGroup { .WithDescription(setResult.Error.Message) .WithColour(ColorsList.Red) .Build(); - if (!failedEmbed.IsDefined(out var failedBuilt)) return Result.FromError(failedEmbed); - return (Result)await _feedbackService.SendContextualEmbedAsync(failedBuilt, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(failedEmbed, CancellationToken); } var builder = new StringBuilder(); @@ -147,8 +145,7 @@ public class SettingsCommandGroup : CommandGroup { .WithDescription(builder.ToString()) .WithColour(ColorsList.Green) .Build(); - if (!embed.IsDefined(out var built)) return Result.FromError(embed); - return (Result)await _feedbackService.SendContextualEmbedAsync(built, ct: CancellationToken); + return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); } } diff --git a/src/Extensions.cs b/src/Extensions.cs index 18a3c06..a9e0d48 100644 --- a/src/Extensions.cs +++ b/src/Extensions.cs @@ -7,9 +7,11 @@ using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Objects; using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Extensions; +using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; +using Remora.Results; namespace Boyfriend; @@ -51,10 +53,9 @@ public static class Extensions { /// The builder to add the small title to. /// The text of the small title. /// The user whose avatar to use in the small title. - /// The URL that will be opened if a user clicks on the small title. /// The builder with the added small title in the author field. public static EmbedBuilder WithSmallTitle( - this EmbedBuilder builder, string text, IUser? avatarSource = null, string? url = default) { + this EmbedBuilder builder, string text, IUser? avatarSource = null) { Uri? avatarUrl = null; if (avatarSource is not null) { var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256); @@ -64,7 +65,7 @@ public static class Extensions { : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity; } - builder.Author = new EmbedAuthorBuilder(text, url, avatarUrl?.AbsoluteUri); + builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl?.AbsoluteUri); return builder; } @@ -191,7 +192,35 @@ public static class Extensions { && context.TryGetUserID(out userId); } + /// + /// Checks whether this Snowflake has any value set. + /// + /// The Snowflake to check. + /// true if the Snowflake has no value set or it's set to 0, false otherwise. public static bool Empty(this Snowflake snowflake) { return snowflake.Value is 0; } + + /// + /// Checks whether this snowflake is empty (see ) or it's equal to + /// + /// + /// The Snowflake to check for emptiness + /// The Snowflake to check for equality with . + /// + /// true if is empty or is equal to , false + /// otherwise. + /// + /// + public static bool EmptyOrEqualTo(this Snowflake snowflake, Snowflake anotherSnowflake) { + return snowflake.Empty() || snowflake == anotherSnowflake; + } + + public static async Task SendContextualEmbedResultAsync( + this FeedbackService feedback, Result embedResult, CancellationToken ct = default) { + if (!embedResult.IsDefined(out var embed)) + return Result.FromError(embedResult); + + return (Result)await feedback.SendContextualEmbedAsync(embed, ct: ct); + } } diff --git a/src/Services/UtilityService.cs b/src/Services/UtilityService.cs index 3b6a2bf..b1ae347 100644 --- a/src/Services/UtilityService.cs +++ b/src/Services/UtilityService.cs @@ -4,6 +4,7 @@ using Boyfriend.Data; using Microsoft.Extensions.Hosting; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; @@ -15,15 +16,18 @@ namespace Boyfriend.Services; /// of some Discord APIs. /// public class UtilityService : IHostedService { + private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildScheduledEventAPI _eventApi; private readonly IDiscordRestGuildAPI _guildApi; private readonly IDiscordRestUserAPI _userApi; public UtilityService( - IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, IDiscordRestGuildScheduledEventAPI eventApi) { + IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi, + IDiscordRestUserAPI userApi) { + _channelApi = channelApi; + _eventApi = eventApi; _guildApi = guildApi; _userApi = userApi; - _eventApi = eventApi; } public Task StartAsync(CancellationToken ct) { @@ -132,4 +136,51 @@ public class UtilityService : IHostedService { .Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} ")); return builder.ToString(); } + + /// + /// Logs an action in the and + /// . + /// + /// The guild configuration. + /// The ID of the channel where the action was executed. + /// The title for the embed. + /// The user whose avatar will be displayed next to the of the embed. + /// The description of the embed. + /// The user who performed the action. + /// The cancellation token for this operation. + /// + public Result LogActionAsync( + JsonNode cfg, Snowflake channelId, string title, IUser avatar, string description, + IUser user, CancellationToken ct = default) { + var publicChannel = GuildSettings.PublicFeedbackChannel.Get(cfg); + var privateChannel = GuildSettings.PrivateFeedbackChannel.Get(cfg); + if (GuildSettings.PublicFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId) + && GuildSettings.PrivateFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId)) + return Result.FromSuccess(); + + var logEmbed = new EmbedBuilder().WithSmallTitle(title, avatar) + .WithDescription(description) + .WithActionFooter(user) + .WithCurrentTimestamp() + .WithColour(ColorsList.Green) + .Build(); + + if (!logEmbed.IsDefined(out var logBuilt)) + return Result.FromError(logEmbed); + + var builtArray = new[] { logBuilt }; + + // Not awaiting to reduce response time + if (publicChannel != channelId.Value) + _ = _channelApi.CreateMessageAsync( + publicChannel, embeds: builtArray, + ct: ct); + if (privateChannel != publicChannel + && privateChannel != channelId.Value) + _ = _channelApi.CreateMessageAsync( + privateChannel, embeds: builtArray, + ct: ct); + + return Result.FromSuccess(); + } }