From 398abad27796eb1ee57ec382ad5fd46331fe930f Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:23:42 +0300 Subject: [PATCH 01/14] Remove unused IDiscordRestChannelAPI in ToolsCommandGroup (#273) Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com> --- src/Commands/ToolsCommandGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs index ea91e1e..9a7c9b4 100644 --- a/src/Commands/ToolsCommandGroup.cs +++ b/src/Commands/ToolsCommandGroup.cs @@ -35,7 +35,7 @@ public class ToolsCommandGroup : CommandGroup public ToolsCommandGroup( ICommandContext context, IFeedbackService feedback, GuildDataService guildData, IDiscordRestGuildAPI guildApi, - IDiscordRestUserAPI userApi, IDiscordRestChannelAPI channelApi) + IDiscordRestUserAPI userApi) { _context = context; _guildData = guildData; From 1894b063aeb2fc611202a32ad068a59679099a1b Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:26:04 +0300 Subject: [PATCH 02/14] Fix auto-unban log spam (#271) Closes #255 Signed-off-by: mctaylors --- src/Services/Update/MemberUpdateService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs index 7674bbe..dfe8219 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/src/Services/Update/MemberUpdateService.cs @@ -151,6 +151,13 @@ public sealed partial class MemberUpdateService : BackgroundService return Result.FromSuccess(); } + var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, id, ct); + if (!existingBanResult.IsDefined()) + { + data.BannedUntil = null; + return Result.FromSuccess(); + } + var unbanResult = await _guildApi.RemoveGuildBanAsync( guildId, id, Messages.PunishmentExpired.EncodeHeader(), ct); if (unbanResult.IsSuccess) From 771750c92282d61bcd1421ee406c92bd97331706 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:27:35 +0300 Subject: [PATCH 03/14] Rename locale Sound to Loaded for clarity (#270) If you go through the locales, sooner or later you will notice `Sound*`, which is used in `GuildLoadedResponder.cs`. A new contributor (most likely) will not understand what it is used for at once, because we use `$"Loaded{i}".Localized()` instead of `Messages.Sound*` directly. Also, if you change the locale's value, for example the same "Loaded!", `Sound` will not fit anymore, because "Loaded!" is not a sound, but a phrase. Other suggestions are welcome. Signed-off-by: mctaylors --- locale/Messages.resx | 6 +++--- locale/Messages.ru.resx | 6 +++--- locale/Messages.tt-ru.resx | 6 +++--- src/Messages.Designer.cs | 12 ++++++------ src/Responders/GuildLoadedResponder.cs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index c8ef510..c2b9abb 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -117,13 +117,13 @@ {0}, welcome to {1} - + Veemo! - + Woomy! - + Ngyes! diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index eb8e57b..814106a 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -117,13 +117,13 @@ {0}, добро пожаловать на сервер {1} - + Виимо! - + Вууми! - + Нгьес! diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index df50d8d..71357ad 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -117,13 +117,13 @@ {0}, добро пожаловать на сервер {1} - + вииимо! - + вуууми! - + нгьес! diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 5ad741e..4694254 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -66,21 +66,21 @@ namespace Octobot { } } - internal static string Sound1 { + internal static string Loaded1 { get { - return ResourceManager.GetString("Sound1", resourceCulture); + return ResourceManager.GetString("Loaded1", resourceCulture); } } - internal static string Sound2 { + internal static string Loaded2 { get { - return ResourceManager.GetString("Sound2", resourceCulture); + return ResourceManager.GetString("Loaded2", resourceCulture); } } - internal static string Sound3 { + internal static string Loaded3 { get { - return ResourceManager.GetString("Sound3", resourceCulture); + return ResourceManager.GetString("Loaded3", resourceCulture); } } diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index a1e7d16..fd289fc 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -88,7 +88,7 @@ public class GuildLoadedResponder : IResponder var i = Random.Shared.Next(1, 4); var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot) - .WithTitle($"Sound{i}".Localized()) + .WithTitle($"Loaded{i}".Localized()) .WithDescription(Messages.Ready) .WithCurrentTimestamp() .WithColour(ColorsList.Blue) From 2342116e87af94406cfc2d745a55739f174b7676 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:51:32 +0300 Subject: [PATCH 04/14] Add GitInfo NuGet package (#268) In this PR, I added a NuGet package called GitInfo. It can replace Octobot.RepositoryUrl and display the bot version as the current branch and commit. --------- Signed-off-by: mctaylors Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com> --- Octobot.csproj | 2 + locale/Messages.resx | 3 ++ locale/Messages.ru.resx | 3 ++ locale/Messages.tt-ru.resx | 3 ++ src/BuildInfo.cs | 52 +++++++++++++++++++ src/Commands/AboutCommandGroup.cs | 5 +- .../Events/ErrorLoggingPostExecutionEvent.cs | 2 +- src/Messages.Designer.cs | 9 +++- src/Octobot.cs | 3 -- src/Responders/GuildLoadedResponder.cs | 2 +- 10 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 src/BuildInfo.cs diff --git a/Octobot.csproj b/Octobot.csproj index ab76400..bdfb46a 100644 --- a/Octobot.csproj +++ b/Octobot.csproj @@ -17,10 +17,12 @@ en A general-purpose Discord bot for moderation written in C# docs/octobot.ico + false + diff --git a/locale/Messages.resx b/locale/Messages.resx index c2b9abb..c2be4cd 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -657,6 +657,9 @@ Example of a valid input: `1h30m` + + Version: {0} + Welcome messages channel diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index 814106a..d38509c 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -657,6 +657,9 @@ Пример правильного ввода: `1ч30м` + + Версия: {0} + Канал для приветствий diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index 71357ad..dfb1ee6 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -657,6 +657,9 @@ правильно пишут так: `1h30m` + + {0} + канал куда говорить здравствуйте diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs new file mode 100644 index 0000000..50f86a2 --- /dev/null +++ b/src/BuildInfo.cs @@ -0,0 +1,52 @@ +namespace Octobot; + +public static class BuildInfo +{ + public static string RepositoryUrl + { + get + { + return ThisAssembly.Git.RepositoryUrl; + } + } + + public static string IssuesUrl + { + get + { + return $"{RepositoryUrl}/issues"; + } + } + + private static string Commit + { + get + { + return ThisAssembly.Git.Commit; + } + } + + private static string Branch + { + get + { + return ThisAssembly.Git.Branch; + } + } + + private static bool IsDirty + { + get + { + return ThisAssembly.Git.IsDirty; + } + } + + public static string Version + { + get + { + return IsDirty ? $"{Branch}-{Commit}-dirty" : $"{Branch}-{Commit}"; + } + } +} diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index e978ec9..05b1855 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -101,20 +101,21 @@ public class AboutCommandGroup : CommandGroup .WithDescription(builder.ToString()) .WithColour(ColorsList.Cyan) .WithImageUrl("https://i.ibb.co/fS6wZhh/octobot-banner.png") + .WithFooter(string.Format(Messages.Version, BuildInfo.Version)) .Build(); var repositoryButton = new ButtonComponent( ButtonComponentStyle.Link, Messages.ButtonOpenRepository, new PartialEmoji(Name: "🌐"), - URL: Octobot.RepositoryUrl + URL: BuildInfo.RepositoryUrl ); var issuesButton = new ButtonComponent( ButtonComponentStyle.Link, Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: Octobot.IssuesUrl + URL: BuildInfo.IssuesUrl ); return await _feedback.SendContextualEmbedResultAsync(embed, diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 87cfc84..5d7830b 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -72,7 +72,7 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent ButtonComponentStyle.Link, Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: Octobot.IssuesUrl + URL: BuildInfo.IssuesUrl ); return await _feedback.SendContextualEmbedResultAsync(embed, diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 4694254..707c814 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1185,8 +1185,13 @@ namespace Octobot { } } - internal static string SettingsWelcomeMessagesChannel - { + internal static string Version { + get { + return ResourceManager.GetString("Version", resourceCulture); + } + } + + internal static string SettingsWelcomeMessagesChannel { get { return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture); } diff --git a/src/Octobot.cs b/src/Octobot.cs index 1ebf7c3..e0d9b07 100644 --- a/src/Octobot.cs +++ b/src/Octobot.cs @@ -22,9 +22,6 @@ namespace Octobot; public sealed class Octobot { - public const string RepositoryUrl = "https://github.com/TeamOctolings/Octobot"; - public const string IssuesUrl = $"{RepositoryUrl}/issues"; - public static readonly AllowedMentions NoMentions = new( Array.Empty(), Array.Empty(), Array.Empty()); diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index fd289fc..c493910 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -117,7 +117,7 @@ public class GuildLoadedResponder : IResponder ButtonComponentStyle.Link, Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: Octobot.IssuesUrl + URL: BuildInfo.IssuesUrl ); return await _channelApi.CreateMessageWithEmbedResultAsync(channel, embedResult: errorEmbed, From cd75780582d82cb53c252054ddf287f9aa721f4d Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:18:40 +0300 Subject: [PATCH 05/14] Add "Member left" message (#234) Closes #231 --------- Signed-off-by: mctaylors Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com> --- locale/Messages.resx | 6 ++ locale/Messages.ru.resx | 6 ++ locale/Messages.tt-ru.resx | 6 ++ src/Commands/BanCommandGroup.cs | 8 ++- src/Commands/KickCommandGroup.cs | 6 +- src/Commands/SettingsCommandGroup.cs | 1 + src/Data/GuildSettings.cs | 16 ++++- src/Data/Options/AllOptionsEnum.cs | 1 + src/Messages.Designer.cs | 24 ++++--- src/Responders/GuildMemberLeftResponder.cs | 73 ++++++++++++++++++++++ 10 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/Responders/GuildMemberLeftResponder.cs diff --git a/locale/Messages.resx b/locale/Messages.resx index c2be4cd..dfaff85 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -585,6 +585,12 @@ Report an issue + + See you soon, {0}! + + + Leave message + Time specified incorrectly! diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index d38509c..6169a13 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -585,6 +585,12 @@ Сообщить о проблеме + + До скорой встречи, {0}! + + + Сообщение о выходе + Неправильно указано время! diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index dfb1ee6..3118e74 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -585,6 +585,12 @@ зарепортить баг + + ну, мы потеряли {0} + + + до свидания (типо настройка) + ты там правильно напиши таймспан diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index c350729..30cff14 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -181,17 +181,19 @@ public class BanCommandGroup : CommandGroup await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); } + var memberData = data.GetOrCreateMemberData(target.ID); + memberData.BannedUntil + = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; + var banResult = await _guildApi.CreateGuildBanAsync( guild.ID, target.ID, reason: $"({executor.GetTag()}) {reason}".EncodeHeader(), ct: ct); if (!banResult.IsSuccess) { + memberData.BannedUntil = null; return Result.FromError(banResult.Error); } - var memberData = data.GetOrCreateMemberData(target.ID); - memberData.BannedUntil - = duration is not null ? DateTimeOffset.UtcNow.Add(duration.Value) : DateTimeOffset.MaxValue; memberData.Roles.Clear(); var embed = new EmbedBuilder().WithSmallTitle( diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs index 0faa1d3..59c4045 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/src/Commands/KickCommandGroup.cs @@ -143,17 +143,19 @@ public class KickCommandGroup : CommandGroup await _channelApi.CreateMessageWithEmbedResultAsync(dmChannel.ID, embedResult: dmEmbed, ct: ct); } + var memberData = data.GetOrCreateMemberData(target.ID); + memberData.Kicked = true; + var kickResult = await _guildApi.RemoveGuildMemberAsync( guild.ID, target.ID, $"({executor.GetTag()}) {reason}".EncodeHeader(), ct); if (!kickResult.IsSuccess) { + memberData.Kicked = false; return Result.FromError(kickResult.Error); } - var memberData = data.GetOrCreateMemberData(target.ID); memberData.Roles.Clear(); - memberData.Kicked = true; var title = string.Format(Messages.UserKicked, target.GetTag()); var description = MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)); diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index 86f031f..97ebc32 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -39,6 +39,7 @@ public class SettingsCommandGroup : CommandGroup [ GuildSettings.Language, GuildSettings.WelcomeMessage, + GuildSettings.LeaveMessage, GuildSettings.ReceiveStartupMessages, GuildSettings.RemoveRolesOnMute, GuildSettings.ReturnRolesOnRejoin, diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs index 5a99505..518465b 100644 --- a/src/Data/GuildSettings.cs +++ b/src/Data/GuildSettings.cs @@ -13,17 +13,29 @@ public static class GuildSettings public static readonly LanguageOption Language = new("Language", "en"); /// - /// Controls what message should be sent in when a new member joins the server. + /// Controls what message should be sent in when a new member joins the guild. /// /// /// /// No message will be sent if set to "off", "disable" or "disabled". - /// will be sent if set to "default" or "reset" + /// will be sent if set to "default" or "reset". /// /// /// public static readonly Option WelcomeMessage = new("WelcomeMessage", "default"); + /// + /// Controls what message should be sent in when a member leaves the guild. + /// + /// + /// + /// No message will be sent if set to "off", "disable" or "disabled". + /// will be sent if set to "default" or "reset". + /// + /// + /// + public static readonly Option LeaveMessage = new("LeaveMessage", "default"); + /// /// Controls whether or not the message should be sent /// in on startup. diff --git a/src/Data/Options/AllOptionsEnum.cs b/src/Data/Options/AllOptionsEnum.cs index e9637d6..6932822 100644 --- a/src/Data/Options/AllOptionsEnum.cs +++ b/src/Data/Options/AllOptionsEnum.cs @@ -14,6 +14,7 @@ public enum AllOptionsEnum { [UsedImplicitly] Language, [UsedImplicitly] WelcomeMessage, + [UsedImplicitly] LeaveMessage, [UsedImplicitly] ReceiveStartupMessages, [UsedImplicitly] RemoveRolesOnMute, [UsedImplicitly] ReturnRolesOnRejoin, diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 707c814..0fa4820 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1037,18 +1037,26 @@ namespace Octobot { } } - internal static string InvalidTimeSpan - { - get - { + 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 - { + internal static string UserInfoKicked { + get { return ResourceManager.GetString("UserInfoKicked", resourceCulture); } } diff --git a/src/Responders/GuildMemberLeftResponder.cs b/src/Responders/GuildMemberLeftResponder.cs new file mode 100644 index 0000000..5c6db36 --- /dev/null +++ b/src/Responders/GuildMemberLeftResponder.cs @@ -0,0 +1,73 @@ +using JetBrains.Annotations; +using Octobot.Data; +using Octobot.Extensions; +using Octobot.Services; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Extensions.Embeds; +using Remora.Discord.Gateway.Responders; +using Remora.Results; + +namespace Octobot.Responders; + +/// +/// Handles sending a guild's if one is set. +/// +/// +[UsedImplicitly] +public class GuildMemberLeftResponder : IResponder +{ + private readonly IDiscordRestChannelAPI _channelApi; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + + public GuildMemberLeftResponder( + IDiscordRestChannelAPI channelApi, GuildDataService guildData, IDiscordRestGuildAPI guildApi) + { + _channelApi = channelApi; + _guildData = guildData; + _guildApi = guildApi; + } + + public async Task RespondAsync(IGuildMemberRemove gatewayEvent, CancellationToken ct = default) + { + var user = gatewayEvent.User; + var data = await _guildData.GetData(gatewayEvent.GuildID, ct); + var cfg = data.Settings; + + var memberData = data.GetOrCreateMemberData(user.ID); + if (memberData.BannedUntil is not null || memberData.Kicked) + { + return Result.FromSuccess(); + } + + if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() + || GuildSettings.LeaveMessage.Get(cfg) is "off" or "disable" or "disabled") + { + return Result.FromSuccess(); + } + + Messages.Culture = GuildSettings.Language.Get(cfg); + var leaveMessage = GuildSettings.LeaveMessage.Get(cfg) is "default" or "reset" + ? Messages.DefaultLeaveMessage + : GuildSettings.LeaveMessage.Get(cfg); + + var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } + + var embed = new EmbedBuilder() + .WithSmallTitle(string.Format(leaveMessage, user.GetTag(), guild.Name), user) + .WithGuildFooter(guild) + .WithTimestamp(DateTimeOffset.UtcNow) + .WithColour(ColorsList.Black) + .Build(); + + return await _channelApi.CreateMessageWithEmbedResultAsync( + GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, + allowedMentions: Octobot.NoMentions, ct: ct); + } +} + From ecb92a318cf1fbe8a655f46e6ac53eaaa5bfa474 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:22:54 +0300 Subject: [PATCH 06/14] Fix /ping not showing correct locale (#276) yeah... --------- Signed-off-by: mctaylors --- locale/Messages.resx | 6 +++--- locale/Messages.ru.resx | 6 +++--- locale/Messages.tt-ru.resx | 6 +++--- src/Commands/PingCommandGroup.cs | 2 +- src/Messages.Designer.cs | 12 ++++++------ src/Responders/GuildLoadedResponder.cs | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index dfaff85..de99b4e 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -117,13 +117,13 @@ {0}, welcome to {1} - + Veemo! - + Woomy! - + Ngyes! diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index 6169a13..991ffcc 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -117,13 +117,13 @@ {0}, добро пожаловать на сервер {1} - + Виимо! - + Вууми! - + Нгьес! diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index 3118e74..a5ba94d 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -117,13 +117,13 @@ {0}, добро пожаловать на сервер {1} - + вииимо! - + вуууми! - + нгьес! diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs index 31fa6dc..f74e5e2 100644 --- a/src/Commands/PingCommandGroup.cs +++ b/src/Commands/PingCommandGroup.cs @@ -91,7 +91,7 @@ public class PingCommandGroup : CommandGroup } var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot) - .WithTitle($"Sound{Random.Shared.Next(1, 4)}".Localized()) + .WithTitle($"Generic{Random.Shared.Next(1, 4)}".Localized()) .WithDescription($"{latency:F0}{Messages.Milliseconds}") .WithColour(latency < 250 ? ColorsList.Green : latency < 500 ? ColorsList.Yellow : ColorsList.Red) .WithCurrentTimestamp() diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 0fa4820..7b44c44 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -66,21 +66,21 @@ namespace Octobot { } } - internal static string Loaded1 { + internal static string Generic1 { get { - return ResourceManager.GetString("Loaded1", resourceCulture); + return ResourceManager.GetString("Generic1", resourceCulture); } } - internal static string Loaded2 { + internal static string Generic2 { get { - return ResourceManager.GetString("Loaded2", resourceCulture); + return ResourceManager.GetString("Generic2", resourceCulture); } } - internal static string Loaded3 { + internal static string Generic3 { get { - return ResourceManager.GetString("Loaded3", resourceCulture); + return ResourceManager.GetString("Generic3", resourceCulture); } } diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index c493910..a0636f6 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -88,7 +88,7 @@ public class GuildLoadedResponder : IResponder var i = Random.Shared.Next(1, 4); var embed = new EmbedBuilder().WithSmallTitle(bot.GetTag(), bot) - .WithTitle($"Loaded{i}".Localized()) + .WithTitle($"Generic{i}".Localized()) .WithDescription(Messages.Ready) .WithCurrentTimestamp() .WithColour(ColorsList.Blue) From ca49231c86f833abc13a9d15de44e68fad2aad63 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:59:25 +0300 Subject: [PATCH 07/14] Disable issue report button if dirty version detected (#275) In this PR, I'm disabling the Report Issue button if a "dirty" version is detected. This is done just in case so that "smart" developers don't accidentally report a bug that they themselves created. --------- Signed-off-by: mctaylors --- locale/Messages.resx | 3 +++ locale/Messages.ru.resx | 3 +++ locale/Messages.tt-ru.resx | 3 +++ src/BuildInfo.cs | 2 +- src/Commands/AboutCommandGroup.cs | 7 +++++-- src/Commands/Events/ErrorLoggingPostExecutionEvent.cs | 7 +++++-- src/Messages.Designer.cs | 6 ++++++ src/Responders/GuildLoadedResponder.cs | 7 +++++-- 8 files changed, 31 insertions(+), 7 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index de99b4e..f7500b6 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -669,4 +669,7 @@ Welcome messages channel + + Can't report an issue in the development version + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index 991ffcc..e28b405 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -669,4 +669,7 @@ Канал для приветствий + + Нельзя сообщить о проблеме в версии под разработкой + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index a5ba94d..58e1178 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -669,4 +669,7 @@ канал куда говорить здравствуйте + + вот иди сам и почини что сломал + diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs index 50f86a2..c704328 100644 --- a/src/BuildInfo.cs +++ b/src/BuildInfo.cs @@ -34,7 +34,7 @@ public static class BuildInfo } } - private static bool IsDirty + public static bool IsDirty { get { diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index 05b1855..ebfe0f2 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -113,9 +113,12 @@ public class AboutCommandGroup : CommandGroup var issuesButton = new ButtonComponent( ButtonComponentStyle.Link, - Messages.ButtonReportIssue, + BuildInfo.IsDirty + ? Messages.ButtonDirty + : Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: BuildInfo.IssuesUrl + URL: BuildInfo.IssuesUrl, + IsDisabled: BuildInfo.IsDirty ); return await _feedback.SendContextualEmbedResultAsync(embed, diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 5d7830b..6f39690 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -70,9 +70,12 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent var issuesButton = new ButtonComponent( ButtonComponentStyle.Link, - Messages.ButtonReportIssue, + BuildInfo.IsDirty + ? Messages.ButtonDirty + : Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: BuildInfo.IssuesUrl + URL: BuildInfo.IssuesUrl, + IsDisabled: BuildInfo.IsDirty ); return await _feedback.SendContextualEmbedResultAsync(embed, diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 7b44c44..3d452a6 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1204,5 +1204,11 @@ namespace Octobot { return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture); } } + + internal static string ButtonDirty { + get { + return ResourceManager.GetString("ButtonDirty", resourceCulture); + } + } } } diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index a0636f6..e60dd4f 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -115,9 +115,12 @@ public class GuildLoadedResponder : IResponder var issuesButton = new ButtonComponent( ButtonComponentStyle.Link, - Messages.ButtonReportIssue, + BuildInfo.IsDirty + ? Messages.ButtonDirty + : Messages.ButtonReportIssue, new PartialEmoji(Name: "⚠️"), - URL: BuildInfo.IssuesUrl + URL: BuildInfo.IssuesUrl, + IsDisabled: BuildInfo.IsDirty ); return await _channelApi.CreateMessageWithEmbedResultAsync(channel, embedResult: errorEmbed, From 0a930dcab16c990c5a25cd9df95430ff5c347a7f Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 20 Mar 2024 21:08:59 +0500 Subject: [PATCH 08/14] Allow using expression-bodied properties (#280) This change significantly reduces the code space used by properties while maintaining clarity on types. Since only properties are allowed to use expression bodies, it is clear to developers that what they are looking at is a property or not Signed-off-by: Octol1ttle --- .editorconfig | 2 +- src/BuildInfo.cs | 48 ++++++------------------------------------------ 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/.editorconfig b/.editorconfig index ff9c068..adbec5a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -42,7 +42,7 @@ csharp_space_between_square_brackets = false csharp_style_expression_bodied_accessors = false:warning csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_methods = false:warning -csharp_style_expression_bodied_properties = false:warning +csharp_style_expression_bodied_properties = true:warning csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_utf8_string_literals = true:warning csharp_style_var_elsewhere = true:warning diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs index c704328..369feff 100644 --- a/src/BuildInfo.cs +++ b/src/BuildInfo.cs @@ -2,51 +2,15 @@ public static class BuildInfo { - public static string RepositoryUrl - { - get - { - return ThisAssembly.Git.RepositoryUrl; - } - } + public static string RepositoryUrl => ThisAssembly.Git.RepositoryUrl; - public static string IssuesUrl - { - get - { - return $"{RepositoryUrl}/issues"; - } - } + public static string IssuesUrl => $"{RepositoryUrl}/issues"; - private static string Commit - { - get - { - return ThisAssembly.Git.Commit; - } - } + private static string Commit => ThisAssembly.Git.Commit; - private static string Branch - { - get - { - return ThisAssembly.Git.Branch; - } - } + private static string Branch => ThisAssembly.Git.Branch; - public static bool IsDirty - { - get - { - return ThisAssembly.Git.IsDirty; - } - } + public static bool IsDirty => ThisAssembly.Git.IsDirty; - public static string Version - { - get - { - return IsDirty ? $"{Branch}-{Commit}-dirty" : $"{Branch}-{Commit}"; - } - } + public static string Version => IsDirty ? $"{Branch}-{Commit}-dirty" : $"{Branch}-{Commit}"; } From 5cc04e9cc32e23c2f2ba4d936c0f965545913d4e Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 20 Mar 2024 22:34:22 +0500 Subject: [PATCH 09/14] Change position of "Jump to ..." action for better consistency (#279) This will better align with how a normal moderator would respond to the log: Before: "see log" -> "jump to message without knowing what changed" -> "read message diff" After: "see log" -> "read message diff" -> "jump to message for context" In addition, the change improves consistency with how reminders are shown. --------- Signed-off-by: Octol1ttle --- src/Responders/MessageDeletedResponder.cs | 9 +++++---- src/Responders/MessageEditedResponder.cs | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs index bfedb22..52be8c9 100644 --- a/src/Responders/MessageDeletedResponder.cs +++ b/src/Responders/MessageDeletedResponder.cs @@ -83,10 +83,11 @@ public class MessageDeletedResponder : IResponder Messages.Culture = GuildSettings.Language.Get(cfg); - var builder = new StringBuilder().AppendLine( - string.Format(Messages.DescriptionActionJumpToChannel, - Mention.Channel(gatewayEvent.ChannelID))) - .AppendLine(message.Content.InBlockCode()); + var builder = new StringBuilder() + .AppendLine(message.Content.InBlockCode()) + .AppendLine( + string.Format(Messages.DescriptionActionJumpToChannel, Mention.Channel(gatewayEvent.ChannelID)) + ); var embed = new EmbedBuilder() .WithSmallTitle( diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs index c7426d2..dd3f51d 100644 --- a/src/Responders/MessageEditedResponder.cs +++ b/src/Responders/MessageEditedResponder.cs @@ -101,10 +101,11 @@ public class MessageEditedResponder : IResponder Messages.Culture = GuildSettings.Language.Get(cfg); - var builder = new StringBuilder().AppendLine( - string.Format(Messages.DescriptionActionJumpToMessage, - $"https://discord.com/channels/{guildId}/{channelId}/{messageId}")) - .AppendLine(diff.AsMarkdown()); + var builder = new StringBuilder() + .AppendLine(diff.AsMarkdown()) + .AppendLine(string.Format(Messages.DescriptionActionJumpToMessage, + $"https://discord.com/channels/{guildId}/{channelId}/{messageId}") + ); var embed = new EmbedBuilder() .WithSmallTitle(string.Format(Messages.CachedMessageEdited, message.Author.GetTag()), message.Author) From 309d9000673723d5dc2c799a5cd41bfac8e35b2c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 20 Mar 2024 23:08:16 +0500 Subject: [PATCH 10/14] Log result failures with stack traces (#282) This feature will improve the debugging experience for developers by providing the information about *where exactly* a result has failed --------- Signed-off-by: Octol1ttle --- src/Attributes/StaticCallersOnlyAttribute.cs | 8 +++ src/Commands/AboutCommandGroup.cs | 2 +- src/Commands/BanCommandGroup.cs | 16 ++--- src/Commands/ClearCommandGroup.cs | 8 +-- .../Events/ErrorLoggingPostExecutionEvent.cs | 7 ++- src/Commands/KickCommandGroup.cs | 10 +-- src/Commands/MuteCommandGroup.cs | 18 +++--- src/Commands/PingCommandGroup.cs | 4 +- src/Commands/RemindCommandGroup.cs | 14 ++--- src/Commands/SettingsCommandGroup.cs | 10 +-- src/Commands/ToolsCommandGroup.cs | 16 ++--- src/Extensions/ChannelApiExtensions.cs | 2 +- src/Extensions/FeedbackServiceExtensions.cs | 2 +- src/Extensions/ResultExtensions.cs | 61 +++++++++++++++++++ src/Octobot.cs | 5 ++ src/Responders/GuildLoadedResponder.cs | 6 +- src/Responders/GuildMemberJoinedResponder.cs | 4 +- src/Responders/GuildMemberLeftResponder.cs | 3 +- src/Responders/MessageDeletedResponder.cs | 6 +- src/Services/Update/MemberUpdateService.cs | 4 +- .../Update/ScheduledEventUpdateService.cs | 10 +-- 21 files changed, 145 insertions(+), 71 deletions(-) create mode 100644 src/Attributes/StaticCallersOnlyAttribute.cs create mode 100644 src/Extensions/ResultExtensions.cs diff --git a/src/Attributes/StaticCallersOnlyAttribute.cs b/src/Attributes/StaticCallersOnlyAttribute.cs new file mode 100644 index 0000000..e8787bf --- /dev/null +++ b/src/Attributes/StaticCallersOnlyAttribute.cs @@ -0,0 +1,8 @@ +namespace Octobot.Attributes; + +/// +/// Any property marked with should only be accessed by static methods. +/// Such properties may be used to provide dependencies where it is not possible to acquire them through normal means. +/// +[AttributeUsage(AttributeTargets.Property)] +public sealed class StaticCallersOnlyAttribute : Attribute; diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index ebfe0f2..b37b2f0 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -73,7 +73,7 @@ public class AboutCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var cfg = await _guildData.GetSettings(guildId, CancellationToken); diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index 30cff14..ef5e9a4 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -88,19 +88,19 @@ public class BanCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); if (!guildResult.IsDefined(out var guild)) { - return Result.FromError(guildResult); + return ResultExtensions.FromError(guildResult); } var data = await _guildData.GetData(guild.ID, CancellationToken); @@ -144,7 +144,7 @@ public class BanCommandGroup : CommandGroup = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct); if (!interactionResult.IsSuccess) { - return Result.FromError(interactionResult); + return ResultExtensions.FromError(interactionResult); } if (interactionResult.Entity is not null) @@ -191,7 +191,7 @@ public class BanCommandGroup : CommandGroup if (!banResult.IsSuccess) { memberData.BannedUntil = null; - return Result.FromError(banResult.Error); + return ResultExtensions.FromError(banResult); } memberData.Roles.Clear(); @@ -242,14 +242,14 @@ public class BanCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } // Needed to get the tag and avatar var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -276,7 +276,7 @@ public class BanCommandGroup : CommandGroup ct); if (!unbanResult.IsSuccess) { - return Result.FromError(unbanResult.Error); + return ResultExtensions.FromError(unbanResult); } data.GetOrCreateMemberData(target.ID).BannedUntil = null; diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs index 395810f..70afede 100644 --- a/src/Commands/ClearCommandGroup.cs +++ b/src/Commands/ClearCommandGroup.cs @@ -75,20 +75,20 @@ public class ClearCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var messagesResult = await _channelApi.GetChannelMessagesAsync( channelId, limit: amount + 1, ct: CancellationToken); if (!messagesResult.IsDefined(out var messages)) { - return Result.FromError(messagesResult); + return ResultExtensions.FromError(messagesResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -133,7 +133,7 @@ public class ClearCommandGroup : CommandGroup channelId, idList, executor.GetTag().EncodeHeader(), ct); if (!deleteResult.IsSuccess) { - return Result.FromError(deleteResult.Error); + return ResultExtensions.FromError(deleteResult); } _utility.LogAction( diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 6f39690..7e97e5c 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -59,7 +59,7 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent var botResult = await _userApi.GetCurrentUserAsync(ct); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var embed = new EmbedBuilder().WithSmallTitle(Messages.CommandExecutionFailed, bot) @@ -78,10 +78,11 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent IsDisabled: BuildInfo.IsDirty ); - return await _feedback.SendContextualEmbedResultAsync(embed, + return ResultExtensions.FromError(await _feedback.SendContextualEmbedResultAsync(embed, new FeedbackMessageOptions(MessageComponents: new[] { new ActionRowComponent(new[] { issuesButton }) - }), ct); + }), ct) + ); } } diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs index 59c4045..5149ad4 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/src/Commands/KickCommandGroup.cs @@ -80,19 +80,19 @@ public class KickCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); if (!guildResult.IsDefined(out var guild)) { - return Result.FromError(guildResult); + return ResultExtensions.FromError(guildResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -118,7 +118,7 @@ public class KickCommandGroup : CommandGroup = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct); if (!interactionResult.IsSuccess) { - return Result.FromError(interactionResult); + return ResultExtensions.FromError(interactionResult); } if (interactionResult.Entity is not null) @@ -152,7 +152,7 @@ public class KickCommandGroup : CommandGroup if (!kickResult.IsSuccess) { memberData.Kicked = false; - return Result.FromError(kickResult.Error); + return ResultExtensions.FromError(kickResult); } memberData.Roles.Clear(); diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index c2542e8..5ac6a04 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -85,13 +85,13 @@ public class MuteCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -130,7 +130,7 @@ public class MuteCommandGroup : CommandGroup guildId, executor.ID, target.ID, "Mute", ct); if (!interactionResult.IsSuccess) { - return Result.FromError(interactionResult); + return ResultExtensions.FromError(interactionResult); } if (interactionResult.Entity is not null) @@ -146,7 +146,7 @@ public class MuteCommandGroup : CommandGroup var muteMethodResult = await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct); if (!muteMethodResult.IsSuccess) { - return muteMethodResult; + return ResultExtensions.FromError(muteMethodResult); } var title = string.Format(Messages.UserMuted, target.GetTag()); @@ -257,14 +257,14 @@ public class MuteCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } // Needed to get the tag and avatar var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -291,7 +291,7 @@ public class MuteCommandGroup : CommandGroup guildId, executor.ID, target.ID, "Unmute", ct); if (!interactionResult.IsSuccess) { - return Result.FromError(interactionResult); + return ResultExtensions.FromError(interactionResult); } if (interactionResult.Entity is not null) @@ -324,14 +324,14 @@ public class MuteCommandGroup : CommandGroup await RemoveMuteRoleAsync(executor, target, reason, guildId, memberData, CancellationToken); if (!removeMuteRoleAsync.IsSuccess) { - return Result.FromError(removeMuteRoleAsync.Error); + return ResultExtensions.FromError(removeMuteRoleAsync); } var removeTimeoutResult = await RemoveTimeoutAsync(executor, target, reason, guildId, communicationDisabledUntil, CancellationToken); if (!removeTimeoutResult.IsSuccess) { - return Result.FromError(removeTimeoutResult.Error); + return ResultExtensions.FromError(removeTimeoutResult); } var title = string.Format(Messages.UserUnmuted, target.GetTag()); diff --git a/src/Commands/PingCommandGroup.cs b/src/Commands/PingCommandGroup.cs index f74e5e2..d64c6dd 100644 --- a/src/Commands/PingCommandGroup.cs +++ b/src/Commands/PingCommandGroup.cs @@ -64,7 +64,7 @@ public class PingCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var cfg = await _guildData.GetSettings(guildId, CancellationToken); @@ -84,7 +84,7 @@ public class PingCommandGroup : CommandGroup channelId, limit: 1, ct: ct); if (!lastMessageResult.IsDefined(out var lastMessage)) { - return Result.FromError(lastMessageResult); + return ResultExtensions.FromError(lastMessageResult); } latency = DateTimeOffset.UtcNow.Subtract(lastMessage.Single().Timestamp).TotalMilliseconds; diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs index f9c006e..aa1ef7e 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/src/Commands/RemindCommandGroup.cs @@ -63,13 +63,13 @@ public class RemindCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -134,13 +134,13 @@ public class RemindCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -226,13 +226,13 @@ public class RemindCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -343,7 +343,7 @@ public class RemindCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var data = await _guildData.GetData(guildId, CancellationToken); diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index 97ebc32..30150ee 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -98,7 +98,7 @@ public class SettingsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var cfg = await _guildData.GetSettings(guildId, CancellationToken); @@ -181,13 +181,13 @@ public class SettingsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -254,7 +254,7 @@ public class SettingsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var cfg = await _guildData.GetSettings(guildId, CancellationToken); @@ -274,7 +274,7 @@ public class SettingsCommandGroup : CommandGroup var resetResult = option.Reset(cfg); if (!resetResult.IsSuccess) { - return Result.FromError(resetResult.Error); + return ResultExtensions.FromError(resetResult); } var embed = new EmbedBuilder().WithSmallTitle( diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs index 9a7c9b4..cc3c2cf 100644 --- a/src/Commands/ToolsCommandGroup.cs +++ b/src/Commands/ToolsCommandGroup.cs @@ -81,13 +81,13 @@ public class ToolsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -274,13 +274,13 @@ public class ToolsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); if (!guildResult.IsDefined(out var guild)) { - return Result.FromError(guildResult); + return ResultExtensions.FromError(guildResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -353,7 +353,7 @@ public class ToolsCommandGroup : CommandGroup var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -439,13 +439,13 @@ public class ToolsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); if (!executorResult.IsDefined(out var executor)) { - return Result.FromError(executorResult); + return ResultExtensions.FromError(executorResult); } var data = await _guildData.GetData(guildId, CancellationToken); @@ -524,7 +524,7 @@ public class ToolsCommandGroup : CommandGroup var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } var data = await _guildData.GetData(guildId, CancellationToken); diff --git a/src/Extensions/ChannelApiExtensions.cs b/src/Extensions/ChannelApiExtensions.cs index 12ccf35..99eff67 100644 --- a/src/Extensions/ChannelApiExtensions.cs +++ b/src/Extensions/ChannelApiExtensions.cs @@ -20,7 +20,7 @@ public static class ChannelApiExtensions { if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed)) { - return Result.FromError(embedResult.Value); + return ResultExtensions.FromError(embedResult.Value); } return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed }, diff --git a/src/Extensions/FeedbackServiceExtensions.cs b/src/Extensions/FeedbackServiceExtensions.cs index 40e0d53..e6ef376 100644 --- a/src/Extensions/FeedbackServiceExtensions.cs +++ b/src/Extensions/FeedbackServiceExtensions.cs @@ -13,7 +13,7 @@ public static class FeedbackServiceExtensions { if (!embedResult.IsDefined(out var embed)) { - return Result.FromError(embedResult); + return ResultExtensions.FromError(embedResult); } return (Result)await feedback.SendContextualEmbedAsync(embed, options, ct); diff --git a/src/Extensions/ResultExtensions.cs b/src/Extensions/ResultExtensions.cs new file mode 100644 index 0000000..f456dac --- /dev/null +++ b/src/Extensions/ResultExtensions.cs @@ -0,0 +1,61 @@ +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Remora.Results; + +namespace Octobot.Extensions; + +public static class ResultExtensions +{ + public static Result FromError(Result result) + { + LogResultStackTrace(result); + + return result; + } + + public static Result FromError(Result result) + { + var casted = (Result)result; + LogResultStackTrace(casted); + + return casted; + } + + [Conditional("DEBUG")] + private static void LogResultStackTrace(Result result) + { + if (Octobot.StaticLogger is null || result.IsSuccess) + { + return; + } + + Octobot.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}", + result.Error.GetType().FullName, result.Error.Message, Environment.NewLine, ConstructStackTrace()); + + var inner = result.Inner; + while (inner is { IsSuccess: false }) + { + Octobot.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}", + inner.Error.GetType().FullName, inner.Error.Message); + + inner = inner.Inner; + } + } + + private static string ConstructStackTrace() + { + var stackArray = new StackTrace(3, true).ToString().Split(Environment.NewLine).ToList(); + for (var i = stackArray.Count - 1; i >= 0; i--) + { + var frame = stackArray[i]; + var trimmed = frame.TrimStart(); + if (trimmed.StartsWith("at System.Threading", StringComparison.Ordinal) + || trimmed.StartsWith("at System.Runtime.CompilerServices", StringComparison.Ordinal)) + { + stackArray.RemoveAt(i); + } + } + + return string.Join(Environment.NewLine, stackArray); + } +} diff --git a/src/Octobot.cs b/src/Octobot.cs index e0d9b07..a4871f4 100644 --- a/src/Octobot.cs +++ b/src/Octobot.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Octobot.Attributes; using Octobot.Commands.Events; using Octobot.Services; using Octobot.Services.Update; @@ -25,10 +26,14 @@ public sealed class Octobot public static readonly AllowedMentions NoMentions = new( Array.Empty(), Array.Empty(), Array.Empty()); + [StaticCallersOnly] + public static ILogger? StaticLogger { get; private set; } + public static async Task Main(string[] args) { var host = CreateHostBuilder(args).UseConsoleLifetime().Build(); var services = host.Services; + StaticLogger = services.GetRequiredService>(); var slashService = services.GetRequiredService(); // Providing a guild ID to this call will result in command duplicates! diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index e60dd4f..80e8735 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -57,7 +57,7 @@ public class GuildLoadedResponder : IResponder var botResult = await _userApi.GetCurrentUserAsync(ct); if (!botResult.IsDefined(out var bot)) { - return Result.FromError(botResult); + return ResultExtensions.FromError(botResult); } if (data.DataLoadFailed) @@ -68,7 +68,7 @@ public class GuildLoadedResponder : IResponder var ownerResult = await _userApi.GetUserAsync(guild.OwnerID, ct); if (!ownerResult.IsDefined(out var owner)) { - return Result.FromError(ownerResult); + return ResultExtensions.FromError(ownerResult); } _logger.LogInformation("Loaded guild \"{Name}\" ({ID}) owned by {Owner} ({OwnerID}) with {MemberCount} members", @@ -103,7 +103,7 @@ public class GuildLoadedResponder : IResponder var channelResult = await _utility.GetEmergencyFeedbackChannel(guild, data, ct); if (!channelResult.IsDefined(out var channel)) { - return Result.FromError(channelResult); + return ResultExtensions.FromError(channelResult); } var errorEmbed = new EmbedBuilder() diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs index 012bfad..05ceb2f 100644 --- a/src/Responders/GuildMemberJoinedResponder.cs +++ b/src/Responders/GuildMemberJoinedResponder.cs @@ -48,7 +48,7 @@ public class GuildMemberJoinedResponder : IResponder var returnRolesResult = await TryReturnRolesAsync(cfg, memberData, gatewayEvent.GuildID, user.ID, ct); if (!returnRolesResult.IsSuccess) { - return Result.FromError(returnRolesResult.Error); + return ResultExtensions.FromError(returnRolesResult); } if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() @@ -65,7 +65,7 @@ public class GuildMemberJoinedResponder : IResponder var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); if (!guildResult.IsDefined(out var guild)) { - return Result.FromError(guildResult); + return ResultExtensions.FromError(guildResult); } var embed = new EmbedBuilder() diff --git a/src/Responders/GuildMemberLeftResponder.cs b/src/Responders/GuildMemberLeftResponder.cs index 5c6db36..b029c96 100644 --- a/src/Responders/GuildMemberLeftResponder.cs +++ b/src/Responders/GuildMemberLeftResponder.cs @@ -55,7 +55,7 @@ public class GuildMemberLeftResponder : IResponder var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct); if (!guildResult.IsDefined(out var guild)) { - return Result.FromError(guildResult); + return ResultExtensions.FromError(guildResult); } var embed = new EmbedBuilder() @@ -70,4 +70,3 @@ public class GuildMemberLeftResponder : IResponder allowedMentions: Octobot.NoMentions, ct: ct); } } - diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs index 52be8c9..7d788a7 100644 --- a/src/Responders/MessageDeletedResponder.cs +++ b/src/Responders/MessageDeletedResponder.cs @@ -51,7 +51,7 @@ public class MessageDeletedResponder : IResponder var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); if (!messageResult.IsDefined(out var message)) { - return Result.FromError(messageResult); + return ResultExtensions.FromError(messageResult); } if (string.IsNullOrWhiteSpace(message.Content)) @@ -63,7 +63,7 @@ public class MessageDeletedResponder : IResponder guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct); if (!auditLogResult.IsDefined(out var auditLogPage)) { - return Result.FromError(auditLogResult); + return ResultExtensions.FromError(auditLogResult); } var auditLog = auditLogPage.AuditLogEntries.Single(); @@ -78,7 +78,7 @@ public class MessageDeletedResponder : IResponder if (!deleterResult.IsDefined(out var deleter)) { - return Result.FromError(deleterResult); + return ResultExtensions.FromError(deleterResult); } Messages.Culture = GuildSettings.Language.Get(cfg); diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs index dfe8219..a21cdd4 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/src/Services/Update/MemberUpdateService.cs @@ -97,7 +97,7 @@ public sealed partial class MemberUpdateService : BackgroundService = await _utility.CheckInteractionsAsync(guildId, null, id, "Update", ct); if (!interactionResult.IsSuccess) { - return Result.FromError(interactionResult); + return ResultExtensions.FromError(interactionResult); } var canInteract = interactionResult.Entity is null; @@ -247,7 +247,7 @@ public sealed partial class MemberUpdateService : BackgroundService reminder.ChannelId.ToSnowflake(), Mention.User(user), embedResult: embed, ct: ct); if (!messageResult.IsSuccess) { - return messageResult; + return ResultExtensions.FromError(messageResult); } data.Reminders.Remove(reminder); diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs index ac5c109..bce9996 100644 --- a/src/Services/Update/ScheduledEventUpdateService.cs +++ b/src/Services/Update/ScheduledEventUpdateService.cs @@ -53,7 +53,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct); if (!eventsResult.IsDefined(out var events)) { - return Result.FromError(eventsResult); + return ResultExtensions.FromError(eventsResult); } SyncScheduledEvents(data, events); @@ -204,7 +204,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService if (!embedDescriptionResult.IsDefined(out var embedDescription)) { - return Result.FromError(embedDescriptionResult); + return ResultExtensions.FromError(embedDescriptionResult); } var embed = new EmbedBuilder() @@ -298,12 +298,12 @@ public sealed class ScheduledEventUpdateService : BackgroundService scheduledEvent, data, ct); if (!contentResult.IsDefined(out var content)) { - return Result.FromError(contentResult); + return ResultExtensions.FromError(contentResult); } if (!embedDescriptionResult.IsDefined(out var embedDescription)) { - return Result.FromError(embedDescriptionResult); + return ResultExtensions.FromError(embedDescriptionResult); } var startedEmbed = new EmbedBuilder() @@ -416,7 +416,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService scheduledEvent, data, ct); if (!contentResult.IsDefined(out var content)) { - return Result.FromError(contentResult); + return ResultExtensions.FromError(contentResult); } var earlyResult = new EmbedBuilder() From a80debf1b17b3d03f27e548be85bf77261ad0f5e Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 21 Mar 2024 20:55:34 +0500 Subject: [PATCH 11/14] Use Result.Success property instead of Result.FromSuccess() (#283) Signed-off-by: Octol1ttle --- .../Events/ErrorLoggingPostExecutionEvent.cs | 4 ++-- .../Events/LoggingPreparationErrorEvent.cs | 2 +- src/Commands/MuteCommandGroup.cs | 16 ++++++++++------ src/Data/Options/BoolOption.cs | 2 +- src/Data/Options/Option.cs | 14 +++++++------- src/Data/Options/SnowflakeOption.cs | 2 +- src/Data/Options/TimeSpanOption.cs | 2 +- src/Extensions/CollectionExtensions.cs | 2 +- src/Extensions/GuildScheduledEventExtensions.cs | 2 +- src/Responders/GuildLoadedResponder.cs | 6 +++--- src/Responders/GuildMemberJoinedResponder.cs | 4 ++-- src/Responders/GuildMemberLeftResponder.cs | 4 ++-- src/Responders/GuildUnloadedResponder.cs | 2 +- src/Responders/MessageDeletedResponder.cs | 6 +++--- src/Responders/MessageEditedResponder.cs | 14 +++++++------- src/Responders/MessageReceivedResponder.cs | 2 +- src/Services/Update/MemberUpdateService.cs | 17 +++++++++-------- .../Update/ScheduledEventUpdateService.cs | 12 ++++++------ 18 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 7e97e5c..5fa2ea8 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -20,8 +20,8 @@ namespace Octobot.Commands.Events; [UsedImplicitly] public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent { - private readonly ILogger _logger; private readonly IFeedbackService _feedback; + private readonly ILogger _logger; private readonly IDiscordRestUserAPI _userApi; public ErrorLoggingPostExecutionEvent(ILogger logger, IFeedbackService feedback, @@ -53,7 +53,7 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent if (result.IsSuccess) { - return Result.FromSuccess(); + return Result.Success; } var botResult = await _userApi.GetCurrentUserAsync(ct); diff --git a/src/Commands/Events/LoggingPreparationErrorEvent.cs b/src/Commands/Events/LoggingPreparationErrorEvent.cs index be48e74..87b4090 100644 --- a/src/Commands/Events/LoggingPreparationErrorEvent.cs +++ b/src/Commands/Events/LoggingPreparationErrorEvent.cs @@ -33,6 +33,6 @@ public class LoggingPreparationErrorEvent : IPreparationErrorEvent { _logger.LogResult(preparationResult, "Error in slash command preparation."); - return Task.FromResult(Result.FromSuccess()); + return Task.FromResult(Result.Success); } } diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index 5ac6a04..8e79830 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -118,7 +118,8 @@ public class MuteCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: CancellationToken); } - return await MuteUserAsync(executor, target, reason, duration, guildId, data, channelId, bot, CancellationToken); + return await MuteUserAsync(executor, target, reason, duration, guildId, data, channelId, bot, + CancellationToken); } private async Task MuteUserAsync( @@ -143,14 +144,16 @@ public class MuteCommandGroup : CommandGroup var until = DateTimeOffset.UtcNow.Add(duration); // >:) - var muteMethodResult = await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct); + var muteMethodResult = + await SelectMuteMethodAsync(executor, target, reason, duration, guildId, data, bot, until, ct); if (!muteMethodResult.IsSuccess) { return ResultExtensions.FromError(muteMethodResult); } var title = string.Format(Messages.UserMuted, target.GetTag()); - var description = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)) + var description = new StringBuilder() + .AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)) .AppendBulletPoint(string.Format( Messages.DescriptionActionExpiresAt, Markdown.Timestamp(until))).ToString(); @@ -348,11 +351,12 @@ public class MuteCommandGroup : CommandGroup } private async Task RemoveMuteRoleAsync( - IUser executor, IUser target, string reason, Snowflake guildId, MemberData memberData, CancellationToken ct = default) + IUser executor, IUser target, string reason, Snowflake guildId, MemberData memberData, + CancellationToken ct = default) { if (memberData.MutedUntil is null) { - return Result.FromSuccess(); + return Result.Success; } var unmuteResult = await _guildApi.ModifyGuildMemberAsync( @@ -372,7 +376,7 @@ public class MuteCommandGroup : CommandGroup { if (communicationDisabledUntil is null) { - return Result.FromSuccess(); + return Result.Success; } var unmuteResult = await _guildApi.ModifyGuildMemberAsync( diff --git a/src/Data/Options/BoolOption.cs b/src/Data/Options/BoolOption.cs index 130687e..6876164 100644 --- a/src/Data/Options/BoolOption.cs +++ b/src/Data/Options/BoolOption.cs @@ -20,7 +20,7 @@ public sealed class BoolOption : Option } settings[Name] = value; - return Result.FromSuccess(); + return Result.Success; } private static bool TryParseBool(string from, out bool value) diff --git a/src/Data/Options/Option.cs b/src/Data/Options/Option.cs index 0ba8ce1..5d703a8 100644 --- a/src/Data/Options/Option.cs +++ b/src/Data/Options/Option.cs @@ -35,7 +35,13 @@ public class Option : IOption public virtual Result Set(JsonNode settings, string from) { settings[Name] = from; - return Result.FromSuccess(); + return Result.Success; + } + + public Result Reset(JsonNode settings) + { + settings[Name] = null; + return Result.Success; } /// @@ -48,10 +54,4 @@ public class Option : IOption var property = settings[Name]; return property != null ? property.GetValue() : DefaultValue; } - - public Result Reset(JsonNode settings) - { - settings[Name] = null; - return Result.FromSuccess(); - } } diff --git a/src/Data/Options/SnowflakeOption.cs b/src/Data/Options/SnowflakeOption.cs index 66ada96..7118da8 100644 --- a/src/Data/Options/SnowflakeOption.cs +++ b/src/Data/Options/SnowflakeOption.cs @@ -32,7 +32,7 @@ public sealed partial class SnowflakeOption : Option } settings[Name] = parsed; - return Result.FromSuccess(); + return Result.Success; } [GeneratedRegex("[^0-9]")] diff --git a/src/Data/Options/TimeSpanOption.cs b/src/Data/Options/TimeSpanOption.cs index c81a02d..d237b6e 100644 --- a/src/Data/Options/TimeSpanOption.cs +++ b/src/Data/Options/TimeSpanOption.cs @@ -22,6 +22,6 @@ public sealed class TimeSpanOption : Option } settings[Name] = span.ToString(); - return Result.FromSuccess(); + return Result.Success; } } diff --git a/src/Extensions/CollectionExtensions.cs b/src/Extensions/CollectionExtensions.cs index 9c873f2..2369532 100644 --- a/src/Extensions/CollectionExtensions.cs +++ b/src/Extensions/CollectionExtensions.cs @@ -32,7 +32,7 @@ public static class CollectionExtensions { return list.Count switch { - 0 => Result.FromSuccess(), + 0 => Result.Success, 1 => list[0], _ => new AggregateError(list.Cast().ToArray()) }; diff --git a/src/Extensions/GuildScheduledEventExtensions.cs b/src/Extensions/GuildScheduledEventExtensions.cs index e3217e3..f1b6985 100644 --- a/src/Extensions/GuildScheduledEventExtensions.cs +++ b/src/Extensions/GuildScheduledEventExtensions.cs @@ -22,7 +22,7 @@ public static class GuildScheduledEventExtensions } return scheduledEvent.ScheduledEndTime.AsOptional().IsDefined(out endTime) - ? Result.FromSuccess() + ? Result.Success : new ArgumentNullError(nameof(scheduledEvent.ScheduledEndTime)); } } diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index 80e8735..cd40134 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -42,7 +42,7 @@ public class GuildLoadedResponder : IResponder { if (!gatewayEvent.Guild.IsT0) // Guild is not IAvailableGuild { - return Result.FromSuccess(); + return Result.Success; } var guild = gatewayEvent.Guild.AsT0; @@ -76,12 +76,12 @@ public class GuildLoadedResponder : IResponder if (!GuildSettings.ReceiveStartupMessages.Get(cfg)) { - return Result.FromSuccess(); + return Result.Success; } if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) { - return Result.FromSuccess(); + return Result.Success; } Messages.Culture = GuildSettings.Language.Get(cfg); diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs index 05ceb2f..61ef5cc 100644 --- a/src/Responders/GuildMemberJoinedResponder.cs +++ b/src/Responders/GuildMemberJoinedResponder.cs @@ -54,7 +54,7 @@ public class GuildMemberJoinedResponder : IResponder if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() || GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled") { - return Result.FromSuccess(); + return Result.Success; } Messages.Culture = GuildSettings.Language.Get(cfg); @@ -85,7 +85,7 @@ public class GuildMemberJoinedResponder : IResponder { if (!GuildSettings.ReturnRolesOnRejoin.Get(cfg)) { - return Result.FromSuccess(); + return Result.Success; } var assignRoles = new List(); diff --git a/src/Responders/GuildMemberLeftResponder.cs b/src/Responders/GuildMemberLeftResponder.cs index b029c96..90cc64c 100644 --- a/src/Responders/GuildMemberLeftResponder.cs +++ b/src/Responders/GuildMemberLeftResponder.cs @@ -38,13 +38,13 @@ public class GuildMemberLeftResponder : IResponder var memberData = data.GetOrCreateMemberData(user.ID); if (memberData.BannedUntil is not null || memberData.Kicked) { - return Result.FromSuccess(); + return Result.Success; } if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() || GuildSettings.LeaveMessage.Get(cfg) is "off" or "disable" or "disabled") { - return Result.FromSuccess(); + return Result.Success; } Messages.Culture = GuildSettings.Language.Get(cfg); diff --git a/src/Responders/GuildUnloadedResponder.cs b/src/Responders/GuildUnloadedResponder.cs index 47bde75..b49d136 100644 --- a/src/Responders/GuildUnloadedResponder.cs +++ b/src/Responders/GuildUnloadedResponder.cs @@ -33,6 +33,6 @@ public class GuildUnloadedResponder : IResponder _logger.LogInformation("Unloaded guild {GuildId}", guildId); } - return Task.FromResult(Result.FromSuccess()); + return Task.FromResult(Result.Success); } } diff --git a/src/Responders/MessageDeletedResponder.cs b/src/Responders/MessageDeletedResponder.cs index 7d788a7..5a69273 100644 --- a/src/Responders/MessageDeletedResponder.cs +++ b/src/Responders/MessageDeletedResponder.cs @@ -39,13 +39,13 @@ public class MessageDeletedResponder : IResponder { if (!gatewayEvent.GuildID.IsDefined(out var guildId)) { - return Result.FromSuccess(); + return Result.Success; } var cfg = await _guildData.GetSettings(guildId, ct); if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) { - return Result.FromSuccess(); + return Result.Success; } var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct); @@ -56,7 +56,7 @@ public class MessageDeletedResponder : IResponder if (string.IsNullOrWhiteSpace(message.Content)) { - return Result.FromSuccess(); + return Result.Success; } var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync( diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs index dd3f51d..2f30732 100644 --- a/src/Responders/MessageEditedResponder.cs +++ b/src/Responders/MessageEditedResponder.cs @@ -48,28 +48,28 @@ public class MessageEditedResponder : IResponder if (!gatewayEvent.GuildID.IsDefined(out var guildId)) { - return Result.FromSuccess(); + return Result.Success; } if (gatewayEvent.Author.IsDefined(out var author) && author.IsBot.OrDefault(false)) { - return Result.FromSuccess(); + return Result.Success; } if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp)) { - return Result.FromSuccess(); // The message wasn't actually edited + return Result.Success; // The message wasn't actually edited } if (!gatewayEvent.Content.IsDefined(out var newContent)) { - return Result.FromSuccess(); + return Result.Success; } var cfg = await _guildData.GetSettings(guildId, ct); if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) { - return Result.FromSuccess(); + return Result.Success; } var cacheKey = new KeyHelpers.MessageCacheKey(channelId, messageId); @@ -78,12 +78,12 @@ public class MessageEditedResponder : IResponder if (!messageResult.IsDefined(out var message)) { _ = _channelApi.GetChannelMessageAsync(channelId, messageId, ct); - return Result.FromSuccess(); + return Result.Success; } if (message.Content == newContent) { - return Result.FromSuccess(); + return Result.Success; } // Custom event responders are called earlier than responders responsible for message caching diff --git a/src/Responders/MessageReceivedResponder.cs b/src/Responders/MessageReceivedResponder.cs index 6ab7199..4c26d8d 100644 --- a/src/Responders/MessageReceivedResponder.cs +++ b/src/Responders/MessageReceivedResponder.cs @@ -34,6 +34,6 @@ public class MessageCreateResponder : IResponder "лан" => "https://i.ibb.co/VYH2QLc/lan.jpg", _ => default(Optional) }); - return Task.FromResult(Result.FromSuccess()); + return Task.FromResult(Result.Success); } } diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs index a21cdd4..45d0476 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/src/Services/Update/MemberUpdateService.cs @@ -121,7 +121,7 @@ public sealed partial class MemberUpdateService : BackgroundService if (!canInteract) { - return Result.FromSuccess(); + return Result.Success; } var autoUnmuteResult = await TryAutoUnmuteAsync(guildId, id, data, ct); @@ -148,14 +148,14 @@ public sealed partial class MemberUpdateService : BackgroundService { if (data.BannedUntil is null || DateTimeOffset.UtcNow <= data.BannedUntil) { - return Result.FromSuccess(); + return Result.Success; } var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, id, ct); if (!existingBanResult.IsDefined()) { data.BannedUntil = null; - return Result.FromSuccess(); + return Result.Success; } var unbanResult = await _guildApi.RemoveGuildBanAsync( @@ -173,7 +173,7 @@ public sealed partial class MemberUpdateService : BackgroundService { if (data.MutedUntil is null || DateTimeOffset.UtcNow <= data.MutedUntil) { - return Result.FromSuccess(); + return Result.Success; } var unmuteResult = await _guildApi.ModifyGuildMemberAsync( @@ -209,7 +209,7 @@ public sealed partial class MemberUpdateService : BackgroundService if (!usernameChanged) { - return Result.FromSuccess(); + return Result.Success; } var newNickname = string.Concat(characterList.ToArray()); @@ -230,12 +230,13 @@ public sealed partial class MemberUpdateService : BackgroundService { if (DateTimeOffset.UtcNow < reminder.At) { - return Result.FromSuccess(); + return Result.Success; } var builder = new StringBuilder() .AppendBulletPointLine(string.Format(Messages.DescriptionReminder, Markdown.InlineCode(reminder.Text))) - .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); + .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, + $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.Reminder, user.GetTag()), user) @@ -251,6 +252,6 @@ public sealed partial class MemberUpdateService : BackgroundService } data.Reminders.Remove(reminder); - return Result.FromSuccess(); + return Result.Success; } } diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs index bce9996..8168fc1 100644 --- a/src/Services/Update/ScheduledEventUpdateService.cs +++ b/src/Services/Update/ScheduledEventUpdateService.cs @@ -147,7 +147,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService || eventData.EarlyNotificationSent || DateTimeOffset.UtcNow < scheduledEvent.ScheduledStartTime - offset) { - return Result.FromSuccess(); + return Result.Success; } var sendResult = await SendEarlyEventNotificationAsync(scheduledEvent, data, ct); @@ -182,7 +182,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService { if (GuildSettings.EventNotificationChannel.Get(settings).Empty()) { - return Result.FromSuccess(); + return Result.Success; } if (!scheduledEvent.Creator.IsDefined(out var creator)) @@ -283,7 +283,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { - return Result.FromSuccess(); + return Result.Success; } var embedDescriptionResult = scheduledEvent.EntityType switch @@ -324,7 +324,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { data.ScheduledEvents.Remove(eventData.Id); - return Result.FromSuccess(); + return Result.Success; } var completedEmbed = new EmbedBuilder() @@ -356,7 +356,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { data.ScheduledEvents.Remove(eventData.Id); - return Result.FromSuccess(); + return Result.Success; } var embed = new EmbedBuilder() @@ -409,7 +409,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService { if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { - return Result.FromSuccess(); + return Result.Success; } var contentResult = await _utility.GetEventNotificationMentions( From e0232f600810e0dd1c347bb77856292f5273f3ca Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 21 Mar 2024 21:31:17 +0500 Subject: [PATCH 12/14] Merge some sequential 'if' statements (#284) i thought there would be a lot of statements that could be merged, but these are only ones I could find, apparently Signed-off-by: Octol1ttle --- src/Responders/GuildLoadedResponder.cs | 8 ++------ src/Responders/MessageEditedResponder.cs | 22 +++++----------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index cd40134..b03fd3f 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -74,12 +74,8 @@ public class GuildLoadedResponder : IResponder _logger.LogInformation("Loaded guild \"{Name}\" ({ID}) owned by {Owner} ({OwnerID}) with {MemberCount} members", guild.Name, guild.ID, owner.GetTag(), owner.ID, guild.MemberCount); - if (!GuildSettings.ReceiveStartupMessages.Get(cfg)) - { - return Result.Success; - } - - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) + if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty() + || !GuildSettings.ReceiveStartupMessages.Get(cfg)) { return Result.Success; } diff --git a/src/Responders/MessageEditedResponder.cs b/src/Responders/MessageEditedResponder.cs index 2f30732..1143652 100644 --- a/src/Responders/MessageEditedResponder.cs +++ b/src/Responders/MessageEditedResponder.cs @@ -46,28 +46,16 @@ public class MessageEditedResponder : IResponder return new ArgumentNullError(nameof(gatewayEvent.ChannelID)); } - if (!gatewayEvent.GuildID.IsDefined(out var guildId)) - { - return Result.Success; - } - - if (gatewayEvent.Author.IsDefined(out var author) && author.IsBot.OrDefault(false)) - { - return Result.Success; - } - - if (!gatewayEvent.EditedTimestamp.IsDefined(out var timestamp)) - { - return Result.Success; // The message wasn't actually edited - } - - if (!gatewayEvent.Content.IsDefined(out var newContent)) + if (!gatewayEvent.GuildID.IsDefined(out var guildId) + || !gatewayEvent.Author.IsDefined(out var author) + || !gatewayEvent.EditedTimestamp.IsDefined(out var timestamp) + || !gatewayEvent.Content.IsDefined(out var newContent)) { return Result.Success; } var cfg = await _guildData.GetSettings(guildId, ct); - if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) + if (author.IsBot.OrDefault(false) || GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) { return Result.Success; } From ac8621a2ec8e54c96fa04020756914c11132caa7 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:45:39 +0300 Subject: [PATCH 13/14] Pre-Wiki Update (#285) This PR has been opened to finally update Octobot's Wiki. Current changes summary: - correct minor spelling issues in some command descriptions - /about: add Octobot's Wiki button --------- Signed-off-by: mctaylors --- locale/Messages.resx | 3 +++ locale/Messages.ru.resx | 3 +++ locale/Messages.tt-ru.resx | 3 +++ src/BuildInfo.cs | 2 ++ src/Commands/AboutCommandGroup.cs | 9 ++++++++- src/Commands/SettingsCommandGroup.cs | 2 +- src/Commands/ToolsCommandGroup.cs | 4 ++-- src/Messages.Designer.cs | 6 ++++++ 8 files changed, 28 insertions(+), 4 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index f7500b6..41bb6ef 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -672,4 +672,7 @@ Can't report an issue in the development version + + Open Octobot's Wiki + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index e28b405..273338b 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -672,4 +672,7 @@ Нельзя сообщить о проблеме в версии под разработкой + + Открыть Octobot's Wiki + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index 58e1178..af2c94d 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -672,4 +672,7 @@ вот иди сам и почини что сломал + + вики Octobot (жмак) + diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs index 369feff..fc3a089 100644 --- a/src/BuildInfo.cs +++ b/src/BuildInfo.cs @@ -6,6 +6,8 @@ public static class BuildInfo public static string IssuesUrl => $"{RepositoryUrl}/issues"; + public static string WikiUrl => $"{RepositoryUrl}/wiki"; + private static string Commit => ThisAssembly.Git.Commit; private static string Branch => ThisAssembly.Git.Branch; diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index b37b2f0..027e7f8 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -111,6 +111,13 @@ public class AboutCommandGroup : CommandGroup URL: BuildInfo.RepositoryUrl ); + var wikiButton = new ButtonComponent( + ButtonComponentStyle.Link, + Messages.ButtonOpenWiki, + new PartialEmoji(Name: "📖"), + URL: BuildInfo.WikiUrl + ); + var issuesButton = new ButtonComponent( ButtonComponentStyle.Link, BuildInfo.IsDirty @@ -124,7 +131,7 @@ public class AboutCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(embed, new FeedbackMessageOptions(MessageComponents: new[] { - new ActionRowComponent(new[] { repositoryButton, issuesButton }) + new ActionRowComponent(new[] { repositoryButton, wikiButton, issuesButton }) }), ct); } } diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index 30150ee..f756e93 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -241,7 +241,7 @@ public class SettingsCommandGroup : CommandGroup [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] [RequireDiscordPermission(DiscordPermission.ManageGuild)] - [Description("Reset settings for this server")] + [Description("Reset settings for this guild")] [UsedImplicitly] public async Task ExecuteResetSettingsAsync( [Description("Setting to reset")] AllOptionsEnum? setting = null) diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs index cc3c2cf..d4f3f75 100644 --- a/src/Commands/ToolsCommandGroup.cs +++ b/src/Commands/ToolsCommandGroup.cs @@ -262,7 +262,7 @@ public class ToolsCommandGroup : CommandGroup /// [Command("guildinfo")] [DiscordDefaultDMPermission(false)] - [Description("Shows info current guild")] + [Description("Shows info about current guild")] [UsedImplicitly] public async Task ExecuteGuildInfoAsync() { @@ -514,7 +514,7 @@ public class ToolsCommandGroup : CommandGroup [UsedImplicitly] public async Task ExecuteEightBallAsync( // let the user think he's actually asking the ball a question - string question) + [Description("Question to ask")] string question) { if (!_context.TryGetContextIDs(out var guildId, out _, out _)) { diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 3d452a6..2910bae 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1210,5 +1210,11 @@ namespace Octobot { return ResourceManager.GetString("ButtonDirty", resourceCulture); } } + + internal static string ButtonOpenWiki { + get { + return ResourceManager.GetString("ButtonOpenWiki", resourceCulture); + } + } } } From a9509deb1cd3dc9b02127d8d27520010c0c97d2d Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Sun, 24 Mar 2024 13:11:00 +0300 Subject: [PATCH 14/14] Don't use BannedUntil in MemberData. (#286) Signed-off-by: mctaylors --- src/Data/MemberData.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Data/MemberData.cs b/src/Data/MemberData.cs index 8e23e54..1b3d0d9 100644 --- a/src/Data/MemberData.cs +++ b/src/Data/MemberData.cs @@ -5,10 +5,9 @@ namespace Octobot.Data; /// public sealed class MemberData { - public MemberData(ulong id, DateTimeOffset? bannedUntil = null, List? reminders = null) + public MemberData(ulong id, List? reminders = null) { Id = id; - BannedUntil = bannedUntil; if (reminders is not null) { Reminders = reminders;