2017-11-01 23:24:22 +00:00
|
|
|
|
using Discord.WebSocket;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using Noikoio.RegexBot.ConfigItem;
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using Npgsql;
|
2017-11-05 22:58:50 +00:00
|
|
|
|
using NpgsqlTypes;
|
2017-11-01 23:24:22 +00:00
|
|
|
|
using System;
|
2017-10-30 03:04:57 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2017-11-07 02:15:52 +00:00
|
|
|
|
namespace Noikoio.RegexBot.Feature.EntityCache
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
2017-11-05 20:01:32 +00:00
|
|
|
|
/// Caches information regarding all known guilds, channels, and users.
|
2017-10-30 03:04:57 +00:00
|
|
|
|
/// The function of this feature should be transparent to the user, and thus no configuration is needed.
|
2017-11-07 02:15:52 +00:00
|
|
|
|
/// This feature should be initialized BEFORE any other features that make use of guild and user cache.
|
2017-10-30 03:04:57 +00:00
|
|
|
|
/// </summary>
|
2017-11-05 20:01:32 +00:00
|
|
|
|
class EntityCache : BotFeature
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-01 23:24:22 +00:00
|
|
|
|
private readonly DatabaseConfig _db;
|
|
|
|
|
|
2017-11-05 20:01:32 +00:00
|
|
|
|
public override string Name => nameof(EntityCache);
|
2017-10-30 03:04:57 +00:00
|
|
|
|
|
2017-11-05 20:01:32 +00:00
|
|
|
|
public EntityCache(DiscordSocketClient client) : base(client)
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-01 23:24:22 +00:00
|
|
|
|
_db = RegexBot.Config.Database;
|
|
|
|
|
|
2017-11-05 22:58:50 +00:00
|
|
|
|
if (_db.Enabled)
|
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
CreateCacheTables();
|
|
|
|
|
|
2017-11-05 22:58:50 +00:00
|
|
|
|
client.GuildAvailable += Client_GuildAvailable;
|
|
|
|
|
client.GuildUpdated += Client_GuildUpdated;
|
|
|
|
|
client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
|
|
|
|
// it may not be necessary to handle JoinedGuild, as GuildAvailable provides this info
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Log("No database storage available.").Wait();
|
|
|
|
|
}
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
2017-11-01 23:24:22 +00:00
|
|
|
|
|
2017-10-30 03:04:57 +00:00
|
|
|
|
public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
|
|
|
|
|
|
2017-11-01 23:24:22 +00:00
|
|
|
|
#region Event handling
|
|
|
|
|
// Guild _and_ guild member information has become available
|
|
|
|
|
private async Task Client_GuildAvailable(SocketGuild arg)
|
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
await Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
await UpdateGuild(arg);
|
|
|
|
|
await UpdateGuildMember(arg.Users);
|
|
|
|
|
}
|
|
|
|
|
);
|
2017-11-01 23:24:22 +00:00
|
|
|
|
}
|
2017-10-30 03:04:57 +00:00
|
|
|
|
|
|
|
|
|
// Guild information has changed
|
2017-11-01 23:24:22 +00:00
|
|
|
|
private async Task Client_GuildUpdated(SocketGuild arg1, SocketGuild arg2)
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-05 20:01:32 +00:00
|
|
|
|
await Task.Run(() => UpdateGuild(arg2));
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-01 23:24:22 +00:00
|
|
|
|
// Guild member information has changed
|
|
|
|
|
private async Task Client_GuildMemberUpdated(SocketGuildUser arg1, SocketGuildUser arg2)
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-01 23:24:22 +00:00
|
|
|
|
await Task.Run(() => UpdateGuildMember(arg2));
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
2017-11-06 04:55:57 +00:00
|
|
|
|
#endregion
|
2017-10-30 03:04:57 +00:00
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
#region Table setup
|
2017-11-05 22:58:50 +00:00
|
|
|
|
public const string TableGuild = "cache_guild";
|
2017-11-06 04:55:57 +00:00
|
|
|
|
public const string TableUser = "cache_users";
|
2017-11-01 23:24:22 +00:00
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
private void CreateCacheTables()
|
2017-11-01 23:24:22 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using (var db = _db.GetOpenConnectionAsync().GetAwaiter().GetResult())
|
2017-11-01 23:24:22 +00:00
|
|
|
|
{
|
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + "("
|
2017-11-05 22:58:50 +00:00
|
|
|
|
+ "guild_id bigint primary key, "
|
2017-11-01 23:24:22 +00:00
|
|
|
|
+ "current_name text not null, "
|
|
|
|
|
+ "display_name text null"
|
|
|
|
|
+ ")";
|
2017-11-06 04:55:57 +00:00
|
|
|
|
// TODO determine if other columns might be needed?
|
|
|
|
|
c.ExecuteNonQuery();
|
2017-11-01 23:24:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + "("
|
2017-11-06 04:55:57 +00:00
|
|
|
|
+ "user_id bigint not null, "
|
|
|
|
|
+ $"guild_id bigint not null references {TableGuild}, "
|
2017-11-01 23:24:22 +00:00
|
|
|
|
+ "cache_date timestamptz not null, "
|
|
|
|
|
+ "username text not null, "
|
|
|
|
|
+ "discriminator text not null, "
|
|
|
|
|
+ "nickname text null, "
|
|
|
|
|
+ "avatar_url text null"
|
|
|
|
|
+ ")";
|
2017-11-06 04:55:57 +00:00
|
|
|
|
c.ExecuteNonQuery();
|
2017-11-05 22:58:50 +00:00
|
|
|
|
}
|
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
|
|
|
|
|
+ $"{TableUser}_idx on {TableUser} (user_id, guild_id)";
|
2017-11-06 04:55:57 +00:00
|
|
|
|
c.ExecuteNonQuery();
|
2017-11-01 23:24:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-06 04:55:57 +00:00
|
|
|
|
#endregion
|
2017-11-05 20:01:32 +00:00
|
|
|
|
|
2017-11-01 23:24:22 +00:00
|
|
|
|
private async Task UpdateGuild(SocketGuild g)
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
try
|
2017-11-05 22:58:50 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using (var db = await _db.GetOpenConnectionAsync())
|
2017-11-05 22:58:50 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
c.CommandText = "INSERT INTO " + TableGuild + " (guild_id, current_name) "
|
|
|
|
|
+ "VALUES (@GuildId, @CurrentName) "
|
|
|
|
|
+ "ON CONFLICT (guild_id) DO UPDATE SET "
|
|
|
|
|
+ "current_name = EXCLUDED.current_name";
|
|
|
|
|
c.Parameters.Add("@GuildId", NpgsqlDbType.Bigint).Value = g.Id;
|
|
|
|
|
c.Parameters.Add("@CurrentName", NpgsqlDbType.Text).Value = g.Name;
|
|
|
|
|
c.Prepare();
|
|
|
|
|
await c.ExecuteNonQueryAsync();
|
|
|
|
|
}
|
2017-11-05 22:58:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-06 04:55:57 +00:00
|
|
|
|
catch (NpgsqlException ex)
|
|
|
|
|
{
|
|
|
|
|
await Log($"SQL error in {nameof(UpdateGuild)}: " + ex.Message);
|
|
|
|
|
}
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
private async Task UpdateGuildMember(IEnumerable<SocketGuildUser> users)
|
2017-10-30 03:04:57 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
try
|
2017-11-05 22:58:50 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using (var db = await _db.GetOpenConnectionAsync())
|
2017-11-05 22:58:50 +00:00
|
|
|
|
{
|
2017-11-06 04:55:57 +00:00
|
|
|
|
using (var c = db.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
c.CommandText = "INSERT INTO " + TableUser
|
|
|
|
|
+ " (user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url)"
|
|
|
|
|
+ " VALUES (@Uid, @Gid, @Date, @Uname, @Disc, @Nname, @Url) "
|
|
|
|
|
+ "ON CONFLICT (user_id, guild_id) DO UPDATE SET "
|
|
|
|
|
+ "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, "
|
|
|
|
|
+ "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time...
|
|
|
|
|
+ "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url";
|
|
|
|
|
|
|
|
|
|
var uid = c.Parameters.Add("@Uid", NpgsqlDbType.Bigint);
|
|
|
|
|
var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint);
|
|
|
|
|
c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
|
|
|
|
|
var uname = c.Parameters.Add("@Uname", NpgsqlDbType.Text);
|
|
|
|
|
var disc = c.Parameters.Add("@Disc", NpgsqlDbType.Text);
|
|
|
|
|
var nname = c.Parameters.Add("@Nname", NpgsqlDbType.Text);
|
|
|
|
|
var url = c.Parameters.Add("@Url", NpgsqlDbType.Text);
|
|
|
|
|
c.Prepare();
|
2017-11-05 22:58:50 +00:00
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
foreach (var item in users)
|
|
|
|
|
{
|
2017-11-07 02:15:52 +00:00
|
|
|
|
if (item.IsBot || item.IsWebhook) continue;
|
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
uid.Value = item.Id;
|
|
|
|
|
gid.Value = item.Guild.Id;
|
|
|
|
|
uname.Value = item.Username;
|
|
|
|
|
disc.Value = item.Discriminator;
|
|
|
|
|
nname.Value = item.Nickname;
|
|
|
|
|
if (nname.Value == null) nname.Value = DBNull.Value; // why can't ?? work here?
|
|
|
|
|
url.Value = item.GetAvatarUrl();
|
|
|
|
|
if (url.Value == null) url.Value = DBNull.Value;
|
2017-11-05 22:58:50 +00:00
|
|
|
|
|
2017-11-06 04:55:57 +00:00
|
|
|
|
await c.ExecuteNonQueryAsync();
|
|
|
|
|
}
|
2017-11-05 22:58:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-06 04:55:57 +00:00
|
|
|
|
catch (NpgsqlException ex)
|
|
|
|
|
{
|
|
|
|
|
await Log($"SQL error in {nameof(UpdateGuildMember)}: " + ex.Message);
|
|
|
|
|
}
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
2017-11-01 23:24:22 +00:00
|
|
|
|
|
|
|
|
|
private Task UpdateGuildMember(SocketGuildUser user)
|
|
|
|
|
{
|
|
|
|
|
var gid = user.Guild.Id;
|
|
|
|
|
var ml = new SocketGuildUser[] { user };
|
2017-11-06 04:55:57 +00:00
|
|
|
|
return UpdateGuildMember(ml);
|
2017-11-01 23:24:22 +00:00
|
|
|
|
}
|
2017-10-30 03:04:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|