2022-07-21 01:55:08 +00:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2022-07-13 06:45:18 +00:00
|
|
|
|
using RegexBot.Common;
|
2022-03-29 05:03:01 +00:00
|
|
|
|
using RegexBot.Data;
|
|
|
|
|
|
|
|
|
|
namespace RegexBot.Services.EntityCache;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides and maintains a database-backed cache of users.
|
|
|
|
|
/// It is meant to work as a supplement to Discord.Net's own user caching capabilities. Its purpose is to
|
|
|
|
|
/// provide information on users which the library may not be aware about, such as users no longer in a guild.
|
|
|
|
|
/// </summary>
|
2022-05-12 03:26:28 +00:00
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
|
2022-03-29 05:03:01 +00:00
|
|
|
|
class UserCachingSubservice {
|
|
|
|
|
internal UserCachingSubservice(RegexbotClient bot) {
|
2022-06-10 23:09:18 +00:00
|
|
|
|
bot.DiscordClient.GuildMembersDownloaded += DiscordClient_GuildMembersDownloaded;
|
2022-03-29 05:03:01 +00:00
|
|
|
|
bot.DiscordClient.GuildMemberUpdated += DiscordClient_GuildMemberUpdated;
|
|
|
|
|
bot.DiscordClient.UserUpdated += DiscordClient_UserUpdated;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 23:09:18 +00:00
|
|
|
|
private async Task DiscordClient_GuildMembersDownloaded(SocketGuild arg) {
|
|
|
|
|
using var db = new BotDatabaseContext();
|
|
|
|
|
foreach (var user in arg.Users) {
|
|
|
|
|
UpdateUser(user, db);
|
|
|
|
|
UpdateGuildUser(user, db);
|
|
|
|
|
}
|
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task DiscordClient_GuildMemberUpdated(Discord.Cacheable<SocketGuildUser, ulong> old, SocketGuildUser current) {
|
|
|
|
|
using var db = new BotDatabaseContext();
|
|
|
|
|
UpdateUser(current, db); // Update user data first (avoid potential foreign key constraint violation)
|
|
|
|
|
UpdateGuildUser(current, db);
|
|
|
|
|
|
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-29 05:03:01 +00:00
|
|
|
|
private async Task DiscordClient_UserUpdated(SocketUser old, SocketUser current) {
|
|
|
|
|
using var db = new BotDatabaseContext();
|
|
|
|
|
UpdateUser(current, db);
|
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 23:09:18 +00:00
|
|
|
|
// IMPORTANT: Do NOT forget to save changes in database after calling this!
|
2022-03-29 05:03:01 +00:00
|
|
|
|
private static void UpdateUser(SocketUser user, BotDatabaseContext db) {
|
2022-07-09 20:23:17 +00:00
|
|
|
|
var uinfo = db.UserCache.Where(c => c.UserId == (long)user.Id).SingleOrDefault();
|
|
|
|
|
if (uinfo == null) {
|
2022-03-29 05:03:01 +00:00
|
|
|
|
uinfo = new() { UserId = (long)user.Id };
|
|
|
|
|
db.UserCache.Add(uinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uinfo.Username = user.Username;
|
|
|
|
|
uinfo.Discriminator = user.Discriminator;
|
|
|
|
|
uinfo.AvatarUrl = user.GetAvatarUrl(size: 512);
|
|
|
|
|
uinfo.ULastUpdateTime = DateTimeOffset.UtcNow;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 23:09:18 +00:00
|
|
|
|
private static void UpdateGuildUser(SocketGuildUser user, BotDatabaseContext db) {
|
2022-07-09 20:23:17 +00:00
|
|
|
|
var guinfo = db.GuildUserCache.Where(c => c.GuildId == (long)user.Guild.Id && c.UserId == (long)user.Id).SingleOrDefault();
|
|
|
|
|
if (guinfo == null) {
|
2022-06-10 23:09:18 +00:00
|
|
|
|
guinfo = new() { GuildId = (long)user.Guild.Id, UserId = (long)user.Id };
|
2022-03-29 05:03:01 +00:00
|
|
|
|
db.GuildUserCache.Add(guinfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guinfo.GULastUpdateTime = DateTimeOffset.UtcNow;
|
2022-06-10 23:09:18 +00:00
|
|
|
|
guinfo.Nickname = user.Nickname;
|
2022-03-29 05:03:01 +00:00
|
|
|
|
// TODO guild-specific avatar, other details?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hooked
|
2022-05-12 03:26:28 +00:00
|
|
|
|
internal CachedUser? DoUserQuery(string search) {
|
2022-03-29 05:03:01 +00:00
|
|
|
|
static CachedUser? innerQuery(ulong? sID, (string name, string? disc)? nameSearch) {
|
|
|
|
|
var db = new BotDatabaseContext();
|
|
|
|
|
|
|
|
|
|
var query = db.UserCache.AsQueryable();
|
|
|
|
|
if (sID.HasValue)
|
|
|
|
|
query = query.Where(c => c.UserId == (long)sID.Value);
|
|
|
|
|
if (nameSearch != null) {
|
|
|
|
|
query = query.Where(c => c.Username.ToLower() == nameSearch.Value.name.ToLower());
|
|
|
|
|
if (nameSearch.Value.disc != null) query = query.Where(c => c.Discriminator == nameSearch.Value.disc);
|
|
|
|
|
}
|
|
|
|
|
query = query.OrderByDescending(e => e.ULastUpdateTime);
|
|
|
|
|
|
|
|
|
|
return query.SingleOrDefault();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 06:45:18 +00:00
|
|
|
|
// Is search actually a ping? Extract ID.
|
|
|
|
|
var m = Utilities.UserMention.Match(search);
|
|
|
|
|
if (m.Success) search = m.Groups["snowflake"].Value;
|
|
|
|
|
|
|
|
|
|
// Is search a number? Assume ID, proceed to query.
|
|
|
|
|
if (ulong.TryParse(search, out var searchid)) {
|
2022-03-29 05:03:01 +00:00
|
|
|
|
var idres = innerQuery(searchid, null);
|
|
|
|
|
if (idres != null) return idres;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 06:45:18 +00:00
|
|
|
|
// All of the above failed. Assume the number may be a string to search.
|
2022-03-29 05:03:01 +00:00
|
|
|
|
var namesplit = SplitNameAndDiscriminator(search);
|
|
|
|
|
return innerQuery(null, namesplit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hooked
|
2022-05-12 03:26:28 +00:00
|
|
|
|
internal CachedGuildUser? DoGuildUserQuery(ulong guildId, string search) {
|
2022-03-29 05:03:01 +00:00
|
|
|
|
static CachedGuildUser? innerQuery(ulong guildId, ulong? sID, (string name, string? disc)? nameSearch) {
|
|
|
|
|
var db = new BotDatabaseContext();
|
2022-07-21 01:55:08 +00:00
|
|
|
|
var query = db.GuildUserCache.Include(gu => gu.User).Where(c => c.GuildId == (long)guildId);
|
2022-03-29 05:03:01 +00:00
|
|
|
|
if (sID.HasValue)
|
|
|
|
|
query = query.Where(c => c.UserId == (long)sID.Value);
|
|
|
|
|
if (nameSearch != null) {
|
|
|
|
|
query = query.Where(c => (c.Nickname != null && c.Nickname.ToLower() == nameSearch.Value.name.ToLower()) ||
|
|
|
|
|
c.User.Username.ToLower() == nameSearch.Value.name.ToLower());
|
|
|
|
|
if (nameSearch.Value.disc != null) query = query.Where(c => c.User.Discriminator == nameSearch.Value.disc);
|
|
|
|
|
}
|
|
|
|
|
query = query.OrderByDescending(e => e.GULastUpdateTime);
|
|
|
|
|
|
|
|
|
|
return query.SingleOrDefault();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 06:45:18 +00:00
|
|
|
|
// Is search actually a ping? Extract ID.
|
|
|
|
|
var m = Utilities.UserMention.Match(search);
|
|
|
|
|
if (m.Success) search = m.Groups["snowflake"].Value;
|
|
|
|
|
|
|
|
|
|
// Is search a number? Assume ID, proceed to query.
|
2022-03-29 05:03:01 +00:00
|
|
|
|
if (ulong.TryParse(search, out var searchid)) {
|
|
|
|
|
var idres = innerQuery(guildId, searchid, null);
|
|
|
|
|
if (idres != null) return idres;
|
|
|
|
|
}
|
2022-07-13 06:45:18 +00:00
|
|
|
|
|
|
|
|
|
// All of the above failed. Assume the number may be a string to search.
|
2022-03-29 05:03:01 +00:00
|
|
|
|
var namesplit = SplitNameAndDiscriminator(search);
|
|
|
|
|
return innerQuery(guildId, null, namesplit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static (string, string?) SplitNameAndDiscriminator(string input) {
|
|
|
|
|
string name;
|
|
|
|
|
string? disc = null;
|
2022-07-13 06:45:18 +00:00
|
|
|
|
var split = Utilities.DiscriminatorSearch.Match(input);
|
2022-03-29 05:03:01 +00:00
|
|
|
|
if (split.Success) {
|
|
|
|
|
name = split.Groups[1].Value;
|
|
|
|
|
disc = split.Groups[2].Value;
|
|
|
|
|
} else {
|
|
|
|
|
name = input;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also strip leading '@' from search
|
|
|
|
|
if (name.Length > 0 && name[0] == '@') name = name[1..];
|
|
|
|
|
|
|
|
|
|
return (name, disc);
|
|
|
|
|
}
|
|
|
|
|
}
|