diff --git a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs index 69be80f..db3a8ce 100644 --- a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs @@ -128,7 +128,7 @@ public sealed class BanCommandGroup : CommandGroup return await BanUserAsync(executor, target, reason, timeSpan, guild, data, channelId, bot, CancellationToken); } - private async Task<Result> BanUserAsync( + public async Task<Result> BanUserAsync( IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default) diff --git a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs index a8fea2a..ccbe2a1 100644 --- a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs @@ -111,7 +111,7 @@ public sealed class KickCommandGroup : CommandGroup return await KickUserAsync(executor, target, reason, guild, channelId, data, bot, CancellationToken); } - private async Task<Result> KickUserAsync( + public async Task<Result> KickUserAsync( IUser executor, IUser target, string reason, IGuild guild, Snowflake channelId, GuildData data, IUser bot, CancellationToken ct = default) { diff --git a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs index 46e8d84..6083e6e 100644 --- a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs @@ -123,7 +123,7 @@ public sealed class MuteCommandGroup : CommandGroup CancellationToken); } - private async Task<Result> MuteUserAsync( + public async Task<Result> MuteUserAsync( IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default) { diff --git a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs index 0acaa88..a03f20d 100644 --- a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs @@ -38,6 +38,7 @@ public sealed class SettingsCommandGroup : CommandGroup private static readonly IGuildOption[] AllOptions = [ GuildSettings.Language, + GuildSettings.WarnPunishment, GuildSettings.WelcomeMessage, GuildSettings.LeaveMessage, GuildSettings.ReceiveStartupMessages, @@ -45,6 +46,7 @@ public sealed class SettingsCommandGroup : CommandGroup GuildSettings.ReturnRolesOnRejoin, GuildSettings.AutoStartEvents, GuildSettings.RenameHoistedUsers, + GuildSettings.WarnsThreshold, GuildSettings.PublicFeedbackChannel, GuildSettings.PrivateFeedbackChannel, GuildSettings.WelcomeMessagesChannel, @@ -53,7 +55,8 @@ public sealed class SettingsCommandGroup : CommandGroup GuildSettings.MuteRole, GuildSettings.ModeratorRole, GuildSettings.EventNotificationRole, - GuildSettings.EventEarlyNotificationOffset + GuildSettings.EventEarlyNotificationOffset, + GuildSettings.WarnPunishmentDuration ]; private readonly ICommandContext _context; diff --git a/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs b/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs new file mode 100644 index 0000000..4b8d712 --- /dev/null +++ b/TeamOctolings.Octobot/Commands/WarnCommandGroup.cs @@ -0,0 +1,560 @@ +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.Json.Nodes; +using JetBrains.Annotations; +using Remora.Commands.Attributes; +using Remora.Commands.Groups; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Abstractions.Rest; +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; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; +using static System.DateTimeOffset; + +namespace TeamOctolings.Octobot.Commands; + +[UsedImplicitly] +public class WarnCommandGroup : CommandGroup +{ + private readonly AccessControlService _access; + private readonly IDiscordRestChannelAPI _channelApi; + private readonly ICommandContext _context; + private readonly IFeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + private readonly Utility _utility; + + public WarnCommandGroup( + ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData, + IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, + Utility utility, AccessControlService access) + { + _context = context; + _channelApi = channelApi; + _guildData = guildData; + _feedback = feedback; + _guildApi = guildApi; + _userApi = userApi; + _utility = utility; + _access = access; + } + + [Command("warn")] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] + [DiscordDefaultDMPermission(false)] + [RequireContext(ChannelContext.Guild)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] + [RequireBotDiscordPermissions(DiscordPermission.KickMembers, + DiscordPermission.ModerateMembers, DiscordPermission.BanMembers)] + [Description("Warn user")] + [UsedImplicitly] + public async Task<Result> ExecuteWarnAsync( + [Description("User to warn")] IUser target, + [Description("Warn reason")] [MaxLength(256)] + string reason) + { + if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) + { + return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + + var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + + var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); + if (!executorResult.IsDefined(out var executor)) + { + return Result.FromError(executorResult); + } + + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } + + var data = await _guildData.GetData(guild.ID, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + var interactionResult + = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Warn", CancellationToken); + if (!interactionResult.IsSuccess) + { + return ResultExtensions.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken); + } + + return await WarnPreparationAsync(executor, target, reason, guild, data, channelId, bot, CancellationToken); + } + + private async Task<Result> WarnPreparationAsync(IUser executor, IUser target, string reason, IGuild guild, + GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default) + { + var memberData = data.GetOrCreateMemberData(target.ID); + var warns = memberData.Warns; + + var settings = data.Settings; + + var warnThreshold = GuildSettings.WarnsThreshold.Get(settings); + var warnPunishment = GuildSettings.WarnPunishment.Get(settings); + var warnDuration = GuildSettings.WarnPunishmentDuration.Get(settings); + + if (warnPunishment is "off" or "disable" or "disabled" + && warns.Count + 1 >= warnThreshold + && warnThreshold is not 0) + { + var errorEmbed = new EmbedBuilder() + .WithSmallTitle(string.Format(Messages.WarnThresholdExceeded, warnThreshold), bot) + .WithDescription(Messages.WarnThresholdExceededDescription) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken); + } + + if (warnPunishment is "ban" or "mute" && warnDuration == TimeSpan.Zero) + { + var errorEmbed = new EmbedBuilder() + .WithSmallTitle(Messages.WarnPunishmentDurationNotSet, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken); + } + + if (warns.Count + 1 < warnThreshold || warnThreshold is 0) + { + return await WarnUserAsync(executor, target, reason, guild, data, channelId, bot, settings, + warns, warnThreshold, warnPunishment, warnDuration, ct); + } + + var interactionResult + = await _access.CheckInteractionsAsync(guild.ID, bot.ID, target.ID, + $"{char.ToUpperInvariant(warnPunishment[0])}{warnPunishment[1..]}", ct); + if (!interactionResult.IsSuccess) + { + return ResultExtensions.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct); + } + + return await WarnUserAsync(executor, target, reason, guild, data, channelId, bot, settings, + warns, warnThreshold, warnPunishment, warnDuration, ct); + } + + private async Task<Result> WarnUserAsync(IUser executor, IUser target, string reason, IGuild guild, + GuildData data, Snowflake channelId, IUser bot, JsonNode settings, IList warns, int warnThreshold, + string warnPunishment, TimeSpan warnDuration, CancellationToken ct = default) + { + warns.Add(new Warn + { + WarnedBy = executor.ID.Value, + At = UtcNow, + Reason = reason + }); + + var builder = new StringBuilder() + .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)) + .AppendBulletPointLine(string.Format(Messages.DescriptionWarns, + warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}")); + + var title = string.Format(Messages.UserWarned, target.GetTag()); + var description = builder.ToString(); + + var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); + if (dmChannelResult.IsDefined(out var dmChannel)) + { + var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) + .WithTitle(Messages.YouHaveBeenWarned) + .WithDescription(description) + .WithActionFooter(executor) + .WithCurrentTimestamp() + .WithColour(ColorsList.Yellow) + .Build(); + + await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); + } + + _utility.LogAction(settings, channelId, executor, title, description, + target, ColorsList.Yellow, false, ct); + + var embed = new EmbedBuilder().WithSmallTitle(title, target) + .WithColour(ColorsList.Green).Build(); + + if (warns.Count >= warnThreshold && warnThreshold is not 0) + { + return await PunishUserAsync(target, guild, data, channelId, bot, warns, warnPunishment, warnDuration, CancellationToken); + } + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + private async Task<Result> PunishUserAsync(IUser target, IGuild guild, GuildData data, + Snowflake channelId, IUser bot, IList warns, string punishment, TimeSpan duration, CancellationToken ct) + { + if (punishment is "ban" && duration != TimeSpan.Zero) + { + var banCommandGroup = new BanCommandGroup( + _access, _channelApi, _context, _feedback, _guildApi, _guildData, _userApi, _utility); + await banCommandGroup.BanUserAsync(bot, target, Messages.ReceivedTooManyWarnings, + duration, guild, data, channelId, bot, ct); + } + + if (punishment is "kick") + { + var kickCommandGroup = new KickCommandGroup( + _access, _channelApi, _context, _feedback, _guildApi, _guildData, _userApi, _utility); + await kickCommandGroup.KickUserAsync(bot, target, Messages.ReceivedTooManyWarnings, + guild, channelId, data, bot, ct); + } + + if (punishment is "mute" && duration != TimeSpan.Zero) + { + var muteCommandGroup = new MuteCommandGroup( + _access, _context, _feedback, _guildApi, _guildData, _userApi, _utility); + await muteCommandGroup.MuteUserAsync(bot, target, Messages.ReceivedTooManyWarnings, + duration, guild.ID, data, channelId, bot, ct); + } + + warns.Clear(); + return Result.FromSuccess(); + } + + [Command("unwarn")] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] + [DiscordDefaultDMPermission(false)] + [RequireContext(ChannelContext.Guild)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] + [Description("Remove warns from user")] + [UsedImplicitly] + public async Task<Result> ExecuteUnwarnAsync( + [Description("User to remove warns from")] + IUser target, + [Description("Warn remove reason")] [MaxLength(256)] + string reason, + [Description("Number of the warning to be deleted")] + int? number = null) + { + if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) + { + return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + + var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + + var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); + if (!executorResult.IsDefined(out var executor)) + { + return Result.FromError(executorResult); + } + + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } + + var data = await _guildData.GetData(guild.ID, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + var interactionResult + = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Unwarn", CancellationToken); + if (!interactionResult.IsSuccess) + { + return ResultExtensions.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: CancellationToken); + } + + if (number is not null) + { + return await RemoveUserWarnAsync(executor, target, reason, number.Value, guild, data, channelId, bot, + CancellationToken); + } + + return await RemoveUserWarnsAsync(executor, target, reason, guild, data, channelId, bot, CancellationToken); + } + + private async Task<Result> RemoveUserWarnAsync(IUser executor, IUser target, string reason, int warnNumber, + IGuild guild, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default) + { + var memberData = data.GetOrCreateMemberData(target.ID); + var warns = memberData.Warns; + + if (warns.Count is 0) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var index = warnNumber - 1; + + if (index >= warns.Count || index < 0) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.WrongWarningNumberSelected, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var builder = new StringBuilder() + .Append("> ").AppendLine(warns[index].Reason) + .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)); + + warns.RemoveAt(index); + + var title = string.Format(Messages.UserWarnRemoved, warnNumber, target.GetTag()); + var description = builder.ToString(); + + var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); + if (dmChannelResult.IsDefined(out var dmChannel)) + { + var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) + .WithTitle(Messages.YourWarningHasBeenRevoked) + .WithDescription(description) + .WithActionFooter(executor) + .WithCurrentTimestamp() + .WithColour(ColorsList.Green) + .Build(); + + await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); + } + + var embed = new EmbedBuilder().WithSmallTitle( + title, target) + .WithColour(ColorsList.Green).Build(); + + _utility.LogAction( + data.Settings, channelId, executor, title, description, target, ColorsList.Yellow, false, ct); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + private async Task<Result> RemoveUserWarnsAsync(IUser executor, IUser target, string reason, + IGuild guild, GuildData data, Snowflake channelId, IUser bot, CancellationToken ct = default) + { + var memberData = data.GetOrCreateMemberData(target.ID); + var warns = memberData.Warns; + + if (warns.Count is 0) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var builder = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)); + + warns.Clear(); + + var title = string.Format(Messages.UserWarnsRemoved, target.GetTag()); + var description = builder.ToString(); + + var dmChannelResult = await _userApi.CreateDMAsync(target.ID, ct); + if (dmChannelResult.IsDefined(out var dmChannel)) + { + var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) + .WithTitle(Messages.YourWarningsHaveBeenRevoked) + .WithDescription(description) + .WithActionFooter(executor) + .WithCurrentTimestamp() + .WithColour(ColorsList.Green) + .Build(); + + await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); + } + + var embed = new EmbedBuilder().WithSmallTitle( + title, target) + .WithColour(ColorsList.Green).Build(); + + _utility.LogAction( + data.Settings, channelId, executor, title, description, target, ColorsList.Yellow, false, ct); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + [Command("listwarn")] + [DiscordDefaultDMPermission(false)] + [Ephemeral] + [Description("(Ephemeral) Get current warns")] + [UsedImplicitly] + public async Task<Result> ExecuteListWarnsAsync( + [Description("(Moderator-only) Get target's current warns")] + IUser? target = null) + { + if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) + { + return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + + var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + + var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); + if (!executorResult.IsDefined(out var executor)) + { + return Result.FromError(executorResult); + } + + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } + + var data = await _guildData.GetData(guild.ID, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + if (target is not null) + { + return await ListTargetWarnsAsync(executor, target, guild, data, bot, CancellationToken); + } + + return await ListExecutorWarnsAsync(executor, data, bot, CancellationToken); + } + + private async Task<Result> ListTargetWarnsAsync(IUser executor, IUser target, IGuild guild, + GuildData data, IUser bot, CancellationToken ct = default) + { + var interactionResult + = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "GetWarns", ct); + if (!interactionResult.IsSuccess) + { + return ResultExtensions.FromError(interactionResult); + } + + if (interactionResult.Entity is not null) + { + var errorEmbed = new EmbedBuilder().WithSmallTitle(interactionResult.Entity, bot) + .WithColour(ColorsList.Red).Build(); + + return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct); + } + + var memberData = data.GetOrCreateMemberData(target.ID); + var warns = memberData.Warns; + + if (warns.Count is 0) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.UserHasNoWarnings, bot) + .WithColour(ColorsList.Green).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var warnThreshold = GuildSettings.WarnsThreshold.Get(data.Settings); + + var punishmentType = GuildSettings.WarnPunishment.Get(data.Settings); + + var description = new StringBuilder() + .AppendLine(string.Format(Messages.DescriptionWarns, + warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}")); + if (punishmentType is not "off" and not "disable" and not "disabled") + { + description.AppendLine(string.Format( + Messages.DescriptionPunishmentType, Markdown.InlineCode(punishmentType))); + } + + var warnCount = 0; + foreach (var warn in warns) + { + warnCount++; + description.Append(warnCount).Append(". ").AppendLine(warn.Reason) + .AppendSubBulletPoint(Messages.IssuedBy).Append(' ').AppendLine(Mention.User(warn.WarnedBy.ToSnowflake())) + .AppendSubBulletPointLine(string.Format(Messages.ReceivedOn, Markdown.Timestamp(warn.At))); + } + + var embed = new EmbedBuilder() + .WithSmallTitle(string.Format(Messages.ListTargetWarnsTitle, target.GetTag()), target) + .WithDescription(description.ToString()) + .WithColour(ColorsList.Default).Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + private async Task<Result> ListExecutorWarnsAsync(IUser executor, GuildData data, IUser bot, + CancellationToken ct = default) + { + var memberData = data.GetOrCreateMemberData(executor.ID); + var warns = memberData.Warns; + + if (warns.Count is 0) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.YouHaveNoWarnings, bot) + .WithColour(ColorsList.Green).Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var warnThreshold = GuildSettings.WarnsThreshold.Get(data.Settings); + + var punishmentType = GuildSettings.WarnPunishment.Get(data.Settings); + + var description = new StringBuilder() + .AppendLine(string.Format(Messages.DescriptionWarns, + warnThreshold is 0 ? warns.Count : $"{warns.Count}/{warnThreshold}")); + if (punishmentType is not "off" and not "disable" and not "disabled") + { + description.AppendLine(string.Format( + Messages.DescriptionPunishmentType, Markdown.InlineCode(punishmentType))); + } + + var warnCount = 0; + foreach (var warn in warns) + { + warnCount++; + description.Append(warnCount).Append(". ").AppendLine(warn.Reason) + .AppendSubBulletPoint(Messages.IssuedBy).Append(' ').AppendLine(Mention.User(warn.WarnedBy.ToSnowflake())) + .AppendSubBulletPointLine(string.Format(Messages.ReceivedOn, Markdown.Timestamp(warn.At))); + } + + var embed = new EmbedBuilder() + .WithSmallTitle(string.Format(Messages.ListExecutorWarnsTitle, executor.GetTag()), executor) + .WithDescription(description.ToString()) + .WithColour(ColorsList.Default).Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } +} diff --git a/TeamOctolings.Octobot/Data/GuildSettings.cs b/TeamOctolings.Octobot/Data/GuildSettings.cs index dc59d6f..26e325a 100644 --- a/TeamOctolings.Octobot/Data/GuildSettings.cs +++ b/TeamOctolings.Octobot/Data/GuildSettings.cs @@ -12,6 +12,8 @@ public static class GuildSettings { public static readonly LanguageOption Language = new("Language", "en"); + public static readonly PunishmentOption WarnPunishment = new("WarnPunishment", "disabled"); + /// <summary> /// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a new member joins the guild. /// </summary> @@ -58,6 +60,8 @@ public static class GuildSettings /// </summary> public static readonly BoolOption RenameHoistedUsers = new("RenameHoistedUsers", false); + public static readonly IntOption WarnsThreshold = new("WarnsThreshold", 0); + /// <summary> /// Controls what channel should all public messages be sent to. /// </summary> @@ -84,4 +88,7 @@ public static class GuildSettings /// </summary> public static readonly TimeSpanOption EventEarlyNotificationOffset = new( "EventEarlyNotificationOffset", TimeSpan.Zero); + + public static readonly TimeSpanOption WarnPunishmentDuration = new( + "WarnPunishmentDuration", TimeSpan.Zero); } diff --git a/TeamOctolings.Octobot/Data/MemberData.cs b/TeamOctolings.Octobot/Data/MemberData.cs index 984d4af..57b5cc9 100644 --- a/TeamOctolings.Octobot/Data/MemberData.cs +++ b/TeamOctolings.Octobot/Data/MemberData.cs @@ -5,13 +5,18 @@ namespace TeamOctolings.Octobot.Data; /// </summary> public sealed class MemberData { - public MemberData(ulong id, List<Reminder>? reminders = null) + public MemberData(ulong id, List<Reminder>? reminders = null, List<Warn>? warns = null) { Id = id; if (reminders is not null) { Reminders = reminders; } + + if (warns is not null) + { + Warns = warns; + } } public ulong Id { get; } @@ -20,4 +25,5 @@ public sealed class MemberData public bool Kicked { get; set; } public List<ulong> Roles { get; set; } = []; public List<Reminder> Reminders { get; } = []; + public List<Warn> Warns { get; } = []; } diff --git a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs index 6a4280e..53b7f63 100644 --- a/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs +++ b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs @@ -13,6 +13,7 @@ namespace TeamOctolings.Octobot.Data.Options; public enum AllOptionsEnum { [UsedImplicitly] Language, + [UsedImplicitly] WarnPunishment, [UsedImplicitly] WelcomeMessage, [UsedImplicitly] LeaveMessage, [UsedImplicitly] ReceiveStartupMessages, @@ -20,6 +21,7 @@ public enum AllOptionsEnum [UsedImplicitly] ReturnRolesOnRejoin, [UsedImplicitly] AutoStartEvents, [UsedImplicitly] RenameHoistedUsers, + [UsedImplicitly] WarnsThreshold, [UsedImplicitly] PublicFeedbackChannel, [UsedImplicitly] PrivateFeedbackChannel, [UsedImplicitly] WelcomeMessagesChannel, @@ -28,5 +30,6 @@ public enum AllOptionsEnum [UsedImplicitly] MuteRole, [UsedImplicitly] ModeratorRole, [UsedImplicitly] EventNotificationRole, - [UsedImplicitly] EventEarlyNotificationOffset + [UsedImplicitly] EventEarlyNotificationOffset, + [UsedImplicitly] WarnPunishmentDuration } diff --git a/TeamOctolings.Octobot/Data/Options/IntOption.cs b/TeamOctolings.Octobot/Data/Options/IntOption.cs new file mode 100644 index 0000000..3cdeb39 --- /dev/null +++ b/TeamOctolings.Octobot/Data/Options/IntOption.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Nodes; +using Remora.Results; + +namespace TeamOctolings.Octobot.Data.Options; + +public sealed class IntOption : GuildOption<int> +{ + public IntOption(string name, int defaultValue) : base(name, defaultValue) { } + + public override string Display(JsonNode settings) + { + return settings[Name]?.GetValue<string>() ?? "0"; + } + + public override Result Set(JsonNode settings, string from) + { + if (!int.TryParse(from, out _)) + { + return new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue); + } + + settings[Name] = from; + return Result.FromSuccess(); + } + + public override int Get(JsonNode settings) + { + var property = settings[Name]; + return property != null ? Convert.ToInt32(property.GetValue<string>()) : DefaultValue; + } +} diff --git a/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs b/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs new file mode 100644 index 0000000..b65ee68 --- /dev/null +++ b/TeamOctolings.Octobot/Data/Options/PunishmentOption.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Nodes; +using Remora.Results; + +namespace TeamOctolings.Octobot.Data.Options; + +/// <inheritdoc /> +public sealed class PunishmentOption : GuildOption<string> +{ + private static readonly List<string> AllowedValues = + [ + "ban", "kick", "mute", "off", "disable", "disabled" + ]; + + public PunishmentOption(string name, string defaultValue) : base(name, defaultValue) { } + + /// <inheritdoc /> + public override Result Set(JsonNode settings, string from) + { + return AllowedValues.Contains(from.ToLowerInvariant()) + ? base.Set(settings, from.ToLowerInvariant()) + : new ArgumentInvalidError(nameof(from), Messages.InvalidWarnPunishment); + } +} diff --git a/TeamOctolings.Octobot/Data/Warn.cs b/TeamOctolings.Octobot/Data/Warn.cs new file mode 100644 index 0000000..ab512e6 --- /dev/null +++ b/TeamOctolings.Octobot/Data/Warn.cs @@ -0,0 +1,8 @@ +namespace TeamOctolings.Octobot.Data; + +public struct Warn +{ + public ulong WarnedBy { get; init; } + public DateTimeOffset At { get; init; } + public string Reason { get; init; } +} diff --git a/TeamOctolings.Octobot/Messages.Designer.cs b/TeamOctolings.Octobot/Messages.Designer.cs index bbc1366..d56dc77 100644 --- a/TeamOctolings.Octobot/Messages.Designer.cs +++ b/TeamOctolings.Octobot/Messages.Designer.cs @@ -9,21 +9,21 @@ namespace TeamOctolings.Octobot { using System; - - + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Messages { - + private static System.Resources.ResourceManager resourceMan; - + private static System.Globalization.CultureInfo resourceCulture; - + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Messages() { } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Resources.ResourceManager ResourceManager { get { @@ -34,7 +34,7 @@ namespace TeamOctolings.Octobot { return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { @@ -44,1157 +44,1270 @@ namespace TeamOctolings.Octobot { resourceCulture = value; } } - + internal static string Ready { get { return ResourceManager.GetString("Ready", resourceCulture); } } - + internal static string CachedMessageDeleted { get { return ResourceManager.GetString("CachedMessageDeleted", resourceCulture); } } - + internal static string CachedMessageEdited { get { return ResourceManager.GetString("CachedMessageEdited", resourceCulture); } } - + internal static string DefaultWelcomeMessage { get { return ResourceManager.GetString("DefaultWelcomeMessage", resourceCulture); } } - + internal static string Generic1 { get { return ResourceManager.GetString("Generic1", resourceCulture); } } - + internal static string Generic2 { get { return ResourceManager.GetString("Generic2", resourceCulture); } } - + internal static string Generic3 { get { return ResourceManager.GetString("Generic3", resourceCulture); } } - + internal static string YouWereBanned { get { return ResourceManager.GetString("YouWereBanned", resourceCulture); } } - + internal static string PunishmentExpired { get { return ResourceManager.GetString("PunishmentExpired", resourceCulture); } } - + internal static string YouWereKicked { get { return ResourceManager.GetString("YouWereKicked", resourceCulture); } } - + internal static string Milliseconds { get { return ResourceManager.GetString("Milliseconds", resourceCulture); } } - + internal static string ChannelNotSpecified { get { return ResourceManager.GetString("ChannelNotSpecified", resourceCulture); } } - + internal static string RoleNotSpecified { get { return ResourceManager.GetString("RoleNotSpecified", resourceCulture); } } - + internal static string SettingsLanguage { get { return ResourceManager.GetString("SettingsLanguage", resourceCulture); } } - + internal static string SettingsPrefix { get { return ResourceManager.GetString("SettingsPrefix", resourceCulture); } } - + internal static string SettingsRemoveRolesOnMute { get { return ResourceManager.GetString("SettingsRemoveRolesOnMute", resourceCulture); } } - + internal static string SettingsSendWelcomeMessages { get { return ResourceManager.GetString("SettingsSendWelcomeMessages", resourceCulture); } } - + internal static string SettingsMuteRole { get { return ResourceManager.GetString("SettingsMuteRole", resourceCulture); } } - + internal static string LanguageNotSupported { get { return ResourceManager.GetString("LanguageNotSupported", resourceCulture); } } - + internal static string Yes { get { return ResourceManager.GetString("Yes", resourceCulture); } } - + internal static string No { get { return ResourceManager.GetString("No", resourceCulture); } } - + internal static string UserNotBanned { get { return ResourceManager.GetString("UserNotBanned", resourceCulture); } } - + internal static string MemberNotMuted { get { return ResourceManager.GetString("MemberNotMuted", resourceCulture); } } - + internal static string SettingsWelcomeMessage { get { return ResourceManager.GetString("SettingsWelcomeMessage", resourceCulture); } } - + internal static string UserBanned { get { return ResourceManager.GetString("UserBanned", resourceCulture); } } - + internal static string SettingsReceiveStartupMessages { get { return ResourceManager.GetString("SettingsReceiveStartupMessages", resourceCulture); } } - + internal static string InvalidSettingValue { get { return ResourceManager.GetString("InvalidSettingValue", resourceCulture); } } - + internal static string DurationRequiredForTimeOuts { get { return ResourceManager.GetString("DurationRequiredForTimeOuts", resourceCulture); } } - + internal static string CannotTimeOutBot { get { return ResourceManager.GetString("CannotTimeOutBot", resourceCulture); } } - + internal static string SettingsEventNotificationRole { get { return ResourceManager.GetString("SettingsEventNotificationRole", resourceCulture); } } - + internal static string SettingsEventNotificationChannel { get { return ResourceManager.GetString("SettingsEventNotificationChannel", resourceCulture); } } - + internal static string SettingsEventStartedReceivers { get { return ResourceManager.GetString("SettingsEventStartedReceivers", resourceCulture); } } - + internal static string EventStarted { get { return ResourceManager.GetString("EventStarted", resourceCulture); } } - + internal static string EventCancelled { get { return ResourceManager.GetString("EventCancelled", resourceCulture); } } - + internal static string EventCompleted { get { return ResourceManager.GetString("EventCompleted", resourceCulture); } } - + internal static string MessagesCleared { get { return ResourceManager.GetString("MessagesCleared", resourceCulture); } } - + internal static string SettingsNothingChanged { get { return ResourceManager.GetString("SettingsNothingChanged", resourceCulture); } } - + internal static string SettingNotDefined { get { return ResourceManager.GetString("SettingNotDefined", resourceCulture); } } - + internal static string MissingUser { get { return ResourceManager.GetString("MissingUser", resourceCulture); } } - + internal static string UserCannotBanMembers { get { return ResourceManager.GetString("UserCannotBanMembers", resourceCulture); } } - + internal static string UserCannotManageMessages { get { return ResourceManager.GetString("UserCannotManageMessages", resourceCulture); } } - + internal static string UserCannotKickMembers { get { return ResourceManager.GetString("UserCannotKickMembers", resourceCulture); } } - + internal static string UserCannotMuteMembers { get { return ResourceManager.GetString("UserCannotMuteMembers", resourceCulture); } } - + internal static string UserCannotUnmuteMembers { get { return ResourceManager.GetString("UserCannotUnmuteMembers", resourceCulture); } } - + internal static string UserCannotManageGuild { get { return ResourceManager.GetString("UserCannotManageGuild", resourceCulture); } } - + internal static string BotCannotBanMembers { get { return ResourceManager.GetString("BotCannotBanMembers", resourceCulture); } } - + internal static string BotCannotManageMessages { get { return ResourceManager.GetString("BotCannotManageMessages", resourceCulture); } } - + internal static string BotCannotKickMembers { get { return ResourceManager.GetString("BotCannotKickMembers", resourceCulture); } } - + internal static string BotCannotModerateMembers { get { return ResourceManager.GetString("BotCannotModerateMembers", resourceCulture); } } - + internal static string BotCannotManageGuild { get { return ResourceManager.GetString("BotCannotManageGuild", resourceCulture); } } - + internal static string UserCannotBanOwner { get { return ResourceManager.GetString("UserCannotBanOwner", resourceCulture); } } - + internal static string UserCannotBanThemselves { get { return ResourceManager.GetString("UserCannotBanThemselves", resourceCulture); } } - + internal static string UserCannotBanBot { get { return ResourceManager.GetString("UserCannotBanBot", resourceCulture); } } - + internal static string BotCannotBanTarget { get { return ResourceManager.GetString("BotCannotBanTarget", resourceCulture); } } - + internal static string UserCannotBanTarget { get { return ResourceManager.GetString("UserCannotBanTarget", resourceCulture); } } - + internal static string UserCannotKickOwner { get { return ResourceManager.GetString("UserCannotKickOwner", resourceCulture); } } - + internal static string UserCannotKickThemselves { get { return ResourceManager.GetString("UserCannotKickThemselves", resourceCulture); } } - + internal static string UserCannotKickBot { get { return ResourceManager.GetString("UserCannotKickBot", resourceCulture); } } - + internal static string BotCannotKickTarget { get { return ResourceManager.GetString("BotCannotKickTarget", resourceCulture); } } - + internal static string UserCannotKickTarget { get { return ResourceManager.GetString("UserCannotKickTarget", resourceCulture); } } - + internal static string UserCannotMuteOwner { get { return ResourceManager.GetString("UserCannotMuteOwner", resourceCulture); } } - + internal static string UserCannotMuteThemselves { get { return ResourceManager.GetString("UserCannotMuteThemselves", resourceCulture); } } - + internal static string UserCannotMuteBot { get { return ResourceManager.GetString("UserCannotMuteBot", resourceCulture); } } - + internal static string BotCannotMuteTarget { get { return ResourceManager.GetString("BotCannotMuteTarget", resourceCulture); } } - + internal static string UserCannotMuteTarget { get { return ResourceManager.GetString("UserCannotMuteTarget", resourceCulture); } } - + internal static string UserCannotUnmuteOwner { get { return ResourceManager.GetString("UserCannotUnmuteOwner", resourceCulture); } } - + internal static string UserCannotUnmuteThemselves { get { return ResourceManager.GetString("UserCannotUnmuteThemselves", resourceCulture); } } - + internal static string UserCannotUnmuteBot { get { return ResourceManager.GetString("UserCannotUnmuteBot", resourceCulture); } } - + internal static string BotCannotUnmuteTarget { get { return ResourceManager.GetString("BotCannotUnmuteTarget", resourceCulture); } } - + internal static string UserCannotUnmuteTarget { get { return ResourceManager.GetString("UserCannotUnmuteTarget", resourceCulture); } } - + internal static string EventEarlyNotification { get { return ResourceManager.GetString("EventEarlyNotification", resourceCulture); } } - + internal static string SettingsEventEarlyNotificationOffset { get { return ResourceManager.GetString("SettingsEventEarlyNotificationOffset", resourceCulture); } } - + internal static string UserNotFound { get { return ResourceManager.GetString("UserNotFound", resourceCulture); } } - + internal static string SettingsDefaultRole { get { return ResourceManager.GetString("SettingsDefaultRole", resourceCulture); } } - + internal static string SettingsPublicFeedbackChannel { get { return ResourceManager.GetString("SettingsPublicFeedbackChannel", resourceCulture); } } - + internal static string SettingsPrivateFeedbackChannel { get { return ResourceManager.GetString("SettingsPrivateFeedbackChannel", resourceCulture); } } - + internal static string SettingsReturnRolesOnRejoin { get { return ResourceManager.GetString("SettingsReturnRolesOnRejoin", resourceCulture); } } - + internal static string SettingsAutoStartEvents { get { return ResourceManager.GetString("SettingsAutoStartEvents", resourceCulture); } } - + internal static string IssuedBy { get { return ResourceManager.GetString("IssuedBy", resourceCulture); } } - + internal static string EventCreatedTitle { get { return ResourceManager.GetString("EventCreatedTitle", resourceCulture); } } - + internal static string DescriptionLocalEventCreated { get { return ResourceManager.GetString("DescriptionLocalEventCreated", resourceCulture); } } - + internal static string DescriptionExternalEventCreated { get { return ResourceManager.GetString("DescriptionExternalEventCreated", resourceCulture); } } - + internal static string ButtonOpenEventInfo { get { return ResourceManager.GetString("ButtonOpenEventInfo", resourceCulture); } } - + internal static string EventDuration { get { return ResourceManager.GetString("EventDuration", resourceCulture); } } - + internal static string DescriptionLocalEventStarted { get { return ResourceManager.GetString("DescriptionLocalEventStarted", resourceCulture); } } - + internal static string DescriptionExternalEventStarted { get { return ResourceManager.GetString("DescriptionExternalEventStarted", resourceCulture); } } - + internal static string UserAlreadyBanned { get { return ResourceManager.GetString("UserAlreadyBanned", resourceCulture); } } - + internal static string UserUnbanned { get { return ResourceManager.GetString("UserUnbanned", resourceCulture); } } - + internal static string UserMuted { get { return ResourceManager.GetString("UserMuted", resourceCulture); } } - + internal static string UserUnmuted { get { return ResourceManager.GetString("UserUnmuted", resourceCulture); } } - + internal static string UserNotMuted { get { return ResourceManager.GetString("UserNotMuted", resourceCulture); } } - + internal static string UserNotFoundShort { get { return ResourceManager.GetString("UserNotFoundShort", resourceCulture); } } - + internal static string UserKicked { get { return ResourceManager.GetString("UserKicked", resourceCulture); } } - + internal static string DescriptionActionReason { get { return ResourceManager.GetString("DescriptionActionReason", resourceCulture); } } - + internal static string DescriptionActionExpiresAt { get { return ResourceManager.GetString("DescriptionActionExpiresAt", resourceCulture); } } - + internal static string UserAlreadyMuted { get { return ResourceManager.GetString("UserAlreadyMuted", resourceCulture); } } - + internal static string MessageFrom { get { return ResourceManager.GetString("MessageFrom", resourceCulture); } } - + internal static string AboutTitleDevelopers { get { return ResourceManager.GetString("AboutTitleDevelopers", resourceCulture); } } - + internal static string ButtonOpenRepository { get { return ResourceManager.GetString("ButtonOpenRepository", resourceCulture); } } - + internal static string AboutBot { get { return ResourceManager.GetString("AboutBot", resourceCulture); } } - + internal static string AboutDeveloper_mctaylors { get { return ResourceManager.GetString("AboutDeveloper@mctaylors", resourceCulture); } } - + internal static string AboutDeveloper_Octol1ttle { get { return ResourceManager.GetString("AboutDeveloper@Octol1ttle", resourceCulture); } } - + internal static string AboutDeveloper_neroduckale { get { return ResourceManager.GetString("AboutDeveloper@neroduckale", resourceCulture); } } - + internal static string ReminderCreated { get { return ResourceManager.GetString("ReminderCreated", resourceCulture); } } - + internal static string Reminder { get { return ResourceManager.GetString("Reminder", resourceCulture); } } - + internal static string DescriptionReminder { get { return ResourceManager.GetString("DescriptionReminder", resourceCulture); } } - + internal static string SettingsListTitle { get { return ResourceManager.GetString("SettingsListTitle", resourceCulture); } } - + internal static string SettingSuccessfullyChanged { get { return ResourceManager.GetString("SettingSuccessfullyChanged", resourceCulture); } } - + internal static string SettingNotChanged { get { return ResourceManager.GetString("SettingNotChanged", resourceCulture); } } - + internal static string SettingIsNow { get { return ResourceManager.GetString("SettingIsNow", resourceCulture); } } - + internal static string SettingsRenameHoistedUsers { get { return ResourceManager.GetString("SettingsRenameHoistedUsers", resourceCulture); } } - + internal static string Page { get { return ResourceManager.GetString("Page", resourceCulture); } } - + internal static string PageNotFound { get { return ResourceManager.GetString("PageNotFound", resourceCulture); } } - + internal static string PagesAllowed { get { return ResourceManager.GetString("PagesAllowed", resourceCulture); } } - + internal static string Next { get { return ResourceManager.GetString("Next", resourceCulture); } } - + internal static string Previous { get { return ResourceManager.GetString("Previous", resourceCulture); } } - + internal static string ReminderList { get { return ResourceManager.GetString("ReminderList", resourceCulture); } } - + internal static string InvalidReminderPosition { get { return ResourceManager.GetString("InvalidReminderPosition", resourceCulture); } } - + internal static string ReminderDeleted { get { return ResourceManager.GetString("ReminderDeleted", resourceCulture); } } - + internal static string NoRemindersFound { get { return ResourceManager.GetString("NoRemindersFound", resourceCulture); } } - + internal static string SingleSettingReset { get { return ResourceManager.GetString("SingleSettingReset", resourceCulture); } } - + internal static string AllSettingsReset { get { return ResourceManager.GetString("AllSettingsReset", resourceCulture); } } - + internal static string DescriptionActionJumpToMessage { get { return ResourceManager.GetString("DescriptionActionJumpToMessage", resourceCulture); } } - + internal static string DescriptionActionJumpToChannel { get { return ResourceManager.GetString("DescriptionActionJumpToChannel", resourceCulture); } } - + internal static string ReminderPosition { get { return ResourceManager.GetString("ReminderPosition", resourceCulture); } } - + internal static string ReminderTime { get { return ResourceManager.GetString("ReminderTime", resourceCulture); } } - + internal static string ReminderText { get { return ResourceManager.GetString("ReminderText", resourceCulture); } } - + internal static string UserInfoDisplayName { get { return ResourceManager.GetString("UserInfoDisplayName", resourceCulture); } } - + internal static string InformationAbout { get { return ResourceManager.GetString("InformationAbout", resourceCulture); } } - + internal static string UserInfoMuted { get { return ResourceManager.GetString("UserInfoMuted", resourceCulture); } } - + internal static string UserInfoDiscordUserSince { get { return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture); } } - + internal static string UserInfoBanned { get { return ResourceManager.GetString("UserInfoBanned", resourceCulture); } } - + internal static string UserInfoPunishments { get { return ResourceManager.GetString("UserInfoPunishments", resourceCulture); } } - + internal static string UserInfoBannedPermanently { get { return ResourceManager.GetString("UserInfoBannedPermanently", resourceCulture); } } - + internal static string UserInfoNotOnGuild { get { return ResourceManager.GetString("UserInfoNotOnGuild", resourceCulture); } } - + internal static string UserInfoMutedByTimeout { get { return ResourceManager.GetString("UserInfoMutedByTimeout", resourceCulture); } } - + internal static string UserInfoMutedByMuteRole { get { return ResourceManager.GetString("UserInfoMutedByMuteRole", resourceCulture); } } - + internal static string UserInfoGuildMemberSince { get { return ResourceManager.GetString("UserInfoGuildMemberSince", resourceCulture); } } - + internal static string UserInfoGuildNickname { get { return ResourceManager.GetString("UserInfoGuildNickname", resourceCulture); } } - + internal static string UserInfoGuildRoles { get { return ResourceManager.GetString("UserInfoGuildRoles", resourceCulture); } } - + internal static string UserInfoGuildMemberPremiumSince { get { return ResourceManager.GetString("UserInfoGuildMemberPremiumSince", resourceCulture); } } - + internal static string RandomTitle { get { return ResourceManager.GetString("RandomTitle", resourceCulture); } } - + internal static string RandomMinMaxSame { get { return ResourceManager.GetString("RandomMinMaxSame", resourceCulture); } } - + internal static string RandomMin { get { return ResourceManager.GetString("RandomMin", resourceCulture); } } - + internal static string RandomMax { get { return ResourceManager.GetString("RandomMax", resourceCulture); } } - + internal static string Default { get { return ResourceManager.GetString("Default", resourceCulture); } } - + internal static string TimestampTitle { get { return ResourceManager.GetString("TimestampTitle", resourceCulture); } } - + internal static string TimestampOffset { get { return ResourceManager.GetString("TimestampOffset", resourceCulture); } } - + internal static string GuildInfoDescription { get { return ResourceManager.GetString("GuildInfoDescription", resourceCulture); } } - + internal static string GuildInfoCreatedAt { get { return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture); } } - + internal static string GuildInfoOwner { get { return ResourceManager.GetString("GuildInfoOwner", resourceCulture); } } - + internal static string GuildInfoServerBoost { get { return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture); } } - + internal static string GuildInfoBoostTier { get { return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture); } } - + internal static string GuildInfoBoostCount { get { return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture); } } - + internal static string NoMessagesToClear { get { return ResourceManager.GetString("NoMessagesToClear", resourceCulture); } } - + internal static string MessagesClearedFiltered { get { return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture); } } - + internal static string DataLoadFailedTitle { get { return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture); } } - + internal static string DataLoadFailedDescription { get { return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture); } } - + internal static string CommandExecutionFailed { get { return ResourceManager.GetString("CommandExecutionFailed", resourceCulture); } } - + internal static string ContactDevelopers { get { return ResourceManager.GetString("ContactDevelopers", resourceCulture); } } - + internal static string ButtonReportIssue { get { return ResourceManager.GetString("ButtonReportIssue", resourceCulture); } } - + internal static string DefaultLeaveMessage { get { return ResourceManager.GetString("DefaultLeaveMessage", resourceCulture); } } - + internal static string SettingsLeaveMessage { get { return ResourceManager.GetString("SettingsLeaveMessage", resourceCulture); } } - + internal static string InvalidTimeSpan { get { return ResourceManager.GetString("InvalidTimeSpan", resourceCulture); } } - + internal static string UserInfoKicked { get { return ResourceManager.GetString("UserInfoKicked", resourceCulture); } } - + internal static string ReminderEdited { get { return ResourceManager.GetString("ReminderEdited", resourceCulture); } } - + internal static string EightBallPositive1 { get { return ResourceManager.GetString("EightBallPositive1", resourceCulture); } } - + internal static string EightBallPositive2 { get { return ResourceManager.GetString("EightBallPositive2", resourceCulture); } } - + internal static string EightBallPositive3 { get { return ResourceManager.GetString("EightBallPositive3", resourceCulture); } } - + internal static string EightBallPositive4 { get { return ResourceManager.GetString("EightBallPositive4", resourceCulture); } } - + internal static string EightBallPositive5 { get { return ResourceManager.GetString("EightBallPositive5", resourceCulture); } } - + internal static string EightBallQuestionable1 { get { return ResourceManager.GetString("EightBallQuestionable1", resourceCulture); } } - + internal static string EightBallQuestionable2 { get { return ResourceManager.GetString("EightBallQuestionable2", resourceCulture); } } - + internal static string EightBallQuestionable3 { get { return ResourceManager.GetString("EightBallQuestionable3", resourceCulture); } } - + internal static string EightBallQuestionable4 { get { return ResourceManager.GetString("EightBallQuestionable4", resourceCulture); } } - + internal static string EightBallQuestionable5 { get { return ResourceManager.GetString("EightBallQuestionable5", resourceCulture); } } - + internal static string EightBallNeutral1 { get { return ResourceManager.GetString("EightBallNeutral1", resourceCulture); } } - + internal static string EightBallNeutral2 { get { return ResourceManager.GetString("EightBallNeutral2", resourceCulture); } } - + internal static string EightBallNeutral3 { get { return ResourceManager.GetString("EightBallNeutral3", resourceCulture); } } - + internal static string EightBallNeutral4 { get { return ResourceManager.GetString("EightBallNeutral4", resourceCulture); } } - + internal static string EightBallNeutral5 { get { return ResourceManager.GetString("EightBallNeutral5", resourceCulture); } } - + internal static string EightBallNegative1 { get { return ResourceManager.GetString("EightBallNegative1", resourceCulture); } } - + internal static string EightBallNegative2 { get { return ResourceManager.GetString("EightBallNegative2", resourceCulture); } } - + internal static string EightBallNegative3 { get { return ResourceManager.GetString("EightBallNegative3", resourceCulture); } } - + internal static string EightBallNegative4 { get { return ResourceManager.GetString("EightBallNegative4", resourceCulture); } } - + internal static string EightBallNegative5 { get { return ResourceManager.GetString("EightBallNegative5", resourceCulture); } } - + internal static string TimeSpanExample { get { return ResourceManager.GetString("TimeSpanExample", resourceCulture); } } - + internal static string Version { get { return ResourceManager.GetString("Version", resourceCulture); } } - + internal static string SettingsWelcomeMessagesChannel { get { return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture); } } - + + internal static string UserWarned { + get { + return ResourceManager.GetString("UserWarned", resourceCulture); + } + } + + internal static string UserWarnsRemoved { + get { + return ResourceManager.GetString("UserWarnsRemoved", resourceCulture); + } + } + + internal static string YouHaveBeenWarned { + get { + return ResourceManager.GetString("YouHaveBeenWarned", resourceCulture); + } + } + + internal static string YourWarningsHaveBeenRevoked { + get { + return ResourceManager.GetString("YourWarningsHaveBeenRevoked", resourceCulture); + } + } + + internal static string DescriptionWarns { + get { + return ResourceManager.GetString("DescriptionWarns", resourceCulture); + } + } + + internal static string UserHasNoWarnings { + get { + return ResourceManager.GetString("UserHasNoWarnings", resourceCulture); + } + } + + internal static string YouHaveNoWarnings { + get { + return ResourceManager.GetString("YouHaveNoWarnings", resourceCulture); + } + } + + internal static string ReceivedTooManyWarnings { + get { + return ResourceManager.GetString("ReceivedTooManyWarnings", resourceCulture); + } + } internal static string ButtonDirty { get { return ResourceManager.GetString("ButtonDirty", resourceCulture); } } - + internal static string ButtonOpenWiki { get { return ResourceManager.GetString("ButtonOpenWiki", resourceCulture); } } - + internal static string SettingsModeratorRole { get { return ResourceManager.GetString("SettingsModeratorRole", resourceCulture); } } + + internal static string ListTargetWarnsTitle { + get { + return ResourceManager.GetString("ListTargetWarnsTitle", resourceCulture); + } + } + + internal static string ReceivedOn { + get { + return ResourceManager.GetString("ReceivedOn", resourceCulture); + } + } + + internal static string UserWarnRemoved { + get { + return ResourceManager.GetString("UserWarnRemoved", resourceCulture); + } + } + + internal static string YourWarningHasBeenRevoked { + get { + return ResourceManager.GetString("YourWarningHasBeenRevoked", resourceCulture); + } + } + + internal static string WrongWarningNumberSelected { + get { + return ResourceManager.GetString("WrongWarningNumberSelected", resourceCulture); + } + } + + internal static string ListExecutorWarnsTitle { + get { + return ResourceManager.GetString("ListExecutorWarnsTitle", resourceCulture); + } + } + + internal static string DescriptionPunishmentType { + get { + return ResourceManager.GetString("DescriptionPunishmentType", resourceCulture); + } + } + + internal static string WarnThresholdExceeded { + get { + return ResourceManager.GetString("WarnThresholdExceeded", resourceCulture); + } + } + + internal static string WarnPunishmentDurationNotSet { + get { + return ResourceManager.GetString("WarnPunishmentDurationNotSet", resourceCulture); + } + } + + internal static string WarnThresholdExceededDescription { + get { + return ResourceManager.GetString("WarnThresholdExceededDescription", resourceCulture); + } + } + + internal static string InvalidWarnPunishment { + get { + return ResourceManager.GetString("InvalidWarnPunishment", resourceCulture); + } + } } } diff --git a/TeamOctolings.Octobot/Messages.resx b/TeamOctolings.Octobot/Messages.resx index 47e7d4f..037ad3f 100644 --- a/TeamOctolings.Octobot/Messages.resx +++ b/TeamOctolings.Octobot/Messages.resx @@ -681,4 +681,124 @@ <data name="SettingsModeratorRole" xml:space="preserve"> <value>Moderator role</value> </data> + <data name="UserWarned" xml:space="preserve"> + <value>{0} received a warning</value> + </data> + <data name="UserWarnsRemoved" xml:space="preserve"> + <value>{0} no longer has warnings</value> + </data> + <data name="YouHaveBeenWarned" xml:space="preserve"> + <value>You have been warned</value> + </data> + <data name="YourWarningsHaveBeenRevoked" xml:space="preserve"> + <value>Your warnings have been revoked</value> + </data> + <data name="DescriptionWarns" xml:space="preserve"> + <value>Warns: {0}</value> + </data> + <data name="UserHasNoWarnings" xml:space="preserve"> + <value>This user has no warnings!</value> + </data> + <data name="ReceivedTooManyWarnings" xml:space="preserve"> + <value>Received too many warnings</value> + </data> + <data name="SettingsWarnPunishment" xml:space="preserve"> + <value>Punishment type for warnings</value> + </data> + <data name="SettingsWarnThreshold" xml:space="preserve"> + <value>Warnings threshold</value> + </data> + <data name="SettingsWarnPunishmentDuration" xml:space="preserve"> + <value>Punishment duration for warnings</value> + </data> + <data name="ListExecutorWarnsTitle" xml:space="preserve"> + <value>Here's your warnings, {0}:</value> + </data> + <data name="YouHaveNoWarnings" xml:space="preserve"> + <value>You have no warnings!</value> + </data> + <data name="ReceivedOn" xml:space="preserve"> + <value>Received on {0}</value> + </data> + <data name="UserWarnRemoved" xml:space="preserve"> + <value>Warning #{0} has been removed from {1}</value> + </data> + <data name="YourWarningHasBeenRevoked" xml:space="preserve"> + <value>Your warning has been revoked</value> + </data> + <data name="WrongWarningNumberSelected" xml:space="preserve"> + <value>Wrong warning number selected!</value> + </data> + <data name="UserCannotWarnBot" xml:space="preserve"> + <value>You cannot warn me!</value> + </data> + <data name="UserCannotWarnOwner" xml:space="preserve"> + <value>You cannot warn the owner of this guild!</value> + </data> + <data name="UserCannotWarnTarget" xml:space="preserve"> + <value>You cannot warn this member!</value> + </data> + <data name="UserCannotWarnThemselves" xml:space="preserve"> + <value>You cannot warn yourself!</value> + </data> + <data name="UserCannotUnwarnBot" xml:space="preserve"> + <value>You cannot unwarn me!</value> + </data> + <data name="UserCannotUnwarnOwner" xml:space="preserve"> + <value>You cannot unwarn the owner of this guild!</value> + </data> + <data name="UserCannotUnwarnTarget" xml:space="preserve"> + <value>You cannot unwarn this member!</value> + </data> + <data name="UserCannotUnwarnThemselves" xml:space="preserve"> + <value>You cannot unwarn yourself!</value> + </data> + <data name="BotCannotWarnMembers" xml:space="preserve"> + <value>I cannot warn members from this guild!</value> + </data> + <data name="BotCannotWarnTarget" xml:space="preserve"> + <value>I cannot warn this member!</value> + </data> + <data name="BotCannotUnwarnTarget" xml:space="preserve"> + <value>I cannot unwarn this member!</value> + </data> + <data name="UserCannotGetWarnsBot" xml:space="preserve"> + <value>You cannot get my warns!</value> + </data> + <data name="UserCannotGetWarnsOwner" xml:space="preserve"> + <value>You cannot get owner's warns!</value> + </data> + <data name="UserCannotGetWarnsTarget" xml:space="preserve"> + <value>You cannot get warns of this member!</value> + </data> + <data name="UserCannotGetWarnsThemselves" xml:space="preserve"> + <value>Use this command without options instead.</value> + </data> + <data name="UserCannotWarnMembers" xml:space="preserve"> + <value>You cannot warn members in this guild!</value> + </data> + <data name="UserCannotUnwarnMembers" xml:space="preserve"> + <value>You cannot unwarn members in this guild!</value> + </data> + <data name="UserCannotGetWarnsMembers" xml:space="preserve"> + <value>You cannot get warns of other members in this guild!</value> + </data> + <data name="ListTargetWarnsTitle" xml:space="preserve"> + <value>Warnings given to {0}:</value> + </data> + <data name="DescriptionPunishmentType" xml:space="preserve"> + <value>Punishment type: {0}</value> + </data> + <data name="WarnThresholdExceeded" xml:space="preserve"> + <value>Warn threshold has been exceeded. ({0})</value> + </data> + <data name="WarnPunishmentDurationNotSet" xml:space="preserve"> + <value>Warn Punishment Duration is not set for the current punishment type.</value> + </data> + <data name="WarnThresholdExceededDescription" xml:space="preserve"> + <value>Increase the Warn Threshold or set a Warn Punishment.</value> + </data> + <data name="InvalidWarnPunishment" xml:space="preserve"> + <value>Invalid Warn Punishment is set.</value> + </data> </root> diff --git a/TeamOctolings.Octobot/Messages.ru.resx b/TeamOctolings.Octobot/Messages.ru.resx index 2eef257..68062d4 100644 --- a/TeamOctolings.Octobot/Messages.ru.resx +++ b/TeamOctolings.Octobot/Messages.ru.resx @@ -681,4 +681,124 @@ <data name="SettingsModeratorRole" xml:space="preserve"> <value>Роль модератора</value> </data> + <data name="UserWarned" xml:space="preserve"> + <value>{0} получил предупреждение</value> + </data> + <data name="UserWarnsRemoved" xml:space="preserve"> + <value>{0} больше не имеет предупреждений</value> + </data> + <data name="YouHaveBeenWarned" xml:space="preserve"> + <value>Вы получили предупреждение</value> + </data> + <data name="YourWarningsHaveBeenRevoked" xml:space="preserve"> + <value>Ваши предупреждения были отозваны</value> + </data> + <data name="DescriptionWarns" xml:space="preserve"> + <value>Предупреждений: {0}</value> + </data> + <data name="UserHasNoWarnings" xml:space="preserve"> + <value>Этот пользователь не имеет предупреждений!</value> + </data> + <data name="ReceivedTooManyWarnings" xml:space="preserve"> + <value>Получил слишком много предупреждений</value> + </data> + <data name="SettingsWarnPunishment" xml:space="preserve"> + <value>Тип наказания для предупреждений</value> + </data> + <data name="SettingsWarnThreshold" xml:space="preserve"> + <value>Порог предупреждений</value> + </data> + <data name="SettingsWarnPunishmentDuration" xml:space="preserve"> + <value>Длительность наказания для предупреждений</value> + </data> + <data name="ListExecutorWarnsTitle" xml:space="preserve"> + <value>Вот ваши предупреждения, {0}:</value> + </data> + <data name="YouHaveNoWarnings" xml:space="preserve"> + <value>У вас нет предупреждений!</value> + </data> + <data name="ReceivedOn" xml:space="preserve"> + <value>Получено {0}</value> + </data> + <data name="UserWarnRemoved" xml:space="preserve"> + <value>Предупреждение №{0} было снято с {1}</value> + </data> + <data name="YourWarningHasBeenRevoked" xml:space="preserve"> + <value>Ваше предупреждение было отозвано</value> + </data> + <data name="WrongWarningNumberSelected" xml:space="preserve"> + <value>Выбрано неверное число предупреждения!</value> + </data> + <data name="UserCannotWarnBot" xml:space="preserve"> + <value>Ты не можешь меня предупредить!</value> + </data> + <data name="UserCannotWarnOwner" xml:space="preserve"> + <value>Ты не можешь предупредить владельца этого сервера!</value> + </data> + <data name="UserCannotWarnTarget" xml:space="preserve"> + <value>Ты не можешь предупредить этого участника!</value> + </data> + <data name="UserCannotWarnThemselves" xml:space="preserve"> + <value>Ты не можешь себя предупредить!</value> + </data> + <data name="UserCannotUnwarnBot" xml:space="preserve"> + <value>Ты не можешь снять с меня предупреждения!</value> + </data> + <data name="UserCannotUnwarnOwner" xml:space="preserve"> + <value>Ты не можешь снять предупреждения с владельца этого сервера!</value> + </data> + <data name="UserCannotUnwarnTarget" xml:space="preserve"> + <value>Ты не можешь снять предупреждения с этого участника!</value> + </data> + <data name="UserCannotUnwarnThemselves" xml:space="preserve"> + <value>Ты не можешь снять с себя предупреждения!</value> + </data> + <data name="BotCannotUnwarnTarget" xml:space="preserve"> + <value>Я не могу снимать предупреждения этого участника!</value> + </data> + <data name="BotCannotWarnTarget" xml:space="preserve"> + <value>Я не могу предупредить этого участника!</value> + </data> + <data name="BotCannotWarnMembers" xml:space="preserve"> + <value>Я не могу предупреждать участников этого сервера!</value> + </data> + <data name="UserCannotGetWarnsBot" xml:space="preserve"> + <value>Ты не можешь просмотреть мои предупреждения!</value> + </data> + <data name="UserCannotGetWarnsOwner" xml:space="preserve"> + <value>Ты не можешь просмотреть предупреждения владельца этого сервера!</value> + </data> + <data name="UserCannotGetWarnsTarget" xml:space="preserve"> + <value>Ты не можешь просмотреть предупреждения этого участника!</value> + </data> + <data name="UserCannotGetWarnsThemselves" xml:space="preserve"> + <value>Вместо этого, используйте эту команду без параметров.</value> + </data> + <data name="UserCannotUnwarnMembers" xml:space="preserve"> + <value>Ты не можешь снимать предупреждения с участников этого сервера!</value> + </data> + <data name="UserCannotWarnMembers" xml:space="preserve"> + <value>Ты не можешь предупреждать участников этого сервера!</value> + </data> + <data name="UserCannotGetWarnsMembers" xml:space="preserve"> + <value>Ты не можешь просматривать предупреждения участников этого сервера!</value> + </data> + <data name="ListTargetWarnsTitle" xml:space="preserve"> + <value>Предупреждения пользователя {0}:</value> + </data> + <data name="DescriptionPunishmentType" xml:space="preserve"> + <value>Тип наказания: {0}</value> + </data> + <data name="WarnThresholdExceeded" xml:space="preserve"> + <value>Превышен порог предупреждений. ({0})</value> + </data> + <data name="WarnPunishmentDurationNotSet" xml:space="preserve"> + <value>Длительность наказания предупреждения не установлена для текущего типа наказания.</value> + </data> + <data name="WarnThresholdExceededDescription" xml:space="preserve"> + <value>Увеличьте порог предупреждения или установите наказание за предупреждение.</value> + </data> + <data name="InvalidWarnPunishment" xml:space="preserve"> + <value>Установлено неверное наказание за предупреждение.</value> + </data> </root> diff --git a/TeamOctolings.Octobot/Services/AccessControlService.cs b/TeamOctolings.Octobot/Services/AccessControlService.cs index d39c9e5..826983e 100644 --- a/TeamOctolings.Octobot/Services/AccessControlService.cs +++ b/TeamOctolings.Octobot/Services/AccessControlService.cs @@ -100,7 +100,8 @@ public sealed class AccessControlService { "Ban" => DiscordPermission.BanMembers, "Kick" => DiscordPermission.KickMembers, - "Mute" or "Unmute" => DiscordPermission.ModerateMembers, + "Mute" or "Unmute" or "Warn" or "Unwarn" or "GetWarns" + => DiscordPermission.ModerateMembers, _ => throw new Exception() });