Adding files from previously deleted branch
This commit is contained in:
parent
e9a4e3e726
commit
26da617cf1
5 changed files with 363 additions and 0 deletions
168
Module/ModLogs/Entry.cs
Normal file
168
Module/ModLogs/Entry.cs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
using Npgsql;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a log entry in the database.
|
||||||
|
/// </summary>
|
||||||
|
class Entry
|
||||||
|
{
|
||||||
|
readonly int _logId;
|
||||||
|
readonly DateTime _ts;
|
||||||
|
readonly ulong _guildId;
|
||||||
|
readonly ulong? _invokeId;
|
||||||
|
readonly ulong _targetId;
|
||||||
|
readonly ulong? _channelId;
|
||||||
|
readonly string _type;
|
||||||
|
readonly string _message;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID value of this log entry.
|
||||||
|
/// </summary>
|
||||||
|
public int Id => _logId;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the timestamp (a <see cref="DateTime"/> with <see cref="DateTimeKind.Utc"/>) of the entry.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Timestamp => _ts;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the guild to which this log entry corresponds.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Guild => _guildId;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the user to which this log entry corresponds.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Target => _targetId;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the invoking user.
|
||||||
|
/// This value exists only if this entry was created through action of another user that is not the target.
|
||||||
|
/// </summary>
|
||||||
|
public ulong? Invoker => _invokeId;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the guild channel ID to which this log entry corresponds, if any.
|
||||||
|
/// </summary>
|
||||||
|
public ulong? TargetChannel => _channelId;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this log entry's category.
|
||||||
|
/// </summary>
|
||||||
|
public string Category => _type;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content of this log entry.
|
||||||
|
/// </summary>
|
||||||
|
public string Message => _message;
|
||||||
|
|
||||||
|
public Entry(DbDataReader r)
|
||||||
|
{
|
||||||
|
// Double-check ordinals if making changes to QueryColumns
|
||||||
|
|
||||||
|
_logId = r.GetInt32(0);
|
||||||
|
_ts = r.GetDateTime(1).ToUniversalTime();
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
_guildId = (ulong)r.GetInt64(2);
|
||||||
|
_targetId = (ulong)r.GetInt64(3);
|
||||||
|
if (r.IsDBNull(4)) _invokeId = null;
|
||||||
|
else _invokeId = (ulong)r.GetInt64(4);
|
||||||
|
if (r.IsDBNull(5)) _channelId = null;
|
||||||
|
else _channelId = (ulong)r.GetInt64(5);
|
||||||
|
}
|
||||||
|
_type = r.GetString(6);
|
||||||
|
_message = r.GetString(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO lazy loading of channel, user, etc from caches
|
||||||
|
// TODO methods for updating this log entry(?)
|
||||||
|
|
||||||
|
// TODO figure out some helper methods to retrieve data of other entities by ID, if it becomes necessary
|
||||||
|
|
||||||
|
#region Queries
|
||||||
|
// Double-check constructor if making changes to this constant
|
||||||
|
const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, category, message";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to look up a log entry with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Null if no result.</returns>
|
||||||
|
public static async Task<Entry> QueryIdAsync(ulong guild, int id)
|
||||||
|
{
|
||||||
|
using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync())
|
||||||
|
{
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} "
|
||||||
|
+ "WHERE guild_id = @Guild and id = @Id";
|
||||||
|
c.Parameters.Add("@Guild", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild;
|
||||||
|
c.Parameters.Add("@Id", NpgsqlTypes.NpgsqlDbType.Numeric).Value = id;
|
||||||
|
c.Prepare();
|
||||||
|
using (var r = await c.ExecuteReaderAsync())
|
||||||
|
{
|
||||||
|
if (r.Read()) return new Entry(r);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<Entry>> QueryLogAsync
|
||||||
|
(ulong guild,
|
||||||
|
ulong? target = null,
|
||||||
|
ulong? invoker = null,
|
||||||
|
ulong? channel = null,
|
||||||
|
IEnumerable<string> category = null)
|
||||||
|
{
|
||||||
|
// Enforce some limits - can't search too broadly here. Requires this at a minimum:
|
||||||
|
if (target.HasValue == false && invoker.HasValue == false)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("Query requires at minimum searching of a target or invoker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<Entry>();
|
||||||
|
using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync())
|
||||||
|
{
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} WHERE";
|
||||||
|
|
||||||
|
bool and = false;
|
||||||
|
if (target.HasValue)
|
||||||
|
{
|
||||||
|
if (and) c.CommandText += " AND";
|
||||||
|
else and = true;
|
||||||
|
c.CommandText += " target_id = @TargetId";
|
||||||
|
c.Parameters.Add("@TargetId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = target.Value;
|
||||||
|
}
|
||||||
|
if (invoker.HasValue)
|
||||||
|
{
|
||||||
|
if (and) c.CommandText += " AND";
|
||||||
|
else and = true;
|
||||||
|
c.CommandText += " invoke_id = @InvokeId";
|
||||||
|
c.Parameters.Add("@InvokeId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = invoker.Value;
|
||||||
|
}
|
||||||
|
if (channel.HasValue)
|
||||||
|
{
|
||||||
|
if (and) c.CommandText += " AND";
|
||||||
|
else and = true;
|
||||||
|
c.CommandText += " target_channel_id = @ChannelId";
|
||||||
|
c.Parameters.Add("@ChannelId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = channel.Value;
|
||||||
|
}
|
||||||
|
c.Prepare();
|
||||||
|
|
||||||
|
using (var r = await c.ExecuteReaderAsync())
|
||||||
|
{
|
||||||
|
while (r.Read())
|
||||||
|
{
|
||||||
|
result.Add(new Entry(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
28
Module/ModLogs/EventListener.cs
Normal file
28
Module/ModLogs/EventListener.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Listens for Discord-based events and writes them to the log (database).
|
||||||
|
/// Additionally writes certain messages to a designated logging channel if configured.
|
||||||
|
/// </summary>
|
||||||
|
class EventListener : BotModule
|
||||||
|
{
|
||||||
|
public override string Name => "ModLogs";
|
||||||
|
public EventListener(DiscordSocketClient client) : base(client)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConfigSection("modlogs")]
|
||||||
|
public override Task<object> ProcessConfiguration(JToken configSection)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
Module/ModLogs/MessageCache.cs
Normal file
110
Module/ModLogs/MessageCache.cs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Noikoio.RegexBot.ConfigItem;
|
||||||
|
using Npgsql;
|
||||||
|
using NpgsqlTypes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.DBCache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Caches information regarding all incoming messages.
|
||||||
|
/// The function of this feature should be transparent to the user, and thus no configuration is needed.
|
||||||
|
/// </summary>
|
||||||
|
class MessageCache : BotFeature
|
||||||
|
{
|
||||||
|
// TODO Something that clears expired cache items
|
||||||
|
private readonly DatabaseConfig _db;
|
||||||
|
|
||||||
|
public override string Name => nameof(MessageCache);
|
||||||
|
|
||||||
|
public MessageCache(DiscordSocketClient client) : base(client)
|
||||||
|
{
|
||||||
|
_db = RegexBot.Config.Database;
|
||||||
|
|
||||||
|
if (_db.Enabled)
|
||||||
|
{
|
||||||
|
CreateCacheTables();
|
||||||
|
|
||||||
|
client.MessageReceived += Client_MessageReceived;
|
||||||
|
//client.MessageUpdated += Client_MessageUpdated;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log("No database storage available.").Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Table setup
|
||||||
|
const string TableMessage = "cache_messages";
|
||||||
|
|
||||||
|
public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
|
||||||
|
|
||||||
|
#region Event handling
|
||||||
|
// A new message has been created
|
||||||
|
private async Task Client_MessageReceived(SocketMessage arg)
|
||||||
|
{
|
||||||
|
await Task.Run(() => CacheMessage(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
//private Task Client_MessageUpdated(Discord.Cacheable<Discord.IMessage, ulong> arg1, SocketMessage arg2, ISocketMessageChannel arg3)
|
||||||
|
/*
|
||||||
|
* Edited messages seem to retain their ID. This is a problem.
|
||||||
|
* The point of this message cache was to have another feature be able to relay
|
||||||
|
* both the previous and current message at once.
|
||||||
|
* For now: Do nothing on updated messages.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void CreateCacheTables()
|
||||||
|
{
|
||||||
|
using (var db = _db.GetOpenConnectionAsync().GetAwaiter().GetResult())
|
||||||
|
{
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableMessage + " ("
|
||||||
|
+ "message_id bigint primary key, "
|
||||||
|
+ "author_id bigint not null, "
|
||||||
|
+ "guild_id bigint not null, "
|
||||||
|
+ "channel_id bigint not null, " // channel cache later? something to think about...
|
||||||
|
+ "created_ts timestamptz not null, "
|
||||||
|
+ "edited_ts timestamptz null, "
|
||||||
|
+ "message text not null, "
|
||||||
|
+ $"FOREIGN KEY (author_id, guild_id) references {EntityCache.Sql.TableUser} (user_id, guild_id)"
|
||||||
|
+ ")";
|
||||||
|
// TODO figure out how to store message edits
|
||||||
|
c.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private async Task CacheMessage(SocketMessage msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var db = await _db.GetOpenConnectionAsync())
|
||||||
|
{
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = "INSERT INTO " + TableMessage
|
||||||
|
+ " (message_id, author_id, guild_id, channel_id, created_ts, message) VALUES "
|
||||||
|
+ "(@MessageId, @UserId, @GuildId, @ChannelId, @Date, @Message)";
|
||||||
|
c.Parameters.Add("@MessageId", NpgsqlDbType.Bigint).Value = msg.Id;
|
||||||
|
c.Parameters.Add("@UserId", NpgsqlDbType.Bigint).Value = msg.Author.Id;
|
||||||
|
c.Parameters.Add("@GuildId", NpgsqlDbType.Bigint).Value = ((SocketGuildUser)msg.Author).Guild.Id;
|
||||||
|
c.Parameters.Add("@ChannelId", NpgsqlDbType.Bigint).Value = msg.Channel.Id;
|
||||||
|
c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = msg.Timestamp;
|
||||||
|
c.Parameters.Add("@Message", NpgsqlDbType.Text).Value = msg.Content;
|
||||||
|
c.Prepare();
|
||||||
|
await c.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NpgsqlException ex)
|
||||||
|
{
|
||||||
|
await Log($"SQL error in {nameof(CacheMessage)}: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
Module/ModLogs/ModLogs.cs
Normal file
10
Module/ModLogs/ModLogs.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
|
{
|
||||||
|
class ModLogs
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
47
Module/ModLogs/Sql.cs
Normal file
47
Module/ModLogs/Sql.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains common constants and static methods used for accessing the log database.
|
||||||
|
/// </summary>
|
||||||
|
class Sql
|
||||||
|
{
|
||||||
|
public const string TableLog = "modlogs_entries";
|
||||||
|
public const string TableLogIncr = TableLog + "_id";
|
||||||
|
public const string TableMsgCache = "modlogs_msgcache";
|
||||||
|
|
||||||
|
static void CreateTables()
|
||||||
|
{
|
||||||
|
using (var db = RegexBot.Config.Database.GetOpenConnectionAsync().GetAwaiter().GetResult())
|
||||||
|
{
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableLog + " ("
|
||||||
|
+ "id int primary key, "
|
||||||
|
+ "entry_ts timestamptz not null, "
|
||||||
|
+ "guild_id bigint not null, "
|
||||||
|
+ "target_id bigint not null, "
|
||||||
|
+ $"invoke_id bigint null references {EntityCache.Sql.TableUser}.user_id, "
|
||||||
|
+ "target_channel_id bigint null, " // TODO channel cache reference?
|
||||||
|
+ "category text not null, "
|
||||||
|
+ "message text not null, "
|
||||||
|
+ $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.Sql.TableUser} (user_id, guild_id)";
|
||||||
|
c.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
using (var c = db.CreateCommand())
|
||||||
|
{
|
||||||
|
c.CommandText = $"CREATE SEQUENCE IF NOT EXISTS {TableLogIncr} "
|
||||||
|
+ $"START 100 MAXVALUE {int.MaxValue}";
|
||||||
|
c.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Log entry manipulation
|
||||||
|
// what was I doing again
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue