using System; using System.Collections.Generic; using System.Data.Common; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Noikoio.RegexBot.Feature.EntityCache { /// /// Represents a cached user. /// class UserCacheItem { readonly ulong _userId; readonly ulong _guildId; readonly DateTime _cacheDate; readonly string _username; readonly string _discriminator; readonly string _nickname; readonly string _avatarUrl; /// /// The cached user's ID (snowflake) value. /// public ulong UserId => _userId; /// /// The guild ID (snowflake) for which this user information corresponds to. /// public ulong GuildId => _guildId; /// /// Timestamp value for when this cache item was last updated, in universal time. /// public DateTime CacheDate => _cacheDate; /// /// Display name, including discriminator. Shows the nickname, if available. /// public string DisplayName => (_nickname ?? _username) + "#" + _discriminator; /// /// String useful for tagging the user. /// public string Mention => $"<@{_userId}>"; /// /// User's cached nickname in the guild. May be null. /// public string Nickname => _nickname; /// /// User's cached username. /// public string Username => _username; /// /// User's cached discriminator value. /// public string Discriminator => _discriminator; /// /// URL for user's last known avatar. May be null or invalid. /// public string AvatarUrl => _avatarUrl; private UserCacheItem(DbDataReader r) { // Double-check ordinals if making changes to QueryColumns unchecked { // PostgreSQL does not support unsigned 64-bit numbers. Must convert. _userId = (ulong)r.GetInt64(0); _guildId = (ulong)r.GetInt64(1); } _cacheDate = r.GetDateTime(2).ToUniversalTime(); _username = r.GetString(3); _discriminator = r.GetString(4); _nickname = r.IsDBNull(5) ? null : r.GetString(5); _avatarUrl = r.IsDBNull(6) ? null : r.GetString(6); } public override string ToString() => DisplayName; #region Queries const string QueryColumns = "user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url"; /// /// Attempts to query for an exact result with the given parameters. /// /// Null on no result. public static async Task QueryAsync(ulong guild, ulong user) { using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) { using (var c = db.CreateCommand()) { c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableUser} WHERE " + "user_id = @Uid AND guild_id = @Gid"; c.Parameters.Add("@Uid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = user; c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild; c.Prepare(); using (var r = await c.ExecuteReaderAsync()) { if (await r.ReadAsync()) { return new UserCacheItem(r); } else { return null; } } } } } private static Regex DiscriminatorSearch = new Regex(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled); /// /// Attempts to look up the user given a search string. /// This string looks up case-insensitive, exact matches of nicknames and usernames. /// /// An containing zero or more query results, sorted by cache date. public static async Task> QueryAsync(ulong guild, string search) { // Is search just a number? It's an ID. if (ulong.TryParse(search, out var presult)) { var r = await QueryAsync(guild, presult); if (r == null) return new UserCacheItem[0]; else return new UserCacheItem[] { r }; } string name; string disc; var split = DiscriminatorSearch.Match(search); if (split.Success) { name = split.Groups[1].Value; disc = split.Groups[2].Value; } else { name = search; disc = null; } // Storing in HashSet to enforce uniqueness HashSet result = new HashSet(_uc); using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) { using (var c = db.CreateCommand()) { c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableUser} WHERE " + "( lower(username) = lower(@NameSearch) OR lower(nickname) = lower(@NameSearch) )"; c.Parameters.Add("@NameSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = name; if (disc != null) { c.CommandText += " AND discriminator = @DiscSearch"; c.Parameters.Add("@DiscSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = disc; } c.Prepare(); using (var r = await c.ExecuteReaderAsync()) { while (await r.ReadAsync()) { result.Add(new UserCacheItem(r)); } } } } return result; } private static UniqueChecker _uc = new UniqueChecker(); class UniqueChecker : IEqualityComparer { public bool Equals(UserCacheItem x, UserCacheItem y) => x.UserId == y.UserId && x.GuildId == y.GuildId; public int GetHashCode(UserCacheItem obj) => unchecked((int)(obj.UserId ^ obj.GuildId)); } #endregion } }