mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-21 21:54:36 +00:00
Add currently completed commands
This commit is contained in:
parent
b0c5ea4e3b
commit
d27663a20a
6 changed files with 242 additions and 39 deletions
|
@ -1,6 +1,7 @@
|
||||||
using BirthdayBot.Data;
|
using BirthdayBot.Data;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace BirthdayBot.ApplicationCommands;
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
|
||||||
|
@ -10,8 +11,9 @@ namespace BirthdayBot.ApplicationCommands;
|
||||||
internal abstract class BotApplicationCommand {
|
internal abstract class BotApplicationCommand {
|
||||||
public delegate Task CommandResponder(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg);
|
public delegate Task CommandResponder(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg);
|
||||||
|
|
||||||
|
protected const string HelpPfxModOnly = "Bot moderators only: ";
|
||||||
protected const string ErrGuildOnly = ":x: This command can only be run within a server.";
|
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 ErrNotAllowed = ":x: Only server moderators may use this command.";
|
||||||
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
||||||
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
|
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ internal abstract class BotApplicationCommand {
|
||||||
protected static string ParseTimeZone(string tzinput) {
|
protected static string ParseTimeZone(string tzinput) {
|
||||||
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 `INSERT COMMAND NAME HERE` to help determine the correct value."); // TODO fix!!!!!!!!!!!!!!!!!!!
|
+ $" Refer to `INSERT COMMAND NAME HERE` to help determine the correct value."); // TODO fix!!!!!!!!!!!!!!!!!!!
|
||||||
|
// put link to tz finder -and- refer to command for elaborate info
|
||||||
return tz!;
|
return tz!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,4 +66,70 @@ internal abstract class BotApplicationCommand {
|
||||||
await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false);
|
await Task.Factory.StartNew(guild.DownloadUsersAsync).ConfigureAwait(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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(@"^(?<day>\d{1,2})[ -](?<month>[A-Za-z]+)$", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex DateParse2 = new(@"^(?<month>[A-Za-z]+)[ -](?<day>\d{1,2})$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a date input.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Tuple: month, day</returns>
|
||||||
|
/// <exception cref="FormatException">
|
||||||
|
/// Thrown for any parsing issue. Reason is expected to be sent to Discord as-is.
|
||||||
|
/// </exception>
|
||||||
|
protected static (int, int) ParseDate(string dateInput) {
|
||||||
|
var m = DateParse1.Match(dateInput);
|
||||||
|
if (!m.Success) {
|
||||||
|
// Flip the fields around, try again
|
||||||
|
m = DateParse2.Match(dateInput);
|
||||||
|
if (!m.Success) throw new FormatException(FormatError);
|
||||||
|
}
|
||||||
|
|
||||||
|
int day, month;
|
||||||
|
string monthVal;
|
||||||
|
try {
|
||||||
|
day = int.Parse(m.Groups["day"].Value);
|
||||||
|
} catch (FormatException) {
|
||||||
|
throw new Exception(FormatError);
|
||||||
|
}
|
||||||
|
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 static (int, int) GetMonth(string input) {
|
||||||
|
return input.ToLower() switch {
|
||||||
|
"jan" or "january" => (1, 31),
|
||||||
|
"feb" or "february" => (2, 29),
|
||||||
|
"mar" or "march" => (3, 31),
|
||||||
|
"apr" or "april" => (4, 30),
|
||||||
|
"may" => (5, 31),
|
||||||
|
"jun" or "june" => (6, 30),
|
||||||
|
"jul" or "july" => (7, 31),
|
||||||
|
"aug" or "august" => (8, 31),
|
||||||
|
"sep" or "september" => (9, 30),
|
||||||
|
"oct" or "october" => (10, 31),
|
||||||
|
"nov" or "november" => (11, 30),
|
||||||
|
"dec" or "december" => (12, 31),
|
||||||
|
_ => throw new FormatException($":x: Can't determine month name `{input}`. Check your spelling and try again."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
52
ApplicationCommands/HelpCommands.cs
Normal file
52
ApplicationCommands/HelpCommands.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
using BirthdayBot.Data;
|
||||||
|
|
||||||
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
|
||||||
|
internal class HelpCommands : BotApplicationCommand {
|
||||||
|
private static readonly EmbedFieldBuilder _helpEmbedRegCommandsField;
|
||||||
|
private static readonly EmbedFieldBuilder _helpEmbedModCommandsField;
|
||||||
|
|
||||||
|
static HelpCommands() {
|
||||||
|
_helpEmbedRegCommandsField = new EmbedFieldBuilder() {
|
||||||
|
Name = "Commands",
|
||||||
|
Value = $"`/set-birthday` - {RegistrationCommands.HelpSet}\n"
|
||||||
|
+ $"`/set-timezone` - {RegistrationCommands.HelpZone}\n"
|
||||||
|
+ $"`/remove-timezone` - {RegistrationCommands.HelpZoneDel}\n"
|
||||||
|
+ $"`/remove-birthday` - {RegistrationCommands.HelpDel}"
|
||||||
|
};
|
||||||
|
_helpEmbedModCommandsField = new EmbedFieldBuilder() {
|
||||||
|
Name = "Moderator commands",
|
||||||
|
Value = $"`/override` - {RegistrationOverrideCommands.HelpOverride}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<ApplicationCommandProperties> GetCommands() => new ApplicationCommandProperties[] {
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("help").WithDescription("Show an overview of available commands.").Build()
|
||||||
|
};
|
||||||
|
public override CommandResponder? GetHandlerFor(string commandName) => commandName switch {
|
||||||
|
"help" => CmdHelp,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task CmdHelp(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
string ver =
|
||||||
|
#if DEBUG
|
||||||
|
"DEBUG flag set";
|
||||||
|
#else
|
||||||
|
"v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version!.ToString(3);
|
||||||
|
#endif
|
||||||
|
var result = new EmbedBuilder()
|
||||||
|
.WithAuthor("Help & About")
|
||||||
|
.WithFooter($"Birthday Bot {ver} - Shard {instance.ShardId:00} up {Program.BotUptime}",
|
||||||
|
instance.DiscordClient.CurrentUser.GetAvatarUrl())
|
||||||
|
.WithDescription("Support, data policy, etc: https://noithecat.dev/bots/BirthdayBot\n"
|
||||||
|
+ "This bot is provided for free, without any paywalls or exclusive paid features. If this bot has been useful to you, "
|
||||||
|
+ "please consider taking a look at the author's Ko-fi: https://ko-fi.com/noithecat.\n"
|
||||||
|
+ "Thank you for using Birthday Bot!")
|
||||||
|
.AddField(_helpEmbedRegCommandsField)
|
||||||
|
.AddField(_helpEmbedModCommandsField)
|
||||||
|
.Build();
|
||||||
|
await arg.RespondAsync(embed: result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
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?");
|
|
||||||
}
|
|
||||||
}
|
|
115
ApplicationCommands/RegistrationCommands.cs
Normal file
115
ApplicationCommands/RegistrationCommands.cs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
using BirthdayBot.Data;
|
||||||
|
|
||||||
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
|
||||||
|
internal class RegistrationCommands : BotApplicationCommand {
|
||||||
|
#region Help strings
|
||||||
|
public const string HelpSet = "Sets or updates your birthday.";
|
||||||
|
public const string HelpZone = "Sets or updates your time zone. For use only if you have already set a birthday.";
|
||||||
|
public const string HelpZoneDel = "Removes your time zone information from the bot.";
|
||||||
|
public const string HelpDel = "Removes your birthday information from the bot.";
|
||||||
|
|
||||||
|
public const string HelpOptDate = "A date, including the month and day. For example, \"15 January\".";
|
||||||
|
public const string HelpOptZone = "A 'tzdata'-compliant time zone name. See help for more details.";
|
||||||
|
|
||||||
|
const string MsgNoData = "This bot does not have your birthday information for this server.";
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override IEnumerable<ApplicationCommandProperties> GetCommands() => new ApplicationCommandProperties[] {
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("set-birthday")
|
||||||
|
.WithDescription(HelpSet)
|
||||||
|
.AddOption("date", ApplicationCommandOptionType.String, HelpOptDate, isRequired: true)
|
||||||
|
.Build(),
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("set-timezone")
|
||||||
|
.WithDescription(HelpZone)
|
||||||
|
.AddOption("zone", ApplicationCommandOptionType.String, HelpOptZone, isRequired: true)
|
||||||
|
.Build(),
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("remove-timezone")
|
||||||
|
.WithDescription(HelpZoneDel)
|
||||||
|
.AddOption("zone", ApplicationCommandOptionType.String, HelpOptZone, isRequired: true)
|
||||||
|
.Build(),
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.WithName("remove-birthday")
|
||||||
|
.WithDescription(HelpDel)
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
public override CommandResponder? GetHandlerFor(string commandName) => commandName switch {
|
||||||
|
"set-birthday" => CmdSetBirthday,
|
||||||
|
"set-timezone" => CmdSetTimezone,
|
||||||
|
"remove-timezone" => CmdDelTimezone,
|
||||||
|
"remove-birthday" => CmdRemove,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
private static async Task CmdSetBirthday(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
int inmonth, inday;
|
||||||
|
try {
|
||||||
|
(inmonth, inday) = ParseDate((string)arg.Data.Options.First().Value);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
// Our parse method's FormatException has its message to send out to Discord.
|
||||||
|
arg.RespondAsync(e.Message).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool known;
|
||||||
|
try {
|
||||||
|
var user = await GuildUserConfiguration.LoadAsync(gconf.GuildId, arg.User.Id).ConfigureAwait(false);
|
||||||
|
known = user.IsKnown;
|
||||||
|
await user.UpdateAsync(inmonth, inday, user.TimeZone).ConfigureAwait(false);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Program.Log("Error", ex.ToString());
|
||||||
|
arg.RespondAsync(ShardInstance.InternalError).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await arg.RespondAsync(":white_check_mark: Your birthday has been " +
|
||||||
|
$"{ (known ? "updated to" : "recorded as") } **{inday:00}-{Common.MonthNames[inmonth]}**.").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CmdSetTimezone(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
var user = await GuildUserConfiguration.LoadAsync(gconf.GuildId, arg.User.Id).ConfigureAwait(false);
|
||||||
|
if (!user.IsKnown) {
|
||||||
|
await arg.RespondAsync(":x: You must have a birthday set before you can use this command.",
|
||||||
|
ephemeral: true).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool hasZone = user.TimeZone != null;
|
||||||
|
|
||||||
|
string inZone;
|
||||||
|
try {
|
||||||
|
inZone = ParseTimeZone((string)arg.Data.Options.First().Value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
arg.RespondAsync(e.Message).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await user.UpdateAsync(user.BirthMonth, user.BirthDay, inZone).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await arg.RespondAsync($":white_check_mark: Your time zone has been { (hasZone ? "updated" : "set") } to **{inZone}**.")
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CmdDelTimezone(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
var u = await GuildUserConfiguration.LoadAsync(gconf.GuildId, arg.User.Id).ConfigureAwait(false);
|
||||||
|
if (!u.IsKnown) {
|
||||||
|
await arg.RespondAsync(":white_check_mark: " + MsgNoData);
|
||||||
|
} else if (u.TimeZone is null) {
|
||||||
|
await arg.RespondAsync(":white_check_mark: You do not have any time zone information.");
|
||||||
|
} else {
|
||||||
|
await u.UpdateAsync(u.BirthMonth, u.BirthDay, null);
|
||||||
|
await arg.RespondAsync(":white_check_mark: Your time zone information has been removed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CmdRemove(ShardInstance instance, GuildConfiguration gconf, SocketSlashCommand arg) {
|
||||||
|
var u = await GuildUserConfiguration.LoadAsync(gconf.GuildId, arg.User.Id).ConfigureAwait(false);
|
||||||
|
if (u.IsKnown) {
|
||||||
|
await u.DeleteAsync().ConfigureAwait(false);
|
||||||
|
await arg.RespondAsync(":white_check_mark: Your birthday information has been removed.");
|
||||||
|
} else {
|
||||||
|
await arg.RespondAsync(":white_check_mark: " + MsgNoData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using NpgsqlTypes;
|
using NpgsqlTypes;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace BirthdayBot.Data;
|
namespace BirthdayBot.Data;
|
||||||
|
|
||||||
|
@ -23,7 +20,7 @@ class GuildUserConfiguration {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BirthDay { get; private set; }
|
public int BirthDay { get; private set; }
|
||||||
|
|
||||||
public string TimeZone { get; private set; }
|
public string? TimeZone { get; private set; }
|
||||||
public bool IsKnown { get { return BirthMonth != 0 && BirthDay != 0; } }
|
public bool IsKnown { get { return BirthMonth != 0 && BirthDay != 0; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -47,7 +44,7 @@ class GuildUserConfiguration {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates user with given information.
|
/// Updates user with given information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task UpdateAsync(int month, int day, string newtz) {
|
public async Task UpdateAsync(int month, int day, string? newtz) {
|
||||||
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false)) {
|
using (var db = await Database.OpenConnectionAsync().ConfigureAwait(false)) {
|
||||||
using var c = db.CreateCommand();
|
using var c = db.CreateCommand();
|
||||||
c.CommandText = $"insert into {BackingTable} "
|
c.CommandText = $"insert into {BackingTable} "
|
||||||
|
|
|
@ -72,7 +72,9 @@ class ShardManager : IDisposable {
|
||||||
|
|
||||||
_appCommands = new List<BotApplicationCommand>() {
|
_appCommands = new List<BotApplicationCommand>() {
|
||||||
// TODO fill this out
|
// TODO fill this out
|
||||||
new ApplicationCommands.HelpInfoCommands()
|
new HelpCommands(),
|
||||||
|
new RegistrationCommands(),
|
||||||
|
new RegistrationOverrideCommands()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allocate shards based on configuration
|
// Allocate shards based on configuration
|
||||||
|
|
Loading…
Reference in a new issue