From 01b784bc858b8c183c9e455c6f52a91f3965157c Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 9 Aug 2022 17:48:14 -0700 Subject: [PATCH 01/11] Switch logging to local time --- Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Program.cs b/Program.cs index 678058c..d61ffe2 100644 --- a/Program.cs +++ b/Program.cs @@ -31,10 +31,10 @@ class Program { /// Sends a formatted message to console. /// public static void Log(string source, string message) { - var ts = DateTime.UtcNow; + var ts = DateTime.Now; var ls = new string[] { "\r\n", "\n" }; foreach (var item in message.Split(ls, StringSplitOptions.None)) - Console.WriteLine($"{ts:u} [{source}] {item}"); + Console.WriteLine($"{ts:s} [{source}] {item}"); } private static void OnCancelKeyPressed(object? sender, ConsoleCancelEventArgs e) { From 53b20db3eb661f56a6a66cd5340233c040d39b44 Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 9 Aug 2022 18:21:03 -0700 Subject: [PATCH 02/11] Clean up logging --- WorldTime.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/WorldTime.cs b/WorldTime.cs index 289e589..d4648ff 100644 --- a/WorldTime.cs +++ b/WorldTime.cs @@ -46,7 +46,9 @@ internal class WorldTime : IDisposable { LogLevel = LogSeverity.Info, DefaultRetryMode = RetryMode.RetryRatelimit, MessageCacheSize = 0, // disable message cache - GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages + GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages, + SuppressUnknownDispatchWarnings = true, + LogGatewayIntentWarnings = false }; _services = new ServiceCollection() .AddSingleton(new DiscordShardedClient(clientConf)) @@ -132,10 +134,7 @@ internal class WorldTime : IDisposable { private Task DiscordClient_Log(LogMessage arg) { // Suppress certain messages if (arg.Message != null) { - // These warnings appear often as of Discord.Net v3... - if (arg.Message.StartsWith("Unknown Dispatch ") || arg.Message.StartsWith("Unknown Channel")) return Task.CompletedTask; - switch (arg.Message) // Connection status messages replaced by ShardManager's output - { + switch (arg.Message) { // Connection status messages replaced by ShardManager's output case "Connecting": case "Connected": case "Ready": @@ -146,7 +145,6 @@ internal class WorldTime : IDisposable { case "Discord.WebSocket.GatewayReconnectException: Server requested a reconnect": return Task.CompletedTask; } - Program.Log("Discord.Net", $"{arg.Severity}: {arg.Message}"); } From b595e701c43924ecfeea1f94aa90feedfd67fb3d Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 9 Aug 2022 18:09:08 -0700 Subject: [PATCH 03/11] Load database config on initialization Adapted from RegexBot. This allows `dotnet ef` tools to make use of actual SQL credentials rather than dummy ones. --- Configuration.cs | 87 +++++++++++++++++--------------------- Data/BotDatabaseContext.cs | 29 ++++++------- Program.cs | 6 +-- WorldTime.cs | 5 ++- 4 files changed, 58 insertions(+), 69 deletions(-) diff --git a/Configuration.cs b/Configuration.cs index 571c1be..2568669 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -1,56 +1,52 @@ using CommandLine; -using CommandLine.Text; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Npgsql; using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace WorldTime; - /// /// Loads and holds configuration values. /// class Configuration { - const string KeySqlHost = "SqlHost"; - const string KeySqlUsername = "SqlUsername"; - const string KeySqlPassword = "SqlPassword"; - const string KeySqlDatabase = "SqlDatabase"; - - public string DbConnectionString { get; } public string BotToken { get; } public string? DBotsToken { get; } public int ShardTotal { get; } - public Configuration(string[] args) { - var cmdline = CmdLineOpts.Parse(args); + public string? SqlHost { get; } + public string? SqlDatabase { get; } + public string SqlUsername { get; } + public string SqlPassword { get; } + + public Configuration() { + var args = CommandLineParameters.Parse(Environment.GetCommandLineArgs()); + var path = args?.ConfigFile ?? Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) + + Path.DirectorySeparatorChar + "." + Path.DirectorySeparatorChar + "settings.json"; // Looks for configuration file - var confPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar; - confPath += cmdline.Config!; - if (!File.Exists(confPath)) throw new Exception("Settings file not found in path: " + confPath); + JObject jc; + try { + var conftxt = File.ReadAllText(path); + jc = JObject.Parse(conftxt); + } catch (Exception ex) { + string pfx; + if (ex is JsonException) pfx = "Unable to parse configuration: "; + else pfx = "Unable to access configuration: "; - var jc = JObject.Parse(File.ReadAllText(confPath)); + throw new Exception(pfx + ex.Message, ex); + } BotToken = ReadConfKey(jc, nameof(BotToken), true); DBotsToken = ReadConfKey(jc, nameof(DBotsToken), false); - ShardTotal = cmdline.ShardTotal ?? ReadConfKey(jc, nameof(ShardTotal), false) ?? 1; + ShardTotal = args.ShardTotal ?? ReadConfKey(jc, nameof(ShardTotal), false) ?? 1; if (ShardTotal < 1) throw new Exception($"'{nameof(ShardTotal)}' must be a positive integer."); - var sqlhost = ReadConfKey(jc, KeySqlHost, false) ?? "localhost"; // Default to localhost - var sqluser = ReadConfKey(jc, KeySqlUsername, false); - var sqlpass = ReadConfKey(jc, KeySqlPassword, false); - if (string.IsNullOrWhiteSpace(sqluser) || string.IsNullOrWhiteSpace(sqlpass)) - throw new Exception("'SqlUsername', 'SqlPassword' must be specified."); - var csb = new NpgsqlConnectionStringBuilder() { - Host = sqlhost, - Username = sqluser, - Password = sqlpass - }; - var sqldb = ReadConfKey(jc, KeySqlDatabase, false); - if (sqldb != null) csb.Database = sqldb; // Optional database setting - DbConnectionString = csb.ToString(); + SqlHost = ReadConfKey(jc, nameof(SqlHost), false); + SqlDatabase = ReadConfKey(jc, nameof(SqlDatabase), false); + SqlUsername = ReadConfKey(jc, nameof(SqlUsername), true); + SqlPassword = ReadConfKey(jc, nameof(SqlPassword), true); } private static T? ReadConfKey(JObject jc, string key, [DoesNotReturnIf(true)] bool failOnEmpty) { @@ -59,29 +55,24 @@ class Configuration { return default; } - private class CmdLineOpts { - [Option('c', "config", Default = "settings.json", - HelpText = "Custom path to instance configuration, relative from executable directory.")] - public string? Config { get; set; } + class CommandLineParameters { + [Option('c', "config")] + public string? ConfigFile { get; set; } - [Option("shardtotal", - HelpText = "Total number of shards online. MUST be the same for all instances.\n" - + "This value overrides the config file value.")] + [Option("shardtotal")] public int? ShardTotal { get; set; } - public static CmdLineOpts Parse(string[] args) { - // Do not automatically print help message - var clp = new Parser(c => c.HelpWriter = null); + public static CommandLineParameters? Parse(string[] args) { + CommandLineParameters? result = null; - CmdLineOpts? result = null; - var r = clp.ParseArguments(args); - r.WithParsed(parsed => result = parsed); - r.WithNotParsed(err => { - var ht = HelpText.AutoBuild(r); - Console.WriteLine(ht.ToString()); - Environment.Exit((int)Program.ExitCodes.BadCommand); - }); - return result!; + new Parser(settings => { + settings.IgnoreUnknownArguments = true; + settings.AutoHelp = false; + settings.AutoVersion = false; + }).ParseArguments(args) + .WithParsed(p => result = p) + .WithNotParsed(e => { /* ignore */ }); + return result; } } } diff --git a/Data/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index 03ac6d1..52c4298 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -1,29 +1,26 @@ using Microsoft.EntityFrameworkCore; +using Npgsql; namespace WorldTime.Data; public class BotDatabaseContext : DbContext { - private static string? _npgsqlConnectionString; - internal static string NpgsqlConnectionString { -#if DEBUG - get { - if (_npgsqlConnectionString != null) return _npgsqlConnectionString; - Program.Log(nameof(BotDatabaseContext), "Using hardcoded connection string!"); - return _npgsqlConnectionString ?? "Host=localhost;Username=worldtime;Password=wt"; - } -#else - get => _npgsqlConnectionString!; -#endif - set => _npgsqlConnectionString ??= value; + private static readonly string _connectionString; + + static BotDatabaseContext() { + // Get our own config loaded just for the SQL stuff + var conf = new Configuration(); + _connectionString = new NpgsqlConnectionStringBuilder() { + Host = conf.SqlHost ?? "localhost", // default to localhost + Database = conf.SqlDatabase, + Username = conf.SqlUsername, + Password = conf.SqlPassword + }.ToString(); } public DbSet UserEntries { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseNpgsql(NpgsqlConnectionString) -#if DEBUG - .LogTo((string line) => Program.Log("EF", line), Microsoft.Extensions.Logging.LogLevel.Information) -#endif + .UseNpgsql(_connectionString) .UseSnakeCaseNamingConvention(); protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/Program.cs b/Program.cs index d61ffe2..3562520 100644 --- a/Program.cs +++ b/Program.cs @@ -9,17 +9,15 @@ class Program { /// public static string BotUptime => (DateTimeOffset.UtcNow - _botStartTime).ToString("d' days, 'hh':'mm':'ss"); - static async Task Main(string[] args) { + static async Task Main() { Configuration? cfg = null; try { - cfg = new Configuration(args); + cfg = new Configuration(); } catch (Exception ex) { Console.WriteLine(ex); Environment.Exit((int)ExitCodes.ConfigError); } - Data.BotDatabaseContext.NpgsqlConnectionString = cfg.DbConnectionString; - Console.CancelKeyPress += OnCancelKeyPressed; _bot = new WorldTime(cfg); await _bot.StartAsync().ConfigureAwait(false); diff --git a/WorldTime.cs b/WorldTime.cs index d4648ff..fb8444b 100644 --- a/WorldTime.cs +++ b/WorldTime.cs @@ -31,6 +31,7 @@ internal class WorldTime : IDisposable { private readonly CancellationTokenSource _mainCancel; private readonly CommandsText _commandsTxt; private readonly IServiceProvider _services; + private readonly HashSet _aotUserDownloadChecked = new(); internal Configuration Config { get; } internal DiscordShardedClient DiscordClient => _services.GetRequiredService(); @@ -186,7 +187,9 @@ internal class WorldTime : IDisposable { if (message.Channel is not SocketTextChannel channel) return; // Proactively fill guild user cache if the bot has any data for the respective guild - // Can skip an extra query if the last_seen update is known to have been successful, otherwise query for any users + lock (_aotUserDownloadChecked) { + if (!_aotUserDownloadChecked.Add(channel.Guild.Id)) return; // ...once. Just once. Not all the time. + } if (!channel.Guild.HasAllMembers) { using var db = _services.GetRequiredService(); if (db.HasAnyUsers(channel.Guild)) { From eb7e3948cd6d953ea9d97a2b153ceb814e1a4e4f Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 9 Aug 2022 18:18:38 -0700 Subject: [PATCH 04/11] Sanity checking to command registration --- WorldTime.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/WorldTime.cs b/WorldTime.cs index fb8444b..0c47c95 100644 --- a/WorldTime.cs +++ b/WorldTime.cs @@ -170,10 +170,14 @@ internal class WorldTime : IDisposable { } #else // Debug: Register our commands locally instead, in each guild we're in - var iasrv = _services.GetRequiredService(); - foreach (var g in arg.Guilds) { - await iasrv.RegisterCommandsToGuildAsync(g.Id, true).ConfigureAwait(false); - Program.Log("Command registration", $"Updated DEBUG command registration in guild {g.Id}."); + if (arg.Guilds.Count > 5) { + Program.Log("Command registration", "Are you debugging in production?! Skipping DEBUG command registration."); + } else { + var iasrv = _services.GetRequiredService(); + foreach (var g in arg.Guilds) { + await iasrv.RegisterCommandsToGuildAsync(g.Id, true).ConfigureAwait(false); + Program.Log("Command registration", $"Updated DEBUG command registration in guild {g.Id}."); + } } #endif } From f8bc9a8227aebb0b83537b720a27515ef1aa49d7 Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 9 Aug 2022 18:43:55 -0700 Subject: [PATCH 05/11] Bump version --- WorldTime.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WorldTime.csproj b/WorldTime.csproj index 7638acd..3f2181c 100644 --- a/WorldTime.csproj +++ b/WorldTime.csproj @@ -5,7 +5,7 @@ net6.0 enable enable - 2.1.4 + 2.2.0 NoiTheCat From e872ba36dfd1eecf49b84086c754a5fa2cfb65f2 Mon Sep 17 00:00:00 2001 From: Noi Date: Fri, 30 Dec 2022 22:07:21 -0800 Subject: [PATCH 06/11] Update dependencies --- WorldTime.csproj | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/WorldTime.csproj b/WorldTime.csproj index 886138b..1bf0c73 100644 --- a/WorldTime.csproj +++ b/WorldTime.csproj @@ -11,18 +11,18 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - + + + + + From 362d288424bff461222d7e97c05226aa211323ea Mon Sep 17 00:00:00 2001 From: Noi Date: Fri, 30 Dec 2022 23:22:22 -0800 Subject: [PATCH 07/11] Move commands into subdirectory, create base class Base class created with potentially common properties from existing class --- .../ApplicationCommands.cs | 109 +----------------- Commands/CommandsBase.cs | 108 +++++++++++++++++ .../RequireGuildContextAttribute.cs | 5 +- WorldTime.cs | 3 +- 4 files changed, 113 insertions(+), 112 deletions(-) rename ApplicationCommands.cs => Commands/ApplicationCommands.cs (64%) create mode 100644 Commands/CommandsBase.cs rename RequireGuildContextAttribute.cs => Commands/RequireGuildContextAttribute.cs (69%) diff --git a/ApplicationCommands.cs b/Commands/ApplicationCommands.cs similarity index 64% rename from ApplicationCommands.cs rename to Commands/ApplicationCommands.cs index 6847fc2..dfd4020 100644 --- a/ApplicationCommands.cs +++ b/Commands/ApplicationCommands.cs @@ -1,14 +1,8 @@ using Discord.Interactions; -using NodaTime; -using System.Collections.ObjectModel; -using System.Globalization; using System.Text; -using WorldTime.Data; - -namespace WorldTime; -public class ApplicationCommands : InteractionModuleBase { - const string ErrNotAllowed = ":x: Only server moderators may use this command."; +namespace WorldTime.Commands; +public class ApplicationCommands : CommandsBase { const string EmbedHelpField1 = $"`/help` - {HelpHelp}\n" + $"`/list` - {HelpList}\n" + $"`/set` - {HelpSet}\n" @@ -23,20 +17,6 @@ public class ApplicationCommands : InteractionModuleBase."; - private const string ErrNoUserCache = ":warning: Please try the command again."; - - private static readonly ReadOnlyDictionary _tzNameMap; - - public DiscordShardedClient ShardedClient { get; set; } = null!; - public BotDatabaseContext DbContext { get; set; } = null!; - - static ApplicationCommands() { - Dictionary tzNameMap = new(StringComparer.OrdinalIgnoreCase); - foreach (var name in DateTimeZoneProviders.Tzdb.Ids) tzNameMap.Add(name, name); - _tzNameMap = new(tzNameMap); - } [SlashCommand("help", HelpHelp)] public async Task CmdHelp() { @@ -217,89 +197,4 @@ public class ApplicationCommands : InteractionModuleBase - /// Returns a string displaying the current time in the given time zone. - /// The result begins with four numbers for sorting purposes. Must be trimmed before output. - /// - private static string TzPrint(string zone) { - var tzdb = DateTimeZoneProviders.Tzdb; - DateTimeZone tz = tzdb.GetZoneOrNull(zone)!; - if (tz == null) throw new Exception("Encountered unknown time zone: " + zone); - - var now = SystemClock.Instance.GetCurrentInstant().InZone(tz); - var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo); - var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); - return $"{sortpfx}● `{fullstr}`"; - } - - /// - /// Checks given time zone input. Returns a valid string for use with NodaTime, or null. - /// - private static string? ParseTimeZone(string tzinput) { - if (tzinput.Equals("Asia/Calcutta", StringComparison.OrdinalIgnoreCase)) tzinput = "Asia/Kolkata"; - if (_tzNameMap.TryGetValue(tzinput, out var name)) return name; - return null; - } - - /// - /// Formats a user's name to a consistent, readable format which makes use of their nickname. - /// - private static string FormatName(SocketGuildUser user) { - static string escapeFormattingCharacters(string input) { - var result = new StringBuilder(); - foreach (var c in input) { - if (c is '\\' or '_' or '~' or '*' or '@') { - result.Append('\\'); - } - result.Append(c); - } - return result.ToString(); - } - - var username = escapeFormattingCharacters(user.Username); - if (user.Nickname != null) { - return $"**{escapeFormattingCharacters(user.Nickname)}** ({username}#{user.Discriminator})"; - } - return $"**{username}**#{user.Discriminator}"; - } - - /// - /// Checks if the given user can be considered a guild admin ('Manage Server' is set). - /// - // TODO replace this with a precondition, or there's also a new permission scheme going around? - private static bool IsUserAdmin(SocketGuildUser user) - => user.GuildPermissions.Administrator || user.GuildPermissions.ManageGuild; - - /// - /// Checks if the member cache for the specified guild needs to be filled, and sends a request if needed. - /// - /// - /// Due to a quirk in Discord.Net, the user cache cannot be filled until the command handler is no longer executing - /// regardless of if the request runs on its own thread, thus requiring the user to run the command again. - /// - /// - /// True if the guild's members are already downloaded. If false, the command handler must notify the user. - /// - private static async Task AreUsersDownloadedAsync(SocketGuild guild) { - static bool HasMostMembersDownloaded(SocketGuild guild) { - if (guild.HasAllMembers) return true; - if (guild.MemberCount > 30) { - // For guilds of size over 30, require 85% or more of the members to be known - // (26/30, 42/50, 255/300, etc) - return guild.DownloadedMemberCount >= (int)(guild.MemberCount * 0.85); - } else { - // For smaller guilds, fail if two or more members are missing - return guild.MemberCount - guild.DownloadedMemberCount <= 2; - } - } - if (HasMostMembersDownloaded(guild)) return true; - else { - // Event handler hangs if awaited normally or used with Task.Run - await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false); - return false; - } - } - #endregion } diff --git a/Commands/CommandsBase.cs b/Commands/CommandsBase.cs new file mode 100644 index 0000000..9aea3f1 --- /dev/null +++ b/Commands/CommandsBase.cs @@ -0,0 +1,108 @@ +using Discord.Interactions; +using NodaTime; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Text; +using WorldTime.Data; + +namespace WorldTime.Commands; +public class CommandsBase : InteractionModuleBase { + protected const string ErrInvalidZone = ":x: Not a valid zone name." + + " To find your time zone, refer to: ."; + protected const string ErrNoUserCache = ":warning: Please try the command again."; + protected const string ErrNotAllowed = ":x: Only server moderators may use this command."; + + private static readonly ReadOnlyDictionary _tzNameMap; + + static CommandsBase() { + Dictionary tzNameMap = new(StringComparer.OrdinalIgnoreCase); + foreach (var name in DateTimeZoneProviders.Tzdb.Ids) tzNameMap.Add(name, name); + _tzNameMap = new(tzNameMap); + } + + public DiscordShardedClient ShardedClient { get; set; } = null!; + public BotDatabaseContext DbContext { get; set; } = null!; + + /// + /// Returns a string displaying the current time in the given time zone. + /// The result begins with four numbers for sorting purposes. Must be trimmed before output. + /// + protected static string TzPrint(string zone) { + var tzdb = DateTimeZoneProviders.Tzdb; + DateTimeZone tz = tzdb.GetZoneOrNull(zone)!; + if (tz == null) throw new Exception("Encountered unknown time zone: " + zone); + + var now = SystemClock.Instance.GetCurrentInstant().InZone(tz); + var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo); + var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); + return $"{sortpfx}● `{fullstr}`"; + } + + /// + /// Checks given time zone input. Returns a valid string for use with NodaTime, or null. + /// + protected static string? ParseTimeZone(string tzinput) { + if (tzinput.Equals("Asia/Calcutta", StringComparison.OrdinalIgnoreCase)) tzinput = "Asia/Kolkata"; + if (_tzNameMap.TryGetValue(tzinput, out var name)) return name; + return null; + } + + /// + /// Formats a user's name to a consistent, readable format which makes use of their nickname. + /// + protected static string FormatName(SocketGuildUser user) { + static string escapeFormattingCharacters(string input) { + var result = new StringBuilder(); + foreach (var c in input) { + if (c is '\\' or '_' or '~' or '*' or '@') { + result.Append('\\'); + } + result.Append(c); + } + return result.ToString(); + } + + var username = escapeFormattingCharacters(user.Username); + if (user.Nickname != null) { + return $"**{escapeFormattingCharacters(user.Nickname)}** ({username}#{user.Discriminator})"; + } + return $"**{username}**#{user.Discriminator}"; + } + + /// + /// Checks if the given user can be considered a guild admin ('Manage Server' is set). + /// + // TODO replace this with a precondition, or there's also a new permission scheme going around? + protected static bool IsUserAdmin(SocketGuildUser user) + => user.GuildPermissions.Administrator || user.GuildPermissions.ManageGuild; + + /// + /// Checks if the member cache for the specified guild needs to be filled, and sends a request if needed. + /// + /// + /// Due to a quirk in Discord.Net, the user cache cannot be filled until the command handler is no longer executing + /// regardless of if the request runs on its own thread, thus requiring the user to run the command again. + /// + /// + /// True if the guild's members are already downloaded. If false, the command handler must notify the user. + /// + protected static async Task AreUsersDownloadedAsync(SocketGuild guild) { + static bool HasMostMembersDownloaded(SocketGuild guild) { + if (guild.HasAllMembers) return true; + if (guild.MemberCount > 30) { + // For guilds of size over 30, require 85% or more of the members to be known + // (26/30, 42/50, 255/300, etc) + return guild.DownloadedMemberCount >= (int)(guild.MemberCount * 0.85); + } else { + // For smaller guilds, fail if two or more members are missing + return guild.MemberCount - guild.DownloadedMemberCount <= 2; + } + } + if (HasMostMembersDownloaded(guild)) return true; + else { + // Event handler hangs if awaited normally or used with Task.Run + await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false); + return false; + } + } +} \ No newline at end of file diff --git a/RequireGuildContextAttribute.cs b/Commands/RequireGuildContextAttribute.cs similarity index 69% rename from RequireGuildContextAttribute.cs rename to Commands/RequireGuildContextAttribute.cs index 806498d..5994111 100644 --- a/RequireGuildContextAttribute.cs +++ b/Commands/RequireGuildContextAttribute.cs @@ -1,9 +1,8 @@ using Discord.Interactions; -namespace WorldTime; +namespace WorldTime.Commands; /// -/// Implements the included precondition from Discord.Net, requiring a guild context while using our custom error message.

-/// Combining this with is redundant. If possible, only use the latter instead. +/// Implements the included precondition from Discord.Net, requiring a guild context while using our custom error message. ///
class RequireGuildContextAttribute : RequireContextAttribute { public const string Error = "Command not received within a guild context."; diff --git a/WorldTime.cs b/WorldTime.cs index 47557c1..07185a0 100644 --- a/WorldTime.cs +++ b/WorldTime.cs @@ -7,7 +7,6 @@ using System.Text; using WorldTime.Data; namespace WorldTime; - /// /// Main class for the program. Configures the client on start and occasionally prints status information. /// @@ -203,7 +202,7 @@ internal class WorldTime : IDisposable { // Specific responses to errors, if necessary if (result.Error == InteractionCommandError.UnmetPrecondition) { string errReply = result.ErrorReason switch { - RequireGuildContextAttribute.Error => RequireGuildContextAttribute.Reply, + Commands.RequireGuildContextAttribute.Error => Commands.RequireGuildContextAttribute.Reply, _ => result.ErrorReason }; await context.Interaction.RespondAsync(errReply, ephemeral: true); From 7a7bc097e8c935b4928b6d1f46cafdc40d58cc91 Mon Sep 17 00:00:00 2001 From: Noi Date: Sat, 14 Jan 2023 20:55:21 -0800 Subject: [PATCH 08/11] Explicit config path in args --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 146f5ab..8669443 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/bin/Debug/net6.0/WorldTime.dll", - "args": [], + "args": [ "-c", "${workspaceFolder}/bin/Debug/net6.0/settings.json" ], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", From 09ba9644dc1dd639db756de7bd90d92616330d9f Mon Sep 17 00:00:00 2001 From: Noi Date: Sat, 14 Jan 2023 21:07:27 -0800 Subject: [PATCH 09/11] Add 12-hour time format (implements #2) --- Commands/CommandsBase.cs | 12 ++-- Commands/ConfigCommands.cs | 28 ++++++++ ...ApplicationCommands.cs => UserCommands.cs} | 21 ++++-- Data/BotDatabaseContext.cs | 6 +- Data/GuildConfiguration.cs | 9 +++ .../20230115041447_Add12HrSetting.Designer.cs | 69 +++++++++++++++++++ .../20230115041447_Add12HrSetting.cs | 33 +++++++++ .../BotDatabaseContextModelSnapshot.cs | 21 +++++- 8 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 Commands/ConfigCommands.cs rename Commands/{ApplicationCommands.cs => UserCommands.cs} (91%) create mode 100644 Data/GuildConfiguration.cs create mode 100644 Data/Migrations/20230115041447_Add12HrSetting.Designer.cs create mode 100644 Data/Migrations/20230115041447_Add12HrSetting.cs diff --git a/Commands/CommandsBase.cs b/Commands/CommandsBase.cs index 9aea3f1..3dfc7e9 100644 --- a/Commands/CommandsBase.cs +++ b/Commands/CommandsBase.cs @@ -25,16 +25,20 @@ public class CommandsBase : InteractionModuleBase { /// /// Returns a string displaying the current time in the given time zone. - /// The result begins with four numbers for sorting purposes. Must be trimmed before output. + /// The result begins with six numbers for sorting purposes. Must be trimmed before output. /// - protected static string TzPrint(string zone) { + protected static string TzPrint(string zone, bool use12hr) { var tzdb = DateTimeZoneProviders.Tzdb; DateTimeZone tz = tzdb.GetZoneOrNull(zone)!; if (tz == null) throw new Exception("Encountered unknown time zone: " + zone); var now = SystemClock.Instance.GetCurrentInstant().InZone(tz); - var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo); - var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); + var sortpfx = now.ToString("MMddHH", DateTimeFormatInfo.InvariantInfo); + string fullstr; + if (use12hr) { + var ap = now.ToString("tt", DateTimeFormatInfo.InvariantInfo).ToLowerInvariant(); + fullstr = now.ToString($"MMM' 'dd', 'hh':'mm'{ap} 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); + } else fullstr = now.ToString("dd'-'MMM', 'HH':'mm' 'x' (UTC'o')'", DateTimeFormatInfo.InvariantInfo); return $"{sortpfx}● `{fullstr}`"; } diff --git a/Commands/ConfigCommands.cs b/Commands/ConfigCommands.cs new file mode 100644 index 0000000..43c3d74 --- /dev/null +++ b/Commands/ConfigCommands.cs @@ -0,0 +1,28 @@ +using Discord.Interactions; + +namespace WorldTime.Commands; +[Group("config", HelpSettings)] +public class ConfigCommands : CommandsBase { + internal const string HelpSettings = "Configuration commands for World Time."; + internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings."; + + [RequireGuildContext] + [SlashCommand("use-12hour", HelpUse12)] + public async Task Cmd12Hour([Summary(description: "True to enable, False to disable.")] bool setting) { + if (!IsUserAdmin((SocketGuildUser)Context.User)) { + await RespondAsync(ErrNotAllowed, ephemeral: true); + return; + } + + using var db = DbContext; + var gs = db.GuildSettings.Where(r => r.GuildId == Context.Guild.Id).SingleOrDefault(); + if (gs == null) { + gs = new() { GuildId = Context.Guild.Id }; + db.Add(gs); + } + + gs.Use12HourTime = setting; + await db.SaveChangesAsync(); + await RespondAsync($":white_check_mark: Time listing set to **{(setting ? "AM/PM" : "24 hour")}** format."); + } +} \ No newline at end of file diff --git a/Commands/ApplicationCommands.cs b/Commands/UserCommands.cs similarity index 91% rename from Commands/ApplicationCommands.cs rename to Commands/UserCommands.cs index dfd4020..d8a237e 100644 --- a/Commands/ApplicationCommands.cs +++ b/Commands/UserCommands.cs @@ -2,12 +2,16 @@ using System.Text; namespace WorldTime.Commands; -public class ApplicationCommands : CommandsBase { - const string EmbedHelpField1 = $"`/help` - {HelpHelp}\n" +public class UserCommands : CommandsBase { + const string EmbedHelpField1 = + $"`/help` - {HelpHelp}\n" + $"`/list` - {HelpList}\n" + $"`/set` - {HelpSet}\n" + $"`/remove` - {HelpRemove}"; - const string EmbedHelpField2 = $"`/set-for` - {HelpSetFor}\n`/remove-for` - {HelpRemoveFor}"; + const string EmbedHelpField2 = + $"`/config use-12hour` - {ConfigCommands.HelpUse12}\n" + + $"`/set-for` - {HelpSetFor}\n" + + $"`/remove-for` - {HelpRemoveFor}"; #region Help strings const string HelpHelp = "Displays a list of available bot commands."; @@ -67,23 +71,25 @@ public class ApplicationCommands : CommandsBase { await RespondAsync(":x: Nothing to show. Register your time zones with the bot using the `/set` command."); return; } + // Order times by popularity to limit how many are shown, group by printed name var sortedlist = new SortedDictionary>(); + var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false; foreach ((string area, List users) in userlist.OrderByDescending(o => o.Value.Count).Take(20)) { // Filter further to top 20 distinct timezones, even if they are not displayed in the final result - var areaprint = TzPrint(area); + var areaprint = TzPrint(area, ampm); if (!sortedlist.ContainsKey(areaprint)) sortedlist.Add(areaprint, new List()); sortedlist[areaprint].AddRange(users); } const int MaxSingleLineLength = 750; - const int MaxSingleOutputLength = 900; + const int MaxSingleOutputLength = 3000; // Build zone listings with users var outputlines = new List(); foreach ((string area, List users) in sortedlist) { var buffer = new StringBuilder(); - buffer.Append(area[4..] + ": "); + buffer.Append(area[6..] + ": "); bool empty = true; foreach (var userid in users) { var userinstance = Context.Guild.GetUser(userid); @@ -137,7 +143,8 @@ public class ApplicationCommands : CommandsBase { return; } - var resulttext = TzPrint(result)[4..] + ": " + FormatName(parameter); + var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false; + var resulttext = TzPrint(result, ampm)[6..] + ": " + FormatName(parameter); await RespondAsync(embed: new EmbedBuilder().WithDescription(resulttext).Build()); } diff --git a/Data/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index 52c4298..a3fab44 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -17,6 +17,7 @@ public class BotDatabaseContext : DbContext { } public DbSet UserEntries { get; set; } = null!; + public DbSet GuildSettings { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder @@ -24,9 +25,8 @@ public class BotDatabaseContext : DbContext { .UseSnakeCaseNamingConvention(); protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey"); - }); + modelBuilder.Entity().HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey"); + modelBuilder.Entity().Property(p => p.Use12HourTime).HasDefaultValue(false); } #region Helper methods / abstractions diff --git a/Data/GuildConfiguration.cs b/Data/GuildConfiguration.cs new file mode 100644 index 0000000..6339bd7 --- /dev/null +++ b/Data/GuildConfiguration.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace WorldTime.Data; +public class GuildConfiguration { + [Key] + public ulong GuildId { get; set; } + + public bool Use12HourTime { get; set; } +} \ No newline at end of file diff --git a/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs b/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs new file mode 100644 index 0000000..92939fe --- /dev/null +++ b/Data/Migrations/20230115041447_Add12HrSetting.Designer.cs @@ -0,0 +1,69 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WorldTime.Data; + +#nullable disable + +namespace WorldTime.Data.Migrations +{ + [DbContext(typeof(BotDatabaseContext))] + [Migration("20230115041447_Add12HrSetting")] + partial class Add12HrSetting + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("Use12HourTime") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("use12hour_time"); + + b.HasKey("GuildId") + .HasName("pk_guild_settings"); + + b.ToTable("guild_settings", (string)null); + }); + + modelBuilder.Entity("WorldTime.Data.UserEntry", b => + { + b.Property("GuildId") + .HasColumnType("bigint") + .HasColumnName("guild_id"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("TimeZone") + .IsRequired() + .HasColumnType("text") + .HasColumnName("zone"); + + b.HasKey("GuildId", "UserId") + .HasName("userdata_pkey"); + + b.ToTable("userdata", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20230115041447_Add12HrSetting.cs b/Data/Migrations/20230115041447_Add12HrSetting.cs new file mode 100644 index 0000000..0953a8c --- /dev/null +++ b/Data/Migrations/20230115041447_Add12HrSetting.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WorldTime.Data.Migrations +{ + /// + public partial class Add12HrSetting : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "guild_settings", + columns: table => new + { + guildid = table.Column(name: "guild_id", type: "numeric(20,0)", nullable: false), + use12hourtime = table.Column(name: "use12hour_time", type: "boolean", nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("pk_guild_settings", x => x.guildid); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "guild_settings"); + } + } +} diff --git a/Data/Migrations/BotDatabaseContextModelSnapshot.cs b/Data/Migrations/BotDatabaseContextModelSnapshot.cs index a8cfd57..63704a5 100644 --- a/Data/Migrations/BotDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/BotDatabaseContextModelSnapshot.cs @@ -16,11 +16,30 @@ namespace WorldTime.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("ProductVersion", "7.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b => + { + b.Property("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("Use12HourTime") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("use12hour_time"); + + b.HasKey("GuildId") + .HasName("pk_guild_settings"); + + b.ToTable("guild_settings", (string)null); + }); + modelBuilder.Entity("WorldTime.Data.UserEntry", b => { b.Property("GuildId") From c8de71854b70af0abcd2ce876b71d83595f5d431 Mon Sep 17 00:00:00 2001 From: Noi Date: Sat, 14 Jan 2023 21:54:23 -0800 Subject: [PATCH 10/11] Remove custom precondition; use Discord permissions --- Commands/CommandsBase.cs | 7 ---- Commands/ConfigCommands.cs | 37 ++++++++++++++---- Commands/RequireGuildContextAttribute.cs | 14 ------- Commands/UserCommands.cs | 48 +++--------------------- WorldTime.cs | 19 +++------- 5 files changed, 40 insertions(+), 85 deletions(-) delete mode 100644 Commands/RequireGuildContextAttribute.cs diff --git a/Commands/CommandsBase.cs b/Commands/CommandsBase.cs index 3dfc7e9..cc25cf8 100644 --- a/Commands/CommandsBase.cs +++ b/Commands/CommandsBase.cs @@ -73,13 +73,6 @@ public class CommandsBase : InteractionModuleBase { return $"**{username}**#{user.Discriminator}"; } - /// - /// Checks if the given user can be considered a guild admin ('Manage Server' is set). - /// - // TODO replace this with a precondition, or there's also a new permission scheme going around? - protected static bool IsUserAdmin(SocketGuildUser user) - => user.GuildPermissions.Administrator || user.GuildPermissions.ManageGuild; - /// /// Checks if the member cache for the specified guild needs to be filled, and sends a request if needed. /// diff --git a/Commands/ConfigCommands.cs b/Commands/ConfigCommands.cs index 43c3d74..8c81f8e 100644 --- a/Commands/ConfigCommands.cs +++ b/Commands/ConfigCommands.cs @@ -1,19 +1,16 @@ using Discord.Interactions; namespace WorldTime.Commands; -[Group("config", HelpSettings)] +[Group("config", "Configuration commands for World Time.")] +[DefaultMemberPermissions(GuildPermission.ManageGuild)] +[EnabledInDm(false)] public class ConfigCommands : CommandsBase { - internal const string HelpSettings = "Configuration commands for World Time."; internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings."; + internal const string HelpSetFor = "Sets/updates time zone for a given user."; + internal const string HelpRemoveFor = "Removes time zone for a given user."; - [RequireGuildContext] [SlashCommand("use-12hour", HelpUse12)] public async Task Cmd12Hour([Summary(description: "True to enable, False to disable.")] bool setting) { - if (!IsUserAdmin((SocketGuildUser)Context.User)) { - await RespondAsync(ErrNotAllowed, ephemeral: true); - return; - } - using var db = DbContext; var gs = db.GuildSettings.Where(r => r.GuildId == Context.Guild.Id).SingleOrDefault(); if (gs == null) { @@ -25,4 +22,28 @@ public class ConfigCommands : CommandsBase { await db.SaveChangesAsync(); await RespondAsync($":white_check_mark: Time listing set to **{(setting ? "AM/PM" : "24 hour")}** format."); } + + [SlashCommand("set-for", HelpSetFor)] + public async Task CmdSetFor([Summary(description: "The user whose time zone to modify.")] SocketGuildUser user, + [Summary(description: "The new time zone to set.")] string zone) { + // Extract parameters + var newtz = ParseTimeZone(zone); + if (newtz == null) { + await RespondAsync(ErrInvalidZone); + return; + } + + using var db = DbContext; + db.UpdateUser(user, newtz); + await RespondAsync($":white_check_mark: Time zone for **{user}** set to **{newtz}**."); + } + + [SlashCommand("remove-for", HelpRemoveFor)] + public async Task CmdRemoveFor([Summary(description: "The user whose time zone to remove.")] SocketGuildUser user) { + using var db = DbContext; + if (db.DeleteUser(user)) + await RespondAsync($":white_check_mark: Removed zone information for {user}."); + else + await RespondAsync($":white_check_mark: No time zone is set for {user}."); + } } \ No newline at end of file diff --git a/Commands/RequireGuildContextAttribute.cs b/Commands/RequireGuildContextAttribute.cs deleted file mode 100644 index 5994111..0000000 --- a/Commands/RequireGuildContextAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Interactions; - -namespace WorldTime.Commands; -/// -/// Implements the included precondition from Discord.Net, requiring a guild context while using our custom error message. -/// -class RequireGuildContextAttribute : RequireContextAttribute { - public const string Error = "Command not received within a guild context."; - public const string Reply = ":x: This command is only available within a server."; - - public override string ErrorMessage => Error; - - public RequireGuildContextAttribute() : base(ContextType.Guild) { } -} diff --git a/Commands/UserCommands.cs b/Commands/UserCommands.cs index d8a237e..8e0345a 100644 --- a/Commands/UserCommands.cs +++ b/Commands/UserCommands.cs @@ -10,16 +10,15 @@ public class UserCommands : CommandsBase { + $"`/remove` - {HelpRemove}"; const string EmbedHelpField2 = $"`/config use-12hour` - {ConfigCommands.HelpUse12}\n" - + $"`/set-for` - {HelpSetFor}\n" - + $"`/remove-for` - {HelpRemoveFor}"; + + $"`/set-for` - {ConfigCommands.HelpSetFor}\n" + + $"`/remove-for` - {ConfigCommands.HelpRemoveFor}"; #region Help strings const string HelpHelp = "Displays a list of available bot commands."; const string HelpList = "Shows the current time for all recently active known users."; const string HelpSet = "Adds or updates your time zone to the bot."; - const string HelpSetFor = "Sets/updates time zone for a given user."; const string HelpRemove = "Removes your time zone information from this bot."; - const string HelpRemoveFor = "Removes time zone for a given user."; + #endregion [SlashCommand("help", HelpHelp)] @@ -46,8 +45,8 @@ public class UserCommands : CommandsBase { ).Build()); } - [RequireGuildContext] [SlashCommand("list", HelpList)] + [EnabledInDm(false)] public async Task CmdList([Summary(description: "A specific user whose time to look up.")]SocketGuildUser? user = null) { if (!await AreUsersDownloadedAsync(Context.Guild)) { await RespondAsync(ErrNoUserCache, ephemeral: true); @@ -149,6 +148,7 @@ public class UserCommands : CommandsBase { } [SlashCommand("set", HelpSet)] + [EnabledInDm(false)] public async Task CmdSet([Summary(description: "The new time zone to set.")]string zone) { var parsedzone = ParseTimeZone(zone); if (parsedzone == null) { @@ -160,48 +160,12 @@ public class UserCommands : CommandsBase { await RespondAsync($":white_check_mark: Your time zone has been set to **{parsedzone}**."); } - [RequireGuildContext] - [SlashCommand("set-for", HelpSetFor)] - public async Task CmdSetFor([Summary(description: "The user whose time zone to modify.")] SocketGuildUser user, - [Summary(description: "The new time zone to set.")] string zone) { - if (!IsUserAdmin((SocketGuildUser)Context.User)) { - await RespondAsync(ErrNotAllowed, ephemeral: true).ConfigureAwait(false); - return; - } - - // Extract parameters - var newtz = ParseTimeZone(zone); - if (newtz == null) { - await RespondAsync(ErrInvalidZone); - return; - } - - using var db = DbContext; - db.UpdateUser(user, newtz); - await RespondAsync($":white_check_mark: Time zone for **{user}** set to **{newtz}**."); - } - - [RequireGuildContext] [SlashCommand("remove", HelpRemove)] + [EnabledInDm(false)] public async Task CmdRemove() { using var db = DbContext; var success = db.DeleteUser((SocketGuildUser)Context.User); if (success) await RespondAsync(":white_check_mark: Your zone has been removed."); else await RespondAsync(":x: You don't have a time zone set."); } - - [RequireGuildContext] - [SlashCommand("remove-for", HelpRemoveFor)] - public async Task CmdRemoveFor([Summary(description: "The user whose time zone to remove.")] SocketGuildUser user) { - if (!IsUserAdmin((SocketGuildUser)Context.User)) { - await RespondAsync(ErrNotAllowed, ephemeral: true).ConfigureAwait(false); - return; - } - - using var db = DbContext; - if (db.DeleteUser(user)) - await RespondAsync($":white_check_mark: Removed zone information for {user}."); - else - await RespondAsync($":white_check_mark: No time zone is set for {user}."); - } } diff --git a/WorldTime.cs b/WorldTime.cs index 07185a0..074cd70 100644 --- a/WorldTime.cs +++ b/WorldTime.cs @@ -199,20 +199,11 @@ internal class WorldTime : IDisposable { // Additional log information with error detail logresult += " " + Enum.GetName(typeof(InteractionCommandError), result.Error) + ": " + result.ErrorReason; - // Specific responses to errors, if necessary - if (result.Error == InteractionCommandError.UnmetPrecondition) { - string errReply = result.ErrorReason switch { - Commands.RequireGuildContextAttribute.Error => Commands.RequireGuildContextAttribute.Reply, - _ => result.ErrorReason - }; - await context.Interaction.RespondAsync(errReply, ephemeral: true); - } else { - // Generic error response - // TODO when implementing proper application error logging, see here - var ia = context.Interaction; - if (ia.HasResponded) await ia.ModifyOriginalResponseAsync(p => p.Content = InternalError); - else await ia.RespondAsync(InternalError); - } + // Generic error response + // TODO when implementing proper application error logging, see here + var ia = context.Interaction; + if (ia.HasResponded) await ia.ModifyOriginalResponseAsync(p => p.Content = InternalError); + else await ia.RespondAsync(InternalError); } Program.Log("Command", logresult); From a01aa15277d47e3ac0bd302b15f86d4215d165c0 Mon Sep 17 00:00:00 2001 From: Noi Date: Sat, 14 Jan 2023 22:05:31 -0800 Subject: [PATCH 11/11] Bump version; update dependencies --- WorldTime.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WorldTime.csproj b/WorldTime.csproj index 1bf0c73..23fd9cf 100644 --- a/WorldTime.csproj +++ b/WorldTime.csproj @@ -5,16 +5,16 @@ net6.0 enable enable - 2.2.0 + 2.3.0 NoiTheCat - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all