From 62709d927be7dcbd19ffe32d03c2a6e77ccf88b9 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:46:53 +0300 Subject: [PATCH 01/44] Add time format example to the description of commands that use TimeSpan. (#267) This PR was made to help users who are trying /remind for the first time by showing an example of the correct time format in the description. --------- Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com> Signed-off-by: mctaylors --- locale/Messages.resx | 3 +++ locale/Messages.ru.resx | 3 +++ locale/Messages.tt-ru.resx | 3 +++ src/Commands/BanCommandGroup.cs | 4 +++- src/Commands/MuteCommandGroup.cs | 3 ++- src/Commands/RemindCommandGroup.cs | 4 +++- src/Commands/ToolsCommandGroup.cs | 1 + src/Messages.Designer.cs | 6 ++++++ 8 files changed, 24 insertions(+), 3 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index b881996..ca48fba 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -654,4 +654,7 @@ Very doubtful + + Example of a valid input: `1h30m` + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index cb318cd..7423347 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -654,4 +654,7 @@ Весьма сомнительно + + Пример правильного ввода: `1ч30м` + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index f0b80a7..dc3bb6f 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -654,4 +654,7 @@ чот сомневаюсь + + правильно пишут так: `1h30m` + diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index 6dbf9b9..c350729 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -76,7 +76,8 @@ public class BanCommandGroup : CommandGroup [Description("User to ban")] IUser target, [Description("Ban reason")] [MaxLength(256)] string reason, - [Description("Ban duration")] string? duration = null) + [Description("Ban duration (e.g. 1h30m)")] + string? duration = null) { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) { @@ -116,6 +117,7 @@ public class BanCommandGroup : CommandGroup { var failedEmbed = new EmbedBuilder() .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithDescription(Messages.TimeSpanExample) .WithColour(ColorsList.Red) .Build(); diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index 788eb2c..c2542e8 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -73,7 +73,7 @@ public class MuteCommandGroup : CommandGroup [Description("Member to mute")] IUser target, [Description("Mute reason")] [MaxLength(256)] string reason, - [Description("Mute duration")] [Option("duration")] + [Description("Mute duration (e.g. 1h30m)")] [Option("duration")] string stringDuration) { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) @@ -111,6 +111,7 @@ public class MuteCommandGroup : CommandGroup { var failedEmbed = new EmbedBuilder() .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithDescription(Messages.TimeSpanExample) .WithColour(ColorsList.Red) .Build(); diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs index c270f30..f9c006e 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/src/Commands/RemindCommandGroup.cs @@ -120,7 +120,7 @@ public class RemindCommandGroup : CommandGroup [RequireContext(ChannelContext.Guild)] [UsedImplicitly] public async Task ExecuteReminderAsync( - [Description("After what period of time mention the reminder")] + [Description("After what period of time mention the reminder (e.g. 1h30m)")] [Option("in")] string timeSpanString, [Description("Reminder text")] [MaxLength(512)] @@ -151,6 +151,7 @@ public class RemindCommandGroup : CommandGroup { var failedEmbed = new EmbedBuilder() .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithDescription(Messages.TimeSpanExample) .WithColour(ColorsList.Red) .Build(); @@ -264,6 +265,7 @@ public class RemindCommandGroup : CommandGroup { var failedEmbed = new EmbedBuilder() .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithDescription(Messages.TimeSpanExample) .WithColour(ColorsList.Red) .Build(); diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs index 3c16232..ea91e1e 100644 --- a/src/Commands/ToolsCommandGroup.cs +++ b/src/Commands/ToolsCommandGroup.cs @@ -461,6 +461,7 @@ public class ToolsCommandGroup : CommandGroup { var failedEmbed = new EmbedBuilder() .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithDescription(Messages.TimeSpanExample) .WithColour(ColorsList.Red) .Build(); diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index 9597bcd..ca460cf 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1178,5 +1178,11 @@ namespace Octobot { return ResourceManager.GetString("EightBallNegative5", resourceCulture); } } + + internal static string TimeSpanExample { + get { + return ResourceManager.GetString("TimeSpanExample", resourceCulture); + } + } } } From 5105b43eff654839edc1d126fa8f33972bfee75b Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:42:37 +0300 Subject: [PATCH 02/44] Add WelcomeMessagesChannel setting (#269) Closes #232 Signed-off-by: mctaylors --- locale/Messages.resx | 3 +++ locale/Messages.ru.resx | 3 +++ locale/Messages.tt-ru.resx | 3 +++ src/Commands/SettingsCommandGroup.cs | 1 + src/Data/GuildSettings.cs | 5 +++++ src/Data/Options/AllOptionsEnum.cs | 1 + src/Messages.Designer.cs | 7 +++++++ src/Responders/GuildMemberJoinedResponder.cs | 4 ++-- 8 files changed, 25 insertions(+), 2 deletions(-) diff --git a/locale/Messages.resx b/locale/Messages.resx index ca48fba..c8ef510 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -657,4 +657,7 @@ Example of a valid input: `1h30m` + + Welcome messages channel + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index 7423347..eb8e57b 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -657,4 +657,7 @@ Пример правильного ввода: `1ч30м` + + Канал для приветствий + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index dc3bb6f..df50d8d 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -657,4 +657,7 @@ правильно пишут так: `1h30m` + + канал куда говорить здравствуйте + diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index acfb8ed..86f031f 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -46,6 +46,7 @@ public class SettingsCommandGroup : CommandGroup GuildSettings.RenameHoistedUsers, GuildSettings.PublicFeedbackChannel, GuildSettings.PrivateFeedbackChannel, + GuildSettings.WelcomeMessagesChannel, GuildSettings.EventNotificationChannel, GuildSettings.DefaultRole, GuildSettings.MuteRole, diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs index cdaede6..5a99505 100644 --- a/src/Data/GuildSettings.cs +++ b/src/Data/GuildSettings.cs @@ -56,6 +56,11 @@ public static class GuildSettings /// public static readonly SnowflakeOption PrivateFeedbackChannel = new("PrivateFeedbackChannel"); + /// + /// Controls what channel should welcome messages be sent to. + /// + public static readonly SnowflakeOption WelcomeMessagesChannel = new("WelcomeMessagesChannel"); + public static readonly SnowflakeOption EventNotificationChannel = new("EventNotificationChannel"); public static readonly SnowflakeOption DefaultRole = new("DefaultRole"); public static readonly SnowflakeOption MuteRole = new("MuteRole"); diff --git a/src/Data/Options/AllOptionsEnum.cs b/src/Data/Options/AllOptionsEnum.cs index a96a9ac..e9637d6 100644 --- a/src/Data/Options/AllOptionsEnum.cs +++ b/src/Data/Options/AllOptionsEnum.cs @@ -21,6 +21,7 @@ public enum AllOptionsEnum [UsedImplicitly] RenameHoistedUsers, [UsedImplicitly] PublicFeedbackChannel, [UsedImplicitly] PrivateFeedbackChannel, + [UsedImplicitly] WelcomeMessagesChannel, [UsedImplicitly] EventNotificationChannel, [UsedImplicitly] DefaultRole, [UsedImplicitly] MuteRole, diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index ca460cf..5ad741e 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1184,5 +1184,12 @@ namespace Octobot { return ResourceManager.GetString("TimeSpanExample", resourceCulture); } } + + internal static string SettingsWelcomeMessagesChannel + { + get { + return ResourceManager.GetString("SettingsWelcomeMessagesChannel", resourceCulture); + } + } } } diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/src/Responders/GuildMemberJoinedResponder.cs index eee93b6..012bfad 100644 --- a/src/Responders/GuildMemberJoinedResponder.cs +++ b/src/Responders/GuildMemberJoinedResponder.cs @@ -51,7 +51,7 @@ public class GuildMemberJoinedResponder : IResponder return Result.FromError(returnRolesResult.Error); } - if (GuildSettings.PublicFeedbackChannel.Get(cfg).Empty() + if (GuildSettings.WelcomeMessagesChannel.Get(cfg).Empty() || GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled") { return Result.FromSuccess(); @@ -76,7 +76,7 @@ public class GuildMemberJoinedResponder : IResponder .Build(); return await _channelApi.CreateMessageWithEmbedResultAsync( - GuildSettings.PublicFeedbackChannel.Get(cfg), embedResult: embed, + GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, allowedMentions: Octobot.NoMentions, ct: ct); } 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 03/44] 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 04/44] 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 05/44] 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 06/44] 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 07/44] 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 08/44] 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 09/44] 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 10/44] 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 11/44] 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 12/44] 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 13/44] 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 14/44] 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 15/44] 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 16/44] 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; From 844615e8bfac768e1b7bdf3165eb0db045ed5d8c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 24 Mar 2024 17:39:26 +0500 Subject: [PATCH 17/44] fix: do not use RepositoryUrl from GitInfo (#287) GitInfo's `RepositoryUrl` string depends on origin URL, which is unvalidated user input that isn't even guaranteed to exist. This can cause issues that are almost impossible to debug Closes #281 --- src/BuildInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/BuildInfo.cs b/src/BuildInfo.cs index fc3a089..2eb6059 100644 --- a/src/BuildInfo.cs +++ b/src/BuildInfo.cs @@ -2,15 +2,15 @@ public static class BuildInfo { - public static string RepositoryUrl => ThisAssembly.Git.RepositoryUrl; + public const string RepositoryUrl = "https://github.com/TeamOctolings/Octobot"; - public static string IssuesUrl => $"{RepositoryUrl}/issues"; + public const string IssuesUrl = $"{RepositoryUrl}/issues"; - public static string WikiUrl => $"{RepositoryUrl}/wiki"; + public const string WikiUrl = $"{RepositoryUrl}/wiki"; - private static string Commit => ThisAssembly.Git.Commit; + private const string Commit = ThisAssembly.Git.Commit; - private static string Branch => ThisAssembly.Git.Branch; + private const string Branch = ThisAssembly.Git.Branch; public static bool IsDirty => ThisAssembly.Git.IsDirty; From c2f7aadaeacdd1f0e88b6bc441f7783cac00ab07 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 24 Mar 2024 20:38:51 +0500 Subject: [PATCH 18/44] Do not use ResultError#IsUserOrEnvironmentError (#289) In LoggerExtensions#LogResult we skip logging the result if the error is "user or environment error". What matches that criteria is defined by Remora's implementation. However, none of errors defined by the implementation should *ever* happen or be ignored: * CommandNotFoundError: The client shouldn't send us non-existing commands. This *can* happen because the client's command list can get out of sync with the server's, but this happens rarely. * AmbiguousCommandInvocationError: We don't have commands that would trigger this error * RequiredParameterValueMissingError: The client shouldn't send us commands without required paremeters * ParameterParsingError: See #220 * ConditionNotSatisfiedError: The client shouldn't send us commands that don't satisfy our conditions Closes #220 --- src/Extensions/LoggerExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Extensions/LoggerExtensions.cs b/src/Extensions/LoggerExtensions.cs index 9df90b8..fca3702 100644 --- a/src/Extensions/LoggerExtensions.cs +++ b/src/Extensions/LoggerExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Remora.Discord.Commands.Extensions; using Remora.Results; namespace Octobot.Extensions; @@ -19,7 +18,7 @@ public static class LoggerExtensions /// The message to use if this result has failed. public static void LogResult(this ILogger logger, IResult result, string? message = "") { - if (result.IsSuccess || result.Error.IsUserOrEnvironmentError()) + if (result.IsSuccess) { return; } From 5e4d0a528c5a604511a02a6f9a06d2d77378b0f4 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 24 Mar 2024 20:48:32 +0500 Subject: [PATCH 19/44] Split message clear log when cleared messages are too long (#288) This change makes Octobot split the message clear log into multiple messages when the combined length of cleared messages exceeds the maximum length for an embed description. Closes #180 --- src/Commands/ClearCommandGroup.cs | 34 +++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs index 70afede..84b69db 100644 --- a/src/Commands/ClearCommandGroup.cs +++ b/src/Commands/ClearCommandGroup.cs @@ -102,7 +102,9 @@ public class ClearCommandGroup : CommandGroup CancellationToken ct = default) { var idList = new List(messages.Count); - var builder = new StringBuilder().AppendLine(Mention.Channel(channelId)).AppendLine(); + + var logEntries = new List { new() }; + var currentLogEntry = 0; for (var i = messages.Count - 1; i >= 1; i--) // '>= 1' to skip last message ('Octobot is thinking...') { var message = messages[i]; @@ -112,8 +114,17 @@ public class ClearCommandGroup : CommandGroup } idList.Add(message.ID); - builder.AppendLine(string.Format(Messages.MessageFrom, Mention.User(message.Author))); - builder.Append(message.Content.InBlockCode()); + + var entry = logEntries[currentLogEntry]; + var str = $"{string.Format(Messages.MessageFrom, Mention.User(message.Author))}\n{message.Content.InBlockCode()}"; + if (entry.Builder.Length + str.Length > EmbedConstants.MaxDescriptionLength) + { + logEntries.Add(entry = new ClearedMessageEntry()); + currentLogEntry++; + } + + entry.Builder.Append(str); + entry.DeletedCount++; } if (idList.Count == 0) @@ -127,7 +138,6 @@ public class ClearCommandGroup : CommandGroup var title = author is not null ? string.Format(Messages.MessagesClearedFiltered, idList.Count.ToString(), author.GetTag()) : string.Format(Messages.MessagesCleared, idList.Count.ToString()); - var description = builder.ToString(); var deleteResult = await _channelApi.BulkDeleteMessagesAsync( channelId, idList, executor.GetTag().EncodeHeader(), ct); @@ -136,12 +146,24 @@ public class ClearCommandGroup : CommandGroup return ResultExtensions.FromError(deleteResult); } - _utility.LogAction( - data.Settings, channelId, executor, title, description, bot, ColorsList.Red, false, ct); + foreach (var log in logEntries) + { + _utility.LogAction( + data.Settings, channelId, executor, author is not null + ? string.Format(Messages.MessagesClearedFiltered, log.DeletedCount.ToString(), author.GetTag()) + : string.Format(Messages.MessagesCleared, log.DeletedCount.ToString()), + log.Builder.ToString(), bot, ColorsList.Red, false, ct); + } var embed = new EmbedBuilder().WithSmallTitle(title, bot) .WithColour(ColorsList.Green).Build(); return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); } + + private sealed class ClearedMessageEntry + { + public StringBuilder Builder { get; } = new(); + public int DeletedCount { get; set; } + } } From 171cfaea1ac2789a189fc8a7da0fede6d3e727cc Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 24 Mar 2024 23:29:10 +0500 Subject: [PATCH 20/44] Add 'ModeratorRole' guild setting (#290) Octobot has various moderation commands such as /ban, /mute, /kick. These commands add multiple features to Discord's built-in functions (such as durations and logging). Some admins may want to force their users to use Octobot's commands instead of Discord UI functions. However, due to the current design, they can't take away the permissions as that remove access to the respective command. This PR adds the `ModeratorRole` option which allows anyone who has `ManageMessages` permission and the role to perform any moderator action. If the role is not set, the Discord permissions are checked instead. If the user doesn't have the role, but has the permission, they can still run the command. --------- Signed-off-by: Octol1ttle --- locale/Messages.resx | 10 +- locale/Messages.ru.resx | 10 +- locale/Messages.tt-ru.resx | 10 +- src/Commands/BanCommandGroup.cs | 32 ++-- src/Commands/KickCommandGroup.cs | 22 +-- src/Commands/MuteCommandGroup.cs | 21 +-- src/Commands/SettingsCommandGroup.cs | 1 + src/Data/GuildSettings.cs | 1 + src/Data/Options/AllOptionsEnum.cs | 1 + src/Octobot.cs | 3 +- src/Services/AccessControlService.cs | 176 +++++++++++++++++++++ src/Services/Update/MemberUpdateService.cs | 10 +- src/Services/Utility.cs | 118 +------------- 13 files changed, 252 insertions(+), 163 deletions(-) create mode 100644 src/Services/AccessControlService.cs diff --git a/locale/Messages.resx b/locale/Messages.resx index 41bb6ef..47e7d4f 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -231,8 +231,11 @@ You cannot kick members from this guild! - - You cannot moderate members in this guild! + + You cannot mute members in this guild! + + + You cannot unmute members in this guild! You cannot manage this guild! @@ -675,4 +678,7 @@ Open Octobot's Wiki + + Moderator role + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index 273338b..2eef257 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -228,8 +228,11 @@ Ты не можешь выгонять участников с этого сервера! - - Ты не можешь модерировать участников этого сервера! + + Ты не можешь глушить участников этого сервера! + + + Ты не можешь разглушать участников этого сервера! Ты не можешь настраивать этот сервер! @@ -675,4 +678,7 @@ Открыть Octobot's Wiki + + Роль модератора + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index af2c94d..4e92a44 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -231,8 +231,11 @@ кик шизиков нельзя - - тебе нельзя управлять шизоидами + + тебе нельзя мутить шизоидов + + + тебе нельзя раззамучивать шизоидов тебе нельзя редактировать дурку @@ -675,4 +678,7 @@ вики Octobot (жмак) + + звание админа + diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index ef5e9a4..02a377a 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -28,6 +28,7 @@ namespace Octobot.Commands; [UsedImplicitly] public class BanCommandGroup : CommandGroup { + private readonly AccessControlService _access; private readonly IDiscordRestChannelAPI _channelApi; private readonly ICommandContext _context; private readonly IFeedbackService _feedback; @@ -36,16 +37,16 @@ public class BanCommandGroup : CommandGroup private readonly IDiscordRestUserAPI _userApi; private readonly Utility _utility; - public BanCommandGroup( - ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData, - IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, - Utility utility) + public BanCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context, + IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData, + IDiscordRestUserAPI userApi, Utility utility) { - _context = context; + _access = access; _channelApi = channelApi; - _guildData = guildData; + _context = context; _feedback = feedback; _guildApi = guildApi; + _guildData = guildData; _userApi = userApi; _utility = utility; } @@ -65,10 +66,10 @@ public class BanCommandGroup : CommandGroup /// /// [Command("ban", "бан")] - [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.BanMembers)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] [Description("Ban user")] [UsedImplicitly] @@ -128,7 +129,8 @@ public class BanCommandGroup : CommandGroup } private async Task BanUserAsync( - IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, Snowflake channelId, + IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, + Snowflake channelId, IUser bot, CancellationToken ct = default) { var existingBanResult = await _guildApi.GetGuildBanAsync(guild.ID, target.ID, ct); @@ -141,7 +143,7 @@ public class BanCommandGroup : CommandGroup } var interactionResult - = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct); + = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct); if (!interactionResult.IsSuccess) { return ResultExtensions.FromError(interactionResult); @@ -155,7 +157,8 @@ public class BanCommandGroup : CommandGroup return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct); } - var builder = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)); + var builder = + new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason)); if (duration is not null) { builder.AppendBulletPoint( @@ -221,10 +224,10 @@ public class BanCommandGroup : CommandGroup /// /// [Command("unban")] - [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.BanMembers)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.BanMembers)] [Description("Unban user")] [UsedImplicitly] @@ -286,7 +289,8 @@ public class BanCommandGroup : CommandGroup .WithColour(ColorsList.Green).Build(); var title = string.Format(Messages.UserUnbanned, target.GetTag()); - var description = new StringBuilder().AppendBulletPoint(string.Format(Messages.DescriptionActionReason, reason)); + var description = + new StringBuilder().AppendBulletPoint(string.Format(Messages.DescriptionActionReason, reason)); _utility.LogAction( data.Settings, channelId, executor, title, description.ToString(), target, ColorsList.Green, ct: ct); diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs index 5149ad4..87b915a 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/src/Commands/KickCommandGroup.cs @@ -24,6 +24,7 @@ namespace Octobot.Commands; [UsedImplicitly] public class KickCommandGroup : CommandGroup { + private readonly AccessControlService _access; private readonly IDiscordRestChannelAPI _channelApi; private readonly ICommandContext _context; private readonly IFeedbackService _feedback; @@ -32,16 +33,16 @@ public class KickCommandGroup : CommandGroup private readonly IDiscordRestUserAPI _userApi; private readonly Utility _utility; - public KickCommandGroup( - ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData, - IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, - Utility utility) + public KickCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context, + IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData, + IDiscordRestUserAPI userApi, Utility utility) { - _context = context; + _access = access; _channelApi = channelApi; - _guildData = guildData; + _context = context; _feedback = feedback; _guildApi = guildApi; + _guildData = guildData; _userApi = userApi; _utility = utility; } @@ -59,10 +60,10 @@ public class KickCommandGroup : CommandGroup /// was kicked and vice-versa. /// [Command("kick", "кик")] - [DiscordDefaultMemberPermissions(DiscordPermission.KickMembers)] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.KickMembers)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.KickMembers)] [Description("Kick member")] [UsedImplicitly] @@ -115,7 +116,7 @@ public class KickCommandGroup : CommandGroup CancellationToken ct = default) { var interactionResult - = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct); + = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct); if (!interactionResult.IsSuccess) { return ResultExtensions.FromError(interactionResult); @@ -134,7 +135,8 @@ public class KickCommandGroup : CommandGroup { var dmEmbed = new EmbedBuilder().WithGuildTitle(guild) .WithTitle(Messages.YouWereKicked) - .WithDescription(MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason))) + .WithDescription( + MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason))) .WithActionFooter(executor) .WithCurrentTimestamp() .WithColour(ColorsList.Red) diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs index 8e79830..ce0a296 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/src/Commands/MuteCommandGroup.cs @@ -28,6 +28,7 @@ namespace Octobot.Commands; [UsedImplicitly] public class MuteCommandGroup : CommandGroup { + private readonly AccessControlService _access; private readonly ICommandContext _context; private readonly IFeedbackService _feedback; private readonly IDiscordRestGuildAPI _guildApi; @@ -35,14 +36,14 @@ public class MuteCommandGroup : CommandGroup private readonly IDiscordRestUserAPI _userApi; private readonly Utility _utility; - public MuteCommandGroup( - ICommandContext context, GuildDataService guildData, IFeedbackService feedback, - IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, Utility utility) + public MuteCommandGroup(AccessControlService access, ICommandContext context, IFeedbackService feedback, + IDiscordRestGuildAPI guildApi, GuildDataService guildData, IDiscordRestUserAPI userApi, Utility utility) { + _access = access; _context = context; - _guildData = guildData; _feedback = feedback; _guildApi = guildApi; + _guildData = guildData; _userApi = userApi; _utility = utility; } @@ -62,10 +63,10 @@ public class MuteCommandGroup : CommandGroup /// /// [Command("mute", "мут")] - [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ModerateMembers)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)] [Description("Mute member")] [UsedImplicitly] @@ -127,7 +128,7 @@ public class MuteCommandGroup : CommandGroup Snowflake channelId, IUser bot, CancellationToken ct = default) { var interactionResult - = await _utility.CheckInteractionsAsync( + = await _access.CheckInteractionsAsync( guildId, executor.ID, target.ID, "Mute", ct); if (!interactionResult.IsSuccess) { @@ -239,10 +240,10 @@ public class MuteCommandGroup : CommandGroup /// /// [Command("unmute", "размут")] - [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)] + [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)] [DiscordDefaultDMPermission(false)] [RequireContext(ChannelContext.Guild)] - [RequireDiscordPermission(DiscordPermission.ModerateMembers)] + [RequireDiscordPermission(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)] [Description("Unmute member")] [UsedImplicitly] @@ -290,7 +291,7 @@ public class MuteCommandGroup : CommandGroup IUser bot, CancellationToken ct = default) { var interactionResult - = await _utility.CheckInteractionsAsync( + = await _access.CheckInteractionsAsync( guildId, executor.ID, target.ID, "Unmute", ct); if (!interactionResult.IsSuccess) { diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs index f756e93..a39e9c7 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/src/Commands/SettingsCommandGroup.cs @@ -51,6 +51,7 @@ public class SettingsCommandGroup : CommandGroup GuildSettings.EventNotificationChannel, GuildSettings.DefaultRole, GuildSettings.MuteRole, + GuildSettings.ModeratorRole, GuildSettings.EventNotificationRole, GuildSettings.EventEarlyNotificationOffset ]; diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs index 518465b..a1d8d74 100644 --- a/src/Data/GuildSettings.cs +++ b/src/Data/GuildSettings.cs @@ -76,6 +76,7 @@ public static class GuildSettings public static readonly SnowflakeOption EventNotificationChannel = new("EventNotificationChannel"); public static readonly SnowflakeOption DefaultRole = new("DefaultRole"); public static readonly SnowflakeOption MuteRole = new("MuteRole"); + public static readonly SnowflakeOption ModeratorRole = new("ModeratorRole"); public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole"); /// diff --git a/src/Data/Options/AllOptionsEnum.cs b/src/Data/Options/AllOptionsEnum.cs index 6932822..d9e0c13 100644 --- a/src/Data/Options/AllOptionsEnum.cs +++ b/src/Data/Options/AllOptionsEnum.cs @@ -26,6 +26,7 @@ public enum AllOptionsEnum [UsedImplicitly] EventNotificationChannel, [UsedImplicitly] DefaultRole, [UsedImplicitly] MuteRole, + [UsedImplicitly] ModeratorRole, [UsedImplicitly] EventNotificationRole, [UsedImplicitly] EventEarlyNotificationOffset } diff --git a/src/Octobot.cs b/src/Octobot.cs index a4871f4..065967e 100644 --- a/src/Octobot.cs +++ b/src/Octobot.cs @@ -88,8 +88,9 @@ public sealed class Octobot .AddPreparationErrorEvent() .AddPostExecutionEvent() // Services - .AddSingleton() + .AddSingleton() .AddSingleton() + .AddSingleton() .AddHostedService(provider => provider.GetRequiredService()) .AddHostedService() .AddHostedService() diff --git a/src/Services/AccessControlService.cs b/src/Services/AccessControlService.cs new file mode 100644 index 0000000..84667c3 --- /dev/null +++ b/src/Services/AccessControlService.cs @@ -0,0 +1,176 @@ +using Octobot.Data; +using Octobot.Extensions; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Commands.Conditions; +using Remora.Discord.Commands.Results; +using Remora.Rest.Core; +using Remora.Results; + +namespace Octobot.Services; + +public sealed class AccessControlService +{ + private readonly GuildDataService _data; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly RequireDiscordPermissionCondition _permission; + private readonly IDiscordRestUserAPI _userApi; + + public AccessControlService(GuildDataService data, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, + RequireDiscordPermissionCondition permission) + { + _data = data; + _guildApi = guildApi; + _userApi = userApi; + _permission = permission; + } + + private async Task> CheckPermissionAsync(GuildData data, Snowflake memberId, IGuildMember member, + DiscordPermission permission, CancellationToken ct = default) + { + var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings); + var result = await _permission.CheckAsync(new RequireDiscordPermissionAttribute([permission]), member, ct); + + if (result.Error is not null and not PermissionDeniedError) + { + return Result.FromError(result); + } + + var hasPermission = result.IsSuccess; + return hasPermission || (!moderatorRole.Empty() && + data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value)); + } + + /// + /// Checks whether or not a member can interact with another member + /// + /// The ID of the guild in which an operation is being performed. + /// The executor of the operation. + /// The target of the operation. + /// The operation. + /// The cancellation token for this operation. + /// + /// + /// A result which has succeeded with a null string if the member can interact with the target. + /// + /// A result which has succeeded with a non-null string containing the error message if the member cannot + /// interact with the target. + /// + /// A result which has failed if an error occurred during the execution of this method. + /// + /// + public async Task> CheckInteractionsAsync( + Snowflake guildId, Snowflake? interacterId, Snowflake targetId, string action, CancellationToken ct = default) + { + if (interacterId == targetId) + { + return Result.FromSuccess($"UserCannot{action}Themselves".Localized()); + } + + var botResult = await _userApi.GetCurrentUserAsync(ct); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); + if (!guildResult.IsDefined(out var guild)) + { + return Result.FromError(guildResult); + } + + var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); + if (!targetMemberResult.IsDefined(out var targetMember)) + { + return Result.FromSuccess(null); + } + + var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); + if (!currentMemberResult.IsDefined(out var currentMember)) + { + return Result.FromError(currentMemberResult); + } + + var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); + if (!rolesResult.IsDefined(out var roles)) + { + return Result.FromError(rolesResult); + } + + if (interacterId is null) + { + return CheckInteractions(action, guild, roles, targetMember, currentMember, currentMember); + } + + var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct); + if (!interacterResult.IsDefined(out var interacter)) + { + return Result.FromError(interacterResult); + } + + var data = await _data.GetData(guildId, ct); + + var permissionResult = await CheckPermissionAsync(data, interacterId.Value, interacter, + action switch + { + "Ban" => DiscordPermission.BanMembers, + "Kick" => DiscordPermission.KickMembers, + "Mute" or "Unmute" => DiscordPermission.ModerateMembers, + _ => throw new Exception() + }, ct); + if (!permissionResult.IsDefined(out var hasPermission)) + { + return Result.FromError(permissionResult); + } + + return hasPermission + ? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter) + : Result.FromSuccess($"UserCannot{action}Members".Localized()); + } + + private static Result CheckInteractions( + string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember, + IGuildMember interacter) + { + if (!targetMember.User.IsDefined(out var targetUser)) + { + return new ArgumentNullError(nameof(targetMember.User)); + } + + if (!interacter.User.IsDefined(out var interacterUser)) + { + return new ArgumentNullError(nameof(interacter.User)); + } + + if (currentMember.User == targetMember.User) + { + return Result.FromSuccess($"UserCannot{action}Bot".Localized()); + } + + if (targetUser.ID == guild.OwnerID) + { + return Result.FromSuccess($"UserCannot{action}Owner".Localized()); + } + + var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); + var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID)); + + var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); + if (targetBotRoleDiff >= 0) + { + return Result.FromSuccess($"BotCannot{action}Target".Localized()); + } + + if (interacterUser.ID == guild.OwnerID) + { + return Result.FromSuccess(null); + } + + var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); + var targetInteracterRoleDiff + = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); + return targetInteracterRoleDiff < 0 + ? Result.FromSuccess(null) + : Result.FromSuccess($"UserCannot{action}Target".Localized()); + } +} diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs index 45d0476..e177fca 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/src/Services/Update/MemberUpdateService.cs @@ -26,20 +26,20 @@ public sealed partial class MemberUpdateService : BackgroundService "Torus", "Violet", "Vortex", "Vulture", "Wagon", "Whale", "Woodpecker", "Zebra", "Zigzag" ]; + private readonly AccessControlService _access; private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildAPI _guildApi; private readonly GuildDataService _guildData; private readonly ILogger _logger; - private readonly Utility _utility; - public MemberUpdateService(IDiscordRestChannelAPI channelApi, IDiscordRestGuildAPI guildApi, - GuildDataService guildData, ILogger logger, Utility utility) + public MemberUpdateService(AccessControlService access, IDiscordRestChannelAPI channelApi, + IDiscordRestGuildAPI guildApi, GuildDataService guildData, ILogger logger) { + _access = access; _channelApi = channelApi; _guildApi = guildApi; _guildData = guildData; _logger = logger; - _utility = utility; } protected override async Task ExecuteAsync(CancellationToken ct) @@ -94,7 +94,7 @@ public sealed partial class MemberUpdateService : BackgroundService } var interactionResult - = await _utility.CheckInteractionsAsync(guildId, null, id, "Update", ct); + = await _access.CheckInteractionsAsync(guildId, null, id, "Update", ct); if (!interactionResult.IsSuccess) { return ResultExtensions.FromError(interactionResult); diff --git a/src/Services/Utility.cs b/src/Services/Utility.cs index ad06315..3b9ab19 100644 --- a/src/Services/Utility.cs +++ b/src/Services/Utility.cs @@ -21,129 +21,13 @@ public sealed class Utility private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildScheduledEventAPI _eventApi; private readonly IDiscordRestGuildAPI _guildApi; - private readonly IDiscordRestUserAPI _userApi; public Utility( - IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi, - IDiscordRestUserAPI userApi) + IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi) { _channelApi = channelApi; _eventApi = eventApi; _guildApi = guildApi; - _userApi = userApi; - } - - /// - /// Checks whether or not a member can interact with another member - /// - /// The ID of the guild in which an operation is being performed. - /// The executor of the operation. - /// The target of the operation. - /// The operation. - /// The cancellation token for this operation. - /// - /// - /// A result which has succeeded with a null string if the member can interact with the target. - /// - /// A result which has succeeded with a non-null string containing the error message if the member cannot - /// interact with the target. - /// - /// A result which has failed if an error occurred during the execution of this method. - /// - /// - public async Task> CheckInteractionsAsync( - Snowflake guildId, Snowflake? interacterId, Snowflake targetId, string action, CancellationToken ct = default) - { - if (interacterId == targetId) - { - return Result.FromSuccess($"UserCannot{action}Themselves".Localized()); - } - - var botResult = await _userApi.GetCurrentUserAsync(ct); - if (!botResult.IsDefined(out var bot)) - { - return Result.FromError(botResult); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); - if (!guildResult.IsDefined(out var guild)) - { - return Result.FromError(guildResult); - } - - var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); - if (!targetMemberResult.IsDefined(out var targetMember)) - { - return Result.FromSuccess(null); - } - - var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); - if (!currentMemberResult.IsDefined(out var currentMember)) - { - return Result.FromError(currentMemberResult); - } - - var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); - if (!rolesResult.IsDefined(out var roles)) - { - return Result.FromError(rolesResult); - } - - if (interacterId is null) - { - return CheckInteractions(action, guild, roles, targetMember, currentMember, currentMember); - } - - var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct); - return interacterResult.IsDefined(out var interacter) - ? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter) - : Result.FromError(interacterResult); - } - - private static Result CheckInteractions( - string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember, - IGuildMember interacter) - { - if (!targetMember.User.IsDefined(out var targetUser)) - { - return new ArgumentNullError(nameof(targetMember.User)); - } - - if (!interacter.User.IsDefined(out var interacterUser)) - { - return new ArgumentNullError(nameof(interacter.User)); - } - - if (currentMember.User == targetMember.User) - { - return Result.FromSuccess($"UserCannot{action}Bot".Localized()); - } - - if (targetUser.ID == guild.OwnerID) - { - return Result.FromSuccess($"UserCannot{action}Owner".Localized()); - } - - var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); - var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID)); - - var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); - if (targetBotRoleDiff >= 0) - { - return Result.FromSuccess($"BotCannot{action}Target".Localized()); - } - - if (interacterUser.ID == guild.OwnerID) - { - return Result.FromSuccess(null); - } - - var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); - var targetInteracterRoleDiff - = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); - return targetInteracterRoleDiff < 0 - ? Result.FromSuccess(null) - : Result.FromSuccess($"UserCannot{action}Target".Localized()); } /// From e76fccd62228190ee528233cc6eb3ab34a2bf1ef Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:40:04 +0300 Subject: [PATCH 21/44] Rename currentMember to botMember (#291) Signed-off-by: mctaylors --- src/Services/AccessControlService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Services/AccessControlService.cs b/src/Services/AccessControlService.cs index 84667c3..aeb16e4 100644 --- a/src/Services/AccessControlService.cs +++ b/src/Services/AccessControlService.cs @@ -85,10 +85,10 @@ public sealed class AccessControlService return Result.FromSuccess(null); } - var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); - if (!currentMemberResult.IsDefined(out var currentMember)) + var botMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); + if (!botMemberResult.IsDefined(out var botMember)) { - return Result.FromError(currentMemberResult); + return Result.FromError(botMemberResult); } var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); @@ -99,7 +99,7 @@ public sealed class AccessControlService if (interacterId is null) { - return CheckInteractions(action, guild, roles, targetMember, currentMember, currentMember); + return CheckInteractions(action, guild, roles, targetMember, botMember, botMember); } var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct); @@ -124,12 +124,12 @@ public sealed class AccessControlService } return hasPermission - ? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter) + ? CheckInteractions(action, guild, roles, targetMember, botMember, interacter) : Result.FromSuccess($"UserCannot{action}Members".Localized()); } private static Result CheckInteractions( - string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember, + string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember botMember, IGuildMember interacter) { if (!targetMember.User.IsDefined(out var targetUser)) @@ -142,7 +142,7 @@ public sealed class AccessControlService return new ArgumentNullError(nameof(interacter.User)); } - if (currentMember.User == targetMember.User) + if (botMember.User == targetMember.User) { return Result.FromSuccess($"UserCannot{action}Bot".Localized()); } @@ -153,7 +153,7 @@ public sealed class AccessControlService } var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); - var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID)); + var botRoles = roles.Where(r => botMember.Roles.Contains(r.ID)); var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); if (targetBotRoleDiff >= 0) From cccc4d6205259d2712567e2de91174112395cc2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:47:05 +0300 Subject: [PATCH 22/44] Bump muno92/resharper_inspectcode from 1.11.7 to 1.11.8 (#292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [muno92/resharper_inspectcode](https://github.com/muno92/resharper_inspectcode) from 1.11.7 to 1.11.8.
Release notes

Sourced from muno92/resharper_inspectcode's releases.

1.11.8

What's Changed

New Contributors

Full Changelog: https://github.com/muno92/resharper_inspectcode/compare/1.11.7...1.11.8

Changelog

Sourced from muno92/resharper_inspectcode's changelog.

1.11.8 - 2024-03-23

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=muno92/resharper_inspectcode&package-manager=github_actions&previous-version=1.11.7&new-version=1.11.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 8002f6f..859f8fa 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.11.7 + uses: muno92/resharper_inspectcode@1.11.8 with: solutionPath: ./Octobot.sln ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor From 9429dfe8d8c64d1c49ee6879fcd82e2b57f740ae Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 26 Mar 2024 15:35:31 +0500 Subject: [PATCH 23/44] Fix "No operation context has been set for this scope." crash on startup (#293) Signed-off-by: Octol1ttle --- src/Services/AccessControlService.cs | 65 +++++++++++----------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/Services/AccessControlService.cs b/src/Services/AccessControlService.cs index aeb16e4..cb235f9 100644 --- a/src/Services/AccessControlService.cs +++ b/src/Services/AccessControlService.cs @@ -2,8 +2,6 @@ using Octobot.Extensions; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; -using Remora.Discord.Commands.Conditions; -using Remora.Discord.Commands.Results; using Remora.Rest.Core; using Remora.Results; @@ -13,32 +11,30 @@ public sealed class AccessControlService { private readonly GuildDataService _data; private readonly IDiscordRestGuildAPI _guildApi; - private readonly RequireDiscordPermissionCondition _permission; private readonly IDiscordRestUserAPI _userApi; - public AccessControlService(GuildDataService data, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, - RequireDiscordPermissionCondition permission) + public AccessControlService(GuildDataService data, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi) { _data = data; _guildApi = guildApi; _userApi = userApi; - _permission = permission; } - private async Task> CheckPermissionAsync(GuildData data, Snowflake memberId, IGuildMember member, - DiscordPermission permission, CancellationToken ct = default) + private static bool CheckPermission(IEnumerable roles, GuildData data, Snowflake memberId, + IGuildMember member, + DiscordPermission permission) { var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings); - var result = await _permission.CheckAsync(new RequireDiscordPermissionAttribute([permission]), member, ct); - - if (result.Error is not null and not PermissionDeniedError) + if (!moderatorRole.Empty() && data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value)) { - return Result.FromError(result); + return true; } - var hasPermission = result.IsSuccess; - return hasPermission || (!moderatorRole.Empty() && - data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value)); + return roles + .Where(r => member.Roles.Contains(r.ID)) + .Any(r => + r.Permissions.HasPermission(permission) + ); } /// @@ -67,30 +63,35 @@ public sealed class AccessControlService return Result.FromSuccess($"UserCannot{action}Themselves".Localized()); } - var botResult = await _userApi.GetCurrentUserAsync(ct); - if (!botResult.IsDefined(out var bot)) - { - return Result.FromError(botResult); - } - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); if (!guildResult.IsDefined(out var guild)) { return Result.FromError(guildResult); } - var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); - if (!targetMemberResult.IsDefined(out var targetMember)) + if (interacterId == guild.OwnerID) { return Result.FromSuccess(null); } + var botResult = await _userApi.GetCurrentUserAsync(ct); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + var botMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); if (!botMemberResult.IsDefined(out var botMember)) { return Result.FromError(botMemberResult); } + var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); + if (!targetMemberResult.IsDefined(out var targetMember)) + { + return Result.FromSuccess(null); + } + var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); if (!rolesResult.IsDefined(out var roles)) { @@ -110,18 +111,14 @@ public sealed class AccessControlService var data = await _data.GetData(guildId, ct); - var permissionResult = await CheckPermissionAsync(data, interacterId.Value, interacter, + var hasPermission = CheckPermission(roles, data, interacterId.Value, interacter, action switch { "Ban" => DiscordPermission.BanMembers, "Kick" => DiscordPermission.KickMembers, "Mute" or "Unmute" => DiscordPermission.ModerateMembers, _ => throw new Exception() - }, ct); - if (!permissionResult.IsDefined(out var hasPermission)) - { - return Result.FromError(permissionResult); - } + }); return hasPermission ? CheckInteractions(action, guild, roles, targetMember, botMember, interacter) @@ -137,11 +134,6 @@ public sealed class AccessControlService return new ArgumentNullError(nameof(targetMember.User)); } - if (!interacter.User.IsDefined(out var interacterUser)) - { - return new ArgumentNullError(nameof(interacter.User)); - } - if (botMember.User == targetMember.User) { return Result.FromSuccess($"UserCannot{action}Bot".Localized()); @@ -161,11 +153,6 @@ public sealed class AccessControlService return Result.FromSuccess($"BotCannot{action}Target".Localized()); } - if (interacterUser.ID == guild.OwnerID) - { - return Result.FromSuccess(null); - } - var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); var targetInteracterRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); From 70fccf83352dcfdb0c22f046e8d08aae58eabb08 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:57:49 +0300 Subject: [PATCH 24/44] Use unicode codes instead of emojis (#295) This change was made to avoid using emoji in the code, which may not render correctly depending on the IDE and/or operating system. --- src/Commands/AboutCommandGroup.cs | 6 +++--- src/Commands/Events/ErrorLoggingPostExecutionEvent.cs | 2 +- src/Responders/GuildLoadedResponder.cs | 2 +- src/Services/Update/ScheduledEventUpdateService.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index 027e7f8..f2fa11f 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -107,14 +107,14 @@ public class AboutCommandGroup : CommandGroup var repositoryButton = new ButtonComponent( ButtonComponentStyle.Link, Messages.ButtonOpenRepository, - new PartialEmoji(Name: "🌐"), + new PartialEmoji(Name: "\ud83c\udf10"), // 'GLOBE WITH MERIDIANS' (U+1F310) URL: BuildInfo.RepositoryUrl ); var wikiButton = new ButtonComponent( ButtonComponentStyle.Link, Messages.ButtonOpenWiki, - new PartialEmoji(Name: "📖"), + new PartialEmoji(Name: "\ud83d\udcd6"), // 'OPEN BOOK' (U+1F4D6) URL: BuildInfo.WikiUrl ); @@ -123,7 +123,7 @@ public class AboutCommandGroup : CommandGroup BuildInfo.IsDirty ? Messages.ButtonDirty : Messages.ButtonReportIssue, - new PartialEmoji(Name: "⚠️"), + new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) URL: BuildInfo.IssuesUrl, IsDisabled: BuildInfo.IsDirty ); diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 5fa2ea8..551c2d0 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -73,7 +73,7 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent BuildInfo.IsDirty ? Messages.ButtonDirty : Messages.ButtonReportIssue, - new PartialEmoji(Name: "⚠️"), + new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) URL: BuildInfo.IssuesUrl, IsDisabled: BuildInfo.IsDirty ); diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs index b03fd3f..55e9673 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/src/Responders/GuildLoadedResponder.cs @@ -114,7 +114,7 @@ public class GuildLoadedResponder : IResponder BuildInfo.IsDirty ? Messages.ButtonDirty : Messages.ButtonReportIssue, - new PartialEmoji(Name: "⚠️"), + new PartialEmoji(Name: "\u26a0\ufe0f"), // 'WARNING SIGN' (U+26A0) URL: BuildInfo.IssuesUrl, IsDisabled: BuildInfo.IsDirty ); diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs index 8168fc1..cb87779 100644 --- a/src/Services/Update/ScheduledEventUpdateService.cs +++ b/src/Services/Update/ScheduledEventUpdateService.cs @@ -223,7 +223,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService var button = new ButtonComponent( ButtonComponentStyle.Link, Messages.ButtonOpenEventInfo, - new PartialEmoji(Name: "📋"), + new PartialEmoji(Name: "\ud83d\udccb"), // 'CLIPBOARD' (U+1F4CB) URL: $"https://discord.com/events/{scheduledEvent.GuildID}/{scheduledEvent.ID}" ); From 96680d3beb4361d20f7efe1edd02c567519476a5 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:59:19 +0300 Subject: [PATCH 25/44] Make the logo in /about independent of image hosting (#296) PR's name speaks for itself. It might also be useful to update the logo more easily. --------- Signed-off-by: Macintxsh <95250141+mctaylors@users.noreply.github.com> Co-authored-by: Octol1ttle --- src/Commands/AboutCommandGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index f2fa11f..b8c6d0f 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -100,7 +100,7 @@ public class AboutCommandGroup : CommandGroup .WithSmallTitle(string.Format(Messages.AboutBot, bot.Username), bot) .WithDescription(builder.ToString()) .WithColour(ColorsList.Cyan) - .WithImageUrl("https://i.ibb.co/fS6wZhh/octobot-banner.png") + .WithImageUrl("https://raw.githubusercontent.com/TeamOctolings/Octobot/master/docs/octobot-banner.png") .WithFooter(string.Format(Messages.Version, BuildInfo.Version)) .Build(); From d3053d87e813bf698851d19b2d3337fe1ee1852e Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 1 Apr 2024 22:20:41 +0300 Subject: [PATCH 26/44] Remove mctaylors' version of the Russian language (#297) 295 PR/issues ~(not 300, however)~ or ~1.5 years ago, I made #2, the Russian language replacement aka mctaylors-ru. This was my first contribution to the Octobot project (formerly known as Boyfriend). This was to add some sort of unique, unusual feature to Octobot, which doesn't have any moderator bots. Everyone loved the language. But it just became difficult to maintain. I certainly don't want to get rid of it, but it leaves me no other choice. This isn't a joke or anything like that. I'm tired of maintaining it. And I'm sure the other contributors are too. This PR removes the mctaylors-ru language. --------- Signed-off-by: mctaylors Co-authored-by: Octol1ttle --- locale/Messages.tt-ru.resx | 684 ----------------------------- src/Data/Options/LanguageOption.cs | 3 +- src/Services/GuildDataService.cs | 18 +- 3 files changed, 17 insertions(+), 688 deletions(-) delete mode 100644 locale/Messages.tt-ru.resx diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx deleted file mode 100644 index 4e92a44..0000000 --- a/locale/Messages.tt-ru.resx +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - я родился! - - - сообщение {0} вырезано: - - - сообщение {0} переделано: - - - {0}, добро пожаловать на сервер {1} - - - вииимо! - - - вуууми! - - - нгьес! - - - вы были забанены - - - время бана закончиловсь - - - вы были кикнуты - - - мс - - - *тут ничего нет* - - - нъет - - - язык - - - префикс - - - удалять звание при муте - - - разглашать о том что пришел новый шизоид - - - звание замученного - - - такого языка нету... - - - да - - - нъет - - - шизик не забанен - - - шизоид не замучен! - - - здравствуйте (типо настройка) - - - {0} забанен - - - получать инфу о старте бота - - - криво настроил прикол, давай по новой - - - ты шо, мутить больше чем на 28 дней таймаут не разрешает, вот настроишь роль мута, тогда поговорим - - - я не могу замутить ботов, сделай что нибудь - - - роль для уведомлений о создании движухи - - - канал для уведомлений о движухах - - - получатели уведомлений о начале движух - - - движуха "{0}" начинается - - - движуха "{0}" отменена! - - - движуха "{0}" завершена! - - - вырезано {0} забавных сообщений - - - ты все сломал! значение прикола `{0}` и так {1} - - - нъет - - - укажи самого шизика - - - бан - - - тебе нельзя иметь власть над сообщениями шизоидов - - - кик шизиков нельзя - - - тебе нельзя мутить шизоидов - - - тебе нельзя раззамучивать шизоидов - - - тебе нельзя редактировать дурку - - - я не могу ваще никого банить чел. - - - я не могу исправлять орфографический кринж участников, сделай что нибудь. - - - я не могу ваще никого кикать чел. - - - я не могу контроллировать за всеми ними, сделай что нибудь. - - - я не могу этому серверу хоть че либо нибудь изменить, сделай что нибудь. - - - ээбля френдли фаер огонь по своим - - - бан админу нельзя - - - бан этому шизику нельзя - - - самобан нельзя - - - я не могу его забанить... - - - кик админу нельзя - - - самокик нельзя - - - ээбля френдли фаер огонь по своим - - - я не могу его кикнуть... - - - кик этому шизику нельзя - - - мут админу нельзя - - - самомут нельзя - - - ээбля френдли фаер огонь по своим - - - я не могу его замутить... - - - мут этому шизику нельзя - - - сильно - - - ты замучен. - - - ... - - - тебе нельзя раззамучивать - - - я не могу его раззамутить... - - - движуха "{0}" начнется {1}! - - - заранее пнуть в минутах до начала движухи - - - у нас такого шизоида нету, проверь, валиден ли ID уважаемого (я забываю о шизоидах если они ливнули минимум месяц назад) - - - дефолтное звание - - - канал для секретных уведомлений - - - канал для не секретных уведомлений - - - вернуть звания при переподключении в дурку - - - автоматом стартить движухи - - - ответственный - - - {0} создает новое событие: - - - движуха произойдет {0} в канале {1} - - - движуха будет происходить с {0} до {1} в {2} - - - открыть ивент - - - все это длилось `{0}` - - - движуха происходит в {0} - - - движуха происходит в {0} до {1} - - - этот шизоид уже лежит в бане - - - {0} раззабанен - - - {0} в муте - - - {0} в размуте - - - этого шизоида никто не мутил. - - - у нас такого шизоида нету... - - - {0} вышел с посторонней помощью - - - причина: {0} - - - до: {0} - - - этот шизоид УЖЕ замучился - - - от {0} - - - девелоперы: - - - репа Octobot (тык) - - - немного об {0} - - - скучный девелопер + дизайнер создавший Octobot's Wiki - - - ВАЖНЫЙ соучастник кодинг-стримов @Octol1ttle - - - САМЫЙ ВАЖНЫЙ чел написавший кода больше всех (99.99%) - - - напоминалка для {0} скрафченА - - - напоминалка для {0} - - - ты хотел чтоб я напомнил тебе {0} - - - приколы Octobot - - - прикол редактирован - - - прикол сдох - - - стало - - - переобувать шизоидов пытающихся поднять себя в табе - - - это страница - - - если я был бы html, я бы сказал 404 - - - ну а если быть точнее, тут всего {0} страниц(-ы) - - - следующее - - - предыдущее - - - напоминалки {0} - - - у тебя нет напоминалки на этом номере! - - - напоминалка уничтожена - - - ты еще не крафтил напоминалки - - - {0} откачен к заводским - - - откатываемся к заводским... - - - чекнуть сообщение: {0} - - - чекнуть канал: {0} - - - номер в списке: {0} - - - время отправки: {0} - - - че там в напоминалке: {0} - - - дисплейнейм - - - деанон {0} - - - замучен - - - юзер Discord со времен - - - забанен - - - приколы полученные по заслугам - - - пермабан - - - вышел из сервера - - - замучен таймаутом - - - замучен ролькой - - - участник сервера со времен - - - сервернейм - - - рольки - - - бустит сервер со времен - - - рандомное число {0}: - - - ну чувак... - - - наибольшее: {0} - - - наименьшее: {0} - - - (дефолт) - - - таймштамп для {0}: - - - офсет: {0} - - - дескрипшон гильдии - - - создался - - - админ гильдии - - - буст гильдии - - - уровень - - - кол-во бустов - - - алло а чё мне удалять-то - - - вырезано {0} забавных сообщений от {1} - - - произошёл тотальный разнос в гилддате. - - - возможно всё съедет с крыши, но знай, что я больше ничё не сохраню. - - - произошёл тотальный разнос в команде, удачи. - - - если ты это читаешь второй раз за сегодня, пиши разрабам - - - зарепортить баг - - - ну, мы потеряли {0} - - - до свидания (типо настройка) - - - ты там правильно напиши таймспан - - - кикнут - - - напоминалка подправлена - - - абсолютли - - - заявлено - - - ваще не сомневайся - - - 100% да - - - будь в этом уверен - - - я считаю что да - - - ну вполне вероятно - - - ну выглядит нормально - - - мне сказали ок - - - мгм - - - ну-ка попробуй снова - - - давай позже - - - щас пока не скажу - - - я не могу сейчас предсказать - - - ну сконцентрируйся и давай еще раз - - - даже не думай - - - мое завление это нет - - - я тут посчитал, короче нет - - - выглядит такое себе - - - чот сомневаюсь - - - правильно пишут так: `1h30m` - - - {0} - - - канал куда говорить здравствуйте - - - вот иди сам и почини что сломал - - - вики Octobot (жмак) - - - звание админа - - diff --git a/src/Data/Options/LanguageOption.cs b/src/Data/Options/LanguageOption.cs index 464c61b..22f98df 100644 --- a/src/Data/Options/LanguageOption.cs +++ b/src/Data/Options/LanguageOption.cs @@ -11,8 +11,7 @@ public sealed class LanguageOption : Option private static readonly Dictionary CultureInfoCache = new() { { "en", new CultureInfo("en-US") }, - { "ru", new CultureInfo("ru-RU") }, - { "mctaylors-ru", new CultureInfo("tt-RU") } + { "ru", new CultureInfo("ru-RU") } }; public LanguageOption(string name, string defaultValue) : base(name, CultureInfoCache[defaultValue]) { } diff --git a/src/Services/GuildDataService.cs b/src/Services/GuildDataService.cs index c9458a0..e503d22 100644 --- a/src/Services/GuildDataService.cs +++ b/src/Services/GuildDataService.cs @@ -78,7 +78,7 @@ public sealed class GuildDataService : BackgroundService var settingsPath = $"{path}/Settings.json"; var scheduledEventsPath = $"{path}/ScheduledEvents.json"; - MigrateGuildData(guildId, path); + MigrateDataDirectory(guildId, path); Directory.CreateDirectory(path); @@ -106,6 +106,11 @@ public sealed class GuildDataService : BackgroundService dataLoadFailed = true; } + if (jsonSettings is not null) + { + FixJsonSettings(jsonSettings); + } + await using var eventsStream = File.OpenRead(scheduledEventsPath); Dictionary? events = null; try @@ -155,7 +160,7 @@ public sealed class GuildDataService : BackgroundService return finalData; } - private void MigrateGuildData(Snowflake guildId, string newPath) + private void MigrateDataDirectory(Snowflake guildId, string newPath) { var oldPath = $"{guildId}"; @@ -169,6 +174,15 @@ public sealed class GuildDataService : BackgroundService } } + private static void FixJsonSettings(JsonNode settings) + { + var language = settings[GuildSettings.Language.Name]?.GetValue(); + if (language is "mctaylors-ru") + { + settings[GuildSettings.Language.Name] = "ru"; + } + } + public async Task GetSettings(Snowflake guildId, CancellationToken ct = default) { return (await GetData(guildId, ct)).Settings; From defa3c2e4acc5e30f6e533a2b3480b73e37a05b2 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Sun, 7 Apr 2024 13:51:28 +0300 Subject: [PATCH 27/44] Listen to Maritime Memory on Wii U Discontinuation Day (#299) Due to the shutdown of Wii U online services on April 8 at 23:00 UTC (which affects Splatoon for Wii U), I'm opening a PR to memorialize Splatoon multiplayer on Wii U by replacing bot music with Maritime Memory on April 8-9. Signed-off-by: mctaylors --- src/Services/Update/SongUpdateService.cs | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Services/Update/SongUpdateService.cs b/src/Services/Update/SongUpdateService.cs index 53cc59b..41d5bf3 100644 --- a/src/Services/Update/SongUpdateService.cs +++ b/src/Services/Update/SongUpdateService.cs @@ -32,6 +32,11 @@ public sealed class SongUpdateService : BackgroundService ("Off the Hook", "Fly Octo Fly ~ Ebb & Flow (Octo)", new TimeSpan(0, 3, 5)) ]; + private static readonly (string Author, string Name, TimeSpan Duration)[] SpecialSongList = + [ + ("Squid Sisters", "Maritime Memory", new TimeSpan(0, 2, 47)) + ]; + private readonly List _activityList = [new Activity("with Remora.Discord", ActivityType.Game)]; private readonly DiscordGatewayClient _client; @@ -54,19 +59,33 @@ public sealed class SongUpdateService : BackgroundService while (!ct.IsCancellationRequested) { - var nextSong = SongList[_nextSongIndex]; + var nextSong = NextSong(); _activityList[0] = new Activity($"{nextSong.Name} / {nextSong.Author}", ActivityType.Listening); _client.SubmitCommand( new UpdatePresence( UserStatus.Online, false, DateTimeOffset.UtcNow, _activityList)); - _nextSongIndex++; - if (_nextSongIndex >= SongList.Length) - { - _nextSongIndex = 0; - } await Task.Delay(nextSong.Duration, ct); } } + + private (string Author, string Name, TimeSpan Duration) NextSong() + { + var today = DateTime.Today; + // Discontinuation of Online Services for Nintendo Wii U + if (today.Day is 8 or 9 && today.Month is 4) + { + return SpecialSongList[0]; // Maritime Memory / Squid Sisters + } + + var nextSong = SongList[_nextSongIndex]; + _nextSongIndex++; + if (_nextSongIndex >= SongList.Length) + { + _nextSongIndex = 0; + } + + return nextSong; + } } From 508edcbd683f5505a6cbe636aaed6b315a6ed378 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:02:31 +0300 Subject: [PATCH 28/44] Don't hardcode logo's link in /about (#301) In this PR, I made the logo link in /about use HEAD instead of hardcoded branch. --------- Signed-off-by: mctaylors Co-authored-by: Octol1ttle --- src/Commands/AboutCommandGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index b8c6d0f..02384f8 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -100,7 +100,7 @@ public class AboutCommandGroup : CommandGroup .WithSmallTitle(string.Format(Messages.AboutBot, bot.Username), bot) .WithDescription(builder.ToString()) .WithColour(ColorsList.Cyan) - .WithImageUrl("https://raw.githubusercontent.com/TeamOctolings/Octobot/master/docs/octobot-banner.png") + .WithImageUrl("https://raw.githubusercontent.com/TeamOctolings/Octobot/HEAD/docs/octobot-banner.png") .WithFooter(string.Format(Messages.Version, BuildInfo.Version)) .Build(); From 3029089385e66e886c9a43f369889287d90cfa8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 12:41:55 +0000 Subject: [PATCH 29/44] Bump muno92/resharper_inspectcode from 1.11.8 to 1.11.10 (#302) --- .github/workflows/build-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 859f8fa..c92386e 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.11.8 + uses: muno92/resharper_inspectcode@1.11.10 with: solutionPath: ./Octobot.sln ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor From 2502beb5df94c0d62bb7e7ed4a0c5bb528f7ecda Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 May 2024 17:43:59 +0500 Subject: [PATCH 30/44] Dependabot: ignore patch updates (#304) This PR *should* disable creating Dependabot PRs for patch updates. These updates often don't contain significant changes and only clutter the PR feed in addition to taking the maintainers' time --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4545f2b..57eea90 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,6 +15,10 @@ updates: labels: - "type: change" - "area: build/ci" + # For all packages, ignore all patch updates + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-patch" ] - package-ecosystem: "nuget" # See documentation for possible values directory: "/" # Location of package manifests @@ -30,3 +34,7 @@ updates: remora: patterns: - "Remora.Discord.*" + # For all packages, ignore all patch updates + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-patch" ] From 19fadead913edc8dea303c337e9f9fcf94d47d12 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 May 2024 18:10:41 +0500 Subject: [PATCH 31/44] Enable result stacktraces in release mode (#305) Originally I did not enable because "stack traces are expensive to retrieve", but let's be honest, who cares, this is a Discord bot, there's no such thing as "good performance" --- src/Extensions/ResultExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Extensions/ResultExtensions.cs b/src/Extensions/ResultExtensions.cs index f456dac..a95a1e2 100644 --- a/src/Extensions/ResultExtensions.cs +++ b/src/Extensions/ResultExtensions.cs @@ -21,7 +21,6 @@ public static class ResultExtensions return casted; } - [Conditional("DEBUG")] private static void LogResultStackTrace(Result result) { if (Octobot.StaticLogger is null || result.IsSuccess) From 793afd0e06fc346ba3161d368df7c87f2f068f38 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 16 May 2024 20:34:26 +0500 Subject: [PATCH 32/44] Apply official naming guidelines to Octobot (#306) 1. The root namespace was changed from `Octobot` to `TeamOctolings.Octobot`: > DO prefix namespace names with a company name to prevent namespaces from different companies from having the same name. 2. `Octobot.cs` was renamed to `Program.cs`: > DO NOT use the same name for a namespace and a type in that namespace. 3. `IOption`, `Option` were renamed to `IGuildOption` and `GuildOption` respectively: > DO NOT introduce generic type names such as Element, Node, Log, and Message. 4. `Utility` was moved out of the `Services` namespace. It didn't belong there anyway 5. `Program` static fields were moved to `Utility` 6. Localisation files were moved back to the project source files. Looks like this fixed `Message.Designer.cs` code generation --------- Signed-off-by: Octol1ttle --- Octobot.sln | 10 +- .../Attributes/StaticCallersOnlyAttribute.cs | 2 +- {src => TeamOctolings.Octobot}/BuildInfo.cs | 2 +- {src => TeamOctolings.Octobot}/ColorsList.cs | 2 +- .../Commands/AboutCommandGroup.cs | 8 +- .../Commands/BanCommandGroup.cs | 12 +- .../Commands/ClearCommandGroup.cs | 8 +- .../Events/ErrorLoggingPostExecutionEvent.cs | 4 +- .../Events/LoggingPreparationErrorEvent.cs | 4 +- .../Commands/KickCommandGroup.cs | 8 +- .../Commands/MuteCommandGroup.cs | 12 +- .../Commands/PingCommandGroup.cs | 8 +- .../Commands/RemindCommandGroup.cs | 10 +- .../Commands/SettingsCommandGroup.cs | 18 +- .../Commands/ToolsCommandGroup.cs | 10 +- .../Data/GuildData.cs | 2 +- .../Data/GuildSettings.cs | 10 +- .../Data/MemberData.cs | 2 +- .../Data/Options/AllOptionsEnum.cs | 4 +- .../Data/Options/BoolOption.cs | 4 +- .../Data/Options/GuildOption.cs | 8 +- .../Data/Options/IGuildOption.cs | 4 +- .../Data/Options/LanguageOption.cs | 4 +- .../Data/Options/SnowflakeOption.cs | 6 +- .../Data/Options/TimeSpanOption.cs | 6 +- .../Data/Reminder.cs | 2 +- .../Data/ScheduledEventData.cs | 2 +- .../Extensions/ChannelApiExtensions.cs | 2 +- .../Extensions/CollectionExtensions.cs | 2 +- .../Extensions/CommandContextExtensions.cs | 2 +- .../Extensions/DiffPaneModelExtensions.cs | 2 +- .../Extensions/EmbedBuilderExtensions.cs | 2 +- .../Extensions/FeedbackServiceExtensions.cs | 2 +- .../GuildScheduledEventExtensions.cs | 2 +- .../Extensions/LoggerExtensions.cs | 2 +- .../Extensions/MarkdownExtensions.cs | 2 +- .../Extensions/ResultExtensions.cs | 13 +- .../Extensions/SnowflakeExtensions.cs | 2 +- .../Extensions/StringBuilderExtensions.cs | 2 +- .../Extensions/StringExtensions.cs | 2 +- .../Extensions/UInt64Extensions.cs | 2 +- .../Extensions/UserExtensions.cs | 2 +- .../Messages.Designer.cs | 564 +++++++++--------- .../Messages.resx | 0 .../Messages.ru.resx | 0 .../Parsers/TimeSpanParser.cs | 2 +- .../Program.cs | 25 +- .../Responders/GuildLoadedResponder.cs | 8 +- .../Responders/GuildMemberJoinedResponder.cs | 10 +- .../Responders/GuildMemberLeftResponder.cs | 10 +- .../Responders/GuildUnloadedResponder.cs | 6 +- .../Responders/MessageDeletedResponder.cs | 10 +- .../Responders/MessageEditedResponder.cs | 10 +- .../Responders/MessageReceivedResponder.cs | 2 +- .../Services/AccessControlService.cs | 8 +- .../Services/GuildDataService.cs | 4 +- .../Services/Update/MemberUpdateService.cs | 6 +- .../Update/ScheduledEventUpdateService.cs | 6 +- .../Services/Update/SongUpdateService.cs | 2 +- .../TeamOctolings.Octobot.csproj | 8 +- .../Utility.cs | 15 +- 61 files changed, 447 insertions(+), 462 deletions(-) rename {src => TeamOctolings.Octobot}/Attributes/StaticCallersOnlyAttribute.cs (88%) rename {src => TeamOctolings.Octobot}/BuildInfo.cs (93%) rename {src => TeamOctolings.Octobot}/ColorsList.cs (95%) rename {src => TeamOctolings.Octobot}/Commands/AboutCommandGroup.cs (97%) rename {src => TeamOctolings.Octobot}/Commands/BanCommandGroup.cs (98%) rename {src => TeamOctolings.Octobot}/Commands/ClearCommandGroup.cs (97%) rename {src => TeamOctolings.Octobot}/Commands/Events/ErrorLoggingPostExecutionEvent.cs (97%) rename {src => TeamOctolings.Octobot}/Commands/Events/LoggingPreparationErrorEvent.cs (93%) rename {src => TeamOctolings.Octobot}/Commands/KickCommandGroup.cs (97%) rename {src => TeamOctolings.Octobot}/Commands/MuteCommandGroup.cs (98%) rename {src => TeamOctolings.Octobot}/Commands/PingCommandGroup.cs (96%) rename {src => TeamOctolings.Octobot}/Commands/RemindCommandGroup.cs (98%) rename {src => TeamOctolings.Octobot}/Commands/SettingsCommandGroup.cs (96%) rename {src => TeamOctolings.Octobot}/Commands/ToolsCommandGroup.cs (99%) rename {src => TeamOctolings.Octobot}/Data/GuildData.cs (97%) rename {src => TeamOctolings.Octobot}/Data/GuildSettings.cs (92%) rename {src => TeamOctolings.Octobot}/Data/MemberData.cs (93%) rename {src => TeamOctolings.Octobot}/Data/Options/AllOptionsEnum.cs (92%) rename {src => TeamOctolings.Octobot}/Data/Options/BoolOption.cs (91%) rename src/Data/Options/Option.cs => TeamOctolings.Octobot/Data/Options/GuildOption.cs (90%) rename src/Data/Options/IOption.cs => TeamOctolings.Octobot/Data/Options/IGuildOption.cs (73%) rename {src => TeamOctolings.Octobot}/Data/Options/LanguageOption.cs (91%) rename {src => TeamOctolings.Octobot}/Data/Options/SnowflakeOption.cs (87%) rename {src => TeamOctolings.Octobot}/Data/Options/TimeSpanOption.cs (82%) rename {src => TeamOctolings.Octobot}/Data/Reminder.cs (83%) rename {src => TeamOctolings.Octobot}/Data/ScheduledEventData.cs (97%) rename {src => TeamOctolings.Octobot}/Extensions/ChannelApiExtensions.cs (96%) rename {src => TeamOctolings.Octobot}/Extensions/CollectionExtensions.cs (96%) rename {src => TeamOctolings.Octobot}/Extensions/CommandContextExtensions.cs (92%) rename {src => TeamOctolings.Octobot}/Extensions/DiffPaneModelExtensions.cs (94%) rename {src => TeamOctolings.Octobot}/Extensions/EmbedBuilderExtensions.cs (99%) rename {src => TeamOctolings.Octobot}/Extensions/FeedbackServiceExtensions.cs (93%) rename {src => TeamOctolings.Octobot}/Extensions/GuildScheduledEventExtensions.cs (95%) rename {src => TeamOctolings.Octobot}/Extensions/LoggerExtensions.cs (96%) rename {src => TeamOctolings.Octobot}/Extensions/MarkdownExtensions.cs (88%) rename {src => TeamOctolings.Octobot}/Extensions/ResultExtensions.cs (82%) rename {src => TeamOctolings.Octobot}/Extensions/SnowflakeExtensions.cs (96%) rename {src => TeamOctolings.Octobot}/Extensions/StringBuilderExtensions.cs (98%) rename {src => TeamOctolings.Octobot}/Extensions/StringExtensions.cs (98%) rename {src => TeamOctolings.Octobot}/Extensions/UInt64Extensions.cs (82%) rename {src => TeamOctolings.Octobot}/Extensions/UserExtensions.cs (85%) rename {src => TeamOctolings.Octobot}/Messages.Designer.cs (89%) rename {locale => TeamOctolings.Octobot}/Messages.resx (100%) rename {locale => TeamOctolings.Octobot}/Messages.ru.resx (100%) rename {src => TeamOctolings.Octobot}/Parsers/TimeSpanParser.cs (98%) rename src/Octobot.cs => TeamOctolings.Octobot/Program.cs (86%) rename {src => TeamOctolings.Octobot}/Responders/GuildLoadedResponder.cs (96%) rename {src => TeamOctolings.Octobot}/Responders/GuildMemberJoinedResponder.cs (94%) rename {src => TeamOctolings.Octobot}/Responders/GuildMemberLeftResponder.cs (91%) rename {src => TeamOctolings.Octobot}/Responders/GuildUnloadedResponder.cs (90%) rename {src => TeamOctolings.Octobot}/Responders/MessageDeletedResponder.cs (94%) rename {src => TeamOctolings.Octobot}/Responders/MessageEditedResponder.cs (95%) rename {src => TeamOctolings.Octobot}/Responders/MessageReceivedResponder.cs (96%) rename {src => TeamOctolings.Octobot}/Services/AccessControlService.cs (97%) rename {src => TeamOctolings.Octobot}/Services/GuildDataService.cs (98%) rename {src => TeamOctolings.Octobot}/Services/Update/MemberUpdateService.cs (98%) rename {src => TeamOctolings.Octobot}/Services/Update/ScheduledEventUpdateService.cs (99%) rename {src => TeamOctolings.Octobot}/Services/Update/SongUpdateService.cs (98%) rename Octobot.csproj => TeamOctolings.Octobot/TeamOctolings.Octobot.csproj (90%) rename {src/Services => TeamOctolings.Octobot}/Utility.cs (92%) diff --git a/Octobot.sln b/Octobot.sln index 9dd2b89..b82f7a9 100644 --- a/Octobot.sln +++ b/Octobot.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octobot", "Octobot.csproj", "{9CA7A44F-167C-46D4-923D-88CE71044144}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamOctolings.Octobot", "TeamOctolings.Octobot\TeamOctolings.Octobot.csproj", "{A1679BA2-3A36-4D98-80C0-EEE771398FBD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -8,9 +8,9 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9CA7A44F-167C-46D4-923D-88CE71044144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9CA7A44F-167C-46D4-923D-88CE71044144}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9CA7A44F-167C-46D4-923D-88CE71044144}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9CA7A44F-167C-46D4-923D-88CE71044144}.Release|Any CPU.Build.0 = Release|Any CPU + {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1679BA2-3A36-4D98-80C0-EEE771398FBD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Attributes/StaticCallersOnlyAttribute.cs b/TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs similarity index 88% rename from src/Attributes/StaticCallersOnlyAttribute.cs rename to TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs index e8787bf..0256f62 100644 --- a/src/Attributes/StaticCallersOnlyAttribute.cs +++ b/TeamOctolings.Octobot/Attributes/StaticCallersOnlyAttribute.cs @@ -1,4 +1,4 @@ -namespace Octobot.Attributes; +namespace TeamOctolings.Octobot.Attributes; /// /// Any property marked with should only be accessed by static methods. diff --git a/src/BuildInfo.cs b/TeamOctolings.Octobot/BuildInfo.cs similarity index 93% rename from src/BuildInfo.cs rename to TeamOctolings.Octobot/BuildInfo.cs index 2eb6059..4b9a09f 100644 --- a/src/BuildInfo.cs +++ b/TeamOctolings.Octobot/BuildInfo.cs @@ -1,4 +1,4 @@ -namespace Octobot; +namespace TeamOctolings.Octobot; public static class BuildInfo { diff --git a/src/ColorsList.cs b/TeamOctolings.Octobot/ColorsList.cs similarity index 95% rename from src/ColorsList.cs rename to TeamOctolings.Octobot/ColorsList.cs index cd40313..3b66c0a 100644 --- a/src/ColorsList.cs +++ b/TeamOctolings.Octobot/ColorsList.cs @@ -1,6 +1,6 @@ using System.Drawing; -namespace Octobot; +namespace TeamOctolings.Octobot; /// /// Contains all colors used in embeds. diff --git a/src/Commands/AboutCommandGroup.cs b/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs similarity index 97% rename from src/Commands/AboutCommandGroup.cs rename to TeamOctolings.Octobot/Commands/AboutCommandGroup.cs index 02384f8..9f05af3 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs @@ -1,9 +1,6 @@ using System.ComponentModel; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -18,8 +15,11 @@ 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; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles the command to show information about this bot: /about. diff --git a/src/Commands/BanCommandGroup.cs b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs similarity index 98% rename from src/Commands/BanCommandGroup.cs rename to TeamOctolings.Octobot/Commands/BanCommandGroup.cs index 02a377a..8d90286 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs @@ -2,11 +2,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Parsers; -using Octobot.Services; -using Octobot.Services.Update; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -19,8 +14,13 @@ 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.Parsers; +using TeamOctolings.Octobot.Services; +using TeamOctolings.Octobot.Services.Update; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles commands related to ban management: /ban and /unban. diff --git a/src/Commands/ClearCommandGroup.cs b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs similarity index 97% rename from src/Commands/ClearCommandGroup.cs rename to TeamOctolings.Octobot/Commands/ClearCommandGroup.cs index 84b69db..8a8cb2f 100644 --- a/src/Commands/ClearCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs @@ -1,9 +1,6 @@ using System.ComponentModel; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -16,8 +13,11 @@ 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; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles the command to clear messages in a channel: /clear. diff --git a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs similarity index 97% rename from src/Commands/Events/ErrorLoggingPostExecutionEvent.cs rename to TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 551c2d0..7ffc4fe 100644 --- a/src/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -1,6 +1,5 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using Octobot.Extensions; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Objects; @@ -11,8 +10,9 @@ using Remora.Discord.Commands.Services; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Results; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Commands.Events; +namespace TeamOctolings.Octobot.Commands.Events; /// /// Handles error logging for slash command groups. diff --git a/src/Commands/Events/LoggingPreparationErrorEvent.cs b/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs similarity index 93% rename from src/Commands/Events/LoggingPreparationErrorEvent.cs rename to TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs index 87b4090..10a6a1f 100644 --- a/src/Commands/Events/LoggingPreparationErrorEvent.cs +++ b/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs @@ -1,11 +1,11 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using Octobot.Extensions; using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Services; using Remora.Results; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Commands.Events; +namespace TeamOctolings.Octobot.Commands.Events; /// /// Handles error logging for slash commands that couldn't be successfully prepared. diff --git a/src/Commands/KickCommandGroup.cs b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs similarity index 97% rename from src/Commands/KickCommandGroup.cs rename to TeamOctolings.Octobot/Commands/KickCommandGroup.cs index 87b915a..4252232 100644 --- a/src/Commands/KickCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs @@ -1,9 +1,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -15,8 +12,11 @@ using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Extensions.Embeds; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles the command to kick members of a guild: /kick. diff --git a/src/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs similarity index 98% rename from src/Commands/MuteCommandGroup.cs rename to TeamOctolings.Octobot/Commands/MuteCommandGroup.cs index ce0a296..8e449f7 100644 --- a/src/Commands/MuteCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs @@ -2,11 +2,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Parsers; -using Octobot.Services; -using Octobot.Services.Update; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -19,8 +14,13 @@ 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.Parsers; +using TeamOctolings.Octobot.Services; +using TeamOctolings.Octobot.Services.Update; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles commands related to mute management: /mute and /unmute. diff --git a/src/Commands/PingCommandGroup.cs b/TeamOctolings.Octobot/Commands/PingCommandGroup.cs similarity index 96% rename from src/Commands/PingCommandGroup.cs rename to TeamOctolings.Octobot/Commands/PingCommandGroup.cs index d64c6dd..70b9f23 100644 --- a/src/Commands/PingCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/PingCommandGroup.cs @@ -1,8 +1,5 @@ using System.ComponentModel; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -15,8 +12,11 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping diff --git a/src/Commands/RemindCommandGroup.cs b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs similarity index 98% rename from src/Commands/RemindCommandGroup.cs rename to TeamOctolings.Octobot/Commands/RemindCommandGroup.cs index aa1ef7e..f40ba6b 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs @@ -2,9 +2,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -17,9 +14,12 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; -using Octobot.Parsers; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Parsers; +using TeamOctolings.Octobot.Services; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles commands to manage reminders: /remind, /listremind, /delremind diff --git a/src/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs similarity index 96% rename from src/Commands/SettingsCommandGroup.cs rename to TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs index a39e9c7..56584bf 100644 --- a/src/Commands/SettingsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs @@ -3,10 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Text; using System.Text.Json.Nodes; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Data.Options; -using Octobot.Extensions; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -19,8 +15,12 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Data.Options; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles the commands to list and modify per-guild settings: /settings and /settings list. @@ -29,13 +29,13 @@ namespace Octobot.Commands; public class SettingsCommandGroup : CommandGroup { /// - /// Represents all options as an array of objects implementing . + /// Represents all options as an array of objects implementing . /// /// /// WARNING: If you update this array in any way, you must also update and make sure /// that the orders match. /// - private static readonly IOption[] AllOptions = + private static readonly IGuildOption[] AllOptions = [ GuildSettings.Language, GuildSettings.WelcomeMessage, @@ -199,7 +199,7 @@ public class SettingsCommandGroup : CommandGroup } private async Task EditSettingAsync( - IOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot, + IGuildOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot, CancellationToken ct = default) { var setResult = option.Set(data.Settings, value); @@ -270,7 +270,7 @@ public class SettingsCommandGroup : CommandGroup } private async Task ResetSingleSettingAsync(JsonNode cfg, IUser bot, - IOption option, CancellationToken ct = default) + IGuildOption option, CancellationToken ct = default) { var resetResult = option.Reset(cfg); if (!resetResult.IsSuccess) diff --git a/src/Commands/ToolsCommandGroup.cs b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs similarity index 99% rename from src/Commands/ToolsCommandGroup.cs rename to TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs index d4f3f75..3e84527 100644 --- a/src/Commands/ToolsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs @@ -2,10 +2,6 @@ using System.ComponentModel; using System.Drawing; using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Parsers; -using Octobot.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; using Remora.Discord.API.Abstractions.Objects; @@ -17,8 +13,12 @@ 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.Parsers; +using TeamOctolings.Octobot.Services; -namespace Octobot.Commands; +namespace TeamOctolings.Octobot.Commands; /// /// Handles tool commands: /userinfo, /guildinfo, /random, /timestamp, /8ball. diff --git a/src/Data/GuildData.cs b/TeamOctolings.Octobot/Data/GuildData.cs similarity index 97% rename from src/Data/GuildData.cs rename to TeamOctolings.Octobot/Data/GuildData.cs index 5a903d6..f393323 100644 --- a/src/Data/GuildData.cs +++ b/TeamOctolings.Octobot/Data/GuildData.cs @@ -1,7 +1,7 @@ using System.Text.Json.Nodes; using Remora.Rest.Core; -namespace Octobot.Data; +namespace TeamOctolings.Octobot.Data; /// /// Stores information about a guild. This information is not accessible via the Discord API. diff --git a/src/Data/GuildSettings.cs b/TeamOctolings.Octobot/Data/GuildSettings.cs similarity index 92% rename from src/Data/GuildSettings.cs rename to TeamOctolings.Octobot/Data/GuildSettings.cs index a1d8d74..dc59d6f 100644 --- a/src/Data/GuildSettings.cs +++ b/TeamOctolings.Octobot/Data/GuildSettings.cs @@ -1,8 +1,8 @@ -using Octobot.Data.Options; -using Octobot.Responders; using Remora.Discord.API.Abstractions.Objects; +using TeamOctolings.Octobot.Data.Options; +using TeamOctolings.Octobot.Responders; -namespace Octobot.Data; +namespace TeamOctolings.Octobot.Data; /// /// Contains all per-guild settings that can be set by a member @@ -22,7 +22,7 @@ public static class GuildSettings /// /// /// - public static readonly Option WelcomeMessage = new("WelcomeMessage", "default"); + public static readonly GuildOption WelcomeMessage = new("WelcomeMessage", "default"); /// /// Controls what message should be sent in when a member leaves the guild. @@ -34,7 +34,7 @@ public static class GuildSettings /// /// /// - public static readonly Option LeaveMessage = new("LeaveMessage", "default"); + public static readonly GuildOption LeaveMessage = new("LeaveMessage", "default"); /// /// Controls whether or not the message should be sent diff --git a/src/Data/MemberData.cs b/TeamOctolings.Octobot/Data/MemberData.cs similarity index 93% rename from src/Data/MemberData.cs rename to TeamOctolings.Octobot/Data/MemberData.cs index 1b3d0d9..984d4af 100644 --- a/src/Data/MemberData.cs +++ b/TeamOctolings.Octobot/Data/MemberData.cs @@ -1,4 +1,4 @@ -namespace Octobot.Data; +namespace TeamOctolings.Octobot.Data; /// /// Stores information about a member diff --git a/src/Data/Options/AllOptionsEnum.cs b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs similarity index 92% rename from src/Data/Options/AllOptionsEnum.cs rename to TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs index d9e0c13..6a4280e 100644 --- a/src/Data/Options/AllOptionsEnum.cs +++ b/TeamOctolings.Octobot/Data/Options/AllOptionsEnum.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; -using Octobot.Commands; +using TeamOctolings.Octobot.Commands; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; /// /// Represents all options as enums. diff --git a/src/Data/Options/BoolOption.cs b/TeamOctolings.Octobot/Data/Options/BoolOption.cs similarity index 91% rename from src/Data/Options/BoolOption.cs rename to TeamOctolings.Octobot/Data/Options/BoolOption.cs index 6876164..6a3c899 100644 --- a/src/Data/Options/BoolOption.cs +++ b/TeamOctolings.Octobot/Data/Options/BoolOption.cs @@ -1,9 +1,9 @@ using System.Text.Json.Nodes; using Remora.Results; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; -public sealed class BoolOption : Option +public sealed class BoolOption : GuildOption { public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { } diff --git a/src/Data/Options/Option.cs b/TeamOctolings.Octobot/Data/Options/GuildOption.cs similarity index 90% rename from src/Data/Options/Option.cs rename to TeamOctolings.Octobot/Data/Options/GuildOption.cs index 5d703a8..5d9f1a2 100644 --- a/src/Data/Options/Option.cs +++ b/TeamOctolings.Octobot/Data/Options/GuildOption.cs @@ -2,18 +2,18 @@ using System.Text.Json.Nodes; using Remora.Discord.Extensions.Formatting; using Remora.Results; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; /// -/// Represents an per-guild option. +/// Represents a per-guild option. /// /// The type of the option. -public class Option : IOption +public class GuildOption : IGuildOption where T : notnull { protected readonly T DefaultValue; - public Option(string name, T defaultValue) + public GuildOption(string name, T defaultValue) { Name = name; DefaultValue = defaultValue; diff --git a/src/Data/Options/IOption.cs b/TeamOctolings.Octobot/Data/Options/IGuildOption.cs similarity index 73% rename from src/Data/Options/IOption.cs rename to TeamOctolings.Octobot/Data/Options/IGuildOption.cs index b8ed03c..a8c3e6e 100644 --- a/src/Data/Options/IOption.cs +++ b/TeamOctolings.Octobot/Data/Options/IGuildOption.cs @@ -1,9 +1,9 @@ using System.Text.Json.Nodes; using Remora.Results; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; -public interface IOption +public interface IGuildOption { string Name { get; } string Display(JsonNode settings); diff --git a/src/Data/Options/LanguageOption.cs b/TeamOctolings.Octobot/Data/Options/LanguageOption.cs similarity index 91% rename from src/Data/Options/LanguageOption.cs rename to TeamOctolings.Octobot/Data/Options/LanguageOption.cs index 22f98df..15ab6ff 100644 --- a/src/Data/Options/LanguageOption.cs +++ b/TeamOctolings.Octobot/Data/Options/LanguageOption.cs @@ -3,10 +3,10 @@ using System.Text.Json.Nodes; using Remora.Discord.Extensions.Formatting; using Remora.Results; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; /// -public sealed class LanguageOption : Option +public sealed class LanguageOption : GuildOption { private static readonly Dictionary CultureInfoCache = new() { diff --git a/src/Data/Options/SnowflakeOption.cs b/TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs similarity index 87% rename from src/Data/Options/SnowflakeOption.cs rename to TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs index 7118da8..b7405f2 100644 --- a/src/Data/Options/SnowflakeOption.cs +++ b/TeamOctolings.Octobot/Data/Options/SnowflakeOption.cs @@ -1,13 +1,13 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; -using Octobot.Extensions; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; -public sealed partial class SnowflakeOption : Option +public sealed partial class SnowflakeOption : GuildOption { public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { } diff --git a/src/Data/Options/TimeSpanOption.cs b/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs similarity index 82% rename from src/Data/Options/TimeSpanOption.cs rename to TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs index d237b6e..3501f09 100644 --- a/src/Data/Options/TimeSpanOption.cs +++ b/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs @@ -1,10 +1,10 @@ using System.Text.Json.Nodes; -using Octobot.Parsers; using Remora.Results; +using TeamOctolings.Octobot.Parsers; -namespace Octobot.Data.Options; +namespace TeamOctolings.Octobot.Data.Options; -public sealed class TimeSpanOption : Option +public sealed class TimeSpanOption : GuildOption { public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { } diff --git a/src/Data/Reminder.cs b/TeamOctolings.Octobot/Data/Reminder.cs similarity index 83% rename from src/Data/Reminder.cs rename to TeamOctolings.Octobot/Data/Reminder.cs index f21b222..c3936da 100644 --- a/src/Data/Reminder.cs +++ b/TeamOctolings.Octobot/Data/Reminder.cs @@ -1,4 +1,4 @@ -namespace Octobot.Data; +namespace TeamOctolings.Octobot.Data; public struct Reminder { diff --git a/src/Data/ScheduledEventData.cs b/TeamOctolings.Octobot/Data/ScheduledEventData.cs similarity index 97% rename from src/Data/ScheduledEventData.cs rename to TeamOctolings.Octobot/Data/ScheduledEventData.cs index 59efc63..7ba6e92 100644 --- a/src/Data/ScheduledEventData.cs +++ b/TeamOctolings.Octobot/Data/ScheduledEventData.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; using Remora.Discord.API.Abstractions.Objects; -namespace Octobot.Data; +namespace TeamOctolings.Octobot.Data; /// /// Stores information about scheduled events. This information is not provided by the Discord API. diff --git a/src/Extensions/ChannelApiExtensions.cs b/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs similarity index 96% rename from src/Extensions/ChannelApiExtensions.cs rename to TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs index 99eff67..2767f96 100644 --- a/src/Extensions/ChannelApiExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs @@ -5,7 +5,7 @@ using Remora.Discord.API.Objects; using Remora.Rest.Core; using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class ChannelApiExtensions { diff --git a/src/Extensions/CollectionExtensions.cs b/TeamOctolings.Octobot/Extensions/CollectionExtensions.cs similarity index 96% rename from src/Extensions/CollectionExtensions.cs rename to TeamOctolings.Octobot/Extensions/CollectionExtensions.cs index 2369532..3ea13a8 100644 --- a/src/Extensions/CollectionExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/CollectionExtensions.cs @@ -1,6 +1,6 @@ using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class CollectionExtensions { diff --git a/src/Extensions/CommandContextExtensions.cs b/TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs similarity index 92% rename from src/Extensions/CommandContextExtensions.cs rename to TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs index a0c02f2..16b8b56 100644 --- a/src/Extensions/CommandContextExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/CommandContextExtensions.cs @@ -2,7 +2,7 @@ using Remora.Discord.Commands.Extensions; using Remora.Rest.Core; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class CommandContextExtensions { diff --git a/src/Extensions/DiffPaneModelExtensions.cs b/TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs similarity index 94% rename from src/Extensions/DiffPaneModelExtensions.cs rename to TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs index 1c3a098..3bb707b 100644 --- a/src/Extensions/DiffPaneModelExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/DiffPaneModelExtensions.cs @@ -1,7 +1,7 @@ using System.Text; using DiffPlex.DiffBuilder.Model; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class DiffPaneModelExtensions { diff --git a/src/Extensions/EmbedBuilderExtensions.cs b/TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs similarity index 99% rename from src/Extensions/EmbedBuilderExtensions.cs rename to TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs index 2d61403..dab0265 100644 --- a/src/Extensions/EmbedBuilderExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/EmbedBuilderExtensions.cs @@ -4,7 +4,7 @@ using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Rest.Core; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class EmbedBuilderExtensions { diff --git a/src/Extensions/FeedbackServiceExtensions.cs b/TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs similarity index 93% rename from src/Extensions/FeedbackServiceExtensions.cs rename to TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs index e6ef376..c66c946 100644 --- a/src/Extensions/FeedbackServiceExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/FeedbackServiceExtensions.cs @@ -3,7 +3,7 @@ using Remora.Discord.Commands.Feedback.Messages; using Remora.Discord.Commands.Feedback.Services; using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class FeedbackServiceExtensions { diff --git a/src/Extensions/GuildScheduledEventExtensions.cs b/TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs similarity index 95% rename from src/Extensions/GuildScheduledEventExtensions.cs rename to TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs index f1b6985..b8eb2d1 100644 --- a/src/Extensions/GuildScheduledEventExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/GuildScheduledEventExtensions.cs @@ -2,7 +2,7 @@ using Remora.Rest.Core; using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class GuildScheduledEventExtensions { diff --git a/src/Extensions/LoggerExtensions.cs b/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs similarity index 96% rename from src/Extensions/LoggerExtensions.cs rename to TeamOctolings.Octobot/Extensions/LoggerExtensions.cs index fca3702..76fa386 100644 --- a/src/Extensions/LoggerExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class LoggerExtensions { diff --git a/src/Extensions/MarkdownExtensions.cs b/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs similarity index 88% rename from src/Extensions/MarkdownExtensions.cs rename to TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs index 7b7f780..202cd37 100644 --- a/src/Extensions/MarkdownExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs @@ -1,4 +1,4 @@ -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class MarkdownExtensions { diff --git a/src/Extensions/ResultExtensions.cs b/TeamOctolings.Octobot/Extensions/ResultExtensions.cs similarity index 82% rename from src/Extensions/ResultExtensions.cs rename to TeamOctolings.Octobot/Extensions/ResultExtensions.cs index a95a1e2..d7ef7d7 100644 --- a/src/Extensions/ResultExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/ResultExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging; using Remora.Results; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class ResultExtensions { @@ -23,18 +23,23 @@ public static class ResultExtensions private static void LogResultStackTrace(Result result) { - if (Octobot.StaticLogger is null || result.IsSuccess) + if (result.IsSuccess) { return; } - Octobot.StaticLogger.LogError("{ErrorType}: {ErrorMessage}{NewLine}{StackTrace}", + if (Utility.StaticLogger is null) + { + throw new InvalidOperationException(); + } + + Utility.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}", + Utility.StaticLogger.LogError("Caused by: {ResultType}: {ResultMessage}", inner.Error.GetType().FullName, inner.Error.Message); inner = inner.Inner; diff --git a/src/Extensions/SnowflakeExtensions.cs b/TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs similarity index 96% rename from src/Extensions/SnowflakeExtensions.cs rename to TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs index e60bc44..70810ef 100644 --- a/src/Extensions/SnowflakeExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/SnowflakeExtensions.cs @@ -1,6 +1,6 @@ using Remora.Rest.Core; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class SnowflakeExtensions { diff --git a/src/Extensions/StringBuilderExtensions.cs b/TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs similarity index 98% rename from src/Extensions/StringBuilderExtensions.cs rename to TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs index ddd24a3..25b7b5b 100644 --- a/src/Extensions/StringBuilderExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/StringBuilderExtensions.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class StringBuilderExtensions { diff --git a/src/Extensions/StringExtensions.cs b/TeamOctolings.Octobot/Extensions/StringExtensions.cs similarity index 98% rename from src/Extensions/StringExtensions.cs rename to TeamOctolings.Octobot/Extensions/StringExtensions.cs index cb8d606..bf7f6c8 100644 --- a/src/Extensions/StringExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/StringExtensions.cs @@ -1,7 +1,7 @@ using System.Net; using Remora.Discord.Extensions.Formatting; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class StringExtensions { diff --git a/src/Extensions/UInt64Extensions.cs b/TeamOctolings.Octobot/Extensions/UInt64Extensions.cs similarity index 82% rename from src/Extensions/UInt64Extensions.cs rename to TeamOctolings.Octobot/Extensions/UInt64Extensions.cs index 5d1db00..2b9c0a2 100644 --- a/src/Extensions/UInt64Extensions.cs +++ b/TeamOctolings.Octobot/Extensions/UInt64Extensions.cs @@ -1,7 +1,7 @@ using Remora.Discord.API; using Remora.Rest.Core; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class UInt64Extensions { diff --git a/src/Extensions/UserExtensions.cs b/TeamOctolings.Octobot/Extensions/UserExtensions.cs similarity index 85% rename from src/Extensions/UserExtensions.cs rename to TeamOctolings.Octobot/Extensions/UserExtensions.cs index 38fe985..d9eff33 100644 --- a/src/Extensions/UserExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/UserExtensions.cs @@ -1,6 +1,6 @@ using Remora.Discord.API.Abstractions.Objects; -namespace Octobot.Extensions; +namespace TeamOctolings.Octobot.Extensions; public static class UserExtensions { diff --git a/src/Messages.Designer.cs b/TeamOctolings.Octobot/Messages.Designer.cs similarity index 89% rename from src/Messages.Designer.cs rename to TeamOctolings.Octobot/Messages.Designer.cs index 2910bae..bbc1366 100644 --- a/src/Messages.Designer.cs +++ b/TeamOctolings.Octobot/Messages.Designer.cs @@ -7,31 +7,34 @@ // //------------------------------------------------------------------------------ -namespace Octobot { +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 { if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Octobot.locale.Messages", typeof(Messages).Assembly); + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("TeamOctolings.Octobot.Messages", typeof(Messages).Assembly); resourceMan = temp; } return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] internal static System.Globalization.CultureInfo Culture { get { @@ -41,1180 +44,1157 @@ namespace 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 SettingsLang { + + internal static string SettingsLanguage { get { - return ResourceManager.GetString("SettingsLang", resourceCulture); + 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 InvalidRole { - get { - return ResourceManager.GetString("InvalidRole", resourceCulture); - } - } - - internal static string InvalidChannel { - get { - return ResourceManager.GetString("InvalidChannel", 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 UserCannotModerateMembers { + + internal static string UserCannotMuteMembers { get { - return ResourceManager.GetString("UserCannotModerateMembers", resourceCulture); + 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 InformationAbout { - get { - return ResourceManager.GetString("InformationAbout", resourceCulture); - } - } - + internal static string UserInfoDisplayName { get { return ResourceManager.GetString("UserInfoDisplayName", resourceCulture); } } - - internal static string UserInfoDiscordUserSince { + + internal static string InformationAbout { get { - return ResourceManager.GetString("UserInfoDiscordUserSince", resourceCulture); + 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 - { + + internal static string RandomTitle { get { return ResourceManager.GetString("RandomTitle", resourceCulture); } } - - internal static string RandomMinMaxSame - { + + internal static string RandomMinMaxSame { get { return ResourceManager.GetString("RandomMinMaxSame", resourceCulture); } } - - internal static string RandomMax - { - get { - return ResourceManager.GetString("RandomMax", resourceCulture); - } - } - - internal static string RandomMin - { + + internal static string RandomMin { get { return ResourceManager.GetString("RandomMin", resourceCulture); } } - - internal static string Default - { + + 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 - { + + internal static string TimestampTitle { + get { return ResourceManager.GetString("TimestampTitle", resourceCulture); } } - - internal static string TimestampOffset - { - get - { + + internal static string TimestampOffset { + get { return ResourceManager.GetString("TimestampOffset", resourceCulture); } } - - internal static string GuildInfoDescription - { - get - { + + internal static string GuildInfoDescription { + get { return ResourceManager.GetString("GuildInfoDescription", resourceCulture); } } - - internal static string GuildInfoCreatedAt - { - get - { + + internal static string GuildInfoCreatedAt { + get { return ResourceManager.GetString("GuildInfoCreatedAt", resourceCulture); } } - - internal static string GuildInfoOwner - { - get - { + + internal static string GuildInfoOwner { + get { return ResourceManager.GetString("GuildInfoOwner", resourceCulture); } } - - internal static string GuildInfoServerBoost - { - get - { + + internal static string GuildInfoServerBoost { + get { return ResourceManager.GetString("GuildInfoServerBoost", resourceCulture); } } - - internal static string GuildInfoBoostTier - { - get - { + + internal static string GuildInfoBoostTier { + get { return ResourceManager.GetString("GuildInfoBoostTier", resourceCulture); } } - - internal static string GuildInfoBoostCount - { - get - { + + internal static string GuildInfoBoostCount { + get { return ResourceManager.GetString("GuildInfoBoostCount", resourceCulture); } } - - internal static string NoMessagesToClear - { - get - { + + internal static string NoMessagesToClear { + get { return ResourceManager.GetString("NoMessagesToClear", resourceCulture); } } - - internal static string MessagesClearedFiltered - { - get - { + + internal static string MessagesClearedFiltered { + get { return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture); } } - - internal static string DataLoadFailedTitle - { - get - { + + internal static string DataLoadFailedTitle { + get { return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture); } } - - internal static string DataLoadFailedDescription - { - get - { + + internal static string DataLoadFailedDescription { + get { return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture); } } - - internal static string CommandExecutionFailed - { - get - { + + internal static string CommandExecutionFailed { + get { return ResourceManager.GetString("CommandExecutionFailed", resourceCulture); } } - - internal static string ContactDevelopers - { - get - { + + internal static string ContactDevelopers { + get { return ResourceManager.GetString("ContactDevelopers", resourceCulture); } } - - internal static string ButtonReportIssue - { - get - { + + 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 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); + } + } } } diff --git a/locale/Messages.resx b/TeamOctolings.Octobot/Messages.resx similarity index 100% rename from locale/Messages.resx rename to TeamOctolings.Octobot/Messages.resx diff --git a/locale/Messages.ru.resx b/TeamOctolings.Octobot/Messages.ru.resx similarity index 100% rename from locale/Messages.ru.resx rename to TeamOctolings.Octobot/Messages.ru.resx diff --git a/src/Parsers/TimeSpanParser.cs b/TeamOctolings.Octobot/Parsers/TimeSpanParser.cs similarity index 98% rename from src/Parsers/TimeSpanParser.cs rename to TeamOctolings.Octobot/Parsers/TimeSpanParser.cs index 1f44d46..99a8b90 100644 --- a/src/Parsers/TimeSpanParser.cs +++ b/TeamOctolings.Octobot/Parsers/TimeSpanParser.cs @@ -4,7 +4,7 @@ using JetBrains.Annotations; using Remora.Commands.Parsers; using Remora.Results; -namespace Octobot.Parsers; +namespace TeamOctolings.Octobot.Parsers; /// /// Parses s. diff --git a/src/Octobot.cs b/TeamOctolings.Octobot/Program.cs similarity index 86% rename from src/Octobot.cs rename to TeamOctolings.Octobot/Program.cs index 065967e..d1d6220 100644 --- a/src/Octobot.cs +++ b/TeamOctolings.Octobot/Program.cs @@ -2,13 +2,8 @@ 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; using Remora.Discord.API.Abstractions.Gateway.Commands; using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Objects; using Remora.Discord.Caching.Extensions; using Remora.Discord.Caching.Services; using Remora.Discord.Commands.Extensions; @@ -16,24 +11,20 @@ using Remora.Discord.Commands.Services; using Remora.Discord.Extensions.Extensions; using Remora.Discord.Gateway; using Remora.Discord.Hosting.Extensions; -using Remora.Rest.Core; using Serilog.Extensions.Logging; +using TeamOctolings.Octobot.Commands.Events; +using TeamOctolings.Octobot.Services; +using TeamOctolings.Octobot.Services.Update; -namespace Octobot; +namespace TeamOctolings.Octobot; -public sealed class Octobot +public sealed class Program { - 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>(); + Utility.StaticLogger = services.GetRequiredService>(); var slashService = services.GetRequiredService(); // Providing a guild ID to this call will result in command duplicates! @@ -82,8 +73,8 @@ public sealed class Octobot // Init .AddDiscordCaching() .AddDiscordCommands(true, false) - .AddRespondersFromAssembly(typeof(Octobot).Assembly) - .AddCommandGroupsFromAssembly(typeof(Octobot).Assembly) + .AddRespondersFromAssembly(typeof(Program).Assembly) + .AddCommandGroupsFromAssembly(typeof(Program).Assembly) // Slash command event handlers .AddPreparationErrorEvent() .AddPostExecutionEvent() diff --git a/src/Responders/GuildLoadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs similarity index 96% rename from src/Responders/GuildLoadedResponder.cs rename to TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs index 55e9673..0c71a06 100644 --- a/src/Responders/GuildLoadedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs @@ -1,8 +1,5 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; @@ -11,8 +8,11 @@ using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles sending a message to a guild that has just initialized if that guild diff --git a/src/Responders/GuildMemberJoinedResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs similarity index 94% rename from src/Responders/GuildMemberJoinedResponder.cs rename to TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs index 61ef5cc..6964fe7 100644 --- a/src/Responders/GuildMemberJoinedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs @@ -1,16 +1,16 @@ using System.Text.Json.Nodes; 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.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles sending a guild's if one is set. @@ -77,7 +77,7 @@ public class GuildMemberJoinedResponder : IResponder return await _channelApi.CreateMessageWithEmbedResultAsync( GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, - allowedMentions: Octobot.NoMentions, ct: ct); + allowedMentions: Utility.NoMentions, ct: ct); } private async Task TryReturnRolesAsync( diff --git a/src/Responders/GuildMemberLeftResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs similarity index 91% rename from src/Responders/GuildMemberLeftResponder.cs rename to TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs index 90cc64c..4f6150c 100644 --- a/src/Responders/GuildMemberLeftResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs @@ -1,14 +1,14 @@ 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; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles sending a guild's if one is set. @@ -67,6 +67,6 @@ public class GuildMemberLeftResponder : IResponder return await _channelApi.CreateMessageWithEmbedResultAsync( GuildSettings.WelcomeMessagesChannel.Get(cfg), embedResult: embed, - allowedMentions: Octobot.NoMentions, ct: ct); + allowedMentions: Utility.NoMentions, ct: ct); } } diff --git a/src/Responders/GuildUnloadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs similarity index 90% rename from src/Responders/GuildUnloadedResponder.cs rename to TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs index b49d136..a4680d4 100644 --- a/src/Responders/GuildUnloadedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs @@ -1,12 +1,12 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using Octobot.Data; -using Octobot.Services; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.Gateway.Responders; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles removing guild ID from if the guild becomes unavailable. diff --git a/src/Responders/MessageDeletedResponder.cs b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs similarity index 94% rename from src/Responders/MessageDeletedResponder.cs rename to TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs index 5a69273..6b09b8d 100644 --- a/src/Responders/MessageDeletedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs @@ -1,8 +1,5 @@ using System.Text; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; @@ -10,8 +7,11 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Discord.Gateway.Responders; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles logging the contents of a deleted message and the user who deleted the message @@ -102,6 +102,6 @@ public class MessageDeletedResponder : IResponder return await _channelApi.CreateMessageWithEmbedResultAsync( GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, - allowedMentions: Octobot.NoMentions, ct: ct); + allowedMentions: Utility.NoMentions, ct: ct); } } diff --git a/src/Responders/MessageEditedResponder.cs b/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs similarity index 95% rename from src/Responders/MessageEditedResponder.cs rename to TeamOctolings.Octobot/Responders/MessageEditedResponder.cs index 1143652..6134214 100644 --- a/src/Responders/MessageEditedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs @@ -1,9 +1,6 @@ using System.Text; using DiffPlex.DiffBuilder; using JetBrains.Annotations; -using Octobot.Data; -using Octobot.Extensions; -using Octobot.Services; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; @@ -12,8 +9,11 @@ using Remora.Discord.Caching.Services; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; +using TeamOctolings.Octobot.Services; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles logging the difference between an edited message's old and new content @@ -104,6 +104,6 @@ public class MessageEditedResponder : IResponder return await _channelApi.CreateMessageWithEmbedResultAsync( GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, - allowedMentions: Octobot.NoMentions, ct: ct); + allowedMentions: Utility.NoMentions, ct: ct); } } diff --git a/src/Responders/MessageReceivedResponder.cs b/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs similarity index 96% rename from src/Responders/MessageReceivedResponder.cs rename to TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs index 4c26d8d..34a8f5c 100644 --- a/src/Responders/MessageReceivedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs @@ -5,7 +5,7 @@ using Remora.Discord.Gateway.Responders; using Remora.Rest.Core; using Remora.Results; -namespace Octobot.Responders; +namespace TeamOctolings.Octobot.Responders; /// /// Handles sending replies to easter egg messages. diff --git a/src/Services/AccessControlService.cs b/TeamOctolings.Octobot/Services/AccessControlService.cs similarity index 97% rename from src/Services/AccessControlService.cs rename to TeamOctolings.Octobot/Services/AccessControlService.cs index cb235f9..b5b98ea 100644 --- a/src/Services/AccessControlService.cs +++ b/TeamOctolings.Octobot/Services/AccessControlService.cs @@ -1,11 +1,11 @@ -using Octobot.Data; -using Octobot.Extensions; -using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Services; +namespace TeamOctolings.Octobot.Services; public sealed class AccessControlService { diff --git a/src/Services/GuildDataService.cs b/TeamOctolings.Octobot/Services/GuildDataService.cs similarity index 98% rename from src/Services/GuildDataService.cs rename to TeamOctolings.Octobot/Services/GuildDataService.cs index e503d22..866ee08 100644 --- a/src/Services/GuildDataService.cs +++ b/TeamOctolings.Octobot/Services/GuildDataService.cs @@ -3,10 +3,10 @@ using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Octobot.Data; using Remora.Rest.Core; +using TeamOctolings.Octobot.Data; -namespace Octobot.Services; +namespace TeamOctolings.Octobot.Services; /// /// Handles saving, loading, initializing and providing . diff --git a/src/Services/Update/MemberUpdateService.cs b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs similarity index 98% rename from src/Services/Update/MemberUpdateService.cs rename to TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs index e177fca..51cf647 100644 --- a/src/Services/Update/MemberUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs @@ -2,16 +2,16 @@ using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Octobot.Data; -using Octobot.Extensions; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Services.Update; +namespace TeamOctolings.Octobot.Services.Update; public sealed partial class MemberUpdateService : BackgroundService { diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs similarity index 99% rename from src/Services/Update/ScheduledEventUpdateService.cs rename to TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs index cb87779..ce9c212 100644 --- a/src/Services/Update/ScheduledEventUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs @@ -1,8 +1,6 @@ using System.Text.Json.Nodes; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Octobot.Data; -using Octobot.Extensions; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Objects; @@ -10,8 +8,10 @@ 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; -namespace Octobot.Services.Update; +namespace TeamOctolings.Octobot.Services.Update; public sealed class ScheduledEventUpdateService : BackgroundService { diff --git a/src/Services/Update/SongUpdateService.cs b/TeamOctolings.Octobot/Services/Update/SongUpdateService.cs similarity index 98% rename from src/Services/Update/SongUpdateService.cs rename to TeamOctolings.Octobot/Services/Update/SongUpdateService.cs index 41d5bf3..b07256f 100644 --- a/src/Services/Update/SongUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/SongUpdateService.cs @@ -4,7 +4,7 @@ using Remora.Discord.API.Gateway.Commands; using Remora.Discord.API.Objects; using Remora.Discord.Gateway; -namespace Octobot.Services.Update; +namespace TeamOctolings.Octobot.Services.Update; public sealed class SongUpdateService : BackgroundService { diff --git a/Octobot.csproj b/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj similarity index 90% rename from Octobot.csproj rename to TeamOctolings.Octobot/TeamOctolings.Octobot.csproj index bdfb46a..9c3d58b 100644 --- a/Octobot.csproj +++ b/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj @@ -1,4 +1,4 @@ - + Exe @@ -16,7 +16,7 @@ TeamOctolings en A general-purpose Discord bot for moderation written in C# - docs/octobot.ico + ../docs/octobot.ico false @@ -35,12 +35,12 @@ - + ResXFileCodeGenerator Messages.Designer.cs - + diff --git a/src/Services/Utility.cs b/TeamOctolings.Octobot/Utility.cs similarity index 92% rename from src/Services/Utility.cs rename to TeamOctolings.Octobot/Utility.cs index 3b9ab19..463212b 100644 --- a/src/Services/Utility.cs +++ b/TeamOctolings.Octobot/Utility.cs @@ -1,16 +1,19 @@ using System.Drawing; using System.Text; using System.Text.Json.Nodes; -using Octobot.Data; -using Octobot.Extensions; +using Microsoft.Extensions.Logging; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; using Remora.Rest.Core; using Remora.Results; +using TeamOctolings.Octobot.Attributes; +using TeamOctolings.Octobot.Data; +using TeamOctolings.Octobot.Extensions; -namespace Octobot.Services; +namespace TeamOctolings.Octobot; /// /// Provides utility methods that cannot be transformed to extension methods because they require usage @@ -18,6 +21,9 @@ namespace Octobot.Services; /// public sealed class Utility { + public static readonly AllowedMentions NoMentions = new( + Array.Empty(), Array.Empty(), Array.Empty()); + private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildScheduledEventAPI _eventApi; private readonly IDiscordRestGuildAPI _guildApi; @@ -30,6 +36,9 @@ public sealed class Utility _guildApi = guildApi; } + [StaticCallersOnly] + public static ILogger? StaticLogger { get; set; } + /// /// Gets the string mentioning the and event subscribers related to /// a scheduled From ebcdcb35f7f4f34fd37209821c939336cf5cd878 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sat, 18 May 2024 21:12:38 +0500 Subject: [PATCH 33/44] Separate /*info commands from ToolsCommandGroup (#308) who tf thought that putting 1234915912 methods responsible for 23981 commands in a single class was a good idea??????? Signed-off-by: Octol1ttle --- .../Commands/InfoCommandGroup.cs | 329 ++++++++++++++++++ .../Commands/ToolsCommandGroup.cs | 325 +---------------- 2 files changed, 347 insertions(+), 307 deletions(-) create mode 100644 TeamOctolings.Octobot/Commands/InfoCommandGroup.cs diff --git a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs new file mode 100644 index 0000000..65ddd53 --- /dev/null +++ b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs @@ -0,0 +1,329 @@ +using System.ComponentModel; +using System.Drawing; +using System.Text; +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.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; + +namespace TeamOctolings.Octobot.Commands; + +/// +/// Handles info commands: /userinfo, /guildinfo. +/// +[UsedImplicitly] +public class InfoCommandGroup : CommandGroup +{ + private readonly ICommandContext _context; + private readonly IFeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + private readonly IDiscordRestUserAPI _userApi; + + public InfoCommandGroup( + ICommandContext context, IFeedbackService feedback, + GuildDataService guildData, IDiscordRestGuildAPI guildApi, + IDiscordRestUserAPI userApi) + { + _context = context; + _guildData = guildData; + _feedback = feedback; + _guildApi = guildApi; + _userApi = userApi; + } + + /// + /// A slash command that shows information about user. + /// + /// + /// Information in the output: + /// + /// Display name + /// Discord user since + /// Guild nickname + /// Guild member since + /// Nitro booster since + /// Guild roles + /// Active mute information + /// Active ban information + /// Is on guild status + /// + /// + /// The user to show info about. + /// + /// A feedback sending result which may or may not have succeeded. + /// + [Command("userinfo")] + [DiscordDefaultDMPermission(false)] + [Description("Shows info about user")] + [UsedImplicitly] + public async Task ExecuteUserInfoAsync( + [Description("User to show info about")] + 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 ResultExtensions.FromError(botResult); + } + + var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); + if (!executorResult.IsDefined(out var executor)) + { + return ResultExtensions.FromError(executorResult); + } + + var data = await _guildData.GetData(guildId, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + return await ShowUserInfoAsync(target ?? executor, bot, data, guildId, CancellationToken); + } + + private async Task ShowUserInfoAsync( + IUser target, IUser bot, GuildData data, Snowflake guildId, CancellationToken ct = default) + { + var builder = new StringBuilder().AppendLine($"### <@{target.ID}>"); + + if (target.GlobalName.IsDefined(out var globalName)) + { + builder.AppendBulletPointLine(Messages.UserInfoDisplayName) + .AppendLine(Markdown.InlineCode(globalName)); + } + + builder.AppendBulletPointLine(Messages.UserInfoDiscordUserSince) + .AppendLine(Markdown.Timestamp(target.ID.Timestamp)); + + var memberData = data.GetOrCreateMemberData(target.ID); + + var embedColor = ColorsList.Cyan; + + var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, ct); + DateTimeOffset? communicationDisabledUntil = null; + if (guildMemberResult.IsDefined(out var guildMember)) + { + communicationDisabledUntil = guildMember.CommunicationDisabledUntil.OrDefault(null); + + embedColor = AppendGuildInformation(embedColor, guildMember, builder); + } + + var wasMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || + communicationDisabledUntil is not null; + var wasBanned = memberData.BannedUntil is not null; + var wasKicked = memberData.Kicked; + + if (wasMuted || wasBanned || wasKicked) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.UserInfoPunishments)); + + embedColor = AppendPunishmentsInformation(wasMuted, wasKicked, wasBanned, memberData, + builder, embedColor, communicationDisabledUntil); + } + + if (!guildMemberResult.IsSuccess && !wasBanned) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.UserInfoNotOnGuild)); + + embedColor = ColorsList.Default; + } + + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.InformationAbout, target.GetTag()), bot) + .WithDescription(builder.ToString()) + .WithColour(embedColor) + .WithLargeUserAvatar(target) + .WithFooter($"ID: {target.ID.ToString()}") + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + private static Color AppendPunishmentsInformation(bool wasMuted, bool wasKicked, bool wasBanned, + MemberData memberData, StringBuilder builder, Color embedColor, DateTimeOffset? communicationDisabledUntil) + { + if (wasMuted) + { + AppendMuteInformation(memberData, communicationDisabledUntil, builder); + embedColor = ColorsList.Red; + } + + if (wasKicked) + { + builder.AppendBulletPointLine(Messages.UserInfoKicked); + } + + if (wasBanned) + { + AppendBanInformation(memberData, builder); + embedColor = ColorsList.Black; + } + + return embedColor; + } + + private static Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder) + { + if (guildMember.Nickname.IsDefined(out var nickname)) + { + builder.AppendBulletPointLine(Messages.UserInfoGuildNickname) + .AppendLine(Markdown.InlineCode(nickname)); + } + + builder.AppendBulletPointLine(Messages.UserInfoGuildMemberSince) + .AppendLine(Markdown.Timestamp(guildMember.JoinedAt)); + + if (guildMember.PremiumSince.IsDefined(out var premiumSince)) + { + builder.AppendBulletPointLine(Messages.UserInfoGuildMemberPremiumSince) + .AppendLine(Markdown.Timestamp(premiumSince.Value)); + color = ColorsList.Magenta; + } + + if (guildMember.Roles.Count > 0) + { + builder.AppendBulletPointLine(Messages.UserInfoGuildRoles); + for (var i = 0; i < guildMember.Roles.Count - 1; i++) + { + builder.Append($"<@&{guildMember.Roles[i]}>, "); + } + + builder.AppendLine($"<@&{guildMember.Roles[^1]}>"); + } + + return color; + } + + private static void AppendBanInformation(MemberData memberData, StringBuilder builder) + { + if (memberData.BannedUntil < DateTimeOffset.MaxValue) + { + builder.AppendBulletPointLine(Messages.UserInfoBanned) + .AppendSubBulletPointLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.BannedUntil.Value))); + return; + } + + builder.AppendBulletPointLine(Messages.UserInfoBannedPermanently); + } + + private static void AppendMuteInformation( + MemberData memberData, DateTimeOffset? communicationDisabledUntil, StringBuilder builder) + { + builder.AppendBulletPointLine(Messages.UserInfoMuted); + if (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) + { + builder.AppendSubBulletPointLine(Messages.UserInfoMutedByMuteRole) + .AppendSubBulletPointLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.MutedUntil.Value))); + } + + if (communicationDisabledUntil is not null) + { + builder.AppendSubBulletPointLine(Messages.UserInfoMutedByTimeout) + .AppendSubBulletPointLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(communicationDisabledUntil.Value))); + } + } + + /// + /// A slash command that shows guild information. + /// + /// + /// Information in the output: + /// + /// Guild description + /// Creation date + /// Guild's language + /// Guild's owner + /// Boost level + /// Boost count + /// + /// + /// + /// A feedback sending result which may or may not have succeeded. + /// + [Command("guildinfo")] + [DiscordDefaultDMPermission(false)] + [Description("Shows info about current guild")] + [UsedImplicitly] + public async Task ExecuteGuildInfoAsync() + { + if (!_context.TryGetContextIDs(out var guildId, out _, out _)) + { + 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 ResultExtensions.FromError(botResult); + } + + var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); + if (!guildResult.IsDefined(out var guild)) + { + return ResultExtensions.FromError(guildResult); + } + + var data = await _guildData.GetData(guildId, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + return await ShowGuildInfoAsync(bot, guild, CancellationToken); + } + + private Task ShowGuildInfoAsync(IUser bot, IGuild guild, CancellationToken ct) + { + var description = new StringBuilder().AppendLine($"## {guild.Name}"); + + if (guild.Description is not null) + { + description.AppendBulletPointLine(Messages.GuildInfoDescription) + .AppendLine(Markdown.InlineCode(guild.Description)); + } + + description.AppendBulletPointLine(Messages.GuildInfoCreatedAt) + .AppendLine(Markdown.Timestamp(guild.ID.Timestamp)) + .AppendBulletPointLine(Messages.GuildInfoOwner) + .AppendLine(Mention.User(guild.OwnerID)); + + var embedColor = ColorsList.Cyan; + + if (guild.PremiumTier > PremiumTier.None) + { + description.Append("### ").AppendLine(Messages.GuildInfoServerBoost) + .AppendBulletPoint(Messages.GuildInfoBoostTier) + .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumTier.ToString())) + .AppendBulletPoint(Messages.GuildInfoBoostCount) + .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumSubscriptionCount.ToString())); + embedColor = ColorsList.Magenta; + } + + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.InformationAbout, guild.Name), bot) + .WithDescription(description.ToString()) + .WithColour(embedColor) + .WithLargeGuildIcon(guild) + .WithGuildBanner(guild) + .WithFooter($"ID: {guild.ID.ToString()}") + .Build(); + + return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } +} diff --git a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs index 3e84527..6af3040 100644 --- a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Drawing; using System.Text; using JetBrains.Annotations; using Remora.Commands.Attributes; @@ -11,7 +10,6 @@ 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; @@ -21,313 +19,42 @@ using TeamOctolings.Octobot.Services; namespace TeamOctolings.Octobot.Commands; /// -/// Handles tool commands: /userinfo, /guildinfo, /random, /timestamp, /8ball. +/// Handles tool commands: /random, /timestamp, /8ball. /// [UsedImplicitly] public class ToolsCommandGroup : CommandGroup { + private static readonly TimestampStyle[] AllStyles = + [ + TimestampStyle.ShortDate, + TimestampStyle.LongDate, + TimestampStyle.ShortTime, + TimestampStyle.LongTime, + TimestampStyle.ShortDateTime, + TimestampStyle.LongDateTime, + TimestampStyle.RelativeTime + ]; + + private static readonly string[] AnswerTypes = + [ + "Positive", "Questionable", "Neutral", "Negative" + ]; + private readonly ICommandContext _context; private readonly IFeedbackService _feedback; - private readonly IDiscordRestGuildAPI _guildApi; private readonly GuildDataService _guildData; private readonly IDiscordRestUserAPI _userApi; public ToolsCommandGroup( ICommandContext context, IFeedbackService feedback, - GuildDataService guildData, IDiscordRestGuildAPI guildApi, - IDiscordRestUserAPI userApi) + GuildDataService guildData, IDiscordRestUserAPI userApi) { _context = context; _guildData = guildData; _feedback = feedback; - _guildApi = guildApi; _userApi = userApi; } - /// - /// A slash command that shows information about user. - /// - /// - /// Information in the output: - /// - /// Display name - /// Discord user since - /// Guild nickname - /// Guild member since - /// Nitro booster since - /// Guild roles - /// Active mute information - /// Active ban information - /// Is on guild status - /// - /// - /// The user to show info about. - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("userinfo")] - [DiscordDefaultDMPermission(false)] - [Description("Shows info about user")] - [UsedImplicitly] - public async Task ExecuteUserInfoAsync( - [Description("User to show info about")] - 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 ResultExtensions.FromError(botResult); - } - - var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); - if (!executorResult.IsDefined(out var executor)) - { - return ResultExtensions.FromError(executorResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ShowUserInfoAsync(target ?? executor, bot, data, guildId, CancellationToken); - } - - private async Task ShowUserInfoAsync( - IUser target, IUser bot, GuildData data, Snowflake guildId, CancellationToken ct = default) - { - var builder = new StringBuilder().AppendLine($"### <@{target.ID}>"); - - if (target.GlobalName.IsDefined(out var globalName)) - { - builder.AppendBulletPointLine(Messages.UserInfoDisplayName) - .AppendLine(Markdown.InlineCode(globalName)); - } - - builder.AppendBulletPointLine(Messages.UserInfoDiscordUserSince) - .AppendLine(Markdown.Timestamp(target.ID.Timestamp)); - - var memberData = data.GetOrCreateMemberData(target.ID); - - var embedColor = ColorsList.Cyan; - - var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, target.ID, ct); - DateTimeOffset? communicationDisabledUntil = null; - if (guildMemberResult.IsDefined(out var guildMember)) - { - communicationDisabledUntil = guildMember.CommunicationDisabledUntil.OrDefault(null); - - embedColor = AppendGuildInformation(embedColor, guildMember, builder); - } - - var wasMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || - communicationDisabledUntil is not null; - var wasBanned = memberData.BannedUntil is not null; - var wasKicked = memberData.Kicked; - - if (wasMuted || wasBanned || wasKicked) - { - builder.Append("### ") - .AppendLine(Markdown.Bold(Messages.UserInfoPunishments)); - - embedColor = AppendPunishmentsInformation(wasMuted, wasKicked, wasBanned, memberData, - builder, embedColor, communicationDisabledUntil); - } - - if (!guildMemberResult.IsSuccess && !wasBanned) - { - builder.Append("### ") - .AppendLine(Markdown.Bold(Messages.UserInfoNotOnGuild)); - - embedColor = ColorsList.Default; - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.InformationAbout, target.GetTag()), bot) - .WithDescription(builder.ToString()) - .WithColour(embedColor) - .WithLargeUserAvatar(target) - .WithFooter($"ID: {target.ID.ToString()}") - .Build(); - - return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - - private static Color AppendPunishmentsInformation(bool wasMuted, bool wasKicked, bool wasBanned, - MemberData memberData, StringBuilder builder, Color embedColor, DateTimeOffset? communicationDisabledUntil) - { - if (wasMuted) - { - AppendMuteInformation(memberData, communicationDisabledUntil, builder); - embedColor = ColorsList.Red; - } - - if (wasKicked) - { - builder.AppendBulletPointLine(Messages.UserInfoKicked); - } - - if (wasBanned) - { - AppendBanInformation(memberData, builder); - embedColor = ColorsList.Black; - } - - return embedColor; - } - - private static Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder) - { - if (guildMember.Nickname.IsDefined(out var nickname)) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildNickname) - .AppendLine(Markdown.InlineCode(nickname)); - } - - builder.AppendBulletPointLine(Messages.UserInfoGuildMemberSince) - .AppendLine(Markdown.Timestamp(guildMember.JoinedAt)); - - if (guildMember.PremiumSince.IsDefined(out var premiumSince)) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildMemberPremiumSince) - .AppendLine(Markdown.Timestamp(premiumSince.Value)); - color = ColorsList.Magenta; - } - - if (guildMember.Roles.Count > 0) - { - builder.AppendBulletPointLine(Messages.UserInfoGuildRoles); - for (var i = 0; i < guildMember.Roles.Count - 1; i++) - { - builder.Append($"<@&{guildMember.Roles[i]}>, "); - } - - builder.AppendLine($"<@&{guildMember.Roles[^1]}>"); - } - - return color; - } - - private static void AppendBanInformation(MemberData memberData, StringBuilder builder) - { - if (memberData.BannedUntil < DateTimeOffset.MaxValue) - { - builder.AppendBulletPointLine(Messages.UserInfoBanned) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.BannedUntil.Value))); - return; - } - - builder.AppendBulletPointLine(Messages.UserInfoBannedPermanently); - } - - private static void AppendMuteInformation( - MemberData memberData, DateTimeOffset? communicationDisabledUntil, StringBuilder builder) - { - builder.AppendBulletPointLine(Messages.UserInfoMuted); - if (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) - { - builder.AppendSubBulletPointLine(Messages.UserInfoMutedByMuteRole) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.MutedUntil.Value))); - } - - if (communicationDisabledUntil is not null) - { - builder.AppendSubBulletPointLine(Messages.UserInfoMutedByTimeout) - .AppendSubBulletPointLine(string.Format( - Messages.DescriptionActionExpiresAt, Markdown.Timestamp(communicationDisabledUntil.Value))); - } - } - - /// - /// A slash command that shows guild information. - /// - /// - /// Information in the output: - /// - /// Guild description - /// Creation date - /// Guild's language - /// Guild's owner - /// Boost level - /// Boost count - /// - /// - /// - /// A feedback sending result which may or may not have succeeded. - /// - [Command("guildinfo")] - [DiscordDefaultDMPermission(false)] - [Description("Shows info about current guild")] - [UsedImplicitly] - public async Task ExecuteGuildInfoAsync() - { - if (!_context.TryGetContextIDs(out var guildId, out _, out _)) - { - 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 ResultExtensions.FromError(botResult); - } - - var guildResult = await _guildApi.GetGuildAsync(guildId, ct: CancellationToken); - if (!guildResult.IsDefined(out var guild)) - { - return ResultExtensions.FromError(guildResult); - } - - var data = await _guildData.GetData(guildId, CancellationToken); - Messages.Culture = GuildSettings.Language.Get(data.Settings); - - return await ShowGuildInfoAsync(bot, guild, CancellationToken); - } - - private Task ShowGuildInfoAsync(IUser bot, IGuild guild, CancellationToken ct) - { - var description = new StringBuilder().AppendLine($"## {guild.Name}"); - - if (guild.Description is not null) - { - description.AppendBulletPointLine(Messages.GuildInfoDescription) - .AppendLine(Markdown.InlineCode(guild.Description)); - } - - description.AppendBulletPointLine(Messages.GuildInfoCreatedAt) - .AppendLine(Markdown.Timestamp(guild.ID.Timestamp)) - .AppendBulletPointLine(Messages.GuildInfoOwner) - .AppendLine(Mention.User(guild.OwnerID)); - - var embedColor = ColorsList.Cyan; - - if (guild.PremiumTier > PremiumTier.None) - { - description.Append("### ").AppendLine(Messages.GuildInfoServerBoost) - .AppendBulletPoint(Messages.GuildInfoBoostTier) - .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumTier.ToString())) - .AppendBulletPoint(Messages.GuildInfoBoostCount) - .Append(": ").AppendLine(Markdown.InlineCode(guild.PremiumSubscriptionCount.ToString())); - embedColor = ColorsList.Magenta; - } - - var embed = new EmbedBuilder().WithSmallTitle( - string.Format(Messages.InformationAbout, guild.Name), bot) - .WithDescription(description.ToString()) - .WithColour(embedColor) - .WithLargeGuildIcon(guild) - .WithGuildBanner(guild) - .WithFooter($"ID: {guild.ID.ToString()}") - .Build(); - - return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); - } - /// /// A slash command that generates a random number using maximum and minimum numbers. /// @@ -405,17 +132,6 @@ public class ToolsCommandGroup : CommandGroup return _feedback.SendContextualEmbedResultAsync(embed, ct: ct); } - private static readonly TimestampStyle[] AllStyles = - [ - TimestampStyle.ShortDate, - TimestampStyle.LongDate, - TimestampStyle.ShortTime, - TimestampStyle.LongTime, - TimestampStyle.ShortDateTime, - TimestampStyle.LongDateTime, - TimestampStyle.RelativeTime - ]; - /// /// A slash command that shows the current timestamp with an optional offset in all styles supported by Discord. /// @@ -533,11 +249,6 @@ public class ToolsCommandGroup : CommandGroup return await AnswerEightBallAsync(bot, CancellationToken); } - private static readonly string[] AnswerTypes = - [ - "Positive", "Questionable", "Neutral", "Negative" - ]; - private Task AnswerEightBallAsync(IUser bot, CancellationToken ct) { var typeNumber = Random.Shared.Next(0, 4); From d03e2504fc0640f8dd2d3a4b335ca9718af75c79 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 23 May 2024 17:47:51 +0500 Subject: [PATCH 34/44] Seal implicitly used classes (#309) Apparently the `[UsedImplicitly]` annotation suppresses the "Class has no inheritors and can be marked sealed" warning. Cool to know. Signed-off-by: Octol1ttle --- TeamOctolings.Octobot/Commands/AboutCommandGroup.cs | 4 ++-- TeamOctolings.Octobot/Commands/BanCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/ClearCommandGroup.cs | 2 +- .../Commands/Events/ErrorLoggingPostExecutionEvent.cs | 2 +- .../Commands/Events/LoggingPreparationErrorEvent.cs | 2 +- TeamOctolings.Octobot/Commands/InfoCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/KickCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/MuteCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/PingCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/RemindCommandGroup.cs | 4 ++-- TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs | 2 +- TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs | 2 +- .../Responders/GuildMemberJoinedResponder.cs | 2 +- TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs | 2 +- TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs | 2 +- TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs | 2 +- TeamOctolings.Octobot/Responders/MessageEditedResponder.cs | 2 +- TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs | 2 +- 19 files changed, 21 insertions(+), 21 deletions(-) diff --git a/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs b/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs index 9f05af3..dbb8b12 100644 --- a/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/AboutCommandGroup.cs @@ -25,7 +25,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles the command to show information about this bot: /about. /// [UsedImplicitly] -public class AboutCommandGroup : CommandGroup +public sealed class AboutCommandGroup : CommandGroup { private static readonly (string Username, Snowflake Id)[] Developers = [ @@ -36,9 +36,9 @@ public class AboutCommandGroup : CommandGroup private readonly ICommandContext _context; private readonly IFeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; private readonly GuildDataService _guildData; private readonly IDiscordRestUserAPI _userApi; - private readonly IDiscordRestGuildAPI _guildApi; public AboutCommandGroup( ICommandContext context, GuildDataService guildData, diff --git a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs index 8d90286..69be80f 100644 --- a/TeamOctolings.Octobot/Commands/BanCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/BanCommandGroup.cs @@ -26,7 +26,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles commands related to ban management: /ban and /unban. /// [UsedImplicitly] -public class BanCommandGroup : CommandGroup +public sealed class BanCommandGroup : CommandGroup { private readonly AccessControlService _access; private readonly IDiscordRestChannelAPI _channelApi; diff --git a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs index 8a8cb2f..38d864b 100644 --- a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs @@ -23,7 +23,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles the command to clear messages in a channel: /clear. /// [UsedImplicitly] -public class ClearCommandGroup : CommandGroup +public sealed class ClearCommandGroup : CommandGroup { private readonly IDiscordRestChannelAPI _channelApi; private readonly ICommandContext _context; diff --git a/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs b/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs index 7ffc4fe..7409d3b 100644 --- a/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs +++ b/TeamOctolings.Octobot/Commands/Events/ErrorLoggingPostExecutionEvent.cs @@ -18,7 +18,7 @@ namespace TeamOctolings.Octobot.Commands.Events; /// Handles error logging for slash command groups. /// [UsedImplicitly] -public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent +public sealed class ErrorLoggingPostExecutionEvent : IPostExecutionEvent { private readonly IFeedbackService _feedback; private readonly ILogger _logger; diff --git a/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs b/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs index 10a6a1f..9e69a7f 100644 --- a/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs +++ b/TeamOctolings.Octobot/Commands/Events/LoggingPreparationErrorEvent.cs @@ -11,7 +11,7 @@ namespace TeamOctolings.Octobot.Commands.Events; /// Handles error logging for slash commands that couldn't be successfully prepared. /// [UsedImplicitly] -public class LoggingPreparationErrorEvent : IPreparationErrorEvent +public sealed class LoggingPreparationErrorEvent : IPreparationErrorEvent { private readonly ILogger _logger; diff --git a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs index 65ddd53..d7798f3 100644 --- a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs @@ -23,7 +23,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles info commands: /userinfo, /guildinfo. /// [UsedImplicitly] -public class InfoCommandGroup : CommandGroup +public sealed class InfoCommandGroup : CommandGroup { private readonly ICommandContext _context; private readonly IFeedbackService _feedback; diff --git a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs index 4252232..a8fea2a 100644 --- a/TeamOctolings.Octobot/Commands/KickCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/KickCommandGroup.cs @@ -22,7 +22,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles the command to kick members of a guild: /kick. /// [UsedImplicitly] -public class KickCommandGroup : CommandGroup +public sealed class KickCommandGroup : CommandGroup { private readonly AccessControlService _access; private readonly IDiscordRestChannelAPI _channelApi; diff --git a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs index 8e449f7..282afe8 100644 --- a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs @@ -26,7 +26,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles commands related to mute management: /mute and /unmute. /// [UsedImplicitly] -public class MuteCommandGroup : CommandGroup +public sealed class MuteCommandGroup : CommandGroup { private readonly AccessControlService _access; private readonly ICommandContext _context; diff --git a/TeamOctolings.Octobot/Commands/PingCommandGroup.cs b/TeamOctolings.Octobot/Commands/PingCommandGroup.cs index 70b9f23..01a1ee2 100644 --- a/TeamOctolings.Octobot/Commands/PingCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/PingCommandGroup.cs @@ -22,7 +22,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles the command to get the time taken for the gateway to respond to the last heartbeat: /ping /// [UsedImplicitly] -public class PingCommandGroup : CommandGroup +public sealed class PingCommandGroup : CommandGroup { private readonly IDiscordRestChannelAPI _channelApi; private readonly DiscordGatewayClient _client; diff --git a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs index f40ba6b..bf59d67 100644 --- a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs @@ -25,13 +25,13 @@ namespace TeamOctolings.Octobot.Commands; /// Handles commands to manage reminders: /remind, /listremind, /delremind /// [UsedImplicitly] -public class RemindCommandGroup : CommandGroup +public sealed class RemindCommandGroup : CommandGroup { private readonly IInteractionCommandContext _context; private readonly IFeedbackService _feedback; private readonly GuildDataService _guildData; - private readonly IDiscordRestUserAPI _userApi; private readonly IDiscordRestInteractionAPI _interactionApi; + private readonly IDiscordRestUserAPI _userApi; public RemindCommandGroup( IInteractionCommandContext context, GuildDataService guildData, IFeedbackService feedback, diff --git a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs index 56584bf..0acaa88 100644 --- a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs @@ -26,7 +26,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles the commands to list and modify per-guild settings: /settings and /settings list. /// [UsedImplicitly] -public class SettingsCommandGroup : CommandGroup +public sealed class SettingsCommandGroup : CommandGroup { /// /// Represents all options as an array of objects implementing . diff --git a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs index 6af3040..b4c3488 100644 --- a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs @@ -22,7 +22,7 @@ namespace TeamOctolings.Octobot.Commands; /// Handles tool commands: /random, /timestamp, /8ball. /// [UsedImplicitly] -public class ToolsCommandGroup : CommandGroup +public sealed class ToolsCommandGroup : CommandGroup { private static readonly TimestampStyle[] AllStyles = [ diff --git a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs index 0c71a06..cebb1ea 100644 --- a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs @@ -19,7 +19,7 @@ namespace TeamOctolings.Octobot.Responders; /// has enabled /// [UsedImplicitly] -public class GuildLoadedResponder : IResponder +public sealed class GuildLoadedResponder : IResponder { private readonly IDiscordRestChannelAPI _channelApi; private readonly GuildDataService _guildData; diff --git a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs index 6964fe7..c1f1da0 100644 --- a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs @@ -18,7 +18,7 @@ namespace TeamOctolings.Octobot.Responders; /// /// [UsedImplicitly] -public class GuildMemberJoinedResponder : IResponder +public sealed class GuildMemberJoinedResponder : IResponder { private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildAPI _guildApi; diff --git a/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs index 4f6150c..9774899 100644 --- a/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildMemberLeftResponder.cs @@ -15,7 +15,7 @@ namespace TeamOctolings.Octobot.Responders; /// /// [UsedImplicitly] -public class GuildMemberLeftResponder : IResponder +public sealed class GuildMemberLeftResponder : IResponder { private readonly IDiscordRestChannelAPI _channelApi; private readonly IDiscordRestGuildAPI _guildApi; diff --git a/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs index a4680d4..c73c134 100644 --- a/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildUnloadedResponder.cs @@ -12,7 +12,7 @@ namespace TeamOctolings.Octobot.Responders; /// Handles removing guild ID from if the guild becomes unavailable. /// [UsedImplicitly] -public class GuildUnloadedResponder : IResponder +public sealed class GuildUnloadedResponder : IResponder { private readonly GuildDataService _guildData; private readonly ILogger _logger; diff --git a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs index 6b09b8d..88a8de2 100644 --- a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs @@ -18,7 +18,7 @@ namespace TeamOctolings.Octobot.Responders; /// to a guild's if one is set. /// [UsedImplicitly] -public class MessageDeletedResponder : IResponder +public sealed class MessageDeletedResponder : IResponder { private readonly IDiscordRestAuditLogAPI _auditLogApi; private readonly IDiscordRestChannelAPI _channelApi; diff --git a/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs b/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs index 6134214..2968562 100644 --- a/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageEditedResponder.cs @@ -20,7 +20,7 @@ namespace TeamOctolings.Octobot.Responders; /// to a guild's if one is set. /// [UsedImplicitly] -public class MessageEditedResponder : IResponder +public sealed class MessageEditedResponder : IResponder { private readonly CacheService _cacheService; private readonly IDiscordRestChannelAPI _channelApi; diff --git a/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs b/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs index 34a8f5c..24d53a5 100644 --- a/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageReceivedResponder.cs @@ -11,7 +11,7 @@ namespace TeamOctolings.Octobot.Responders; /// Handles sending replies to easter egg messages. /// [UsedImplicitly] -public class MessageCreateResponder : IResponder +public sealed class MessageCreateResponder : IResponder { private readonly IDiscordRestChannelAPI _channelApi; From ea9302e1858807d1a79293ae7e9ed43773373329 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 23 May 2024 18:21:52 +0500 Subject: [PATCH 35/44] Use MemberData roles when checking permissions & interactions (#312) Closes #311 This change fixes unexpected behavior when a member's Discord roles get desynchronized with their MemberData roles (e.g. when a member gets role-muted). In addition this results in less API requests being made when there are cache misses (commands should execute faster) --------- Signed-off-by: Octol1ttle --- .../Services/AccessControlService.cs | 57 ++++++------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/TeamOctolings.Octobot/Services/AccessControlService.cs b/TeamOctolings.Octobot/Services/AccessControlService.cs index b5b98ea..d39c9e5 100644 --- a/TeamOctolings.Octobot/Services/AccessControlService.cs +++ b/TeamOctolings.Octobot/Services/AccessControlService.cs @@ -20,18 +20,17 @@ public sealed class AccessControlService _userApi = userApi; } - private static bool CheckPermission(IEnumerable roles, GuildData data, Snowflake memberId, - IGuildMember member, + private static bool CheckPermission(IEnumerable roles, GuildData data, MemberData memberData, DiscordPermission permission) { var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings); - if (!moderatorRole.Empty() && data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value)) + if (!moderatorRole.Empty() && memberData.Roles.Contains(moderatorRole.Value)) { return true; } return roles - .Where(r => member.Roles.Contains(r.ID)) + .Where(r => memberData.Roles.Contains(r.ID.Value)) .Any(r => r.Permissions.HasPermission(permission) ); @@ -80,38 +79,23 @@ public sealed class AccessControlService return Result.FromError(botResult); } - var botMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct); - if (!botMemberResult.IsDefined(out var botMember)) - { - return Result.FromError(botMemberResult); - } - - var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); - if (!targetMemberResult.IsDefined(out var targetMember)) - { - return Result.FromSuccess(null); - } - var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct); if (!rolesResult.IsDefined(out var roles)) { return Result.FromError(rolesResult); } + var data = await _data.GetData(guildId, ct); + var targetData = data.GetOrCreateMemberData(targetId); + var botData = data.GetOrCreateMemberData(bot.ID); + if (interacterId is null) { - return CheckInteractions(action, guild, roles, targetMember, botMember, botMember); + return CheckInteractions(action, guild, roles, targetData, botData, botData); } - var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct); - if (!interacterResult.IsDefined(out var interacter)) - { - return Result.FromError(interacterResult); - } - - var data = await _data.GetData(guildId, ct); - - var hasPermission = CheckPermission(roles, data, interacterId.Value, interacter, + var interacterData = data.GetOrCreateMemberData(interacterId.Value); + var hasPermission = CheckPermission(roles, data, interacterData, action switch { "Ban" => DiscordPermission.BanMembers, @@ -121,31 +105,26 @@ public sealed class AccessControlService }); return hasPermission - ? CheckInteractions(action, guild, roles, targetMember, botMember, interacter) + ? CheckInteractions(action, guild, roles, targetData, botData, interacterData) : Result.FromSuccess($"UserCannot{action}Members".Localized()); } private static Result CheckInteractions( - string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember botMember, - IGuildMember interacter) + string action, IGuild guild, IReadOnlyList roles, MemberData targetData, MemberData botData, + MemberData interacterData) { - if (!targetMember.User.IsDefined(out var targetUser)) - { - return new ArgumentNullError(nameof(targetMember.User)); - } - - if (botMember.User == targetMember.User) + if (botData.Id == targetData.Id) { return Result.FromSuccess($"UserCannot{action}Bot".Localized()); } - if (targetUser.ID == guild.OwnerID) + if (targetData.Id == guild.OwnerID) { return Result.FromSuccess($"UserCannot{action}Owner".Localized()); } - var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); - var botRoles = roles.Where(r => botMember.Roles.Contains(r.ID)); + var targetRoles = roles.Where(r => targetData.Roles.Contains(r.ID.Value)).ToList(); + var botRoles = roles.Where(r => botData.Roles.Contains(r.ID.Value)); var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position); if (targetBotRoleDiff >= 0) @@ -153,7 +132,7 @@ public sealed class AccessControlService return Result.FromSuccess($"BotCannot{action}Target".Localized()); } - var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); + var interacterRoles = roles.Where(r => interacterData.Roles.Contains(r.ID.Value)); var targetInteracterRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); return targetInteracterRoleDiff < 0 From daef4f1d48874b9f2cec25fad70414061f46aa0c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 30 May 2024 15:32:27 +0500 Subject: [PATCH 36/44] Upgrade NuGet dependencies (#313) Signed-off-by: Octol1ttle --- .../Extensions/ChannelApiExtensions.cs | 7 ++++--- TeamOctolings.Octobot/TeamOctolings.Octobot.csproj | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs b/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs index 2767f96..82f8889 100644 --- a/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/ChannelApiExtensions.cs @@ -12,11 +12,12 @@ public static class ChannelApiExtensions public static async Task CreateMessageWithEmbedResultAsync(this IDiscordRestChannelAPI channelApi, Snowflake channelId, Optional message = default, Optional nonce = default, Optional isTextToSpeech = default, Optional> embedResult = default, - Optional allowedMentions = default, Optional messageRefenence = default, + Optional allowedMentions = default, Optional messageReference = default, Optional> components = default, Optional> stickerIds = default, Optional>> attachments = default, - Optional flags = default, CancellationToken ct = default) + Optional flags = default, Optional enforceNonce = default, + Optional poll = default, CancellationToken ct = default) { if (!embedResult.IsDefined() || !embedResult.Value.IsDefined(out var embed)) { @@ -24,6 +25,6 @@ public static class ChannelApiExtensions } return (Result)await channelApi.CreateMessageAsync(channelId, message, nonce, isTextToSpeech, new[] { embed }, - allowedMentions, messageRefenence, components, stickerIds, attachments, flags, ct); + allowedMentions, messageReference, components, stickerIds, attachments, flags, enforceNonce, poll, ct); } } diff --git a/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj b/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj index 9c3d58b..19e37f9 100644 --- a/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj +++ b/TeamOctolings.Octobot/TeamOctolings.Octobot.csproj @@ -22,16 +22,16 @@ - + - - - - + + + + From 29a1eb986987d9a633b1acf21fc8b74598e3f04e Mon Sep 17 00:00:00 2001 From: Fakeintxsh <95250141+mctaylors@users.noreply.github.com> Date: Fri, 31 May 2024 21:46:12 +0500 Subject: [PATCH 37/44] Add description to /clear's author to clarify its functionality (#315) Closes #314 What? The title speaks for itself. --- TeamOctolings.Octobot/Commands/ClearCommandGroup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs index 38d864b..7c1b516 100644 --- a/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ClearCommandGroup.cs @@ -64,6 +64,7 @@ public sealed class ClearCommandGroup : CommandGroup public async Task ExecuteClear( [Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)] int amount, + [Description("Ignore messages except from the specified author")] IUser? author = null) { if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var executorId)) From 2b0c4b62d3e8af97ff96215d6e6b05aa3f44b323 Mon Sep 17 00:00:00 2001 From: Fakeintxsh <95250141+mctaylors@users.noreply.github.com> Date: Tue, 11 Jun 2024 09:28:06 +0300 Subject: [PATCH 38/44] README: Refer to the Octobot's Wiki in Building Octobot (#316) Signed-off-by: Fakeintxsh <95250141+mctaylors@users.noreply.github.com> --- docs/README.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/README.md b/docs/README.md index 7056857..ccc3b83 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,23 +15,16 @@ Veemo! I'm a general-purpose bot for moderation (formerly known as Boyfriend) wr * Reminding everyone about that new event you made * Renaming those annoying self-hoisting members * Log everything from joining the server to deleting messages -* Listen to music! +* Listen to Inkantation! *...a-a-and more!* ## Building Octobot -1. Install [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) -2. Go to the [Discord Developer Portal](https://discord.com/developers), create a new application and get a bot token. Don't forget to also enable all intents! -3. Clone this repository and open `Octobot` folder. -``` -git clone https://github.com/TeamOctolings/Octobot -cd Octobot -``` -4. Run Octobot using `dotnet` with `BOT_TOKEN` variable. -``` -dotnet run BOT_TOKEN='ENTER_TOKEN_HERE' -``` +Check out the Octobot's Wiki for details. + +| [Windows](https://github.com/TeamOctolings/Octobot/wiki/Installing-Windows) | [Linux/macOS](https://github.com/TeamOctolings/Octobot/wiki/Installing-Unix) | +| --- | --- | ## Contributing From a953053f1d7ad7012f80d2bb338c7ee107e5bf9c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sat, 22 Jun 2024 00:28:29 +0500 Subject: [PATCH 39/44] Handle audit log entries for message deletion being empty (#317) In order to determine who deleted a message, Octobot fetches the audit log with a filter on the action "Message Delete", gets the latest entry and uses its author if the timestamps roughly match. However, if the filter returns no entries (as in, no message deletions are present in the audit log), `Single()` will throw an exception with the message `Sequence contains no elements`. To fix this, this PR replaces `Single()` with `SingleOrDefault()` and adds a null-check on `auditLog` in the form of a pattern access Signed-off-by: Octol1ttle --- TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs index 88a8de2..f0e3d22 100644 --- a/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs +++ b/TeamOctolings.Octobot/Responders/MessageDeletedResponder.cs @@ -66,10 +66,10 @@ public sealed class MessageDeletedResponder : IResponder return ResultExtensions.FromError(auditLogResult); } - var auditLog = auditLogPage.AuditLogEntries.Single(); - var deleterResult = Result.FromSuccess(message.Author); - if (auditLog.UserID is not null + + var auditLog = auditLogPage.AuditLogEntries.SingleOrDefault(); + if (auditLog is { UserID: not null } && auditLog.Options.Value.ChannelID == gatewayEvent.ChannelID && DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) { From a0e7b3a6116c6ff44e1ea2877ffb33d35b931bf8 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 25 Jun 2024 15:09:45 +0500 Subject: [PATCH 40/44] Always default cancellation tokens (#319) This PR makes sure that a cancellation token is never *required* to use an `async` method. This does not affect user experience in any way, only code quality. --------- Signed-off-by: Octol1ttle --- TeamOctolings.Octobot/Commands/InfoCommandGroup.cs | 2 +- TeamOctolings.Octobot/Commands/MuteCommandGroup.cs | 6 +++--- TeamOctolings.Octobot/Commands/RemindCommandGroup.cs | 4 ++-- TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs | 6 +++--- .../Responders/GuildLoadedResponder.cs | 2 +- .../Responders/GuildMemberJoinedResponder.cs | 2 +- TeamOctolings.Octobot/Services/GuildDataService.cs | 4 ++-- .../Services/Update/MemberUpdateService.cs | 12 ++++++------ .../Services/Update/ScheduledEventUpdateService.cs | 12 ++++++------ TeamOctolings.Octobot/Utility.cs | 2 +- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs index d7798f3..f07b210 100644 --- a/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/InfoCommandGroup.cs @@ -288,7 +288,7 @@ public sealed class InfoCommandGroup : CommandGroup return await ShowGuildInfoAsync(bot, guild, CancellationToken); } - private Task ShowGuildInfoAsync(IUser bot, IGuild guild, CancellationToken ct) + private Task ShowGuildInfoAsync(IUser bot, IGuild guild, CancellationToken ct = default) { var description = new StringBuilder().AppendLine($"## {guild.Name}"); diff --git a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs index 282afe8..46e8d84 100644 --- a/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/MuteCommandGroup.cs @@ -170,7 +170,7 @@ public sealed class MuteCommandGroup : CommandGroup private async Task SelectMuteMethodAsync( IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, GuildData data, - IUser bot, DateTimeOffset until, CancellationToken ct) + IUser bot, DateTimeOffset until, CancellationToken ct = default) { var muteRole = GuildSettings.MuteRole.Get(data.Settings); @@ -186,7 +186,7 @@ public sealed class MuteCommandGroup : CommandGroup private async Task RoleMuteUserAsync( IUser executor, IUser target, string reason, Snowflake guildId, GuildData data, - DateTimeOffset until, Snowflake muteRole, CancellationToken ct) + DateTimeOffset until, Snowflake muteRole, CancellationToken ct = default) { var assignRoles = new List { muteRole }; var memberData = data.GetOrCreateMemberData(target.ID); @@ -208,7 +208,7 @@ public sealed class MuteCommandGroup : CommandGroup private async Task TimeoutUserAsync( IUser executor, IUser target, string reason, TimeSpan duration, Snowflake guildId, - IUser bot, DateTimeOffset until, CancellationToken ct) + IUser bot, DateTimeOffset until, CancellationToken ct = default) { if (duration.TotalDays >= 28) { diff --git a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs index bf59d67..be53ed7 100644 --- a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs @@ -78,7 +78,7 @@ public sealed class RemindCommandGroup : CommandGroup return await ListRemindersAsync(data.GetOrCreateMemberData(executorId), guildId, executor, bot, CancellationToken); } - private Task ListRemindersAsync(MemberData data, Snowflake guildId, IUser executor, IUser bot, CancellationToken ct) + private Task ListRemindersAsync(MemberData data, Snowflake guildId, IUser executor, IUser bot, CancellationToken ct = default) { if (data.Reminders.Count == 0) { @@ -353,7 +353,7 @@ public sealed class RemindCommandGroup : CommandGroup } private Task DeleteReminderAsync(MemberData data, int index, IUser bot, - CancellationToken ct) + CancellationToken ct = default) { if (index >= data.Reminders.Count) { diff --git a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs index b4c3488..2936392 100644 --- a/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/ToolsCommandGroup.cs @@ -90,7 +90,7 @@ public sealed class ToolsCommandGroup : CommandGroup } private Task SendRandomNumberAsync(long first, long? secondNullable, - IUser executor, CancellationToken ct) + IUser executor, CancellationToken ct = default) { const long secondDefault = 0; var second = secondNullable ?? secondDefault; @@ -187,7 +187,7 @@ public sealed class ToolsCommandGroup : CommandGroup return await SendTimestampAsync(offset, executor, CancellationToken); } - private Task SendTimestampAsync(TimeSpan? offset, IUser executor, CancellationToken ct) + private Task SendTimestampAsync(TimeSpan? offset, IUser executor, CancellationToken ct = default) { var timestamp = DateTimeOffset.UtcNow.Add(offset ?? TimeSpan.Zero).ToUnixTimeSeconds(); @@ -249,7 +249,7 @@ public sealed class ToolsCommandGroup : CommandGroup return await AnswerEightBallAsync(bot, CancellationToken); } - private Task AnswerEightBallAsync(IUser bot, CancellationToken ct) + private Task AnswerEightBallAsync(IUser bot, CancellationToken ct = default) { var typeNumber = Random.Shared.Next(0, 4); var embedColor = typeNumber switch diff --git a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs index cebb1ea..b420db2 100644 --- a/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildLoadedResponder.cs @@ -94,7 +94,7 @@ public sealed class GuildLoadedResponder : IResponder GuildSettings.PrivateFeedbackChannel.Get(cfg), embedResult: embed, ct: ct); } - private async Task SendDataLoadFailed(IGuild guild, GuildData data, IUser bot, CancellationToken ct) + private async Task SendDataLoadFailed(IGuild guild, GuildData data, IUser bot, CancellationToken ct = default) { var channelResult = await _utility.GetEmergencyFeedbackChannel(guild, data, ct); if (!channelResult.IsDefined(out var channel)) diff --git a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs index c1f1da0..ae9f174 100644 --- a/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs +++ b/TeamOctolings.Octobot/Responders/GuildMemberJoinedResponder.cs @@ -81,7 +81,7 @@ public sealed class GuildMemberJoinedResponder : IResponder } private async Task TryReturnRolesAsync( - JsonNode cfg, MemberData memberData, Snowflake guildId, Snowflake userId, CancellationToken ct) + JsonNode cfg, MemberData memberData, Snowflake guildId, Snowflake userId, CancellationToken ct = default) { if (!GuildSettings.ReturnRolesOnRejoin.Get(cfg)) { diff --git a/TeamOctolings.Octobot/Services/GuildDataService.cs b/TeamOctolings.Octobot/Services/GuildDataService.cs index 866ee08..a7af7c9 100644 --- a/TeamOctolings.Octobot/Services/GuildDataService.cs +++ b/TeamOctolings.Octobot/Services/GuildDataService.cs @@ -27,7 +27,7 @@ public sealed class GuildDataService : BackgroundService return SaveAsync(ct); } - private Task SaveAsync(CancellationToken ct) + private Task SaveAsync(CancellationToken ct = default) { var tasks = new List(); var datas = _datas.Values.ToArray(); @@ -44,7 +44,7 @@ public sealed class GuildDataService : BackgroundService return Task.WhenAll(tasks); } - private static async Task SerializeObjectSafelyAsync(T obj, string path, CancellationToken ct) + private static async Task SerializeObjectSafelyAsync(T obj, string path, CancellationToken ct = default) { var tempFilePath = path + ".tmp"; await using (var tempFileStream = File.Create(tempFilePath)) diff --git a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs index 51cf647..0c49c24 100644 --- a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs @@ -62,7 +62,7 @@ public sealed partial class MemberUpdateService : BackgroundService } } - private async Task TickMemberDatasAsync(Snowflake guildId, CancellationToken ct) + private async Task TickMemberDatasAsync(Snowflake guildId, CancellationToken ct = default) { var guildData = await _guildData.GetData(guildId, ct); var defaultRole = GuildSettings.DefaultRole.Get(guildData.Settings); @@ -79,7 +79,7 @@ public sealed partial class MemberUpdateService : BackgroundService private async Task TickMemberDataAsync(Snowflake guildId, GuildData guildData, Snowflake defaultRole, MemberData data, - CancellationToken ct) + CancellationToken ct = default) { var failedResults = new List(); var id = data.Id.ToSnowflake(); @@ -144,7 +144,7 @@ public sealed partial class MemberUpdateService : BackgroundService } private async Task TryAutoUnbanAsync( - Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct) + Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct = default) { if (data.BannedUntil is null || DateTimeOffset.UtcNow <= data.BannedUntil) { @@ -169,7 +169,7 @@ public sealed partial class MemberUpdateService : BackgroundService } private async Task TryAutoUnmuteAsync( - Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct) + Snowflake guildId, Snowflake id, MemberData data, CancellationToken ct = default) { if (data.MutedUntil is null || DateTimeOffset.UtcNow <= data.MutedUntil) { @@ -188,7 +188,7 @@ public sealed partial class MemberUpdateService : BackgroundService } private async Task FilterNicknameAsync(Snowflake guildId, IUser user, IGuildMember member, - CancellationToken ct) + CancellationToken ct = default) { var currentNickname = member.Nickname.IsDefined(out var nickname) ? nickname @@ -226,7 +226,7 @@ public sealed partial class MemberUpdateService : BackgroundService private static partial Regex IllegalChars(); private async Task TickReminderAsync(Reminder reminder, IUser user, MemberData data, Snowflake guildId, - CancellationToken ct) + CancellationToken ct = default) { if (DateTimeOffset.UtcNow < reminder.At) { diff --git a/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs b/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs index ce9c212..ef145aa 100644 --- a/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/ScheduledEventUpdateService.cs @@ -46,7 +46,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService } } - private async Task TickScheduledEventsAsync(Snowflake guildId, CancellationToken ct) + private async Task TickScheduledEventsAsync(Snowflake guildId, CancellationToken ct = default) { var failedResults = new List(); var data = await _guildData.GetData(guildId, ct); @@ -133,7 +133,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService private async Task TickScheduledEventAsync( Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData, - CancellationToken ct) + CancellationToken ct = default) { if (GuildSettings.AutoStartEvents.Get(data.Settings) && DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime @@ -160,7 +160,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService } private async Task AutoStartEventAsync( - Snowflake guildId, IGuildScheduledEvent scheduledEvent, CancellationToken ct) + Snowflake guildId, IGuildScheduledEvent scheduledEvent, CancellationToken ct = default) { return (Result)await _eventApi.ModifyGuildScheduledEventAsync( guildId, scheduledEvent.ID, @@ -319,7 +319,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService } private async Task SendScheduledEventCompletedMessage(ScheduledEventData eventData, GuildData data, - CancellationToken ct) + CancellationToken ct = default) { if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { @@ -351,7 +351,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService } private async Task SendScheduledEventCancelledMessage(ScheduledEventData eventData, GuildData data, - CancellationToken ct) + CancellationToken ct = default) { if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { @@ -405,7 +405,7 @@ public sealed class ScheduledEventUpdateService : BackgroundService } private async Task SendEarlyEventNotificationAsync( - IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct) + IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default) { if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) { diff --git a/TeamOctolings.Octobot/Utility.cs b/TeamOctolings.Octobot/Utility.cs index 463212b..f337d93 100644 --- a/TeamOctolings.Octobot/Utility.cs +++ b/TeamOctolings.Octobot/Utility.cs @@ -125,7 +125,7 @@ public sealed class Utility } } - public async Task> GetEmergencyFeedbackChannel(IGuild guild, GuildData data, CancellationToken ct) + public async Task> GetEmergencyFeedbackChannel(IGuild guild, GuildData data, CancellationToken ct = default) { var privateFeedback = GuildSettings.PrivateFeedbackChannel.Get(data.Settings); if (!privateFeedback.Empty()) From 930b7ca6eda76a09883296d8657cf82a4e3e2d21 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 3 Jul 2024 22:09:39 +0500 Subject: [PATCH 41/44] Bump InspectCode from 1.11.10 to 1.11.12 (#323) I <3 breaking changes. --- .github/workflows/build-pr.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index c92386e..b2991db 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -22,8 +22,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.301' + - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.11.10 + uses: muno92/resharper_inspectcode@1.11.12 with: solutionPath: ./Octobot.sln ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor From 07e8784d2e10e0db5dd8b1645b95d674dfbaa1a6 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:12:32 +0500 Subject: [PATCH 42/44] Redesign reminder-related commands (#321) In this PR, I redesigned the reminder-related commands and they will now have a quote block instead of a inline code block (to avoid some visual bugs). Except /listremind. It just has the inline code block removed. ![image](https://github.com/TeamOctolings/Octobot/assets/95250141/3521af97-ee11-405f-8cc2-7bf9a747e757) --- TeamOctolings.Octobot/Commands/RemindCommandGroup.cs | 10 +++++----- .../Extensions/MarkdownExtensions.cs | 12 ++++++++++++ .../Services/Update/MemberUpdateService.cs | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs index be53ed7..3188d27 100644 --- a/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/RemindCommandGroup.cs @@ -94,7 +94,7 @@ public sealed class RemindCommandGroup : CommandGroup { var reminder = data.Reminders[i]; builder.AppendBulletPointLine(string.Format(Messages.ReminderPosition, Markdown.InlineCode((i + 1).ToString()))) - .AppendSubBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(reminder.Text))) + .AppendSubBulletPointLine(string.Format(Messages.ReminderText, reminder.Text)) .AppendSubBulletPointLine(string.Format(Messages.ReminderTime, Markdown.Timestamp(reminder.At))) .AppendSubBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); } @@ -182,7 +182,7 @@ public sealed class RemindCommandGroup : CommandGroup }); var builder = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(text))) + .AppendLine(MarkdownExtensions.Quote(text)) .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(remindAt))); var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.ReminderCreated, executor.GetTag()), executor) @@ -279,7 +279,7 @@ public sealed class RemindCommandGroup : CommandGroup data.Reminders.RemoveAt(index); var builder = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(oldReminder.Text))) + .AppendLine(MarkdownExtensions.Quote(oldReminder.Text)) .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(remindAt))); var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.ReminderEdited, executor.GetTag()), executor) @@ -309,7 +309,7 @@ public sealed class RemindCommandGroup : CommandGroup data.Reminders.RemoveAt(index); var builder = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(value))) + .AppendLine(MarkdownExtensions.Quote(value)) .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(oldReminder.At))); var embed = new EmbedBuilder().WithSmallTitle( string.Format(Messages.ReminderEdited, executor.GetTag()), executor) @@ -367,7 +367,7 @@ public sealed class RemindCommandGroup : CommandGroup var reminder = data.Reminders[index]; var description = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(reminder.Text))) + .AppendLine(MarkdownExtensions.Quote(reminder.Text)) .AppendBulletPointLine(string.Format(Messages.ReminderTime, Markdown.Timestamp(reminder.At))); data.Reminders.RemoveAt(index); diff --git a/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs b/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs index 202cd37..30ddff5 100644 --- a/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/MarkdownExtensions.cs @@ -13,4 +13,16 @@ public static class MarkdownExtensions { return $"- {text}"; } + + /// + /// Formats a string to use Markdown Quote formatting. + /// + /// The input text to format. + /// + /// A markdown-formatted quote string. + /// + public static string Quote(string text) + { + return $"> {text}"; + } } diff --git a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs index 0c49c24..3170060 100644 --- a/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs +++ b/TeamOctolings.Octobot/Services/Update/MemberUpdateService.cs @@ -234,7 +234,7 @@ public sealed partial class MemberUpdateService : BackgroundService } var builder = new StringBuilder() - .AppendBulletPointLine(string.Format(Messages.DescriptionReminder, Markdown.InlineCode(reminder.Text))) + .AppendLine(MarkdownExtensions.Quote(reminder.Text)) .AppendBulletPointLine(string.Format(Messages.DescriptionActionJumpToMessage, $"https://discord.com/channels/{guildId.Value}/{reminder.ChannelId}/{reminder.MessageId}")); From d6d2660fb02b8eb3979f2acc16869d037c7d8b04 Mon Sep 17 00:00:00 2001 From: Macintxsh <95250141+mctaylors@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:13:29 +0300 Subject: [PATCH 43/44] Show an error when entering the same value from the settings (#326) Closes #324 --- .../Commands/SettingsCommandGroup.cs | 21 +++++++++++++++++++ .../Data/Options/BoolOption.cs | 10 +++++++++ .../Data/Options/GuildOption.cs | 12 ++++++++++- .../Data/Options/IGuildOption.cs | 1 + .../Data/Options/LanguageOption.cs | 5 ++--- .../Data/Options/TimeSpanOption.cs | 10 +++++++++ TeamOctolings.Octobot/Messages.Designer.cs | 6 ++++++ TeamOctolings.Octobot/Messages.resx | 3 +++ TeamOctolings.Octobot/Messages.ru.resx | 3 +++ 9 files changed, 67 insertions(+), 4 deletions(-) diff --git a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs index 0acaa88..15aa42b 100644 --- a/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs +++ b/TeamOctolings.Octobot/Commands/SettingsCommandGroup.cs @@ -202,6 +202,27 @@ public sealed class SettingsCommandGroup : CommandGroup IGuildOption option, string value, GuildData data, Snowflake channelId, IUser executor, IUser bot, CancellationToken ct = default) { + var equalsResult = option.ValueEquals(data.Settings, value); + if (!equalsResult.IsSuccess) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, bot) + .WithDescription(equalsResult.Error.Message) + .WithColour(ColorsList.Red) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + if (equalsResult.Entity) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, bot) + .WithDescription(Messages.SettingValueEquals) + .WithColour(ColorsList.Red) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + var setResult = option.Set(data.Settings, value); if (!setResult.IsSuccess) { diff --git a/TeamOctolings.Octobot/Data/Options/BoolOption.cs b/TeamOctolings.Octobot/Data/Options/BoolOption.cs index 6a3c899..3b81abb 100644 --- a/TeamOctolings.Octobot/Data/Options/BoolOption.cs +++ b/TeamOctolings.Octobot/Data/Options/BoolOption.cs @@ -12,6 +12,16 @@ public sealed class BoolOption : GuildOption return Get(settings) ? Messages.Yes : Messages.No; } + public override Result ValueEquals(JsonNode settings, string value) + { + if (!TryParseBool(value, out var boolean)) + { + return new ArgumentInvalidError(nameof(value), Messages.InvalidSettingValue); + } + + return Value(settings).Equals(boolean.ToString()); + } + public override Result Set(JsonNode settings, string from) { if (!TryParseBool(from, out var value)) diff --git a/TeamOctolings.Octobot/Data/Options/GuildOption.cs b/TeamOctolings.Octobot/Data/Options/GuildOption.cs index 5d9f1a2..ea9c30e 100644 --- a/TeamOctolings.Octobot/Data/Options/GuildOption.cs +++ b/TeamOctolings.Octobot/Data/Options/GuildOption.cs @@ -21,9 +21,19 @@ public class GuildOption : IGuildOption public string Name { get; } + protected virtual string Value(JsonNode settings) + { + return Get(settings).ToString() ?? throw new InvalidOperationException(); + } + public virtual string Display(JsonNode settings) { - return Markdown.InlineCode(Get(settings).ToString() ?? throw new InvalidOperationException()); + return Markdown.InlineCode(Value(settings)); + } + + public virtual Result ValueEquals(JsonNode settings, string value) + { + return Value(settings).Equals(value); } /// diff --git a/TeamOctolings.Octobot/Data/Options/IGuildOption.cs b/TeamOctolings.Octobot/Data/Options/IGuildOption.cs index a8c3e6e..9920281 100644 --- a/TeamOctolings.Octobot/Data/Options/IGuildOption.cs +++ b/TeamOctolings.Octobot/Data/Options/IGuildOption.cs @@ -7,6 +7,7 @@ public interface IGuildOption { string Name { get; } string Display(JsonNode settings); + Result ValueEquals(JsonNode settings, string value); Result Set(JsonNode settings, string from); Result Reset(JsonNode settings); } diff --git a/TeamOctolings.Octobot/Data/Options/LanguageOption.cs b/TeamOctolings.Octobot/Data/Options/LanguageOption.cs index 15ab6ff..f58e011 100644 --- a/TeamOctolings.Octobot/Data/Options/LanguageOption.cs +++ b/TeamOctolings.Octobot/Data/Options/LanguageOption.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Text.Json.Nodes; -using Remora.Discord.Extensions.Formatting; using Remora.Results; namespace TeamOctolings.Octobot.Data.Options; @@ -16,9 +15,9 @@ public sealed class LanguageOption : GuildOption public LanguageOption(string name, string defaultValue) : base(name, CultureInfoCache[defaultValue]) { } - public override string Display(JsonNode settings) + protected override string Value(JsonNode settings) { - return Markdown.InlineCode(settings[Name]?.GetValue() ?? "en"); + return settings[Name]?.GetValue() ?? "en"; } /// diff --git a/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs b/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs index 3501f09..7e21343 100644 --- a/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs +++ b/TeamOctolings.Octobot/Data/Options/TimeSpanOption.cs @@ -8,6 +8,16 @@ public sealed class TimeSpanOption : GuildOption { public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { } + public override Result ValueEquals(JsonNode settings, string value) + { + if (!TimeSpanParser.TryParse(value).IsDefined(out var span)) + { + return new ArgumentInvalidError(nameof(value), Messages.InvalidSettingValue); + } + + return Value(settings).Equals(span.ToString()); + } + public override TimeSpan Get(JsonNode settings) { var property = settings[Name]; diff --git a/TeamOctolings.Octobot/Messages.Designer.cs b/TeamOctolings.Octobot/Messages.Designer.cs index bbc1366..ce59f1e 100644 --- a/TeamOctolings.Octobot/Messages.Designer.cs +++ b/TeamOctolings.Octobot/Messages.Designer.cs @@ -1196,5 +1196,11 @@ namespace TeamOctolings.Octobot { return ResourceManager.GetString("SettingsModeratorRole", resourceCulture); } } + + internal static string SettingValueEquals { + get { + return ResourceManager.GetString("SettingValueEquals", resourceCulture); + } + } } } diff --git a/TeamOctolings.Octobot/Messages.resx b/TeamOctolings.Octobot/Messages.resx index 47e7d4f..059584a 100644 --- a/TeamOctolings.Octobot/Messages.resx +++ b/TeamOctolings.Octobot/Messages.resx @@ -681,4 +681,7 @@ Moderator role + + The setting value is the same as the input value. + diff --git a/TeamOctolings.Octobot/Messages.ru.resx b/TeamOctolings.Octobot/Messages.ru.resx index 2eef257..fc8a594 100644 --- a/TeamOctolings.Octobot/Messages.ru.resx +++ b/TeamOctolings.Octobot/Messages.ru.resx @@ -681,4 +681,7 @@ Роль модератора + + Значение настройки такое же, как и вводное значение. + From e457b4609ed7630ac8920cea90c9e1282b4914ca Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 31 Jul 2024 23:57:21 +0500 Subject: [PATCH 44/44] Don't log stack traces for cancelled operations (#327) This PR fixes an issue where the `LogResultStackTrace` method would log stack traces for results that encountered an error due to a cancelled operation/task. The `LoggerExtensions` class already skipped `TaskCanceledException`s, but didn't skip `OperationCanceledException`s (which is a parent of `TaskCanceledException`). The patch specifically does not affect *inner* results which are canceled. Skipping logging these could hide the true cause of an error which appears important --- TeamOctolings.Octobot/Extensions/LoggerExtensions.cs | 2 +- TeamOctolings.Octobot/Extensions/ResultExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs b/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs index 76fa386..fac4dda 100644 --- a/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/LoggerExtensions.cs @@ -25,7 +25,7 @@ public static class LoggerExtensions if (result.Error is ExceptionError exe) { - if (exe.Exception is TaskCanceledException) + if (exe.Exception is OperationCanceledException) { return; } diff --git a/TeamOctolings.Octobot/Extensions/ResultExtensions.cs b/TeamOctolings.Octobot/Extensions/ResultExtensions.cs index d7ef7d7..6872d34 100644 --- a/TeamOctolings.Octobot/Extensions/ResultExtensions.cs +++ b/TeamOctolings.Octobot/Extensions/ResultExtensions.cs @@ -23,7 +23,7 @@ public static class ResultExtensions private static void LogResultStackTrace(Result result) { - if (result.IsSuccess) + if (result.IsSuccess || result.Error is ExceptionError { Exception: OperationCanceledException }) { return; }