Add log item reporting to ModLogs module
This commit is contained in:
parent
785e69773e
commit
6544d4844b
7 changed files with 114 additions and 49 deletions
|
@ -1,5 +1,6 @@
|
|||
using Discord;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RegexBot.Common;
|
||||
|
@ -64,4 +65,45 @@ public static class Utilities {
|
|||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns an embed which displays this log entry.
|
||||
/// </summary>
|
||||
public static Embed BuildEmbed(this Data.ModLogEntry entry, RegexbotClient bot) {
|
||||
var logEmbed = new EmbedBuilder()
|
||||
.WithTitle("Moderation log entry")
|
||||
.WithTimestamp(entry.Timestamp)
|
||||
.WithFooter($"Log ID {entry.LogId}");
|
||||
|
||||
string? issuedDisplay = null;
|
||||
try {
|
||||
var entityTry = new EntityName(entry.IssuedBy, EntityType.User);
|
||||
var issueq = bot.EcQueryUser(entityTry.Id!.Value.ToString());
|
||||
if (issueq != null) issuedDisplay = $"<@{issueq.UserId}> - {issueq.Username}#{issueq.Discriminator} `{issueq.UserId}`";
|
||||
else issuedDisplay = $"Unknown user with ID `{entityTry.Id!.Value}`";
|
||||
} catch (Exception) { }
|
||||
issuedDisplay ??= entry.IssuedBy;
|
||||
string targetDisplay;
|
||||
var targetq = bot.EcQueryUser(entry.UserId.ToString());
|
||||
if (targetq != null) targetDisplay = $"<@{targetq.UserId}> - {targetq.Username}#{targetq.Discriminator} `{targetq.UserId}`";
|
||||
else targetDisplay = $"Unknown user with ID `{entry.UserId}`";
|
||||
|
||||
var contextStr = new StringBuilder();
|
||||
contextStr.AppendLine($"Log type: {Enum.GetName(typeof(ModLogType), entry.LogType)}");
|
||||
contextStr.AppendLine($"Regarding user: {targetDisplay}");
|
||||
contextStr.AppendLine($"Logged by: {issuedDisplay}");
|
||||
|
||||
logEmbed.AddField(new EmbedFieldBuilder() {
|
||||
Name = "Context",
|
||||
Value = contextStr.ToString()
|
||||
});
|
||||
if (entry.Message != null) {
|
||||
logEmbed.AddField(new EmbedFieldBuilder() {
|
||||
Name = "Message",
|
||||
Value = entry.Message
|
||||
});
|
||||
}
|
||||
|
||||
return logEmbed.Build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ internal partial class ModLogs : RegexbotModule {
|
|||
// TODO consider resurrecting 2.x idea of logging actions to db, making it searchable?
|
||||
|
||||
public ModLogs(RegexbotClient bot) : base(bot) {
|
||||
// TODO missing logging features: joins, leaves, bans, kicks, user edits (nick/username/discr)
|
||||
// TODO missing logging features: joins, leaves, user edits (nick/username/discr)
|
||||
DiscordClient.MessageDeleted += HandleDelete;
|
||||
bot.SharedEventReceived += FilterIncomingEvents;
|
||||
bot.SharedEventReceived += HandleReceivedSharedEvent;
|
||||
}
|
||||
|
||||
public override Task<object?> CreateGuildStateAsync(ulong guildID, JToken config) {
|
||||
|
@ -24,6 +24,11 @@ internal partial class ModLogs : RegexbotModule {
|
|||
return Task.FromResult<object?>(new ModuleConfig((JObject)config));
|
||||
}
|
||||
|
||||
private async Task HandleReceivedSharedEvent(ISharedEvent ev) {
|
||||
if (ev is MessageCacheUpdateEvent upd) await HandleUpdate(upd.OldMessage, upd.NewMessage);
|
||||
else if (ev is Data.ModLogEntry log) await HandleLog(log);
|
||||
}
|
||||
|
||||
private static string MakeTimestamp(DateTimeOffset time) {
|
||||
var result = new StringBuilder();
|
||||
//result.Append(time.ToString("yyyy-MM-dd hh:mm:ss"));
|
||||
|
|
17
Modules/ModLogs/ModLogs_Logging.cs
Normal file
17
Modules/ModLogs/ModLogs_Logging.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using RegexBot.Common;
|
||||
using RegexBot.Data;
|
||||
|
||||
namespace RegexBot.Modules.ModLogs;
|
||||
// Contains all logic relating to reporting new database mod log entries
|
||||
internal partial class ModLogs {
|
||||
public async Task HandleLog(ModLogEntry entry) {
|
||||
var guild = Bot.DiscordClient.GetGuild((ulong)entry.GuildId);
|
||||
if (guild == null) return;
|
||||
var conf = GetGuildState<ModuleConfig>(guild.Id);
|
||||
if ((conf?.LogModLogs ?? false) == false) return;
|
||||
var reportChannel = conf?.ReportingChannel?.FindChannelIn(guild, true);
|
||||
if (reportChannel == null) return;
|
||||
|
||||
await reportChannel.SendMessageAsync(embed: entry.BuildEmbed(Bot));
|
||||
}
|
||||
}
|
|
@ -14,11 +14,11 @@ internal partial class ModLogs {
|
|||
const int MaxPreviewLength = 750;
|
||||
if (argChannel.Value is not SocketTextChannel channel) return;
|
||||
var conf = GetGuildState<ModuleConfig>(channel.Guild.Id);
|
||||
if ((conf?.LogMessageDeletions ?? false) == false) return;
|
||||
var reportChannel = conf?.ReportingChannel?.FindChannelIn(channel.Guild, true);
|
||||
if (reportChannel == null) return;
|
||||
if ((conf?.LogMessageDeletions ?? false) == false) return;
|
||||
if (reportChannel.Id == channel.Id) {
|
||||
Log($"[{channel.Guild.Name}] Message deletion detected in the reporting channel. Regular report has been suppressed.");
|
||||
Log(channel.Guild, "Message deleted in the reporting channel. Suppressing report.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -53,41 +53,27 @@ internal partial class ModLogs {
|
|||
IconUrl = cachedMsg.Author.AvatarUrl ?? GetDefaultAvatarUrl(cachedMsg.Author.Discriminator)
|
||||
};
|
||||
}
|
||||
var attach = CheckAttachments(cachedMsg.AttachmentNames);
|
||||
if (attach != null) reportEmbed.AddField(attach);
|
||||
SetAttachmentsField(reportEmbed, cachedMsg.AttachmentNames);
|
||||
} else {
|
||||
reportEmbed.Description = NotCached;
|
||||
}
|
||||
|
||||
var contextStr = new StringBuilder();
|
||||
contextStr.AppendLine($"User: {(cachedMsg != null ? $"<@!{cachedMsg.AuthorId}>" : "Unknown")}");
|
||||
contextStr.AppendLine($"Channel: <#{channel.Id}> (#{channel.Name})");
|
||||
contextStr.AppendLine($"Posted: {MakeTimestamp(SnowflakeUtils.FromSnowflake(argMsg.Id))}");
|
||||
if (cachedMsg?.EditedAt != null) contextStr.AppendLine($"Last edit: {MakeTimestamp(cachedMsg.EditedAt.Value)}");
|
||||
contextStr.AppendLine($"Message ID: {argMsg.Id}");
|
||||
reportEmbed.AddField(new EmbedFieldBuilder() {
|
||||
Name = "Context",
|
||||
Value = contextStr.ToString()
|
||||
});
|
||||
var editLine = $"Posted: {MakeTimestamp(SnowflakeUtils.FromSnowflake(argMsg.Id))}";
|
||||
if (cachedMsg?.EditedAt != null) editLine += $"\nLast edit: {MakeTimestamp(cachedMsg.EditedAt.Value)}";
|
||||
SetContextField(reportEmbed, (ulong?)cachedMsg?.AuthorId, channel, editLine, argMsg.Id);
|
||||
|
||||
await reportChannel.SendMessageAsync(embed: reportEmbed.Build());
|
||||
}
|
||||
|
||||
private async Task FilterIncomingEvents(ISharedEvent ev) {
|
||||
if (ev is MessageCacheUpdateEvent upd) {
|
||||
await HandleUpdate(upd.OldMessage, upd.NewMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleUpdate(CachedGuildMessage? oldMsg, SocketMessage newMsg) {
|
||||
const int MaxPreviewLength = 500;
|
||||
var channel = (SocketTextChannel)newMsg.Channel;
|
||||
var conf = GetGuildState<ModuleConfig>(channel.Guild.Id);
|
||||
|
||||
var reportChannel = conf?.ReportingChannel?.FindChannelIn(channel.Guild, true);
|
||||
if (reportChannel == null) return;
|
||||
if ((conf?.LogMessageEdits ?? false) == false) return;
|
||||
if (reportChannel.Id == channel.Id) {
|
||||
Log($"[{channel.Guild.Name}] Message edit detected in the reporting channel. Regular report has been suppressed.");
|
||||
Log(channel.Guild, "Message edited in the reporting channel. Suppressing report.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -128,25 +114,39 @@ internal partial class ModLogs {
|
|||
}
|
||||
reportEmbed.AddField(newField);
|
||||
|
||||
var attach = CheckAttachments(newMsg.Attachments.Select(a => a.Filename));
|
||||
if (attach != null) reportEmbed.AddField(attach);
|
||||
SetAttachmentsField(reportEmbed, newMsg.Attachments.Select(a => a.Filename));
|
||||
|
||||
var contextStr = new StringBuilder();
|
||||
contextStr.AppendLine($"User: <@!{newMsg.Author.Id}>");
|
||||
contextStr.AppendLine($"Channel: <#{channel.Id}> (#{channel.Name})");
|
||||
if ((oldMsg?.EditedAt) == null) contextStr.AppendLine($"Posted: {MakeTimestamp(SnowflakeUtils.FromSnowflake(newMsg.Id))}");
|
||||
else contextStr.AppendLine($"Previous edit: {MakeTimestamp(oldMsg.EditedAt.Value)}");
|
||||
contextStr.AppendLine($"Message ID: {newMsg.Id}");
|
||||
var contextField = new EmbedFieldBuilder() {
|
||||
Name = "Context",
|
||||
Value = contextStr.ToString()
|
||||
};
|
||||
reportEmbed.AddField(contextField);
|
||||
string editLine;
|
||||
if ((oldMsg?.EditedAt) == null) editLine = $"Posted: {MakeTimestamp(SnowflakeUtils.FromSnowflake(newMsg.Id))}";
|
||||
else editLine = $"Previous edit: {MakeTimestamp(oldMsg.EditedAt.Value)}";
|
||||
SetContextField(reportEmbed, newMsg.Author.Id, channel, editLine, newMsg.Id);
|
||||
|
||||
await reportChannel.SendMessageAsync(embed: reportEmbed.Build());
|
||||
}
|
||||
|
||||
private static EmbedFieldBuilder? CheckAttachments(IEnumerable<string> attachments) {
|
||||
private void SetContextField(EmbedBuilder e, ulong? userId, SocketTextChannel channel, string editLine, ulong msgId) {
|
||||
string userDisplay;
|
||||
if (userId.HasValue) {
|
||||
var q = Bot.EcQueryUser(userId.Value.ToString());
|
||||
if (q != null) userDisplay = $"<@{q.UserId}> - {q.Username}#{q.Discriminator} `{q.UserId}`";
|
||||
else userDisplay = $"Unknown user with ID `{userId}`";
|
||||
} else {
|
||||
userDisplay = "Unknown";
|
||||
}
|
||||
|
||||
var contextStr = new StringBuilder();
|
||||
contextStr.AppendLine($"User: {userDisplay}");
|
||||
contextStr.AppendLine($"Channel: <#{channel.Id}> (#{channel.Name})");
|
||||
contextStr.AppendLine(editLine);
|
||||
contextStr.AppendLine($"Message ID: {msgId}");
|
||||
|
||||
e.AddField(new EmbedFieldBuilder() {
|
||||
Name = "Context",
|
||||
Value = contextStr.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
private static void SetAttachmentsField(EmbedBuilder e, IEnumerable<string> attachments) {
|
||||
if (attachments.Any()) {
|
||||
var field = new EmbedFieldBuilder { Name = "Attachments" };
|
||||
var attachNames = new StringBuilder();
|
||||
|
@ -154,8 +154,7 @@ internal partial class ModLogs {
|
|||
attachNames.AppendLine($"`{name}`");
|
||||
}
|
||||
field.Value = attachNames.ToString().TrimEnd();
|
||||
return field;
|
||||
e.AddField(field);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ class ModuleConfig {
|
|||
|
||||
public bool LogMessageDeletions { get; }
|
||||
public bool LogMessageEdits { get; }
|
||||
public bool LogModLogs { get; }
|
||||
|
||||
public ModuleConfig(JObject config) {
|
||||
const string RptChError = $"'{nameof(ReportingChannel)}' must be set to a valid channel name.";
|
||||
|
@ -18,5 +19,6 @@ class ModuleConfig {
|
|||
// Individual logging settings - all default to false
|
||||
LogMessageDeletions = config[nameof(LogMessageDeletions)]?.Value<bool>() ?? false;
|
||||
LogMessageEdits = config[nameof(LogMessageEdits)]?.Value<bool>() ?? false;
|
||||
LogModLogs = config[nameof(LogModLogs)]?.Value<bool>() ?? false;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#pragma warning disable CA1822 // "Mark members as static" - will not make static to encourage better structure
|
||||
#pragma warning disable CA1822 // "Mark members as static" - members should only be callable by code with access to this instance
|
||||
using Discord.Net;
|
||||
using RegexBot.Common;
|
||||
using RegexBot.Data;
|
||||
|
@ -22,7 +22,7 @@ partial class RegexbotClient {
|
|||
/// <returns>
|
||||
/// The resulting <see cref="ModLogEntry"/> from the creation of this note.
|
||||
/// </returns>
|
||||
public ModLogEntry AddUserNote(SocketGuild guild, ulong targetUser, string source, string? message) {
|
||||
public async Task<ModLogEntry> AddUserNote(SocketGuild guild, ulong targetUser, string source, string? message) {
|
||||
var entry = new ModLogEntry() {
|
||||
GuildId = (long)guild.Id,
|
||||
UserId = (long)targetUser,
|
||||
|
@ -32,9 +32,9 @@ partial class RegexbotClient {
|
|||
};
|
||||
using (var db = new BotDatabaseContext()) {
|
||||
db.Add(entry);
|
||||
db.SaveChanges();
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
// TODO notify
|
||||
await PushSharedEventAsync(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ partial class RegexbotClient {
|
|||
db.Add(entry);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
// TODO notify
|
||||
await PushSharedEventAsync(entry);
|
||||
|
||||
// Attempt warning message
|
||||
var userSearch = _svcEntityCache.QueryUserCache(targetUser.ToString());
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#pragma warning disable CA1822 // "Mark members as static" - will not make static to encourage better structure
|
||||
#pragma warning disable CA1822 // "Mark members as static" - members should only be callable by code with access to this instance
|
||||
using Discord.Net;
|
||||
using RegexBot.Data;
|
||||
|
||||
|
@ -24,7 +24,7 @@ internal partial class CommonFunctionsService : Service {
|
|||
db.Add(entry);
|
||||
db.SaveChanges();
|
||||
}
|
||||
// TODO notify entry
|
||||
BotClient.PushSharedEventAsync(entry);
|
||||
}
|
||||
|
||||
internal async Task<HttpException?> SendUserWarningAsync(SocketGuildUser target, string? reason) {
|
||||
|
|
Loading…
Reference in a new issue