From 1e8b7e53737dab676d5e99550cf549c53fe19d35 Mon Sep 17 00:00:00 2001 From: Macintosh II <95250141+mctaylors@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:16:09 +0300 Subject: [PATCH] Add mute role support & fix /unmute (#109) - Added support for `MuteRole`, now if you add any role to this setting, then try to mute a member, all his roles will be removed except for the one you set in this setting. - Fixed `/unmute`, that tried to set target's display name to unmute reason. --------- Signed-off-by: Macintosh II --- src/Commands/MuteCommandGroup.cs | 138 +++++++++++++++++- src/Data/MemberData.cs | 1 + .../GuildMemberRolesUpdatedResponder.cs | 6 +- src/Services/Update/MemberUpdateService.cs | 43 ++++-- 4 files changed, 172 insertions(+), 16 deletions(-) diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index ce70d68..bc64a64 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -101,11 +101,17 @@ public class MuteCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(embed, CancellationToken); } - return await MuteUserAsync( + if (GuildSettings.MuteRole.Get(data.Settings) != 0) + { + return await RoleMuteUserAsync( + target, reason, duration, guildId, data, channelId, user, currentUser, CancellationToken); + } + + return await TimeoutUserAsync( target, reason, duration, guildId, data, channelId, user, currentUser, CancellationToken); } - private async Task MuteUserAsync( + private async Task RoleMuteUserAsync( IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, IUser currentUser, CancellationToken ct = default) { @@ -125,10 +131,80 @@ public class MuteCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); } + var until = DateTimeOffset.UtcNow.Add(duration); // >:) + var memberData = data.GetOrCreateMemberData(target.ID); + memberData.MutedUntil = until; + var assignRoles = new List + { + GuildSettings.MuteRole.Get(data.Settings) + }; + if (!GuildSettings.RemoveRolesOnMute.Get(data.Settings)) + { + assignRoles.AddRange(memberData.Roles.ConvertAll(r => r.ToSnowflake())); + } + + var muteResult = await _guildApi.ModifyGuildMemberAsync( + guildId, target.ID, roles: assignRoles, + reason: $"({user.GetTag()}) {reason}".EncodeHeader(), ct: ct); + if (!muteResult.IsSuccess) + { + return Result.FromError(muteResult.Error); + } + + var title = string.Format(Messages.UserMuted, target.GetTag()); + var description = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason)) + .Append( + string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(until))).ToString(); + + var logResult = _utility.LogActionAsync( + data.Settings, channelId, user, title, description, target, ColorsList.Red, ct: ct); + if (!logResult.IsSuccess) + { + return Result.FromError(logResult.Error); + } + + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.UserMuted, target.GetTag()), target) + .WithColour(ColorsList.Green).Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct); + } + + private async Task TimeoutUserAsync( + IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, Snowflake channelId, + IUser user, IUser currentUser, CancellationToken ct = default) + { + if (duration.TotalDays >= 28) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.BotCannotMuteTarget, currentUser) + .WithDescription(Messages.DurationRequiredForTimeOuts) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, CancellationToken); + } + + var interactionResult + = await _utility.CheckInteractionsAsync( + guildId, user.ID, target.ID, "Mute", ct); + if (!interactionResult.IsSuccess) + { + return Result.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); + } + var until = DateTimeOffset.UtcNow.Add(duration); // >:) var muteResult = await _guildApi.ModifyGuildMemberAsync( guildId, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), communicationDisabledUntil: until, ct: ct); + if (!muteResult.IsSuccess) { return Result.FromError(muteResult.Error); @@ -211,11 +287,63 @@ public class MuteCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(embed, CancellationToken); } - return await UnmuteUserAsync( + if (data.GetOrCreateMemberData(target.ID).MutedUntil is not null) + { + return await RemoveMuteRoleUserAsync( + target, reason, guildId, data, channelId, user, currentUser, CancellationToken); + } + + return await RemoveTimeoutUserAsync( target, reason, guildId, data, channelId, user, currentUser, CancellationToken); } - private async Task UnmuteUserAsync( + private async Task RemoveMuteRoleUserAsync( + IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, + IUser currentUser, CancellationToken ct = default) + { + var interactionResult + = await _utility.CheckInteractionsAsync( + guildId, user.ID, target.ID, "Unmute", ct); + if (!interactionResult.IsSuccess) + { + return Result.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, currentUser) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct); + } + + var memberData = data.GetOrCreateMemberData(target.ID); + var unmuteResult = await _guildApi.ModifyGuildMemberAsync( + guildId, target.ID, roles: memberData.Roles.ConvertAll(r => r.ToSnowflake()), + reason: $"({user.GetTag()}) {reason}".EncodeHeader(), ct: ct); + memberData.MutedUntil = null; + if (!unmuteResult.IsSuccess) + { + return Result.FromError(unmuteResult.Error); + } + + var title = string.Format(Messages.UserUnmuted, target.GetTag()); + var description = string.Format(Messages.DescriptionActionReason, reason); + var logResult = _utility.LogActionAsync( + data.Settings, channelId, user, title, description, target, ColorsList.Green, ct: ct); + if (!logResult.IsSuccess) + { + return Result.FromError(logResult.Error); + } + + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.UserUnmuted, target.GetTag()), target) + .WithColour(ColorsList.Green).Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct); + } + + private async Task RemoveTimeoutUserAsync( IUser target, string reason, Snowflake guildId, GuildData data, Snowflake channelId, IUser user, IUser currentUser, CancellationToken ct = default) { @@ -236,7 +364,7 @@ public class MuteCommandGroup : CommandGroup } var unmuteResult = await _guildApi.ModifyGuildMemberAsync( - guildId, target.ID, $"({user.GetTag()}) {reason}".EncodeHeader(), + guildId, target.ID, reason: $"({user.GetTag()}) {reason}".EncodeHeader(), communicationDisabledUntil: null, ct: ct); if (!unmuteResult.IsSuccess) { diff --git a/src/Data/MemberData.cs b/src/Data/MemberData.cs index 1ff2bc0..72a9ee1 100644 --- a/src/Data/MemberData.cs +++ b/src/Data/MemberData.cs @@ -13,6 +13,7 @@ public sealed class MemberData public ulong Id { get; } public DateTimeOffset? BannedUntil { get; set; } + public DateTimeOffset? MutedUntil { get; set; } public List Roles { get; set; } = new(); public List Reminders { get; } = new(); } diff --git a/src/Responders/GuildMemberRolesUpdatedResponder.cs b/src/Responders/GuildMemberRolesUpdatedResponder.cs index dbd8b3a..eade781 100644 --- a/src/Responders/GuildMemberRolesUpdatedResponder.cs +++ b/src/Responders/GuildMemberRolesUpdatedResponder.cs @@ -23,7 +23,11 @@ public class GuildMemberUpdateResponder : IResponder public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) { var memberData = await _guildData.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct); - memberData.Roles = gatewayEvent.Roles.ToList().ConvertAll(r => r.Value); + if (memberData.MutedUntil is null) + { + memberData.Roles = gatewayEvent.Roles.ToList().ConvertAll(r => r.Value); + } + return Result.FromSuccess(); } } diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs index 631cc50..d4424ec 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/src/Services/Update/MemberUpdateService.cs @@ -79,17 +79,9 @@ public sealed partial class MemberUpdateService : BackgroundService { var failedResults = new List(); var id = data.Id.ToSnowflake(); - if (DateTimeOffset.UtcNow > data.BannedUntil) - { - var unbanResult = await _guildApi.RemoveGuildBanAsync( - guildId, id, Messages.PunishmentExpired.EncodeHeader(), ct); - if (unbanResult.IsSuccess) - { - data.BannedUntil = null; - } - return unbanResult; - } + var punishmentsResult = await CheckMemberPunishmentsAsync(guildId, id, data, ct); + failedResults.AddIfFailed(punishmentsResult); if (defaultRole.Value is not 0 && !data.Roles.Contains(defaultRole.Value)) { @@ -125,6 +117,37 @@ public sealed partial class MemberUpdateService : BackgroundService return failedResults.AggregateErrors(); } + private async Task CheckMemberPunishmentsAsync( + Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct) + { + if (DateTimeOffset.UtcNow > data.BannedUntil) + { + var unbanResult = await _guildApi.RemoveGuildBanAsync( + guildId, id, Messages.PunishmentExpired.EncodeHeader(), ct); + if (unbanResult.IsSuccess) + { + data.BannedUntil = null; + } + + return unbanResult; + } + + if (DateTimeOffset.UtcNow > data.MutedUntil) + { + var unmuteResult = await _guildApi.ModifyGuildMemberAsync( + guildId, id, roles: data.Roles.ConvertAll(r => r.ToSnowflake()), + reason: Messages.PunishmentExpired.EncodeHeader(), ct: ct); + if (unmuteResult.IsSuccess) + { + data.MutedUntil = null; + } + + return unmuteResult; + } + + return Result.FromSuccess(); + } + private async Task FilterNicknameAsync(Snowflake guildId, IUser user, IGuildMember member, CancellationToken ct) {