Update commands to use EF queries

This commit is contained in:
Noi 2022-03-20 01:07:17 -07:00
parent 67decd6fb4
commit b1b7c60211
4 changed files with 164 additions and 95 deletions

View file

@ -41,8 +41,13 @@ public class BirthdayModule : BotModuleBase {
}
}
var user = await ((SocketGuildUser)Context.User).GetConfigAsync().ConfigureAwait(false);
await user.UpdateAsync(inmonth, inday, inzone ?? user.TimeZone).ConfigureAwait(false);
using var db = new BotDatabaseContext();
var user = ((SocketGuildUser)Context.User).GetUserEntryOrNew(db);
if (user.IsNew) db.UserEntries.Add(user);
user.BirthMonth = inmonth;
user.BirthDay = inday;
user.TimeZone = inzone;
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: Your birthday has been set to **{FormatDate(inmonth, inday)}**" +
(inzone == null ? "" : $", with time zone {inzone}") + ".").ConfigureAwait(false);
@ -50,31 +55,35 @@ public class BirthdayModule : BotModuleBase {
[SlashCommand("timezone", HelpCmdSetZone)]
public async Task CmdSetZone([Summary(description: HelpOptZone)] string zone) {
var user = await ((SocketGuildUser)Context.User).GetConfigAsync().ConfigureAwait(false);
if (!user.IsKnown) {
using var db = new BotDatabaseContext();
var user = ((SocketGuildUser)Context.User).GetUserEntryOrNew(db);
if (user.IsNew) {
await RespondAsync(":x: You do not have a birthday set.", ephemeral: true).ConfigureAwait(false);
return;
}
string inzone;
string newzone;
try {
inzone = ParseTimeZone(zone);
newzone = ParseTimeZone(zone);
} catch (FormatException e) {
await RespondAsync(e.Message, ephemeral: true).ConfigureAwait(false);
return;
}
await user.UpdateAsync(user.BirthMonth, user.BirthDay, inzone).ConfigureAwait(false);
await RespondAsync($":white_check_mark: Your time zone has been set to **{inzone}**.").ConfigureAwait(false);
user.TimeZone = newzone;
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: Your time zone has been set to **{newzone}**.").ConfigureAwait(false);
}
}
[SlashCommand("remove", HelpCmdRemove)]
public async Task CmdRemove() {
var user = await ((SocketGuildUser)Context.User).GetConfigAsync().ConfigureAwait(false);
if (user.IsKnown) {
await user.DeleteAsync().ConfigureAwait(false);
await RespondAsync(":white_check_mark: Your birthday in this server has been removed.")
.ConfigureAwait(false);
using var db = new BotDatabaseContext();
var user = ((SocketGuildUser)Context.User).GetUserEntryOrNew(db);
if (!user.IsNew) {
db.UserEntries.Remove(user);
await db.SaveChangesAsync();
await RespondAsync(":white_check_mark: Your birthday in this server has been removed.");
} else {
await RespondAsync(":white_check_mark: Your birthday is not registered.")
.ConfigureAwait(false);
@ -83,12 +92,15 @@ public class BirthdayModule : BotModuleBase {
[SlashCommand("get", "Gets a user's birthday.")]
public async Task CmdGetBday([Summary(description: "Optional: The user's birthday to look up.")] SocketGuildUser? user = null) {
var self = user is null;
if (self) user = (SocketGuildUser)Context.User;
var targetdata = await user!.GetConfigAsync().ConfigureAwait(false);
using var db = new BotDatabaseContext();
if (!targetdata.IsKnown) {
if (self) await RespondAsync(":x: You do not have your birthday registered.", ephemeral: true).ConfigureAwait(false);
var isSelf = user is null;
if (isSelf) user = (SocketGuildUser)Context.User;
var targetdata = user!.GetUserEntryOrNew(db);
if (targetdata.IsNew) {
if (isSelf) await RespondAsync(":x: You do not have your birthday registered.", ephemeral: true).ConfigureAwait(false);
else await RespondAsync(":x: The given user does not have their birthday registered.", ephemeral: true).ConfigureAwait(false);
return;
}
@ -111,7 +123,7 @@ public class BirthdayModule : BotModuleBase {
var search = DateIndex(now.Month, now.Day) - 8; // begin search 8 days prior to current date UTC
if (search <= 0) search = 366 - Math.Abs(search);
var query = await GetSortedUsersAsync(Context.Guild).ConfigureAwait(false);
var query = GetSortedUserList(Context.Guild);
// TODO pagination instead of this workaround
bool hasOutputOneLine = false;
@ -182,7 +194,7 @@ public class BirthdayModule : BotModuleBase {
return;
}
var bdlist = await GetSortedUsersAsync(Context.Guild).ConfigureAwait(false);
var bdlist = GetSortedUserList(Context.Guild);
var filename = "birthdaybot-" + Context.Guild.Id;
Stream fileoutput;
@ -201,27 +213,26 @@ public class BirthdayModule : BotModuleBase {
/// Fetches all guild birthdays and places them into an easily usable structure.
/// Users currently not in the guild are not included in the result.
/// </summary>
private static async Task<List<ListItem>> GetSortedUsersAsync(SocketGuild guild) {
using var db = await Database.OpenConnectionAsync();
using var c = db.CreateCommand();
c.CommandText = "select user_id, birth_month, birth_day from " + GuildUserConfiguration.BackingTable
+ " where guild_id = @Gid order by birth_month, birth_day";
c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guild.Id;
c.Prepare();
using var r = await c.ExecuteReaderAsync();
var result = new List<ListItem>();
while (await r.ReadAsync()) {
var id = (ulong)r.GetInt64(0);
var month = r.GetInt32(1);
var day = r.GetInt32(2);
private static List<ListItem> GetSortedUserList(SocketGuild guild) {
using var db = new BotDatabaseContext();
var query = from row in db.UserEntries
where row.GuildId == (long)guild.Id
orderby row.BirthMonth, row.BirthDay
select new {
UserId = (ulong)row.UserId,
Month = row.BirthMonth,
Day = row.BirthDay
};
var guildUser = guild.GetUser(id);
var result = new List<ListItem>();
foreach (var row in query) {
var guildUser = guild.GetUser(row.UserId);
if (guildUser == null) continue; // Skip user not in guild
result.Add(new ListItem() {
BirthMonth = month,
BirthDay = day,
DateIndex = DateIndex(month, day),
BirthMonth = row.Month,
BirthDay = row.Day,
DateIndex = DateIndex(row.Month, row.Day),
UserId = guildUser.Id,
DisplayName = Common.FormatName(guildUser, false)
});

View file

@ -24,8 +24,12 @@ public class BirthdayOverrideModule : BotModuleBase {
return;
}
var user = await target.GetConfigAsync().ConfigureAwait(false);
await user.UpdateAsync(inmonth, inday, user.TimeZone).ConfigureAwait(false);
using var db = new BotDatabaseContext();
var user = target.GetUserEntryOrNew(db);
if (user.IsNew) db.UserEntries.Add(user);
user.BirthMonth = inmonth;
user.BirthDay = inday;
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: {Common.FormatName(target, false)}'s birthday has been set to " +
$"**{FormatDate(inmonth, inday)}**.").ConfigureAwait(false);
@ -34,30 +38,35 @@ public class BirthdayOverrideModule : BotModuleBase {
[SlashCommand("set-timezone", HelpPfxModOnly + "Set a user's time zone on their behalf.")]
public async Task OvSetTimezone([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
[Summary(description: HelpOptZone)]string zone) {
var user = await target.GetConfigAsync().ConfigureAwait(false);
if (!user.IsKnown) {
using var db = new BotDatabaseContext();
var user = target.GetUserEntryOrNew(db);
if (user.IsNew) {
await RespondAsync($":x: {Common.FormatName(target, false)} does not have a birthday set.")
.ConfigureAwait(false);
return;
}
string inzone;
string newzone;
try {
inzone = ParseTimeZone(zone);
newzone = ParseTimeZone(zone);
} catch (FormatException e) {
await RespondAsync(e.Message, ephemeral: true).ConfigureAwait(false);
return;
}
await user.UpdateAsync(user.BirthMonth, user.BirthDay, inzone).ConfigureAwait(false);
user.TimeZone = newzone;
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: {Common.FormatName(target, false)}'s time zone has been set to " +
$"**{inzone}**.").ConfigureAwait(false);
$"**{newzone}**.").ConfigureAwait(false);
}
[SlashCommand("remove-birthday", HelpPfxModOnly + "Remove a user's birthday information on their behalf.")]
public async Task OvRemove([Summary(description: HelpOptOvTarget)]SocketGuildUser target) {
var user = await target.GetConfigAsync().ConfigureAwait(false);
if (user.IsKnown) {
await user.DeleteAsync().ConfigureAwait(false);
using var db = new BotDatabaseContext();
var user = ((SocketGuildUser)Context.User).GetUserEntryOrNew(db);
if (!user.IsNew) {
db.UserEntries.Remove(user);
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: {Common.FormatName(target, false)}'s birthday in this server has been removed.")
.ConfigureAwait(false);
} else {

View file

@ -53,9 +53,7 @@ public class ConfigModule : BotModuleBase {
[SlashCommand("set-channel", HelpPfxModOnly + HelpSubCmdChannel + HelpPofxBlankUnset)]
public async Task CmdSetChannel([Summary(description: HelpOptRole)] SocketTextChannel? channel = null) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
gconf.AnnounceChannelId = channel?.Id;
await gconf.UpdateAsync().ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.ChannelAnnounceId = (long?)channel?.Id);
await RespondAsync(":white_check_mark: The announcement channel has been " +
(channel == null ? "unset." : $"set to **{channel.Name}**."));
}
@ -71,9 +69,7 @@ public class ConfigModule : BotModuleBase {
[SlashCommand("set-ping", HelpPfxModOnly + HelpSubCmdPing)]
public async Task CmdSetPing([Summary(description: "Set True to ping users, False to display them normally.")]bool option) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
gconf.AnnouncePing = option;
await gconf.UpdateAsync().ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.AnnouncePing = option);
await RespondAsync($":white_check_mark: Announcement pings are now **{(option ? "on" : "off")}**.").ConfigureAwait(false);
}
}
@ -82,17 +78,13 @@ public class ConfigModule : BotModuleBase {
public class SubCmdsConfigRole : BotModuleBase {
[SlashCommand("set-birthday-role", HelpPfxModOnly + "Set the role given to users having a birthday.")]
public async Task CmdSetBRole([Summary(description: HelpOptRole)] SocketRole role) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
gconf.RoleId = role.Id;
await gconf.UpdateAsync().ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.RoleId = (long)role.Id);
await RespondAsync($":white_check_mark: The birthday role has been set to **{role.Name}**.").ConfigureAwait(false);
}
[SlashCommand("set-moderator-role", HelpPfxModOnly + "Designate a role whose members can configure the bot." + HelpPofxBlankUnset)]
public async Task CmdSetModRole([Summary(description: HelpOptRole)]SocketRole? role = null) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
gconf.ModeratorRole = role?.Id;
await gconf.UpdateAsync().ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.ModeratorRole = (long?)role?.Id);
await RespondAsync(":white_check_mark: The moderator role has been " +
(role == null ? "unset." : $"set to **{role.Name}**."));
}
@ -107,26 +99,36 @@ public class ConfigModule : BotModuleBase {
public Task CmdDelBlock([Summary(description: "The user to unblock.")] SocketGuildUser user) => UpdateBlockAsync(user, false);
private async Task UpdateBlockAsync(SocketGuildUser user, bool setting) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
bool already = setting == await gconf.IsUserBlockedAsync(user.Id).ConfigureAwait(false);
// setting: true to add (set), false to remove (unset)
using var db = new BotDatabaseContext();
var existing = db.BlocklistEntries
.Where(bl => bl.GuildId == (long)user.Guild.Id && bl.UserId == (long)user.Id).FirstOrDefault();
bool already = (existing != null) == setting;
if (already) {
await RespondAsync($":white_check_mark: User is already {(setting ? "" : "not ")}blocked.").ConfigureAwait(false);
} else {
if (setting) await gconf.BlockUserAsync(user.Id).ConfigureAwait(false);
else await gconf.UnblockUserAsync(user.Id).ConfigureAwait(false);
await RespondAsync($":white_check_mark: {Common.FormatName(user, false)} has been {(setting ? "" : "un")}blocked.");
return;
}
if (setting) db.BlocklistEntries.Add(new BlocklistEntry() { GuildId = (long)user.Guild.Id, UserId = (long)user.Id });
else db.Remove(existing!);
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: {Common.FormatName(user, false)} has been {(setting ? "" : "un")}blocked.");
}
[SlashCommand("set-moderated", HelpPfxModOnly + "Set moderated mode on the server.")]
public async Task CmdAddBlock([Summary(name: "enable", description: "The moderated mode setting.")] bool setting) {
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
bool already = setting == gconf.IsModerated;
public async Task CmdSetModerated([Summary(name: "enable", description: "The moderated mode setting.")] bool setting) {
bool current = false;
await DoDatabaseUpdate(Context, s => {
current = s.Moderated;
s.Moderated = setting;
});
bool already = setting == current;
if (already) {
await RespondAsync($":white_check_mark: Moderated mode is already **{(setting ? "en" : "dis")}abled**.").ConfigureAwait(false);
await RespondAsync($":white_check_mark: Moderated mode is already **{(setting ? "en" : "dis")}abled**.");
} else {
gconf.IsModerated = setting;
await gconf.UpdateAsync().ConfigureAwait(false);
await RespondAsync($":white_check_mark: Moderated mode is now **{(setting ? "en" : "dis")}abled**.").ConfigureAwait(false);
}
}
@ -135,15 +137,19 @@ public class ConfigModule : BotModuleBase {
[SlashCommand("check", HelpPfxModOnly + HelpCmdCheck)]
public async Task CmdCheck() {
static string DoTestFor(string label, Func<bool> test) => $"{label}: { (test() ? ":white_check_mark: Yes" : ":x: No") }";
var result = new StringBuilder();
SocketTextChannel channel = (SocketTextChannel)Context.Channel;
var guild = Context.Guild;
var conf = await guild.GetConfigAsync().ConfigureAwait(false);
var usercfgs = await guild.GetUserConfigurationsAsync().ConfigureAwait(false);
using var db = new BotDatabaseContext();
var guildconf = guild.GetConfigOrNew(db);
await db.Entry(guildconf).Collection(t => t.UserEntries).LoadAsync();
var result = new StringBuilder();
result.AppendLine($"Server ID: `{guild.Id}` | Bot shard ID: `{Shard.ShardId:00}`");
result.AppendLine($"Number of registered birthdays: `{ usercfgs.Count() }`");
result.AppendLine($"Server time zone: `{ (conf?.TimeZone ?? "Not set - using UTC") }`");
result.AppendLine($"Number of registered birthdays: `{ guildconf.UserEntries.Count() }`");
result.AppendLine($"Server time zone: `{ (guildconf.TimeZone ?? "Not set - using UTC") }`");
result.AppendLine();
bool hasMembers = Common.HasMostMembersDownloaded(guild);
@ -152,7 +158,7 @@ public class ConfigModule : BotModuleBase {
int bdayCount = -1;
result.Append(DoTestFor("Birthday processing", delegate {
if (!hasMembers) return false;
bdayCount = BackgroundServices.BirthdayRoleUpdate.GetGuildCurrentBirthdays(usercfgs, conf?.TimeZone).Count;
bdayCount = BackgroundServices.BirthdayRoleUpdate.GetGuildCurrentBirthdays(guildconf.UserEntries, guildconf.TimeZone).Count;
return true;
}));
if (hasMembers) result.AppendLine($" - `{bdayCount}` user(s) currently having a birthday.");
@ -160,13 +166,13 @@ public class ConfigModule : BotModuleBase {
result.AppendLine();
result.AppendLine(DoTestFor("Birthday role set with `bb.config role`", delegate {
if (conf == null) return false;
SocketRole? role = guild.GetRole(conf.RoleId ?? 0);
if (guildconf.IsNew) return false;
SocketRole? role = guild.GetRole((ulong)(guildconf.RoleId ?? 0));
return role != null;
}));
result.AppendLine(DoTestFor("Birthday role can be managed by bot", delegate {
if (conf == null) return false;
SocketRole? role = guild.GetRole(conf.RoleId ?? 0);
if (guildconf.IsNew) return false;
SocketRole? role = guild.GetRole((ulong)(guildconf.RoleId ?? 0));
if (role == null) return false;
return guild.CurrentUser.GuildPermissions.ManageRoles && role.Position < guild.CurrentUser.Hierarchy;
}));
@ -174,8 +180,8 @@ public class ConfigModule : BotModuleBase {
SocketTextChannel? announcech = null;
result.AppendLine(DoTestFor("(Optional) Announcement channel set with `bb.config channel`", delegate {
if (conf == null) return false;
announcech = guild.GetTextChannel(conf.AnnounceChannelId ?? 0);
if (guildconf.IsNew) return false;
announcech = guild.GetTextChannel((ulong)(guildconf.ChannelAnnounceId ?? 0));
return announcech != null;
}));
string disp = announcech == null ? "announcement channel" : $"<#{announcech.Id}>";
@ -197,14 +203,14 @@ public class ConfigModule : BotModuleBase {
result.AppendLine($"> {line}");
return result.ToString();
}
if (conf != null && (conf.AnnounceMessages.Item1 != null || conf.AnnounceMessages.Item2 != null)) {
if (!guildconf.IsNew && (guildconf.AnnounceMessage != null || guildconf.AnnounceMessagePl != null)) {
var em = new EmbedBuilder().WithAuthor(new EmbedAuthorBuilder() { Name = "Custom announce messages:" });
var dispAnnounces = new StringBuilder("Custom announcement message(s):\n");
if (conf.AnnounceMessages.Item1 != null) {
em = em.AddField("Single", prepareAnnouncePreview(conf.AnnounceMessages.Item1));
if (guildconf.AnnounceMessage != null) {
em = em.AddField("Single", prepareAnnouncePreview(guildconf.AnnounceMessage));
}
if (conf.AnnounceMessages.Item2 != null) {
em = em.AddField("Multi", prepareAnnouncePreview(conf.AnnounceMessages.Item2));
if (guildconf.AnnounceMessagePl != null) {
em = em.AddField("Multi", prepareAnnouncePreview(guildconf.AnnounceMessagePl));
}
await ReplyAsync(embed: em.Build()).ConfigureAwait(false);
}
@ -213,11 +219,9 @@ public class ConfigModule : BotModuleBase {
[SlashCommand("set-timezone", HelpPfxModOnly + "Configure the time zone to use by default in the server." + HelpPofxBlankUnset)]
public async Task CmdSetTimezone([Summary(description: HelpOptZone)] string? zone = null) {
const string Response = ":white_check_mark: The server's time zone has been ";
var gconf = await Context.Guild.GetConfigAsync().ConfigureAwait(false);
if (zone == null) {
gconf.TimeZone = null;
await gconf.UpdateAsync().ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.TimeZone = null);
await RespondAsync(Response + "unset.").ConfigureAwait(false);
} else {
string parsedZone;
@ -228,9 +232,22 @@ public class ConfigModule : BotModuleBase {
return;
}
gconf.TimeZone = parsedZone;
await gconf.UpdateAsync().ConfigureAwait(false);
await RespondAsync(Response + $"set to **{zone}**.").ConfigureAwait(false);
await DoDatabaseUpdate(Context, s => s.TimeZone = parsedZone);
await RespondAsync(Response + $"set to **{parsedZone}**.").ConfigureAwait(false);
}
}
/// <summary>
/// Helper method for updating arbitrary <see cref="GuildConfig"/> values without all the boilerplate.
/// </summary>
/// <param name="valueUpdater">A delegate which modifies <see cref="GuildConfig"/> properties as needed.</param>
private static async Task DoDatabaseUpdate(SocketInteractionContext context, Action<GuildConfig> valueUpdater) {
using var db = new BotDatabaseContext();
var settings = context.Guild.GetConfigOrNew(db);
valueUpdater(settings);
if (settings.IsNew) db.GuildConfigurations.Add(settings);
await db.SaveChangesAsync();
}
}

View file

@ -77,6 +77,9 @@ class BirthdayRoleUpdate : BackgroundService {
/// Gets all known users from the given guild and returns a list including only those who are
/// currently experiencing a birthday in the respective time zone.
/// </summary>
#pragma warning disable 618
[Obsolete(Database.ObsoleteReason)]
#pragma warning restore 618
public static HashSet<ulong> GetGuildCurrentBirthdays(IEnumerable<GuildUserConfiguration> guildUsers, string? defaultTzStr) {
var tzdb = DateTimeZoneProviders.Tzdb;
DateTimeZone defaultTz = (defaultTzStr != null ? DateTimeZoneProviders.Tzdb.GetZoneOrNull(defaultTzStr) : null) ?? tzdb.GetZoneOrNull("UTC")!;
@ -102,6 +105,35 @@ class BirthdayRoleUpdate : BackgroundService {
return birthdayUsers;
}
/// <summary>
/// Gets all known users from the given guild and returns a list including only those who are
/// currently experiencing a birthday in the respective time zone.
/// </summary>
public static HashSet<ulong> GetGuildCurrentBirthdays(IEnumerable<UserEntry> guildUsers, string? defaultTzStr) {
var tzdb = DateTimeZoneProviders.Tzdb;
DateTimeZone defaultTz = (defaultTzStr != null ? DateTimeZoneProviders.Tzdb.GetZoneOrNull(defaultTzStr) : null) ?? tzdb.GetZoneOrNull("UTC")!;
var birthdayUsers = new HashSet<ulong>();
foreach (var item in guildUsers) {
// Determine final time zone to use for calculation
DateTimeZone tz = (item.TimeZone != null ? tzdb.GetZoneOrNull(item.TimeZone) : null) ?? defaultTz;
var targetMonth = item.BirthMonth;
var targetDay = item.BirthDay;
var checkNow = SystemClock.Instance.GetCurrentInstant().InZone(tz);
// Special case: If birthday is February 29 and it's not a leap year, recognize it on March 1st
if (targetMonth == 2 && targetDay == 29 && !DateTime.IsLeapYear(checkNow.Year)) {
targetMonth = 3;
targetDay = 1;
}
if (targetMonth == checkNow.Month && targetDay == checkNow.Day) {
birthdayUsers.Add((ulong)item.UserId);
}
}
return birthdayUsers;
}
/// <summary>
/// Sets the birthday role to all applicable users. Unsets it from all others who may have it.
/// </summary>