2020-07-22 23:58:38 +00:00
|
|
|
|
using BirthdayBot.Data;
|
|
|
|
|
using Discord.WebSocket;
|
2020-04-02 18:27:55 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace BirthdayBot.UserInterface
|
|
|
|
|
{
|
|
|
|
|
internal class UserCommands : CommandsCommon
|
|
|
|
|
{
|
2020-10-05 04:40:38 +00:00
|
|
|
|
public UserCommands(Configuration db) : base(db) { }
|
2020-04-02 18:27:55 +00:00
|
|
|
|
|
|
|
|
|
public override IEnumerable<(string, CommandHandler)> Commands
|
|
|
|
|
=> new List<(string, CommandHandler)>()
|
|
|
|
|
{
|
|
|
|
|
("set", CmdSet),
|
|
|
|
|
("zone", CmdZone),
|
2020-06-04 01:58:03 +00:00
|
|
|
|
("remove", CmdRemove)
|
2020-04-02 18:27:55 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-02-04 05:55:37 +00:00
|
|
|
|
#region Date parsing
|
|
|
|
|
const string FormatError = ":x: Unrecognized date format. The following formats are accepted, as examples: "
|
|
|
|
|
+ "`15-jan`, `jan-15`, `15 jan`, `jan 15`, `15 January`, `January 15`.";
|
|
|
|
|
|
|
|
|
|
private static readonly Regex DateParse1 = new Regex(@"^(?<day>\d{1,2})[ -](?<month>[A-Za-z]+)$", RegexOptions.Compiled);
|
|
|
|
|
private static readonly Regex DateParse2 = new Regex(@"^(?<month>[A-Za-z]+)[ -](?<day>\d{1,2})$", RegexOptions.Compiled);
|
|
|
|
|
|
2020-04-02 18:27:55 +00:00
|
|
|
|
/// <summary>
|
2021-02-04 05:55:37 +00:00
|
|
|
|
/// Parses a date input.
|
2020-04-02 18:27:55 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Tuple: month, day</returns>
|
2021-02-04 05:55:37 +00:00
|
|
|
|
/// <exception cref="FormatException">
|
|
|
|
|
/// Thrown for any parsing issue. Reason is expected to be sent to Discord as-is.
|
|
|
|
|
/// </exception>
|
2020-04-02 18:27:55 +00:00
|
|
|
|
private (int, int) ParseDate(string dateInput)
|
|
|
|
|
{
|
2021-02-04 05:55:37 +00:00
|
|
|
|
var m = DateParse1.Match(dateInput);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
if (!m.Success)
|
|
|
|
|
{
|
|
|
|
|
// Flip the fields around, try again
|
2021-02-04 05:55:37 +00:00
|
|
|
|
m = DateParse2.Match(dateInput);
|
2020-06-04 01:58:03 +00:00
|
|
|
|
if (!m.Success) throw new FormatException(FormatError);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
2021-02-04 05:55:37 +00:00
|
|
|
|
|
|
|
|
|
int day, month;
|
|
|
|
|
string monthVal;
|
2020-04-02 18:27:55 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
day = int.Parse(m.Groups["day"].Value);
|
|
|
|
|
}
|
|
|
|
|
catch (FormatException)
|
|
|
|
|
{
|
2020-06-04 01:58:03 +00:00
|
|
|
|
throw new Exception(FormatError);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
2021-02-04 05:55:37 +00:00
|
|
|
|
monthVal = m.Groups["month"].Value;
|
|
|
|
|
|
|
|
|
|
int dayUpper; // upper day of month check
|
|
|
|
|
(month, dayUpper) = GetMonth(monthVal);
|
|
|
|
|
|
|
|
|
|
if (day == 0 || day > dayUpper) throw new FormatException(":x: The date you specified is not a valid calendar date.");
|
|
|
|
|
|
|
|
|
|
return (month, day);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns information for a given month input.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
/// <returns>Tuple: Month value, upper limit of days in the month</returns>
|
|
|
|
|
/// <exception cref="FormatException">
|
|
|
|
|
/// Thrown on error. Send out to Discord as-is.
|
|
|
|
|
/// </exception>
|
|
|
|
|
private (int, int) GetMonth(string input)
|
|
|
|
|
{
|
|
|
|
|
switch (input.ToLower())
|
2020-04-02 18:27:55 +00:00
|
|
|
|
{
|
|
|
|
|
case "jan":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "january":
|
|
|
|
|
return (1, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "feb":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "february":
|
|
|
|
|
return (2, 29);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "mar":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "march":
|
|
|
|
|
return (3, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "apr":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "april":
|
|
|
|
|
return (4, 30);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "may":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
return (5, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "jun":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "june":
|
|
|
|
|
return (6, 30);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "jul":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "july":
|
|
|
|
|
return (7, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "aug":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "august":
|
|
|
|
|
return (8, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "sep":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "september":
|
|
|
|
|
return (9, 30);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "oct":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "october":
|
|
|
|
|
return (10, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "nov":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "november":
|
|
|
|
|
return (11, 30);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
case "dec":
|
2021-02-04 05:55:37 +00:00
|
|
|
|
case "december":
|
|
|
|
|
return (12, 31);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
default:
|
2021-02-04 05:55:37 +00:00
|
|
|
|
throw new FormatException($":x: Can't determine month name `{input}`. Check your spelling and try again.");
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-04 05:55:37 +00:00
|
|
|
|
#endregion
|
2020-04-02 18:27:55 +00:00
|
|
|
|
|
2020-06-04 01:58:03 +00:00
|
|
|
|
#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
|
|
|
|
|
|
2020-10-05 04:40:38 +00:00
|
|
|
|
private async Task CmdSet(ShardInstance instance, GuildConfiguration gconf,
|
|
|
|
|
string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
|
2020-04-02 18:27:55 +00:00
|
|
|
|
{
|
2021-02-04 05:55:37 +00:00
|
|
|
|
// Requires *some* parameter.
|
|
|
|
|
if (param.Length < 2)
|
2020-04-02 18:27:55 +00:00
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(ParameterError, embed: DocSet.UsageEmbed).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-04 05:55:37 +00:00
|
|
|
|
// Date format accepts spaces, and then we look for an additional parameter.
|
|
|
|
|
// This is weird. Gotta compensate.
|
|
|
|
|
var fullinput = "";
|
|
|
|
|
for (int i = 1; i < param.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
fullinput += " " + param[i];
|
|
|
|
|
}
|
|
|
|
|
fullinput = fullinput[1..];
|
|
|
|
|
// Attempt to get last "parameter"; check if it's a time zone value
|
|
|
|
|
string timezone = null;
|
|
|
|
|
var fli = fullinput.LastIndexOf(' ');
|
|
|
|
|
if (fli != -1)
|
|
|
|
|
{
|
|
|
|
|
var tzstring = fullinput[(fli+1)..];
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
timezone = ParseTimeZone(tzstring);
|
|
|
|
|
// If we got here, last parameter was indeed a time zone. Trim it away for what comes next.
|
|
|
|
|
fullinput = fullinput[0..fli];
|
|
|
|
|
}
|
|
|
|
|
catch (FormatException) { } // Was not a time zone name. Do nothing.
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-02 18:27:55 +00:00
|
|
|
|
int bmonth, bday;
|
|
|
|
|
try
|
|
|
|
|
{
|
2021-02-04 05:55:37 +00:00
|
|
|
|
(bmonth, bday) = ParseDate(fullinput);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
catch (FormatException ex)
|
|
|
|
|
{
|
2021-02-04 05:55:37 +00:00
|
|
|
|
// Our parse method's FormatException has its message to send out to Discord.
|
2020-06-04 01:58:03 +00:00
|
|
|
|
reqChannel.SendMessageAsync(ex.Message, embed: DocSet.UsageEmbed).Wait();
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parsing successful. Update user information.
|
|
|
|
|
bool known; // Extra detail: Bot's response changes if the user was previously unknown.
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
var user = await GuildUserConfiguration.LoadAsync(gconf.GuildId, reqUser.Id).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
known = user.IsKnown;
|
2021-02-04 05:55:37 +00:00
|
|
|
|
await user.UpdateAsync(bmonth, bday, timezone).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Program.Log("Error", ex.ToString());
|
2020-05-22 07:27:31 +00:00
|
|
|
|
// TODO webhook report
|
2020-06-04 01:58:03 +00:00
|
|
|
|
reqChannel.SendMessageAsync(InternalError).Wait();
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (known)
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(":white_check_mark: Your information has been updated.")
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(":white_check_mark: Your birthday has been recorded.")
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 04:40:38 +00:00
|
|
|
|
private async Task CmdZone(ShardInstance instance, GuildConfiguration gconf,
|
|
|
|
|
string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
|
2020-04-02 18:27:55 +00:00
|
|
|
|
{
|
|
|
|
|
if (param.Length != 2)
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(ParameterError, embed: DocZone.UsageEmbed).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-10 07:28:11 +00:00
|
|
|
|
var user = await GuildUserConfiguration.LoadAsync(gconf.GuildId, reqUser.Id).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
if (!user.IsKnown)
|
|
|
|
|
{
|
2020-06-04 01:58:03 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(":x: You may only update your time zone when you have a birthday registered."
|
2020-10-10 07:28:11 +00:00
|
|
|
|
+ $" Refer to the `{CommandPrefix}set` command.", embed: DocZone.UsageEmbed)
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 01:58:03 +00:00
|
|
|
|
string btz;
|
2020-04-02 18:27:55 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
btz = ParseTimeZone(param[1]);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2020-06-04 01:58:03 +00:00
|
|
|
|
reqChannel.SendMessageAsync(ex.Message, embed: DocZone.UsageEmbed).Wait();
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await user.UpdateAsync(user.BirthMonth, user.BirthDay, btz).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync($":white_check_mark: Your time zone has been updated to **{btz}**.")
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 04:40:38 +00:00
|
|
|
|
private async Task CmdRemove(ShardInstance instance, GuildConfiguration gconf,
|
|
|
|
|
string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
|
2020-04-02 18:27:55 +00:00
|
|
|
|
{
|
|
|
|
|
// Parameter count check
|
|
|
|
|
if (param.Length != 1)
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(NoParameterError, embed: DocRemove.UsageEmbed).ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extra detail: Send a notification if the user isn't actually known by the bot.
|
|
|
|
|
bool known;
|
2020-10-10 07:28:11 +00:00
|
|
|
|
var u = await GuildUserConfiguration.LoadAsync(gconf.GuildId, reqUser.Id).ConfigureAwait(false);
|
2020-07-22 23:58:38 +00:00
|
|
|
|
known = u.IsKnown;
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await u.DeleteAsync().ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
if (!known)
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(":white_check_mark: This bot already does not contain your information.")
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-10-10 07:28:11 +00:00
|
|
|
|
await reqChannel.SendMessageAsync(":white_check_mark: Your information has been removed.")
|
|
|
|
|
.ConfigureAwait(false);
|
2020-04-02 18:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|