2017-12-23 06:20:06 +00:00
|
|
|
|
using Discord.WebSocket;
|
|
|
|
|
using Npgsql;
|
|
|
|
|
using System;
|
2017-11-08 06:36:18 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Data.Common;
|
2017-12-23 06:20:06 +00:00
|
|
|
|
using System.Linq;
|
2017-11-08 06:36:18 +00:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
namespace Noikoio.RegexBot.EntityCache
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
2017-12-23 06:20:06 +00:00
|
|
|
|
/// Representation of a cached user.
|
2017-11-08 06:36:18 +00:00
|
|
|
|
/// </summary>
|
2017-12-23 06:20:06 +00:00
|
|
|
|
class CacheUser
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
|
|
|
|
readonly ulong _userId;
|
|
|
|
|
readonly ulong _guildId;
|
2018-04-24 21:29:19 +00:00
|
|
|
|
readonly DateTimeOffset _cacheDate;
|
|
|
|
|
readonly DateTimeOffset _firstSeen;
|
2017-11-08 06:36:18 +00:00
|
|
|
|
readonly string _username;
|
|
|
|
|
readonly string _discriminator;
|
|
|
|
|
readonly string _nickname;
|
|
|
|
|
readonly string _avatarUrl;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The cached user's ID (snowflake) value.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public ulong UserId => _userId;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The guild ID (snowflake) for which this user information corresponds to.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public ulong GuildId => _guildId;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Timestamp value for when this cache item was last updated, in universal time.
|
|
|
|
|
/// </summary>
|
2018-04-24 21:29:19 +00:00
|
|
|
|
public DateTimeOffset CacheDate => _cacheDate;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Timestamp value for when this user was first seen by the bot, in universal time.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public DateTimeOffset FirstSeenDate => _firstSeen;
|
2017-11-08 06:36:18 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Display name, including discriminator. Shows the nickname, if available.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string DisplayName => (_nickname ?? _username) + "#" + _discriminator;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// String useful for tagging the user.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string Mention => $"<@{_userId}>";
|
|
|
|
|
/// <summary>
|
2017-11-12 02:12:23 +00:00
|
|
|
|
/// User's cached nickname in the guild. May be null.
|
2017-11-08 06:36:18 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public string Nickname => _nickname;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// User's cached username.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string Username => _username;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// User's cached discriminator value.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string Discriminator => _discriminator;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// URL for user's last known avatar. May be null or invalid.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string AvatarUrl => _avatarUrl;
|
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
private CacheUser(SocketGuildUser u)
|
|
|
|
|
{
|
|
|
|
|
_userId = u.Id;
|
|
|
|
|
_guildId = u.Guild.Id;
|
|
|
|
|
_cacheDate = DateTime.UtcNow;
|
|
|
|
|
_username = u.Username;
|
|
|
|
|
_discriminator = u.Discriminator;
|
|
|
|
|
_nickname = u.Nickname;
|
|
|
|
|
_avatarUrl = u.GetAvatarUrl();
|
|
|
|
|
}
|
2017-11-08 06:36:18 +00:00
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// Double-check SqlHelper if making changes to this constant
|
2018-04-24 21:29:19 +00:00
|
|
|
|
const string QueryColumns = "user_id, guild_id, first_seen, cache_date, username, discriminator, nickname, avatar_url";
|
2017-12-23 06:20:06 +00:00
|
|
|
|
private CacheUser(DbDataReader r)
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2018-04-24 21:29:19 +00:00
|
|
|
|
_firstSeen = r.GetDateTime(2).ToUniversalTime();
|
|
|
|
|
_cacheDate = r.GetDateTime(3).ToUniversalTime();
|
|
|
|
|
_username = r.GetString(4);
|
|
|
|
|
_discriminator = r.GetString(5);
|
|
|
|
|
_nickname = r.IsDBNull(6) ? null : r.GetString(6);
|
|
|
|
|
_avatarUrl = r.IsDBNull(7) ? null : r.GetString(7);
|
2017-11-08 06:36:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString() => DisplayName;
|
|
|
|
|
|
|
|
|
|
#region Queries
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// Accessible by EntityCache. Documentation is there.
|
|
|
|
|
internal static async Task<CacheUser> QueryAsync(DiscordSocketClient c, ulong guild, ulong user)
|
|
|
|
|
{
|
|
|
|
|
// Local cache search
|
|
|
|
|
var lresult = LocalQueryAsync(c, guild, user);
|
|
|
|
|
if (lresult != null) return lresult;
|
|
|
|
|
|
|
|
|
|
// Database cache search
|
2018-02-17 07:41:12 +00:00
|
|
|
|
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
|
2017-12-23 06:20:06 +00:00
|
|
|
|
if (db == null) return null; // Database not available for query.
|
2018-04-04 18:00:58 +00:00
|
|
|
|
return await DbQueryAsync(db, guild, user);
|
2017-12-23 06:20:06 +00:00
|
|
|
|
}
|
2017-11-08 06:36:18 +00:00
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
private static CacheUser LocalQueryAsync(DiscordSocketClient c, ulong guild, ulong user)
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
var u = c.GetGuild(guild)?.GetUser(user);
|
|
|
|
|
if (u == null) return null;
|
|
|
|
|
return new CacheUser(u);
|
|
|
|
|
}
|
|
|
|
|
private static async Task<CacheUser> DbQueryAsync(NpgsqlConnection db, ulong guild, ulong user)
|
|
|
|
|
{
|
|
|
|
|
using (db)
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
c.CommandText = $"SELECT {QueryColumns} FROM {SqlHelper.TableUser} WHERE "
|
2017-11-08 06:36:18 +00:00
|
|
|
|
+ "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())
|
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
return new CacheUser(r);
|
2017-11-08 06:36:18 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// -----
|
2017-11-08 06:36:18 +00:00
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
private static Regex DiscriminatorSearch = new Regex(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled);
|
|
|
|
|
// Accessible by EntityCache. Documentation is there.
|
|
|
|
|
internal static async Task<IEnumerable<CacheUser>> QueryAsync(DiscordSocketClient c, ulong guild, string search)
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// Is search just a number? Assume ID, pass it on to the correct place.
|
2017-11-08 06:36:18 +00:00
|
|
|
|
if (ulong.TryParse(search, out var presult))
|
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
var r = await QueryAsync(c, guild, presult);
|
|
|
|
|
if (r == null) return new CacheUser[0];
|
|
|
|
|
else return new CacheUser[] { r };
|
2017-11-08 06:36:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// Split name/discriminator
|
2017-11-08 06:36:18 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-26 18:53:19 +00:00
|
|
|
|
// Strip leading @ from name, if any
|
|
|
|
|
if (name.Length > 0 && name[0] == '@') name = name.Substring(1);
|
|
|
|
|
|
2017-12-23 06:20:06 +00:00
|
|
|
|
// Local cache search
|
|
|
|
|
var lresult = LocalQueryAsync(c, guild, name, disc);
|
|
|
|
|
if (lresult.Count() != 0) return lresult;
|
|
|
|
|
|
|
|
|
|
// Database cache search
|
2018-02-17 07:41:12 +00:00
|
|
|
|
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
|
2018-04-04 18:00:58 +00:00
|
|
|
|
if (db == null) return new CacheUser[0]; // Database not available for query.
|
|
|
|
|
return await DbQueryAsync(db, guild, name, disc);
|
2017-12-23 06:20:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IEnumerable<CacheUser> LocalQueryAsync(DiscordSocketClient c, ulong guild, string name, string disc)
|
|
|
|
|
{
|
|
|
|
|
var g = c.GetGuild(guild);
|
|
|
|
|
if (g == null) return new CacheUser[] { };
|
|
|
|
|
|
|
|
|
|
bool Filter(string iun, string inn, string idc)
|
|
|
|
|
{
|
|
|
|
|
// Same logic as in the SQL query in the method below this one
|
|
|
|
|
bool match =
|
|
|
|
|
string.Equals(iun, name, StringComparison.InvariantCultureIgnoreCase)
|
|
|
|
|
|| string.Equals(inn, name, StringComparison.InvariantCultureIgnoreCase);
|
|
|
|
|
|
|
|
|
|
if (match && disc != null)
|
|
|
|
|
match = idc.Equals(disc);
|
|
|
|
|
|
|
|
|
|
return match;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var qresult = g.Users.Where(i => Filter(i.Username, i.Nickname, i.Discriminator));
|
|
|
|
|
var result = new List<CacheUser>();
|
|
|
|
|
foreach (var item in qresult)
|
|
|
|
|
{
|
|
|
|
|
result.Add(new CacheUser(item));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<IEnumerable<CacheUser>> DbQueryAsync(NpgsqlConnection db, ulong guild, string name, string disc)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<CacheUser>();
|
2017-11-08 06:36:18 +00:00
|
|
|
|
|
2018-04-04 18:00:58 +00:00
|
|
|
|
using (db)
|
2017-11-08 06:36:18 +00:00
|
|
|
|
{
|
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
2018-03-26 18:53:19 +00:00
|
|
|
|
c.CommandText = $"SELECT {QueryColumns} FROM {SqlHelper.TableUser} WHERE"
|
|
|
|
|
+ " ( lower(username) = lower(@NameSearch) OR lower(nickname) = lower(@NameSearch) )";
|
2017-11-08 06:36:18 +00:00
|
|
|
|
if (disc != null)
|
|
|
|
|
{
|
|
|
|
|
c.CommandText += " AND discriminator = @DiscSearch";
|
|
|
|
|
c.Parameters.Add("@DiscSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = disc;
|
|
|
|
|
}
|
2018-03-26 18:53:19 +00:00
|
|
|
|
c.CommandText += " ORDER BY cache_date desc, username";
|
|
|
|
|
c.Parameters.Add("@NameSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = name;
|
|
|
|
|
|
2017-11-08 06:36:18 +00:00
|
|
|
|
c.Prepare();
|
|
|
|
|
|
|
|
|
|
using (var r = await c.ExecuteReaderAsync())
|
|
|
|
|
{
|
|
|
|
|
while (await r.ReadAsync())
|
|
|
|
|
{
|
2017-12-23 06:20:06 +00:00
|
|
|
|
result.Add(new CacheUser(r));
|
2017-11-08 06:36:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|