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]
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 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);
@ -68,7 +71,8 @@ namespace BirthdayBot.UserInterface
// Just check if the input exists in the map. Get the "true" value, or reject it altogether.
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;

View file

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

View file

@ -21,15 +21,84 @@ namespace BirthdayBot.UserInterface
{
("list", CmdList),
("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.
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.
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.");
return;
}
@ -41,13 +110,13 @@ namespace BirthdayBot.UserInterface
if (param[1].ToLower() == "csv") useCsv = true;
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;
}
}
else if (param.Length > 2)
{
await reqChannel.SendMessageAsync(GenericError);
await reqChannel.SendMessageAsync(ParameterError, embed: DocList.UsageEmbed);
return;
}
@ -78,7 +147,7 @@ namespace BirthdayBot.UserInterface
catch (Exception ex)
{
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
}
finally

View file

@ -16,8 +16,7 @@ namespace BirthdayBot.UserInterface
{
("set", CmdSet),
("zone", CmdZone),
("remove", CmdRemove),
("when", CmdWhen)
("remove", CmdRemove)
};
/// <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>
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.
// 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})$");
@ -34,7 +35,7 @@ namespace BirthdayBot.UserInterface
{
// Flip the fields around, try again
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;
try
@ -43,7 +44,7 @@ namespace BirthdayBot.UserInterface
}
catch (FormatException)
{
throw new Exception(GenericError);
throw new Exception(FormatError);
}
var monthVal = m.Groups["month"].Value;
int month;
@ -99,12 +100,23 @@ namespace BirthdayBot.UserInterface
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)
{
// Requires one parameter. Optionally two.
if (param.Length < 2 || param.Length > 3)
{
await reqChannel.SendMessageAsync(GenericError);
await reqChannel.SendMessageAsync(ParameterError, embed: DocSet.UsageEmbed);
return;
}
@ -120,7 +132,7 @@ namespace BirthdayBot.UserInterface
catch (FormatException ex)
{
// 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;
}
@ -135,8 +147,8 @@ namespace BirthdayBot.UserInterface
catch (Exception ex)
{
Program.Log("Error", ex.ToString());
reqChannel.SendMessageAsync(":x: An unknown error occurred. The bot owner has been notified.").Wait();
// TODO webhook report
reqChannel.SendMessageAsync(InternalError).Wait();
return;
}
if (known)
@ -153,25 +165,26 @@ namespace BirthdayBot.UserInterface
{
if (param.Length != 2)
{
await reqChannel.SendMessageAsync(GenericError);
await reqChannel.SendMessageAsync(ParameterError, embed: DocZone.UsageEmbed);
return;
}
string btz = null;
var user = Instance.GuildCache[reqChannel.Guild.Id].GetUser(reqUser.Id);
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;
}
string btz;
try
{
btz = ParseTimeZone(param[1]);
}
catch (Exception ex)
{
reqChannel.SendMessageAsync(ex.Message).Wait();
reqChannel.SendMessageAsync(ex.Message, embed: DocZone.UsageEmbed).Wait();
return;
}
await user.UpdateAsync(user.BirthMonth, user.BirthDay, btz, BotConfig.DatabaseSettings);
@ -184,7 +197,7 @@ namespace BirthdayBot.UserInterface
// Parameter count check
if (param.Length != 1)
{
await reqChannel.SendMessageAsync(ExpectedNoParametersError);
await reqChannel.SendMessageAsync(NoParameterError, embed: DocRemove.UsageEmbed);
return;
}
@ -196,69 +209,12 @@ namespace BirthdayBot.UserInterface
await g.DeleteUserAsync(reqUser.Id);
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
{
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);
}
}
}