Improved user-facing error messages

Also moved bb.when handler to a different class
This commit is contained in:
Noi 2020-06-03 18:58:03 -07:00
parent fdb8e2bbb9
commit 1137777092
4 changed files with 112 additions and 90 deletions

View file

@ -20,7 +20,10 @@ namespace BirthdayBot.UserInterface
[Obsolete] [Obsolete]
public const string GenericError = ":x: Invalid usage. Consult the help command."; public const string GenericError = ":x: Invalid usage. Consult the help command.";
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 ExpectedNoParametersError = ":x: This command does not take parameters. Did you mean to use another?"; public const string ParameterError = ":x: Incorrect number of parameters. Be sure you have not added spaces"
+ " where the bot is not expecting them or that all required information has been provided.";
public const string NoParameterError = ":x: This command does not accept parameters.";
public const string InternalError = ":x: An internal bot error occurred. The bot maintainer has been notified of the issue.";
public delegate Task CommandHandler(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser); public delegate Task CommandHandler(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser);
@ -68,7 +71,8 @@ namespace BirthdayBot.UserInterface
// Just check if the input exists in the map. Get the "true" value, or reject it altogether. // Just check if the input exists in the map. Get the "true" value, or reject it altogether.
if (!TzNameMap.TryGetValue(tzinput, out tz)) if (!TzNameMap.TryGetValue(tzinput, out tz))
{ {
throw new FormatException(":x: Unknown or invalid time zone name."); throw new FormatException(":x: Unexpected time zone name."
+ $" Refer to `{CommandPrefix}help-tzdata` to help determine the correct value.");
} }
} }
return tz; return tz;

View file

@ -37,25 +37,18 @@ namespace BirthdayBot.UserInterface
Name = "Commands", Name = "Commands",
Value = $"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}help-tzdata`\n" Value = $"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}help-tzdata`\n"
+ $" » Help and informational messages.\n" + $" » Help and informational messages.\n"
+ $"{cpfx}recent` and `{CommandPrefix}upcoming`\n" + ListingCommands.DocUpcoming.Export() + "\n"
+ $" » Lists recent and upcoming birthdays.\n" + UserCommands.DocSet.Export() + "\n"
+ $"{cpfx}set (date) [zone]`\n" + UserCommands.DocZone.Export() + "\n"
+ $" » Registers your birth date. Time zone is optional.\n" + UserCommands.DocRemove.Export() + "\n"
+ $" »» Examples: `{CommandPrefix}set jan-31`, `{CommandPrefix}set 15-aug America/Los_Angeles`.\n" + ListingCommands.DocWhen.Export()
+ $"{cpfx}zone (zone)`\n"
+ $" » Sets your local time zone. See `{CommandPrefix}help-tzdata`.\n"
+ $"{cpfx}remove`\n"
+ $" » Removes your birthday information from this bot.\n"
+ $"{cpfx}when (user)`\n"
+ $" » Displays birthday information of the given user."
}; };
var cmdModField = new EmbedFieldBuilder() var cmdModField = new EmbedFieldBuilder()
{ {
Name = "Commands", Name = "Moderator actions",
Value = $"{cpfx}config`\n" Value = $"{cpfx}config`\n"
+ $" » Edit bot configuration. See `{CommandPrefix}help-config`.\n" + $" » Edit bot configuration. See `{CommandPrefix}help-config`.\n"
+ $"{cpfx}list`\n" + ListingCommands.DocList.Export() + "\n"
+ $" » Exports all birthdays to file. Accepts `csv` as a parameter.\n"
+ $"{cpfx}override (user ping or ID) (command w/ parameters)`\n" + $"{cpfx}override (user ping or ID) (command w/ parameters)`\n"
+ " » Perform certain commands on behalf of another user." + " » Perform certain commands on behalf of another user."
}; };

View file

@ -21,15 +21,84 @@ namespace BirthdayBot.UserInterface
{ {
("list", CmdList), ("list", CmdList),
("upcoming", CmdUpcoming), ("upcoming", CmdUpcoming),
("recent", CmdUpcoming) ("recent", CmdUpcoming),
("when", CmdWhen)
}; };
#region Documentation
public static readonly CommandDocumentation DocList =
new CommandDocumentation(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);
public static readonly CommandDocumentation DocWhen =
new CommandDocumentation(new string[] { "when" }, "Displays the given user's birthday information.", null);
#endregion
private async Task CmdWhen(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{
// Requires a parameter
if (param.Length == 1)
{
await reqChannel.SendMessageAsync(ParameterError, embed: DocWhen.UsageEmbed);
return;
}
var search = param[1];
if (param.Length == 3)
{
// param maxes out at 3 values. param[2] might contain part of the search string (if name has a space)
search += " " + param[2];
}
SocketGuildUser searchTarget = null;
ulong searchId = 0;
if (!TryGetUserId(search, out searchId)) // ID lookup
{
// name lookup without discriminator
foreach (var searchuser in reqChannel.Guild.Users)
{
if (string.Equals(search, searchuser.Username, StringComparison.OrdinalIgnoreCase))
{
searchTarget = searchuser;
break;
}
}
}
else
{
searchTarget = reqChannel.Guild.GetUser(searchId);
}
if (searchTarget == null)
{
await reqChannel.SendMessageAsync(BadUserError, embed: DocWhen.UsageEmbed);
return;
}
var users = Instance.GuildCache[reqChannel.Guild.Id].Users;
var searchTargetData = users.FirstOrDefault(u => u.UserId == searchTarget.Id);
if (searchTargetData == null)
{
await reqChannel.SendMessageAsync("I do not have birthday information for that user.");
return;
}
string result = Common.FormatName(searchTarget, false);
result += ": ";
result += $"`{searchTargetData.BirthDay:00}-{Common.MonthNames[searchTargetData.BirthMonth]}`";
result += searchTargetData.TimeZone == null ? "" : $" - `{searchTargetData.TimeZone}`";
await reqChannel.SendMessageAsync(result);
}
// Creates a file with all birthdays. // Creates a file with all birthdays.
private async Task CmdList(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdList(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// For now, we're restricting this command to moderators only. This may turn into an option later. // For now, we're restricting this command to moderators only. This may turn into an option later.
if (!Instance.GuildCache[reqChannel.Guild.Id].IsUserModerator(reqUser)) if (!Instance.GuildCache[reqChannel.Guild.Id].IsUserModerator(reqUser))
{ {
// Do not add detailed usage information to this error message.
await reqChannel.SendMessageAsync(":x: Only bot moderators may use this command."); await reqChannel.SendMessageAsync(":x: Only bot moderators may use this command.");
return; return;
} }
@ -41,13 +110,13 @@ namespace BirthdayBot.UserInterface
if (param[1].ToLower() == "csv") useCsv = true; if (param[1].ToLower() == "csv") useCsv = true;
else else
{ {
await reqChannel.SendMessageAsync(":x: That is not available as an export format."); await reqChannel.SendMessageAsync(":x: That is not available as an export format.", embed: DocList.UsageEmbed);
return; return;
} }
} }
else if (param.Length > 2) else if (param.Length > 2)
{ {
await reqChannel.SendMessageAsync(GenericError); await reqChannel.SendMessageAsync(ParameterError, embed: DocList.UsageEmbed);
return; return;
} }
@ -78,7 +147,7 @@ namespace BirthdayBot.UserInterface
catch (Exception ex) catch (Exception ex)
{ {
Program.Log("Listing", ex.ToString()); Program.Log("Listing", ex.ToString());
reqChannel.SendMessageAsync(":x: An internal error occurred. It has been reported to the bot owner.").Wait(); reqChannel.SendMessageAsync(InternalError).Wait();
// TODO webhook report // TODO webhook report
} }
finally finally

View file

@ -16,8 +16,7 @@ namespace BirthdayBot.UserInterface
{ {
("set", CmdSet), ("set", CmdSet),
("zone", CmdZone), ("zone", CmdZone),
("remove", CmdRemove), ("remove", CmdRemove)
("when", CmdWhen)
}; };
/// <summary> /// <summary>
@ -27,6 +26,8 @@ namespace BirthdayBot.UserInterface
/// <exception cref="FormatException">Thrown for any parsing issue. Reason is expected to be sent to Discord as-is.</exception> /// <exception cref="FormatException">Thrown for any parsing issue. Reason is expected to be sent to Discord as-is.</exception>
private (int, int) ParseDate(string dateInput) private (int, int) ParseDate(string dateInput)
{ {
const string FormatError = ":x: Incorrect date format. Use a three-letter abbreviation and a number separated by "
+ "hyphen to specify a date. Examples: `jan-15` `23-aug` `may-12` `5-jun`";
// Not doing DateTime.Parse. Setting it up is rather complicated, and it's probably case sensitive. // Not doing DateTime.Parse. Setting it up is rather complicated, and it's probably case sensitive.
// Admittedly, doing it the way it's being done here probably isn't any better. // Admittedly, doing it the way it's being done here probably isn't any better.
var m = Regex.Match(dateInput, @"^(?<day>\d{1,2})-(?<month>[A-Za-z]{3})$"); var m = Regex.Match(dateInput, @"^(?<day>\d{1,2})-(?<month>[A-Za-z]{3})$");
@ -34,7 +35,7 @@ namespace BirthdayBot.UserInterface
{ {
// Flip the fields around, try again // Flip the fields around, try again
m = Regex.Match(dateInput, @"^(?<month>[A-Za-z]{3})-(?<day>\d{1,2})$"); m = Regex.Match(dateInput, @"^(?<month>[A-Za-z]{3})-(?<day>\d{1,2})$");
if (!m.Success) throw new FormatException(GenericError); if (!m.Success) throw new FormatException(FormatError);
} }
int day; int day;
try try
@ -43,7 +44,7 @@ namespace BirthdayBot.UserInterface
} }
catch (FormatException) catch (FormatException)
{ {
throw new Exception(GenericError); throw new Exception(FormatError);
} }
var monthVal = m.Groups["month"].Value; var monthVal = m.Groups["month"].Value;
int month; int month;
@ -99,12 +100,23 @@ namespace BirthdayBot.UserInterface
return (month, day); return (month, day);
} }
#region Documentation
public static readonly CommandDocumentation DocSet =
new CommandDocumentation(new string[] { "set (date) [zone]" }, "Registers your birth date. Time zone is optional.",
$"`{CommandPrefix}set jan-31`, `{CommandPrefix}set 15-aug America/Los_Angeles`.");
public static readonly CommandDocumentation DocZone =
new CommandDocumentation(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);
#endregion
private async Task CmdSet(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser) private async Task CmdSet(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{ {
// Requires one parameter. Optionally two. // Requires one parameter. Optionally two.
if (param.Length < 2 || param.Length > 3) if (param.Length < 2 || param.Length > 3)
{ {
await reqChannel.SendMessageAsync(GenericError); await reqChannel.SendMessageAsync(ParameterError, embed: DocSet.UsageEmbed);
return; return;
} }
@ -120,7 +132,7 @@ namespace BirthdayBot.UserInterface
catch (FormatException ex) catch (FormatException ex)
{ {
// Our parse methods' FormatException has its message to send out to Discord. // Our parse methods' FormatException has its message to send out to Discord.
reqChannel.SendMessageAsync(ex.Message).Wait(); reqChannel.SendMessageAsync(ex.Message, embed: DocSet.UsageEmbed).Wait();
return; return;
} }
@ -135,8 +147,8 @@ namespace BirthdayBot.UserInterface
catch (Exception ex) catch (Exception ex)
{ {
Program.Log("Error", ex.ToString()); Program.Log("Error", ex.ToString());
reqChannel.SendMessageAsync(":x: An unknown error occurred. The bot owner has been notified.").Wait();
// TODO webhook report // TODO webhook report
reqChannel.SendMessageAsync(InternalError).Wait();
return; return;
} }
if (known) if (known)
@ -153,25 +165,26 @@ namespace BirthdayBot.UserInterface
{ {
if (param.Length != 2) if (param.Length != 2)
{ {
await reqChannel.SendMessageAsync(GenericError); await reqChannel.SendMessageAsync(ParameterError, embed: DocZone.UsageEmbed);
return; return;
} }
string btz = null;
var user = Instance.GuildCache[reqChannel.Guild.Id].GetUser(reqUser.Id); var user = Instance.GuildCache[reqChannel.Guild.Id].GetUser(reqUser.Id);
if (!user.IsKnown) if (!user.IsKnown)
{ {
await reqChannel.SendMessageAsync(":x: Can't set your time zone if your birth date isn't registered."); await reqChannel.SendMessageAsync(":x: You may only update your time zone when you have a birthday registered."
+ $" Refer to the `{CommandPrefix}set` command.", embed: DocZone.UsageEmbed);
return; return;
} }
string btz;
try try
{ {
btz = ParseTimeZone(param[1]); btz = ParseTimeZone(param[1]);
} }
catch (Exception ex) catch (Exception ex)
{ {
reqChannel.SendMessageAsync(ex.Message).Wait(); reqChannel.SendMessageAsync(ex.Message, embed: DocZone.UsageEmbed).Wait();
return; return;
} }
await user.UpdateAsync(user.BirthMonth, user.BirthDay, btz, BotConfig.DatabaseSettings); await user.UpdateAsync(user.BirthMonth, user.BirthDay, btz, BotConfig.DatabaseSettings);
@ -184,7 +197,7 @@ namespace BirthdayBot.UserInterface
// Parameter count check // Parameter count check
if (param.Length != 1) if (param.Length != 1)
{ {
await reqChannel.SendMessageAsync(ExpectedNoParametersError); await reqChannel.SendMessageAsync(NoParameterError, embed: DocRemove.UsageEmbed);
return; return;
} }
@ -196,69 +209,12 @@ namespace BirthdayBot.UserInterface
await g.DeleteUserAsync(reqUser.Id); await g.DeleteUserAsync(reqUser.Id);
if (!known) if (!known)
{ {
await reqChannel.SendMessageAsync(":white_check_mark: I don't have your information. Nothing to remove."); await reqChannel.SendMessageAsync(":white_check_mark: This bot already does not contain your information.");
} }
else else
{ {
await reqChannel.SendMessageAsync(":white_check_mark: Your information has been removed."); await reqChannel.SendMessageAsync(":white_check_mark: Your information has been removed.");
} }
} }
private async Task CmdWhen(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
{
// Requires a parameter
if (param.Length == 1)
{
await reqChannel.SendMessageAsync(GenericError);
return;
}
var search = param[1];
if (param.Length == 3)
{
// param maxes out at 3 values. param[2] might contain part of the search string (if name has a space)
search += " " + param[2];
}
SocketGuildUser searchTarget = null;
ulong searchId = 0;
if (!TryGetUserId(search, out searchId)) // ID lookup
{
// name lookup without discriminator
foreach (var searchuser in reqChannel.Guild.Users)
{
if (string.Equals(search, searchuser.Username, StringComparison.OrdinalIgnoreCase))
{
searchTarget = searchuser;
break;
}
}
}
else
{
searchTarget = reqChannel.Guild.GetUser(searchId);
}
if (searchTarget == null)
{
await reqChannel.SendMessageAsync(BadUserError);
return;
}
var users = Instance.GuildCache[reqChannel.Guild.Id].Users;
var searchTargetData = users.FirstOrDefault(u => u.UserId == searchTarget.Id);
if (searchTargetData == null)
{
await reqChannel.SendMessageAsync("The given user does not exist or has not set a birthday.");
return;
}
string result = Common.FormatName(searchTarget, false);
result += ": ";
result += $"`{searchTargetData.BirthDay:00}-{Common.MonthNames[searchTargetData.BirthMonth]}`";
result += searchTargetData.TimeZone == null ? "" : $" - `{searchTargetData.TimeZone}`";
await reqChannel.SendMessageAsync(result);
}
} }
} }