mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-25 01:44:12 +00:00
Begin implementing slash commands
Set up a structure not unlike what exists for text commands in order to neatly separate subsets of commands/features into their own files. Changed references for existing commands to "text commands" where it made sense to do so.
This commit is contained in:
parent
ffdb1cd0e6
commit
f8b3f429bb
7 changed files with 221 additions and 29 deletions
66
ApplicationCommands/BotApplicationCommand.cs
Normal file
66
ApplicationCommands/BotApplicationCommand.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using BirthdayBot.Data;
|
||||||
|
using NodaTime;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for classes handling slash command execution.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class BotApplicationCommand {
|
||||||
|
public delegate Task CommandResponder(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg);
|
||||||
|
|
||||||
|
protected const string ErrGuildOnly = ":x: This command can only be run within a server.";
|
||||||
|
protected string ErrNotAllowed = ":x: Only server moderators may use this command.";
|
||||||
|
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
||||||
|
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
|
||||||
|
|
||||||
|
protected static ReadOnlyDictionary<string, string> TzNameMap { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of application command definitions handled by the implementing class,
|
||||||
|
/// for use when registering/updating this bot's available slash commands.
|
||||||
|
/// </summary>
|
||||||
|
public abstract IEnumerable<ApplicationCommandProperties> GetCommands();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given the command name, returns the designated handler to execute to fulfill the command.
|
||||||
|
/// Returns null if this class does not contain a handler for the given command.
|
||||||
|
/// </summary>
|
||||||
|
public abstract CommandResponder? GetHandlerFor(string commandName);
|
||||||
|
|
||||||
|
static BotApplicationCommand() {
|
||||||
|
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var name in DateTimeZoneProviders.Tzdb.Ids) dict.Add(name, name);
|
||||||
|
TzNameMap = new(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks given time zone input. Returns a valid string for use with NodaTime,
|
||||||
|
/// throwing a FormatException if the input is not recognized.
|
||||||
|
/// </summary>
|
||||||
|
protected static string ParseTimeZone(string tzinput) {
|
||||||
|
if (!TzNameMap.TryGetValue(tzinput, out string? tz)) throw new FormatException(":x: Unexpected time zone name."
|
||||||
|
+ $" Refer to `INSERT COMMAND NAME HERE` to help determine the correct value."); // TODO fix!!!!!!!!!!!!!!!!!!!
|
||||||
|
return tz!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An alternative to <see cref="SocketGuild.HasAllMembers"/> to be called by command handlers needing a full member cache.
|
||||||
|
/// Creates a download request if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the member cache is already filled, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Any updates to the member cache aren't accessible until the event handler finishes execution, meaning proactive downloading
|
||||||
|
/// is necessary, and is handled by <seealso cref="BackgroundServices.AutoUserDownload"/>. In situations where
|
||||||
|
/// this approach fails, this is to be called, and the user must be asked to attempt the command again if this returns false.
|
||||||
|
/// </remarks>
|
||||||
|
protected static async Task<bool> HasMemberCacheAsync(SocketGuild guild) {
|
||||||
|
if (Common.HasMostMembersDownloaded(guild)) return true;
|
||||||
|
// Event handling thread hangs if awaited normally or used with Task.Run
|
||||||
|
await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
32
ApplicationCommands/HelpInfoCommands.cs
Normal file
32
ApplicationCommands/HelpInfoCommands.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using BirthdayBot.Data;
|
||||||
|
|
||||||
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
|
||||||
|
internal class HelpInfoCommands : BotApplicationCommand {
|
||||||
|
private static readonly ApplicationCommandProperties[] _commands;
|
||||||
|
|
||||||
|
static HelpInfoCommands() {
|
||||||
|
_commands = new ApplicationCommandProperties[] {
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("help").WithDescription("attempts to get help").Build(),
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("not-help").WithDescription("you're not getting help here").Build()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<ApplicationCommandProperties> GetCommands() {
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
public override CommandResponder? GetHandlerFor(string commandName) {
|
||||||
|
switch (commandName) {
|
||||||
|
case "help":
|
||||||
|
return CmdHelp;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CmdHelp(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
await arg.RespondAsync("i am help. is this help?");
|
||||||
|
}
|
||||||
|
}
|
113
ShardInstance.cs
113
ShardInstance.cs
|
@ -1,6 +1,8 @@
|
||||||
using BirthdayBot.BackgroundServices;
|
using BirthdayBot.ApplicationCommands;
|
||||||
|
using BirthdayBot.BackgroundServices;
|
||||||
using BirthdayBot.Data;
|
using BirthdayBot.Data;
|
||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
|
using static BirthdayBot.ApplicationCommands.BotApplicationCommand;
|
||||||
using static BirthdayBot.TextCommands.CommandsCommon;
|
using static BirthdayBot.TextCommands.CommandsCommon;
|
||||||
|
|
||||||
namespace BirthdayBot;
|
namespace BirthdayBot;
|
||||||
|
@ -11,7 +13,8 @@ namespace BirthdayBot;
|
||||||
class ShardInstance : IDisposable {
|
class ShardInstance : IDisposable {
|
||||||
private readonly ShardManager _manager;
|
private readonly ShardManager _manager;
|
||||||
private readonly ShardBackgroundWorker _background;
|
private readonly ShardBackgroundWorker _background;
|
||||||
private readonly Dictionary<string, CommandHandler> _dispatchCommands;
|
private readonly Dictionary<string, CommandHandler> _textDispatch;
|
||||||
|
private readonly IEnumerable<BotApplicationCommand> _slashCmdHandlers;
|
||||||
|
|
||||||
public DiscordSocketClient DiscordClient { get; }
|
public DiscordSocketClient DiscordClient { get; }
|
||||||
public int ShardId => DiscordClient.ShardId;
|
public int ShardId => DiscordClient.ShardId;
|
||||||
|
@ -25,17 +28,22 @@ class ShardInstance : IDisposable {
|
||||||
public string? CurrentExecutingService => _background.CurrentExecutingService;
|
public string? CurrentExecutingService => _background.CurrentExecutingService;
|
||||||
public Configuration Config => _manager.Config;
|
public Configuration Config => _manager.Config;
|
||||||
|
|
||||||
|
public const string InternalError = ":x: An unknown error occurred. If it persists, please notify the bot owner.";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prepares and configures the shard instances, but does not yet start its connection.
|
/// Prepares and configures the shard instances, but does not yet start its connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ShardInstance(ShardManager manager, DiscordSocketClient client, Dictionary<string, CommandHandler> commands) {
|
public ShardInstance(ShardManager manager, DiscordSocketClient client,
|
||||||
|
Dictionary<string, CommandHandler> textCmds, IEnumerable<BotApplicationCommand> appCmdHandlers) {
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_dispatchCommands = commands;
|
_textDispatch = textCmds;
|
||||||
|
_slashCmdHandlers = appCmdHandlers;
|
||||||
|
|
||||||
DiscordClient = client;
|
DiscordClient = client;
|
||||||
DiscordClient.Log += Client_Log;
|
DiscordClient.Log += Client_Log;
|
||||||
DiscordClient.Ready += Client_Ready;
|
DiscordClient.Ready += Client_Ready;
|
||||||
DiscordClient.MessageReceived += Client_MessageReceived;
|
DiscordClient.MessageReceived += Client_MessageReceived;
|
||||||
|
DiscordClient.SlashCommandExecuted += DiscordClient_SlashCommandExecuted;
|
||||||
|
|
||||||
// Background task constructor begins background processing immediately.
|
// Background task constructor begins background processing immediately.
|
||||||
_background = new ShardBackgroundWorker(this);
|
_background = new ShardBackgroundWorker(this);
|
||||||
|
@ -66,7 +74,6 @@ class ShardInstance : IDisposable {
|
||||||
|
|
||||||
public void Log(string source, string message) => Program.Log($"Shard {ShardId:00}] [{source}", message);
|
public void Log(string source, string message) => Program.Log($"Shard {ShardId:00}] [{source}", message);
|
||||||
|
|
||||||
#region Event handling
|
|
||||||
private Task Client_Log(LogMessage arg) {
|
private Task Client_Log(LogMessage arg) {
|
||||||
// Suppress certain messages
|
// Suppress certain messages
|
||||||
if (arg.Message != null) {
|
if (arg.Message != null) {
|
||||||
|
@ -94,9 +101,45 @@ class ShardInstance : IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the shard's status to display the help command.
|
/// Registers all available slash commands.
|
||||||
|
/// Additionally, sets the shard's status to display the help command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task Client_Ready() => await DiscordClient.SetGameAsync(CommandPrefix + "help");
|
private async Task Client_Ready() {
|
||||||
|
await DiscordClient.SetGameAsync(CommandPrefix + "help");
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
// Update our commands here, only when the first shard connects
|
||||||
|
if (ShardId != 0) return;
|
||||||
|
#endif
|
||||||
|
var commands = new List<ApplicationCommandProperties>();
|
||||||
|
foreach (var source in _slashCmdHandlers) {
|
||||||
|
commands.AddRange(source.GetCommands());
|
||||||
|
}
|
||||||
|
#if !DEBUG
|
||||||
|
// Remove any unneeded/unused commands
|
||||||
|
var existingcmdnames = cmds.Select(c => c.Name.Value).ToHashSet();
|
||||||
|
foreach (var gcmd in await DiscordClient.GetGlobalApplicationCommandsAsync()) {
|
||||||
|
if (!existingcmdnames.Contains(gcmd.Name)) {
|
||||||
|
Log("Command registration", $"Found registered unused command /{gcmd.Name} - sending removal request");
|
||||||
|
await gcmd.DeleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// And update what we have
|
||||||
|
Log("Command registration", $"Bulk updating {cmds.Length} global command(s)");
|
||||||
|
await DiscordClient.BulkOverwriteGlobalApplicationCommandsAsync(cmds).ConfigureAwait(false);
|
||||||
|
#else
|
||||||
|
// Debug: Register our commands locally instead, in each guild we're in
|
||||||
|
foreach (var g in DiscordClient.Guilds) {
|
||||||
|
await g.DeleteApplicationCommandsAsync().ConfigureAwait(false);
|
||||||
|
await g.BulkOverwriteApplicationCommandAsync(commands.ToArray()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var gcmd in await DiscordClient.GetGlobalApplicationCommandsAsync()) {
|
||||||
|
Program.Log("Command registration", $"Found global command /{gcmd.Name} and we're DEBUG - sending removal request");
|
||||||
|
await gcmd.DeleteAsync();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if the incoming message is an incoming command, and dispatches to the appropriate handler if necessary.
|
/// Determines if the incoming message is an incoming command, and dispatches to the appropriate handler if necessary.
|
||||||
|
@ -113,7 +156,7 @@ class ShardInstance : IDisposable {
|
||||||
var csplit = msg.Content.Split(" ", 3, StringSplitOptions.RemoveEmptyEntries);
|
var csplit = msg.Content.Split(" ", 3, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (csplit.Length > 0 && csplit[0].StartsWith(CommandPrefix, StringComparison.OrdinalIgnoreCase)) {
|
if (csplit.Length > 0 && csplit[0].StartsWith(CommandPrefix, StringComparison.OrdinalIgnoreCase)) {
|
||||||
// Determine if it's something we're listening for.
|
// Determine if it's something we're listening for.
|
||||||
if (!_dispatchCommands.TryGetValue(csplit[0][CommandPrefix.Length..], out CommandHandler? command)) return;
|
if (!_textDispatch.TryGetValue(csplit[0][CommandPrefix.Length..], out CommandHandler? command)) return;
|
||||||
|
|
||||||
// Load guild information here
|
// Load guild information here
|
||||||
var gconf = await GuildConfiguration.LoadAsync(channel.Guild.Id, false);
|
var gconf = await GuildConfiguration.LoadAsync(channel.Guild.Id, false);
|
||||||
|
@ -132,10 +175,60 @@ class ShardInstance : IDisposable {
|
||||||
if (ex is HttpException) return;
|
if (ex is HttpException) return;
|
||||||
Log("Command", ex.ToString());
|
Log("Command", ex.ToString());
|
||||||
try {
|
try {
|
||||||
channel.SendMessageAsync(TextCommands.CommandsCommon.InternalError).Wait();
|
channel.SendMessageAsync(InternalError).Wait();
|
||||||
} catch (HttpException) { } // Fail silently
|
} catch (HttpException) { } // Fail silently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispatches to the appropriate slash command handler while catching any exceptions that may occur.
|
||||||
|
/// </summary>
|
||||||
|
private async Task DiscordClient_SlashCommandExecuted(SocketSlashCommand arg) {
|
||||||
|
SocketGuildChannel? rptChannel = arg.Channel as SocketGuildChannel;
|
||||||
|
var rptId = rptChannel?.Guild.Id ?? arg.User.Id;
|
||||||
|
var logLine = $"/{arg.CommandName} by {arg.User} in { (rptChannel != null ? "guild" : "DM. User") } ID {rptId}. Result: ";
|
||||||
|
|
||||||
|
// Specific reply for DM messages
|
||||||
|
if (rptChannel == null) {
|
||||||
|
// TODO do not hardcode message
|
||||||
|
// TODO figure out appropriate message
|
||||||
|
Log("Command", logLine + "Sending message.");
|
||||||
|
await arg.RespondAsync("don't dm me").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine handler to use
|
||||||
|
CommandResponder? handler = null;
|
||||||
|
foreach (var source in _slashCmdHandlers) {
|
||||||
|
handler = source.GetHandlerFor(arg.CommandName);
|
||||||
|
if (handler != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler == null) { // Handler not found
|
||||||
|
Log("Command", logLine + "Unknown command.");
|
||||||
|
await arg.RespondAsync("Oops, that command isn't supposed to be there... Please try something else.",
|
||||||
|
ephemeral: true).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gconf = await GuildConfiguration.LoadAsync(rptChannel.Guild.Id, false);
|
||||||
|
// Block check here
|
||||||
|
if (!gconf!.IsBotModerator((SocketGuildUser)arg.User)) // Except if moderator
|
||||||
|
{
|
||||||
|
if (await gconf.IsUserBlockedAsync(arg.User.Id)) {
|
||||||
|
Log("Command", logLine + "Blocked per guild policy.");
|
||||||
|
await arg.RespondAsync(AccessDeniedError, ephemeral: true).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the handler
|
||||||
|
try {
|
||||||
|
await handler(this, gconf, arg).ConfigureAwait(false);
|
||||||
|
Log("Command", logLine + "Executed normally.");
|
||||||
|
} catch (Exception ex) when (ex is not HttpException) {
|
||||||
|
Log("Command", logLine + ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
global using Discord;
|
global using Discord;
|
||||||
global using Discord.WebSocket;
|
global using Discord.WebSocket;
|
||||||
|
using BirthdayBot.ApplicationCommands;
|
||||||
using BirthdayBot.BackgroundServices;
|
using BirthdayBot.BackgroundServices;
|
||||||
using BirthdayBot.TextCommands;
|
using BirthdayBot.TextCommands;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -43,11 +44,8 @@ class ShardManager : IDisposable {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<int, ShardInstance?> _shards;
|
private readonly Dictionary<int, ShardInstance?> _shards;
|
||||||
|
|
||||||
private readonly Dictionary<string, CommandHandler> _dispatchCommands;
|
private readonly Dictionary<string, CommandHandler> _textCommands;
|
||||||
private readonly UserCommands _cmdsUser;
|
private readonly List<BotApplicationCommand> _appCommands;
|
||||||
private readonly ListingCommands _cmdsListing;
|
|
||||||
private readonly HelpInfoCommands _cmdsHelp;
|
|
||||||
private readonly ManagerCommands _cmdsMods;
|
|
||||||
|
|
||||||
private readonly Task _statusTask;
|
private readonly Task _statusTask;
|
||||||
private readonly CancellationTokenSource _mainCancel;
|
private readonly CancellationTokenSource _mainCancel;
|
||||||
|
@ -62,15 +60,20 @@ class ShardManager : IDisposable {
|
||||||
Config = cfg;
|
Config = cfg;
|
||||||
|
|
||||||
// Command handler setup
|
// Command handler setup
|
||||||
_dispatchCommands = new Dictionary<string, CommandHandler>(StringComparer.OrdinalIgnoreCase);
|
_textCommands = new Dictionary<string, CommandHandler>(StringComparer.OrdinalIgnoreCase);
|
||||||
_cmdsUser = new UserCommands(cfg);
|
var cmdsUser = new UserCommands(cfg);
|
||||||
foreach (var item in _cmdsUser.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
foreach (var item in cmdsUser.Commands) _textCommands.Add(item.Item1, item.Item2);
|
||||||
_cmdsListing = new ListingCommands(cfg);
|
var cmdsListing = new ListingCommands(cfg);
|
||||||
foreach (var item in _cmdsListing.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
foreach (var item in cmdsListing.Commands) _textCommands.Add(item.Item1, item.Item2);
|
||||||
_cmdsHelp = new HelpInfoCommands(cfg);
|
var cmdsHelp = new TextCommands.HelpInfoCommands(cfg);
|
||||||
foreach (var item in _cmdsHelp.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
foreach (var item in cmdsHelp.Commands) _textCommands.Add(item.Item1, item.Item2);
|
||||||
_cmdsMods = new ManagerCommands(cfg, _cmdsUser.Commands);
|
var cmdsMods = new ManagerCommands(cfg, cmdsUser.Commands);
|
||||||
foreach (var item in _cmdsMods.Commands) _dispatchCommands.Add(item.Item1, item.Item2);
|
foreach (var item in cmdsMods.Commands) _textCommands.Add(item.Item1, item.Item2);
|
||||||
|
|
||||||
|
_appCommands = new List<BotApplicationCommand>() {
|
||||||
|
// TODO fill this out
|
||||||
|
new ApplicationCommands.HelpInfoCommands()
|
||||||
|
};
|
||||||
|
|
||||||
// Allocate shards based on configuration
|
// Allocate shards based on configuration
|
||||||
_shards = new Dictionary<int, ShardInstance?>();
|
_shards = new Dictionary<int, ShardInstance?>();
|
||||||
|
@ -119,7 +122,7 @@ class ShardManager : IDisposable {
|
||||||
GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages
|
GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages
|
||||||
};
|
};
|
||||||
var newClient = new DiscordSocketClient(clientConf);
|
var newClient = new DiscordSocketClient(clientConf);
|
||||||
newInstance = new ShardInstance(this, newClient, _dispatchCommands);
|
newInstance = new ShardInstance(this, newClient, _textCommands, _appCommands);
|
||||||
await newInstance.StartAsync().ConfigureAwait(false);
|
await newInstance.StartAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
return newInstance;
|
return newInstance;
|
||||||
|
|
|
@ -17,7 +17,6 @@ internal abstract class CommandsCommon {
|
||||||
public const string BadUserError = ":x: Unable to find user. Specify their `@` mention or their ID.";
|
public const string BadUserError = ":x: Unable to find user. Specify their `@` mention or their ID.";
|
||||||
public const string ParameterError = ":x: Invalid usage. Refer to how to use the command and try again.";
|
public const string ParameterError = ":x: Invalid usage. Refer to how to use the command and try again.";
|
||||||
public const string NoParameterError = ":x: This command does not accept any parameters.";
|
public const string NoParameterError = ":x: This command does not accept any parameters.";
|
||||||
public const string InternalError = ":x: An unknown error occurred. If it persists, please notify the bot owner.";
|
|
||||||
public const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
public const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
||||||
|
|
||||||
public delegate Task CommandHandler(ShardInstance instance, GuildConfiguration gconf,
|
public delegate Task CommandHandler(ShardInstance instance, GuildConfiguration gconf,
|
||||||
|
@ -48,7 +47,6 @@ internal abstract class CommandsCommon {
|
||||||
/// Checks given time zone input. Returns a valid string for use with NodaTime.
|
/// Checks given time zone input. Returns a valid string for use with NodaTime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected static string ParseTimeZone(string tzinput) {
|
protected static string ParseTimeZone(string tzinput) {
|
||||||
if (tzinput.Equals("Asia/Calcutta", StringComparison.OrdinalIgnoreCase)) tzinput = "Asia/Kolkata";
|
|
||||||
if (!TzNameMap.TryGetValue(tzinput, out string? tz)) throw new FormatException(":x: Unexpected time zone name."
|
if (!TzNameMap.TryGetValue(tzinput, out string? tz)) throw new FormatException(":x: Unexpected time zone name."
|
||||||
+ $" Refer to `{CommandPrefix}help-tzdata` to help determine the correct value.");
|
+ $" Refer to `{CommandPrefix}help-tzdata` to help determine the correct value.");
|
||||||
return tz;
|
return tz;
|
||||||
|
|
|
@ -128,7 +128,7 @@ internal class ListingCommands : CommandsCommon {
|
||||||
reqChannel.SendMessageAsync(":x: Unable to send list due to a permissions issue. Check the 'Attach Files' permission.").Wait();
|
reqChannel.SendMessageAsync(":x: Unable to send list due to a permissions issue. Check the 'Attach Files' permission.").Wait();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Program.Log("Listing", ex.ToString());
|
Program.Log("Listing", ex.ToString());
|
||||||
reqChannel.SendMessageAsync(InternalError).Wait();
|
reqChannel.SendMessageAsync(ShardInstance.InternalError).Wait();
|
||||||
} finally {
|
} finally {
|
||||||
File.Delete(filepath);
|
File.Delete(filepath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ internal class UserCommands : CommandsCommon {
|
||||||
await user.UpdateAsync(bmonth, bday, user.TimeZone).ConfigureAwait(false);
|
await user.UpdateAsync(bmonth, bday, user.TimeZone).ConfigureAwait(false);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Program.Log("Error", ex.ToString());
|
Program.Log("Error", ex.ToString());
|
||||||
reqChannel.SendMessageAsync(InternalError).Wait();
|
reqChannel.SendMessageAsync(ShardInstance.InternalError).Wait();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await reqChannel.SendMessageAsync($":white_check_mark: Your birthday has been { (known ? "updated" : "recorded") }.")
|
await reqChannel.SendMessageAsync($":white_check_mark: Your birthday has been { (known ? "updated" : "recorded") }.")
|
||||||
|
|
Loading…
Reference in a new issue