From dcb9bfdd9ab8dd26498b61041ba47f356a2ceff2 Mon Sep 17 00:00:00 2001 From: Noi Date: Wed, 13 Oct 2021 20:38:52 -0700 Subject: [PATCH] Apply suggestions from code analysis --- BackgroundServices/BirthdayRoleUpdate.cs | 22 +++--- BackgroundServices/DataRetention.cs | 15 ++-- .../ExternalStatisticsReporting.cs | 2 +- .../SelectiveAutoUserDownload.cs | 12 ++-- Common.cs | 6 +- Configuration.cs | 2 +- ShardInstance.cs | 8 +-- ShardManager.cs | 3 +- UserInterface/CommandDocumentation.cs | 4 +- UserInterface/CommandsCommon.cs | 8 +-- UserInterface/HelpInfoCommands.cs | 2 +- UserInterface/ListingCommands.cs | 14 ++-- UserInterface/ManagerCommands.cs | 10 +-- UserInterface/UserCommands.cs | 69 ++++++------------- 14 files changed, 74 insertions(+), 103 deletions(-) diff --git a/BackgroundServices/BirthdayRoleUpdate.cs b/BackgroundServices/BirthdayRoleUpdate.cs index e9bfcf1..9e2d889 100644 --- a/BackgroundServices/BirthdayRoleUpdate.cs +++ b/BackgroundServices/BirthdayRoleUpdate.cs @@ -19,8 +19,7 @@ namespace BirthdayBot.BackgroundServices public BirthdayRoleUpdate(ShardInstance instance) : base(instance) { } /// - /// Processes birthday updates for all available guilds synchronously - /// (to avoid database connection pool bottlenecks and rate limiting). + /// Processes birthday updates for all available guilds synchronously. /// public override async Task OnTick(CancellationToken token) { @@ -41,25 +40,24 @@ namespace BirthdayBot.BackgroundServices } catch (Exception ex) { - // Catch all exceptions per-guild but continue processing, throw at end + // Catch all exceptions per-guild but continue processing, throw at end. exs.Add(ex); } } if (exs.Count != 0) throw new AggregateException(exs); - - // TODO metrics for role sets, unsets, announcements - and how to do that for singles too? } /// /// Access to for the testing command. /// /// Diagnostic data in string form. - public async Task SingleProcessGuildAsync(SocketGuild guild) => (await ProcessGuildAsync(guild).ConfigureAwait(false)).Export(); + public static async Task SingleProcessGuildAsync(SocketGuild guild) + => (await ProcessGuildAsync(guild).ConfigureAwait(false)).Export(); /// /// Main method where actual guild processing occurs. /// - private async Task ProcessGuildAsync(SocketGuild guild) + private static async Task ProcessGuildAsync(SocketGuild guild) { var diag = new PGDiagnostic(); @@ -100,7 +98,7 @@ namespace BirthdayBot.BackgroundServices var announceping = gc.AnnouncePing; SocketTextChannel channel = null; if (gc.AnnounceChannelId.HasValue) channel = guild.GetTextChannel(gc.AnnounceChannelId.Value); - if (announcementList.Count() != 0) + if (announcementList.Any()) { var announceResult = await AnnounceBirthdaysAsync(announce, announceping, channel, announcementList).ConfigureAwait(false); @@ -117,7 +115,7 @@ namespace BirthdayBot.BackgroundServices /// /// Checks if the bot may be allowed to alter roles. /// - private string CheckCorrectRoleSettings(SocketGuild guild, SocketRole role) + private static string CheckCorrectRoleSettings(SocketGuild guild, SocketRole role) { if (role == null) return "Designated role is not set, or target role cannot be found."; @@ -139,7 +137,7 @@ namespace BirthdayBot.BackgroundServices /// Gets all known users from the given guild and returns a list including only those who are /// currently experiencing a birthday in the respective time zone. /// - private HashSet GetGuildCurrentBirthdays(IEnumerable guildUsers, string defaultTzStr) + private static HashSet GetGuildCurrentBirthdays(IEnumerable guildUsers, string defaultTzStr) { var birthdayUsers = new HashSet(); @@ -184,7 +182,7 @@ namespace BirthdayBot.BackgroundServices /// First item: List of users who had the birthday role applied, used to announce. /// Second item: Counts of users who have had roles added/removed, used for operation reporting. /// - private async Task<(IEnumerable, (int, int))> UpdateGuildBirthdayRoles( + private static async Task<(IEnumerable, (int, int))> UpdateGuildBirthdayRoles( SocketGuild g, SocketRole r, HashSet names) { // Check members currently with the role. Figure out which users to remove it from. @@ -224,7 +222,7 @@ namespace BirthdayBot.BackgroundServices /// who have just had their birthday role added. /// /// The message to place into operation status log. - private async Task AnnounceBirthdaysAsync( + private static async Task AnnounceBirthdaysAsync( (string, string) announce, bool announcePing, SocketTextChannel c, IEnumerable names) { if (c == null) return "Announcement channel is not set, or previous announcement channel has been deleted."; diff --git a/BackgroundServices/DataRetention.cs b/BackgroundServices/DataRetention.cs index 6807f04..92f13f9 100644 --- a/BackgroundServices/DataRetention.cs +++ b/BackgroundServices/DataRetention.cs @@ -15,7 +15,7 @@ namespace BirthdayBot.BackgroundServices /// class DataRetention : BackgroundService { - private static readonly SemaphoreSlim _updateLock = new SemaphoreSlim(ShardManager.MaxConcurrentOperations); + private static readonly SemaphoreSlim _updateLock = new(ShardManager.MaxConcurrentOperations); const int ProcessInterval = 3600 / ShardBackgroundWorker.Interval; // Process about once per hour private int _tickCount = -1; @@ -33,10 +33,11 @@ namespace BirthdayBot.BackgroundServices try { // A semaphore is used to restrict this work being done concurrently on other shards - // to avoid putting pressure on the SQL connection pool. Updating this is a low priority. + // to avoid putting pressure on the SQL connection pool. Clearing old database information + // ultimately is a low priority among other tasks. await _updateLock.WaitAsync(token).ConfigureAwait(false); } - catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException) + catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException) { // Calling thread does not expect the exception that SemaphoreSlim throws... throw new TaskCanceledException(); @@ -81,13 +82,13 @@ namespace BirthdayBot.BackgroundServices var userlist = item.Value; pUpdateG.Value = (long)guild; - updatedGuilds += await cUpdateGuild.ExecuteNonQueryAsync().ConfigureAwait(false); + updatedGuilds += await cUpdateGuild.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); pUpdateGU_g.Value = (long)guild; foreach (var userid in userlist) { pUpdateGU_u.Value = (long)userid; - updatedUsers += await cUpdateGuildUser.ExecuteNonQueryAsync().ConfigureAwait(false); + updatedUsers += await cUpdateGuildUser.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); } } var resultText = new StringBuilder(); @@ -100,13 +101,13 @@ namespace BirthdayBot.BackgroundServices { // Delete data for guilds not seen in 4 weeks c.CommandText = $"delete from {GuildConfiguration.BackingTable} where (now() - interval '28 days') > last_seen"; - staleGuilds = await c.ExecuteNonQueryAsync().ConfigureAwait(false); + staleGuilds = await c.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); } using (var c = db.CreateCommand()) { // Delete data for users not seen in 8 weeks c.CommandText = $"delete from {GuildUserConfiguration.BackingTable} where (now() - interval '56 days') > last_seen"; - staleUsers = await c.ExecuteNonQueryAsync().ConfigureAwait(false); + staleUsers = await c.ExecuteNonQueryAsync(CancellationToken.None).ConfigureAwait(false); } if (staleGuilds != 0 || staleUsers != 0) { diff --git a/BackgroundServices/ExternalStatisticsReporting.cs b/BackgroundServices/ExternalStatisticsReporting.cs index 6e2fc0c..2c9007b 100644 --- a/BackgroundServices/ExternalStatisticsReporting.cs +++ b/BackgroundServices/ExternalStatisticsReporting.cs @@ -14,7 +14,7 @@ namespace BirthdayBot.BackgroundServices const int ProcessInterval = 600 / ShardBackgroundWorker.Interval; // Process every ~5 minutes private int _tickCount = 0; - private static readonly HttpClient _httpClient = new HttpClient(); + private static readonly HttpClient _httpClient = new(); public ExternalStatisticsReporting(ShardInstance instance) : base(instance) { } diff --git a/BackgroundServices/SelectiveAutoUserDownload.cs b/BackgroundServices/SelectiveAutoUserDownload.cs index 3ca2c4d..06de92a 100644 --- a/BackgroundServices/SelectiveAutoUserDownload.cs +++ b/BackgroundServices/SelectiveAutoUserDownload.cs @@ -14,9 +14,9 @@ namespace BirthdayBot.BackgroundServices /// class SelectiveAutoUserDownload : BackgroundService { - private static readonly SemaphoreSlim _updateLock = new SemaphoreSlim(2); + private static readonly SemaphoreSlim _updateLock = new(2); - private readonly HashSet _fetchRequests = new HashSet(); + private readonly HashSet _fetchRequests = new(); public SelectiveAutoUserDownload(ShardInstance instance) : base(instance) { } @@ -42,7 +42,7 @@ namespace BirthdayBot.BackgroundServices if (requests.Contains(guild.Id) || await GuildUserAnyAsync(guild.Id, token).ConfigureAwait(false)) { await guild.DownloadUsersAsync().ConfigureAwait(false); - await Task.Delay(500).ConfigureAwait(false); + await Task.Delay(500, CancellationToken.None).ConfigureAwait(false); } } } @@ -51,13 +51,13 @@ namespace BirthdayBot.BackgroundServices /// Determines if the user database contains any entries corresponding to this guild. /// /// True if any entries exist. - private async Task GuildUserAnyAsync(ulong guildId, CancellationToken token) + private static async Task GuildUserAnyAsync(ulong guildId, CancellationToken token) { try { await _updateLock.WaitAsync(token).ConfigureAwait(false); } - catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException) + catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException) { // Calling thread does not expect the exception that SemaphoreSlim throws... throw new TaskCanceledException(); @@ -68,7 +68,7 @@ namespace BirthdayBot.BackgroundServices using var c = db.CreateCommand(); c.CommandText = $"select count(*) from {GuildUserConfiguration.BackingTable} where guild_id = @Gid"; c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guildId; - await c.PrepareAsync().ConfigureAwait(false); + await c.PrepareAsync(CancellationToken.None).ConfigureAwait(false); var r = (long)await c.ExecuteScalarAsync(token).ConfigureAwait(false); return r != 0; } diff --git a/Common.cs b/Common.cs index 56d54d6..6a49ea0 100644 --- a/Common.cs +++ b/Common.cs @@ -14,12 +14,12 @@ namespace BirthdayBot { if (ping) return member.Mention; - string escapeFormattingCharacters(string input) + static string escapeFormattingCharacters(string input) { var result = new StringBuilder(); foreach (var c in input) { - if (c == '\\' || c == '_' || c == '~' || c == '*' || c == '@') + if (c is '\\' or '_' or '~' or '*' or '@') { result.Append('\\'); } @@ -36,7 +36,7 @@ namespace BirthdayBot return $"**{username}**#{member.Discriminator}"; } - public static readonly Dictionary MonthNames = new Dictionary() + public static readonly Dictionary MonthNames = new() { {1, "Jan"}, {2, "Feb"}, {3, "Mar"}, {4, "Apr"}, {5, "May"}, {6, "Jun"}, {7, "Jul"}, {8, "Aug"}, {9, "Sep"}, {10, "Oct"}, {11, "Nov"}, {12, "Dec"} diff --git a/Configuration.cs b/Configuration.cs index 01f2e4e..882ac46 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -86,7 +86,7 @@ namespace BirthdayBot string srVal = jc[ShardLenConfKey]?.Value(); if (!string.IsNullOrWhiteSpace(srVal)) { - Regex srPicker = new Regex(@"(?\d{1,2})[-,]{1}(?\d{1,2})"); + Regex srPicker = new(@"(?\d{1,2})[-,]{1}(?\d{1,2})"); var m = srPicker.Match(srVal); if (m.Success) { diff --git a/ShardInstance.cs b/ShardInstance.cs index ecafa1c..c70ad69 100644 --- a/ShardInstance.cs +++ b/ShardInstance.cs @@ -108,8 +108,8 @@ namespace BirthdayBot /// /// Direct access to invoke the background task of updating birthdays in a guild, for use by the testing command. /// - public Task ForceBirthdayUpdateAsync(SocketGuild guild) - => _background.BirthdayUpdater.SingleProcessGuildAsync(guild); + public static Task ForceBirthdayUpdateAsync(SocketGuild guild) + => BirthdayRoleUpdate.SingleProcessGuildAsync(guild); public void RequestDownloadUsers(ulong guildId) => _background.UserDownloader.RequestDownload(guildId); @@ -166,7 +166,7 @@ namespace BirthdayBot /// private async Task Client_MessageReceived(SocketMessage msg) { - if (!(msg.Channel is SocketTextChannel channel)) return; + if (msg.Channel is not SocketTextChannel channel) return; if (msg.Author.IsBot || msg.Author.IsWebhook) return; if (((IMessage)msg).Type != MessageType.Default) return; var author = (SocketGuildUser)msg.Author; @@ -178,7 +178,7 @@ namespace BirthdayBot if (csplit.Length > 0 && csplit[0].StartsWith(CommandPrefix, StringComparison.OrdinalIgnoreCase)) { // Determine if it's something we're listening for. - if (!_dispatchCommands.TryGetValue(csplit[0].Substring(CommandPrefix.Length), out CommandHandler command)) return; + if (!_dispatchCommands.TryGetValue(csplit[0][CommandPrefix.Length..], out CommandHandler command)) return; // Load guild information here var gconf = await GuildConfiguration.LoadAsync(channel.Guild.Id, false); diff --git a/ShardManager.cs b/ShardManager.cs index f3cc415..c5596f6 100644 --- a/ShardManager.cs +++ b/ShardManager.cs @@ -39,7 +39,7 @@ namespace BirthdayBot /// Amount of time without a completed background service run before a shard instance /// is considered "dead" and tasked to be removed. /// - private static readonly TimeSpan DeadShardThreshold = new TimeSpan(0, 20, 0); + private static readonly TimeSpan DeadShardThreshold = new(0, 20, 0); /// /// A dictionary with shard IDs as its keys and shard instances as its values. @@ -132,7 +132,6 @@ namespace BirthdayBot LogLevel = LogSeverity.Info, DefaultRetryMode = RetryMode.RetryRatelimit, MessageCacheSize = 0, // not needed at all - ExclusiveBulkDelete = true, // not relevant, but this is configured to skip the warning GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages }; var newClient = new DiscordSocketClient(clientConf); diff --git a/UserInterface/CommandDocumentation.cs b/UserInterface/CommandDocumentation.cs index d7dbb83..27ba3c7 100644 --- a/UserInterface/CommandDocumentation.cs +++ b/UserInterface/CommandDocumentation.cs @@ -15,9 +15,9 @@ namespace BirthdayBot.UserInterface { var cmds = new List(); foreach (var item in commands) cmds.Add(CommandsCommon.CommandPrefix + item); - if (cmds.Count == 0) throw new ArgumentException(nameof(commands)); + if (cmds.Count == 0) throw new ArgumentException(null, nameof(commands)); Commands = cmds.ToArray(); - Usage = usage ?? throw new ArgumentException(nameof(usage)); + Usage = usage ?? throw new ArgumentException(null, nameof(usage)); Examples = examples; } diff --git a/UserInterface/CommandsCommon.cs b/UserInterface/CommandsCommon.cs index 048e489..9bca783 100644 --- a/UserInterface/CommandsCommon.cs +++ b/UserInterface/CommandsCommon.cs @@ -59,7 +59,7 @@ namespace BirthdayBot.UserInterface /// /// Checks given time zone input. Returns a valid string for use with NodaTime. /// - protected string ParseTimeZone(string tzinput) + protected static string ParseTimeZone(string tzinput) { string tz = null; if (tzinput != null) @@ -80,16 +80,14 @@ namespace BirthdayBot.UserInterface /// Given user input where a user-like parameter is expected, attempts to resolve to an ID value. /// Input must be a mention or explicit ID. No name resolution is done here. /// - protected bool TryGetUserId(string input, out ulong result) + protected static bool TryGetUserId(string input, out ulong result) { string doParse; var m = UserMention.Match(input); if (m.Success) doParse = m.Groups[1].Value; else doParse = input; - ulong resultVal; - if (ulong.TryParse(doParse, out resultVal)) - { + if (ulong.TryParse(doParse, out ulong resultVal)) { result = resultVal; return true; } diff --git a/UserInterface/HelpInfoCommands.cs b/UserInterface/HelpInfoCommands.cs index f85f28b..d026927 100644 --- a/UserInterface/HelpInfoCommands.cs +++ b/UserInterface/HelpInfoCommands.cs @@ -33,7 +33,7 @@ namespace BirthdayBot.UserInterface ("invite", CmdInfo) }; - private (Embed, Embed) BuildHelpEmbeds() + private static (Embed, Embed) BuildHelpEmbeds() { var cpfx = $"●`{CommandPrefix}"; diff --git a/UserInterface/ListingCommands.cs b/UserInterface/ListingCommands.cs index 713abaf..0861569 100644 --- a/UserInterface/ListingCommands.cs +++ b/UserInterface/ListingCommands.cs @@ -27,12 +27,12 @@ namespace BirthdayBot.UserInterface #region Documentation public static readonly CommandDocumentation DocList = - new CommandDocumentation(new string[] { "list" }, "Exports all birthdays to a file." + new(new string[] { "list" }, "Exports all birthdays to a file." + " Accepts `csv` as an optional parameter.", null); public static readonly CommandDocumentation DocUpcoming = - new CommandDocumentation(new string[] { "recent", "upcoming" }, "Lists recent and upcoming birthdays.", null); + new(new string[] { "recent", "upcoming" }, "Lists recent and upcoming birthdays.", null); public static readonly CommandDocumentation DocWhen = - new CommandDocumentation(new string[] { "when" }, "Displays the given user's birthday information.", null); + new(new string[] { "when" }, "Displays the given user's birthday information.", null); #endregion private async Task CmdWhen(ShardInstance instance, GuildConfiguration gconf, @@ -202,7 +202,7 @@ namespace BirthdayBot.UserInterface search += 1; if (search > 366) search = 1; // wrap to beginning of year - if (results.Count() == 0) continue; // back out early + if (!results.Any()) continue; // back out early resultCount += results.Count(); // Build sorted name list @@ -245,7 +245,7 @@ namespace BirthdayBot.UserInterface /// Fetches all guild birthdays and places them into an easily usable structure. /// Users currently not in the guild are not included in the result. /// - private async Task> GetSortedUsersAsync(SocketGuild guild) + private static async Task> GetSortedUsersAsync(SocketGuild guild) { using var db = await Database.OpenConnectionAsync(); using var c = db.CreateCommand(); @@ -323,7 +323,7 @@ namespace BirthdayBot.UserInterface return result.ToString(); } - private string CsvEscape(string input) + private static string CsvEscape(string input) { var result = new StringBuilder(); result.Append('"'); @@ -336,7 +336,7 @@ namespace BirthdayBot.UserInterface return result.ToString(); } - private int DateIndex(int month, int day) + private static int DateIndex(int month, int day) { var dateindex = 0; // Add month offsets diff --git a/UserInterface/ManagerCommands.cs b/UserInterface/ManagerCommands.cs index e910e41..10d0175 100644 --- a/UserInterface/ManagerCommands.cs +++ b/UserInterface/ManagerCommands.cs @@ -48,7 +48,7 @@ namespace BirthdayBot.UserInterface #region Documentation public static readonly CommandDocumentation DocOverride = - new CommandDocumentation(new string[] { "override (user ping or ID) (command w/ parameters)" }, + new(new string[] { "override (user ping or ID) (command w/ parameters)" }, "Perform certain commands on behalf of another user.", null); #endregion @@ -426,7 +426,7 @@ namespace BirthdayBot.UserInterface if (cmdsearch.StartsWith(CommandPrefix)) { // Strip command prefix to search for the given command. - cmdsearch = cmdsearch.Substring(CommandPrefix.Length); + cmdsearch = cmdsearch[CommandPrefix.Length..]; } else { @@ -472,7 +472,7 @@ namespace BirthdayBot.UserInterface var guild = reqChannel.Guild; string result = $"\nServer ID: {guild.Id} | Bot shard ID: {instance.ShardId:00}"; result += $"\nLocally cached members: {guild.DownloadedMemberCount} out of {guild.MemberCount}"; - result += "\n" + await instance.ForceBirthdayUpdateAsync(guild).ConfigureAwait(false); + result += "\n" + await ShardInstance.ForceBirthdayUpdateAsync(guild).ConfigureAwait(false); await reqChannel.SendMessageAsync(result).ConfigureAwait(false); } catch (Exception ex) @@ -485,9 +485,9 @@ namespace BirthdayBot.UserInterface #region Common/helper methods private const string RoleInputError = ":x: Unable to determine the given role."; - private static readonly Regex RoleMention = new Regex(@"<@?&(?\d+)>", RegexOptions.Compiled); + private static readonly Regex RoleMention = new(@"<@?&(?\d+)>", RegexOptions.Compiled); - private SocketRole FindUserInputRole(string inputStr, SocketGuild guild) + private static SocketRole FindUserInputRole(string inputStr, SocketGuild guild) { // Resembles a role mention? Strip it to the pure number var input = inputStr; diff --git a/UserInterface/UserCommands.cs b/UserInterface/UserCommands.cs index 2e244cf..e3d62be 100644 --- a/UserInterface/UserCommands.cs +++ b/UserInterface/UserCommands.cs @@ -23,8 +23,8 @@ namespace BirthdayBot.UserInterface const string FormatError = ":x: Unrecognized date format. The following formats are accepted, as examples: " + "`15-jan`, `jan-15`, `15 jan`, `jan 15`, `15 January`, `January 15`."; - private static readonly Regex DateParse1 = new Regex(@"^(?\d{1,2})[ -](?[A-Za-z]+)$", RegexOptions.Compiled); - private static readonly Regex DateParse2 = new Regex(@"^(?[A-Za-z]+)[ -](?\d{1,2})$", RegexOptions.Compiled); + private static readonly Regex DateParse1 = new(@"^(?\d{1,2})[ -](?[A-Za-z]+)$", RegexOptions.Compiled); + private static readonly Regex DateParse2 = new(@"^(?[A-Za-z]+)[ -](?\d{1,2})$", RegexOptions.Compiled); /// /// Parses a date input. @@ -33,7 +33,7 @@ namespace BirthdayBot.UserInterface /// /// Thrown for any parsing issue. Reason is expected to be sent to Discord as-is. /// - private (int, int) ParseDate(string dateInput) + private static (int, int) ParseDate(string dateInput) { var m = DateParse1.Match(dateInput); if (!m.Success) @@ -71,60 +71,35 @@ namespace BirthdayBot.UserInterface /// /// Thrown on error. Send out to Discord as-is. /// - private (int, int) GetMonth(string input) + private static (int, int) GetMonth(string input) { - switch (input.ToLower()) - { - case "jan": - case "january": - return (1, 31); - case "feb": - case "february": - return (2, 29); - case "mar": - case "march": - return (3, 31); - case "apr": - case "april": - return (4, 30); - case "may": - return (5, 31); - case "jun": - case "june": - return (6, 30); - case "jul": - case "july": - return (7, 31); - case "aug": - case "august": - return (8, 31); - case "sep": - case "september": - return (9, 30); - case "oct": - case "october": - return (10, 31); - case "nov": - case "november": - return (11, 30); - case "dec": - case "december": - return (12, 31); - default: - throw new FormatException($":x: Can't determine month name `{input}`. Check your spelling and try again."); - } + return input.ToLower() switch { + "jan" or "january" => (1, 31), + "feb" or "february" => (2, 29), + "mar" or "march" => (3, 31), + "apr" or "april" => (4, 30), + "may" => (5, 31), + "jun" or "june" => (6, 30), + "jul" or "july" => (7, 31), + "aug" or "august" => (8, 31), + "sep" or "september" => (9, 30), + "oct" or "october" => (10, 31), + "nov" or "november" => (11, 30), + "dec" or "december" => (12, 31), + _ => throw new FormatException($":x: Can't determine month name `{input}`. Check your spelling and try again."), + }; } #endregion #region Documentation public static readonly CommandDocumentation DocSet = - new CommandDocumentation(new string[] { "set (date)" }, "Registers your birth month and day.", + new(new string[] { "set (date)" }, "Registers your birth month and day.", $"`{CommandPrefix}set jan-31`, `{CommandPrefix}set 15 may`."); public static readonly CommandDocumentation DocZone = - new CommandDocumentation(new string[] { "zone (zone)" }, "Sets your local time zone. " + new(new string[] { "zone (zone)" }, "Sets your local time zone. " + $"See also `{CommandPrefix}help-tzdata`.", null); public static readonly CommandDocumentation DocRemove = - new CommandDocumentation(new string[] { "remove" }, "Removes your birthday information from this bot.", null); + new(new string[] { "remove" }, "Removes your birthday information from this bot.", null); #endregion private async Task CmdSet(ShardInstance instance, GuildConfiguration gconf,