From cc148d5257449c73477825235cecf37fcb6e5d8d Mon Sep 17 00:00:00 2001 From: Noi Date: Sat, 8 Jun 2024 21:07:35 -0700 Subject: [PATCH] Implement style suggestions --- Common/EntityList.cs | 3 ++- Common/RateLimit.cs | 2 +- Common/Utilities.cs | 25 +++++++++++-------- Configuration.cs | 5 ++-- ModuleLoadException.cs | 7 +----- ModuleLoader.cs | 2 +- Modules/AutoResponder/AutoResponder.cs | 2 +- Modules/EntryRole/EntryRole.cs | 2 +- Modules/EntryRole/GuildData.cs | 2 +- Modules/ModCommands/ModuleConfig.cs | 4 +-- Modules/RegexModerator/ResponseExecutor.cs | 2 +- Modules/VoiceRoleSync/ModuleConfig.cs | 4 +-- RegexbotModule.cs | 12 +++------ .../CommonFunctions/CommonFunctionsService.cs | 3 +-- Services/EntityCache/UserCachingSubservice.cs | 13 +++++----- Services/Logging/LoggingService.cs | 2 +- Services/ModuleState/ModuleStateService.cs | 19 ++++++++------ Services/Service.cs | 6 ++--- 18 files changed, 54 insertions(+), 61 deletions(-) diff --git a/Common/EntityList.cs b/Common/EntityList.cs index 4755a6f..c8a0f02 100644 --- a/Common/EntityList.cs +++ b/Common/EntityList.cs @@ -1,11 +1,12 @@ using System.Collections; +using System.Collections.ObjectModel; namespace RegexBot.Common; /// /// Represents a commonly-used configuration structure: an array of strings consisting of values. /// public class EntityList : IEnumerable { - private readonly IReadOnlyCollection _innerList; + private readonly ReadOnlyCollection _innerList; /// Gets an enumerable collection of all role names defined in this list. public IEnumerable Roles diff --git a/Common/RateLimit.cs b/Common/RateLimit.cs index 72377d8..2ae398e 100644 --- a/Common/RateLimit.cs +++ b/Common/RateLimit.cs @@ -11,7 +11,7 @@ public class RateLimit where T : notnull { /// Time until an entry within this instance expires, in seconds. /// public int Timeout { get; } - private Dictionary Entries { get; } = new Dictionary(); + private Dictionary Entries { get; } = []; /// /// Creates a new instance with the default timeout value. diff --git a/Common/Utilities.cs b/Common/Utilities.cs index f3e96cb..474743d 100644 --- a/Common/Utilities.cs +++ b/Common/Utilities.cs @@ -1,7 +1,6 @@ using Discord; using RegexBot.Data; using System.Diagnostics.CodeAnalysis; -using System.Net; using System.Text; using System.Text.RegularExpressions; @@ -9,26 +8,30 @@ namespace RegexBot.Common; /// /// Miscellaneous utility methods useful for the bot and modules. /// -public static class Utilities { +public static partial class Utilities { /// - /// Gets a compiled regex that matches a channel tag and pulls its snowflake value. + /// Gets a precompiled regex that matches a channel tag and pulls its snowflake value. /// - public static Regex ChannelMention { get; } = new(@"<#(?\d+)>", RegexOptions.Compiled); + [GeneratedRegex(@"<#(?\d+)>")] + public static partial Regex ChannelMentionRegex(); /// - /// Gets a compiled regex that matches a custom emoji and pulls its name and ID. + /// Gets a precompiled regex that matches a custom emoji and pulls its name and ID. /// - public static Regex CustomEmoji { get; } = new(@"<:(?[A-Za-z0-9_]{2,}):(?\d+)>", RegexOptions.Compiled); + [GeneratedRegex(@"<:(?[A-Za-z0-9_]{2,}):(?\d+)>")] + public static partial Regex CustomEmojiRegex(); /// - /// Gets a compiled regex that matches a fully formed Discord handle, extracting the name and discriminator. + /// Gets a precompiled regex that matches a fully formed Discord handle, extracting the name and discriminator. /// - public static Regex DiscriminatorSearch { get; } = new(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled); + [GeneratedRegex(@"(.+)#(\d{4}(?!\d))")] + public static partial Regex DiscriminatorSearchRegex(); /// - /// Gets a compiled regex that matches a user tag and pulls its snowflake value. + /// Gets a precompiled regex that matches a user tag and pulls its snowflake value. /// - public static Regex UserMention { get; } = new(@"<@!?(?\d+)>", RegexOptions.Compiled); + [GeneratedRegex(@"<@!?(?\d+)>")] + public static partial Regex UserMentionRegex(); /// /// Performs common checks on the specified message to see if it fits all the criteria of a @@ -77,7 +80,7 @@ public static class Utilities { /// If given string is in an EntityName format, returns a displayable representation of it based on /// a cache query. Otherwise, returns the input string as-is. /// - [return: NotNullIfNotNull("input")] + [return: NotNullIfNotNull(nameof(input))] public static string? TryFromEntityNameString(string? input, RegexbotClient bot) { string? result = null; try { diff --git a/Configuration.cs b/Configuration.cs index eef0dbe..75dfbd1 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -51,9 +51,8 @@ class Configuration { throw new Exception($"'{nameof(Assemblies)}' is not properly specified in configuration."); } - var dbconf = conf["DatabaseOptions"]?.Value(); - if (dbconf == null) throw new Exception("Database settings were not specified in configuration."); - // TODO more detailed database configuration? password file, other advanced authentication settings... look into this. + var dbconf = (conf["DatabaseOptions"]?.Value()) + ?? throw new Exception("Database settings were not specified in configuration."); Host = ReadConfKey(dbconf, nameof(Host), false); Database = ReadConfKey(dbconf, nameof(Database), false); Username = ReadConfKey(dbconf, nameof(Username), true); diff --git a/ModuleLoadException.cs b/ModuleLoadException.cs index 41f6bef..0f859de 100644 --- a/ModuleLoadException.cs +++ b/ModuleLoadException.cs @@ -4,9 +4,4 @@ /// Represents an error occurring when a module attempts to create a new guild state object /// (that is, read or refresh its configuration). /// -public class ModuleLoadException : Exception { - /// - /// Initializes this exception class with the specified error message. - /// - public ModuleLoadException(string message) : base(message) { } -} +public class ModuleLoadException(string message) : Exception(message) { } diff --git a/ModuleLoader.cs b/ModuleLoader.cs index 9bc9e34..a200982 100644 --- a/ModuleLoader.cs +++ b/ModuleLoader.cs @@ -38,7 +38,7 @@ static class ModuleLoader { return modules.AsReadOnly(); } - static IEnumerable LoadModulesFromAssembly(Assembly asm, RegexbotClient rb) { + static List LoadModulesFromAssembly(Assembly asm, RegexbotClient rb) { var eligibleTypes = from type in asm.GetTypes() where !type.IsAssignableFrom(typeof(RegexbotModule)) where type.GetCustomAttribute() != null diff --git a/Modules/AutoResponder/AutoResponder.cs b/Modules/AutoResponder/AutoResponder.cs index 64b5d70..6a3021e 100644 --- a/Modules/AutoResponder/AutoResponder.cs +++ b/Modules/AutoResponder/AutoResponder.cs @@ -45,7 +45,7 @@ internal class AutoResponder : RegexbotModule { if (def.Command == null) { await msg.Channel.SendMessageAsync(def.GetResponse()); } else { - var cmdline = def.Command.Split(new char[] { ' ' }, 2); + var cmdline = def.Command.Split([' '], 2); var ps = new ProcessStartInfo() { FileName = cmdline[0], diff --git a/Modules/EntryRole/EntryRole.cs b/Modules/EntryRole/EntryRole.cs index 7c2815f..7b12489 100644 --- a/Modules/EntryRole/EntryRole.cs +++ b/Modules/EntryRole/EntryRole.cs @@ -83,7 +83,7 @@ internal sealed class EntryRole : RegexbotModule, IDisposable { foreach (var g in DiscordClient.Guilds) { subworkers.Add(RoleApplyGuildSubWorker(g)); } - Task.WaitAll(subworkers.ToArray()); + Task.WaitAll([.. subworkers]); } } diff --git a/Modules/EntryRole/GuildData.cs b/Modules/EntryRole/GuildData.cs index b87c8ba..0000951 100644 --- a/Modules/EntryRole/GuildData.cs +++ b/Modules/EntryRole/GuildData.cs @@ -21,7 +21,7 @@ class GuildData { const int WaitTimeMax = 600; // 10 minutes - public GuildData(JObject conf) : this(conf, new Dictionary()) { } + public GuildData(JObject conf) : this(conf, []) { } public GuildData(JObject conf, Dictionary _waitingList) { WaitingList = _waitingList; diff --git a/Modules/ModCommands/ModuleConfig.cs b/Modules/ModCommands/ModuleConfig.cs index 9b7418e..bc71d4b 100644 --- a/Modules/ModCommands/ModuleConfig.cs +++ b/Modules/ModCommands/ModuleConfig.cs @@ -17,9 +17,9 @@ class ModuleConfig { Label = def[nameof(Label)]?.Value() ?? throw new ModuleLoadException($"'{nameof(Label)}' was not defined in a command definition."); var cmd = CreateCommandInstance(instance, def); - if (commands.ContainsKey(cmd.Command)) { + if (commands.TryGetValue(cmd.Command, out CommandConfig? existing)) { throw new ModuleLoadException( - $"{Label}: The command name '{cmd.Command}' is already in use by '{commands[cmd.Command].Label}'."); + $"{Label}: The command name '{cmd.Command}' is already in use by '{existing.Label}'."); } commands.Add(cmd.Command, cmd); } diff --git a/Modules/RegexModerator/ResponseExecutor.cs b/Modules/RegexModerator/ResponseExecutor.cs index 33ee347..e4500f0 100644 --- a/Modules/RegexModerator/ResponseExecutor.cs +++ b/Modules/RegexModerator/ResponseExecutor.cs @@ -33,7 +33,7 @@ class ResponseExecutor { _user = (SocketGuildUser)msg.Author; _guild = _user.Guild; - _reports = new(); + _reports = []; Log = logger; } diff --git a/Modules/VoiceRoleSync/ModuleConfig.cs b/Modules/VoiceRoleSync/ModuleConfig.cs index b959b27..7c4cb40 100644 --- a/Modules/VoiceRoleSync/ModuleConfig.cs +++ b/Modules/VoiceRoleSync/ModuleConfig.cs @@ -23,9 +23,7 @@ class ModuleConfig { } catch (FormatException) { throw new ModuleLoadException($"'{item.Name}' is not specified as a role."); } - var role = name.FindRoleIn(g); - if (role == null) throw new ModuleLoadException($"Unable to find role '{name}'."); - + var role = name.FindRoleIn(g) ?? throw new ModuleLoadException($"Unable to find role '{name}'."); var channels = Utilities.LoadStringOrStringArray(item.Value); if (channels.Count == 0) throw new ModuleLoadException($"One or more channels must be defined under '{name}'."); foreach (var id in channels) { diff --git a/RegexbotModule.cs b/RegexbotModule.cs index ca84ada..ca95e89 100644 --- a/RegexbotModule.cs +++ b/RegexbotModule.cs @@ -10,24 +10,20 @@ namespace RegexBot; /// /// Implementing classes should not rely on local variables to store runtime or state data for guilds. /// Instead, use and . +///

+/// Additionally, do not assume that is available during the constructor. ///
-public abstract class RegexbotModule { +public abstract class RegexbotModule(RegexbotClient bot) { /// /// Retrieves the bot instance. /// - public RegexbotClient Bot { get; } + public RegexbotClient Bot { get; } = bot; /// /// Retrieves the Discord client instance. /// public DiscordSocketClient DiscordClient { get => Bot.DiscordClient; } - /// - /// Called when a module is being loaded. - /// At this point, all bot services are available, but Discord is not. Do not use . - /// - public RegexbotModule(RegexbotClient bot) => Bot = bot; - /// /// Gets the name of this module. /// diff --git a/Services/CommonFunctions/CommonFunctionsService.cs b/Services/CommonFunctions/CommonFunctionsService.cs index 5b33f8a..6356c85 100644 --- a/Services/CommonFunctions/CommonFunctionsService.cs +++ b/Services/CommonFunctions/CommonFunctionsService.cs @@ -6,10 +6,9 @@ namespace RegexBot.Services.CommonFunctions { /// functions may help enforce a sense of consistency across modules when performing common actions, and may /// inform services which provide any additional features the ability to respond to those actions ahead of time. /// - internal partial class CommonFunctionsService : Service { + internal partial class CommonFunctionsService(RegexbotClient bot) : Service(bot) { // Note: Several classes within this service created by its hooks are meant to be sent to modules, // therefore those public classes are placed into the root RegexBot namespace for the developer's convenience. - public CommonFunctionsService(RegexbotClient bot) : base(bot) { } } } diff --git a/Services/EntityCache/UserCachingSubservice.cs b/Services/EntityCache/UserCachingSubservice.cs index e05360b..4f8b63f 100644 --- a/Services/EntityCache/UserCachingSubservice.cs +++ b/Services/EntityCache/UserCachingSubservice.cs @@ -86,7 +86,7 @@ class UserCachingSubservice { if (sID.HasValue) query = query.Where(c => c.UserId == (long)sID.Value); if (nameSearch != null) { - query = query.Where(c => c.Username.ToLower() == nameSearch.Value.name.ToLower()); + query = query.Where(c => c.Username.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase)); if (nameSearch.Value.disc != null) query = query.Where(c => c.Discriminator == nameSearch.Value.disc); } query = query.OrderByDescending(e => e.ULastUpdateTime); @@ -95,7 +95,7 @@ class UserCachingSubservice { } // Is search actually a ping? Extract ID. - var m = Utilities.UserMention.Match(search); + var m = Utilities.UserMentionRegex().Match(search); if (m.Success) search = m.Groups["snowflake"].Value; // Is search a number? Assume ID, proceed to query. @@ -117,8 +117,9 @@ class UserCachingSubservice { if (sID.HasValue) query = query.Where(c => c.UserId == (long)sID.Value); if (nameSearch != null) { - query = query.Where(c => (c.Nickname != null && c.Nickname.ToLower() == nameSearch.Value.name.ToLower()) || - c.User.Username.ToLower() == nameSearch.Value.name.ToLower()); + query = query.Where(c => (c.Nickname != null + && c.Nickname.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase)) + || c.User.Username.Equals(nameSearch.Value.name, StringComparison.OrdinalIgnoreCase)); if (nameSearch.Value.disc != null) query = query.Where(c => c.User.Discriminator == nameSearch.Value.disc); } query = query.OrderByDescending(e => e.GULastUpdateTime); @@ -127,7 +128,7 @@ class UserCachingSubservice { } // Is search actually a ping? Extract ID. - var m = Utilities.UserMention.Match(search); + var m = Utilities.UserMentionRegex().Match(search); if (m.Success) search = m.Groups["snowflake"].Value; // Is search a number? Assume ID, proceed to query. @@ -144,7 +145,7 @@ class UserCachingSubservice { private static (string, string?) SplitNameAndDiscriminator(string input) { string name; string? disc = null; - var split = Utilities.DiscriminatorSearch.Match(input); + var split = Utilities.DiscriminatorSearchRegex().Match(input); if (split.Success) { name = split.Groups[1].Value; disc = split.Groups[2].Value; diff --git a/Services/Logging/LoggingService.cs b/Services/Logging/LoggingService.cs index d3744e0..f9fbdc4 100644 --- a/Services/Logging/LoggingService.cs +++ b/Services/Logging/LoggingService.cs @@ -52,7 +52,7 @@ class LoggingService : Service { var now = DateTimeOffset.Now; var output = new StringBuilder(); var prefix = $"[{now:s}] [{source}] "; - foreach (var line in message.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None)) { + foreach (var line in message.Split(["\r\n", "\n"], StringSplitOptions.None)) { output.Append(prefix).AppendLine(line); } var outstr = output.ToString(); diff --git a/Services/ModuleState/ModuleStateService.cs b/Services/ModuleState/ModuleStateService.cs index 93623e7..ceed686 100644 --- a/Services/ModuleState/ModuleStateService.cs +++ b/Services/ModuleState/ModuleStateService.cs @@ -6,12 +6,12 @@ namespace RegexBot.Services.ModuleState; /// class ModuleStateService : Service { private readonly Dictionary _moderators; - private readonly Dictionary> _stateData; + private readonly Dictionary> _guildStates; private readonly JObject _serverConfs; public ModuleStateService(RegexbotClient bot, JObject servers) : base(bot) { - _moderators = new(); - _stateData = new(); + _moderators = []; + _guildStates = []; _serverConfs = servers; bot.DiscordClient.GuildAvailable += RefreshGuildState; @@ -25,17 +25,20 @@ class ModuleStateService : Service { } private Task RemoveGuildData(SocketGuild arg) { - _stateData.Remove(arg.Id); + _guildStates.Remove(arg.Id); _moderators.Remove(arg.Id); return Task.CompletedTask; } // Hooked public T? DoGetStateObj(ulong guildId, Type t) { - if (_stateData.ContainsKey(guildId) && _stateData[guildId].ContainsKey(t)) { - // Leave handling of potential InvalidCastException to caller. - return (T?)_stateData[guildId][t]; + if (_guildStates.TryGetValue(guildId, out var guildConfs)) { + if (guildConfs.TryGetValue(t, out var moduleConf)) { + // Leave handling of potential InvalidCastException to caller. + return (T?)moduleConf; + } } + return default; } @@ -71,7 +74,7 @@ class ModuleStateService : Service { } } _moderators[guild.Id] = mods; - _stateData[guild.Id] = newStates; + _guildStates[guild.Id] = newStates; return true; } } diff --git a/Services/Service.cs b/Services/Service.cs index cdf0b2d..a504286 100644 --- a/Services/Service.cs +++ b/Services/Service.cs @@ -6,13 +6,11 @@ /// Services provide core and shared functionality for this program. Modules are expected to call into services /// directly or indirectly in order to access bot features. /// -internal abstract class Service { - public RegexbotClient BotClient { get; } +internal abstract class Service(RegexbotClient bot) { + public RegexbotClient BotClient { get; } = bot; public string Name => GetType().Name; - public Service(RegexbotClient bot) => BotClient = bot; - /// /// Emits a log message. ///