feat: member role and key role logging
This commit is contained in:
parent
d445b5ba44
commit
a56cb87294
3 changed files with 140 additions and 6 deletions
|
|
@ -28,6 +28,7 @@ public class AuditLogResponder(AuditLogCache auditLogCache, ILogger logger)
|
||||||
case AuditLogEvent.MemberBanRemove:
|
case AuditLogEvent.MemberBanRemove:
|
||||||
auditLogCache.SetUnban(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
auditLogCache.SetUnban(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
||||||
break;
|
break;
|
||||||
|
case AuditLogEvent.MemberRoleUpdate:
|
||||||
case AuditLogEvent.MemberUpdate:
|
case AuditLogEvent.MemberUpdate:
|
||||||
auditLogCache.SetMemberUpdate(
|
auditLogCache.SetMemberUpdate(
|
||||||
evt.GuildID,
|
evt.GuildID,
|
||||||
|
|
@ -38,8 +39,9 @@ public class AuditLogResponder(AuditLogCache auditLogCache, ILogger logger)
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_logger.Debug(
|
_logger.Debug(
|
||||||
"Received audit log event {Id} that we don't care about, ignoring",
|
"Received audit log event {Id} with type {Type} that we don't care about, ignoring",
|
||||||
evt.ID
|
evt.ID,
|
||||||
|
evt.ActionType
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
using Remora.Discord.Extensions.Embeds;
|
using Remora.Discord.Extensions.Embeds;
|
||||||
using Remora.Discord.Gateway.Responders;
|
using Remora.Discord.Gateway.Responders;
|
||||||
|
using Remora.Rest.Core;
|
||||||
using Remora.Results;
|
using Remora.Results;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Bot.Responders.MemberUpdate;
|
namespace Catalogger.Backend.Bot.Responders.MemberUpdate;
|
||||||
|
|
@ -16,6 +17,7 @@ public class GuildMemberUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
RoleCache roleCache,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
WebhookExecutorService webhookExecutor,
|
WebhookExecutorService webhookExecutor,
|
||||||
AuditLogCache auditLogCache
|
AuditLogCache auditLogCache
|
||||||
|
|
@ -67,6 +69,14 @@ public class GuildMemberUpdateResponder(
|
||||||
{
|
{
|
||||||
return await HandleTimeoutAsync(newMember, ct);
|
return await HandleTimeoutAsync(newMember, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
newMember.Roles.Except(oldMember.Roles).Any()
|
||||||
|
|| oldMember.Roles.Except(newMember.Roles).Any()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return await HandleRoleUpdateAsync(newMember, oldMember.Roles, ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -234,4 +244,120 @@ public class GuildMemberUpdateResponder(
|
||||||
);
|
);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Result> HandleRoleUpdateAsync(
|
||||||
|
IGuildMemberUpdate member,
|
||||||
|
IReadOnlyList<Snowflake> oldRoles,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var guildConfig = await db.GetGuildAsync(member.GuildID, ct);
|
||||||
|
var guildRoles = roleCache.GuildRoles(member.GuildID).ToList();
|
||||||
|
|
||||||
|
var keyRoleUpdate = new EmbedBuilder()
|
||||||
|
.WithAuthor(member.User.Tag(), null, member.User.AvatarUrl())
|
||||||
|
.WithTitle("Key roles added or removed")
|
||||||
|
.WithDescription($"<@{member.User.ID}>")
|
||||||
|
.WithColour(DiscordUtils.Purple)
|
||||||
|
.WithFooter($"User ID: {member.User.ID}")
|
||||||
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
|
var roleUpdate = new EmbedBuilder()
|
||||||
|
.WithAuthor(member.User.Tag(), null, member.User.AvatarUrl())
|
||||||
|
.WithTitle("Roles added or removed")
|
||||||
|
.WithDescription($"<@{member.User.ID}>")
|
||||||
|
.WithColour(DiscordUtils.Purple)
|
||||||
|
.WithFooter($"User ID: {member.User.ID}")
|
||||||
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
|
var addedRoles = member.Roles.Except(oldRoles).Select(s => s.Value).ToList();
|
||||||
|
var removedRoles = oldRoles.Except(member.Roles).Select(s => s.Value).ToList();
|
||||||
|
|
||||||
|
if (addedRoles.Count != 0)
|
||||||
|
{
|
||||||
|
roleUpdate.AddField("Added", string.Join(", ", addedRoles.Select(id => $"<@&{id}>")));
|
||||||
|
|
||||||
|
// Add all added key roles to the log
|
||||||
|
if (!addedRoles.Except(guildConfig.KeyRoles).Any())
|
||||||
|
{
|
||||||
|
var value = string.Join(
|
||||||
|
"\n",
|
||||||
|
addedRoles
|
||||||
|
.Where(guildConfig.KeyRoles.Contains)
|
||||||
|
.Select(id =>
|
||||||
|
{
|
||||||
|
var role = guildRoles.FirstOrDefault(r => r.ID.Value == id);
|
||||||
|
return role != null ? $"{role.Name} <@&{role.ID}>" : $"<@&{id}>";
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
keyRoleUpdate.AddField("Added", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedRoles.Count != 0)
|
||||||
|
{
|
||||||
|
roleUpdate.AddField(
|
||||||
|
"Removed",
|
||||||
|
string.Join(", ", removedRoles.Select(id => $"<@&{id}>"))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add all removed key roles to the log
|
||||||
|
if (!removedRoles.Except(guildConfig.KeyRoles).Any())
|
||||||
|
{
|
||||||
|
var value = string.Join(
|
||||||
|
"\n",
|
||||||
|
removedRoles
|
||||||
|
.Where(guildConfig.KeyRoles.Contains)
|
||||||
|
.Select(id =>
|
||||||
|
{
|
||||||
|
var role = guildRoles.FirstOrDefault(r => r.ID.Value == id);
|
||||||
|
return role != null ? $"{role.Name} <@&{role.ID}>" : $"<@&{id}>";
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
keyRoleUpdate.AddField("Added", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are any fields in the role update embed, we should send it
|
||||||
|
if (roleUpdate.Fields.Count != 0)
|
||||||
|
{
|
||||||
|
webhookExecutor.QueueLog(
|
||||||
|
guildConfig,
|
||||||
|
LogChannelType.GuildMemberUpdate,
|
||||||
|
roleUpdate.Build().GetOrThrow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same for the key role update embed, but we also need to fetch the moderator that updated them
|
||||||
|
if (keyRoleUpdate.Fields.Count != 0)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
|
||||||
|
keyRoleUpdate.AddField(
|
||||||
|
"Responsible moderator",
|
||||||
|
moderator == null
|
||||||
|
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
|
||||||
|
: $"{moderator.Tag()} <@{moderator.ID}>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keyRoleUpdate.AddField("Responsible moderator", "*(unknown)*");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, send the embed
|
||||||
|
webhookExecutor.QueueLog(
|
||||||
|
guildConfig,
|
||||||
|
LogChannelType.GuildKeyRoleUpdate,
|
||||||
|
keyRoleUpdate.Build().GetOrThrow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ public class RedisMemberCache(
|
||||||
|
|
||||||
public async Task UpdateAsync(IGuildMemberUpdate newMember)
|
public async Task UpdateAsync(IGuildMemberUpdate newMember)
|
||||||
{
|
{
|
||||||
|
_logger.Debug(
|
||||||
|
"Updating member {MemberId} in {GuildId}",
|
||||||
|
newMember.User.ID,
|
||||||
|
newMember.GuildID
|
||||||
|
);
|
||||||
|
|
||||||
var oldMember = await TryGetInnerAsync(newMember.GuildID, newMember.User.ID);
|
var oldMember = await TryGetInnerAsync(newMember.GuildID, newMember.User.ID);
|
||||||
if (oldMember == null)
|
if (oldMember == null)
|
||||||
{
|
{
|
||||||
|
|
@ -109,7 +115,7 @@ public class RedisMemberCache(
|
||||||
User = RedisUser.FromIUser(newMember.User),
|
User = RedisUser.FromIUser(newMember.User),
|
||||||
Nickname = newMember.Nickname.HasValue ? newMember.Nickname.Value : oldMember.Nickname,
|
Nickname = newMember.Nickname.HasValue ? newMember.Nickname.Value : oldMember.Nickname,
|
||||||
Avatar = newMember.Avatar.HasValue ? newMember.Avatar.Value?.Value : oldMember.Avatar,
|
Avatar = newMember.Avatar.HasValue ? newMember.Avatar.Value?.Value : oldMember.Avatar,
|
||||||
Roles = newMember.Roles.ToArray(),
|
Roles = newMember.Roles.Select(id => id.Value).ToArray(),
|
||||||
JoinedAt = newMember.JoinedAt ?? oldMember.JoinedAt,
|
JoinedAt = newMember.JoinedAt ?? oldMember.JoinedAt,
|
||||||
PremiumSince = newMember.PremiumSince.HasValue
|
PremiumSince = newMember.PremiumSince.HasValue
|
||||||
? newMember.PremiumSince.Value
|
? newMember.PremiumSince.Value
|
||||||
|
|
@ -134,7 +140,7 @@ internal record RedisMember(
|
||||||
RedisUser User,
|
RedisUser User,
|
||||||
string? Nickname,
|
string? Nickname,
|
||||||
string? Avatar,
|
string? Avatar,
|
||||||
Snowflake[] Roles,
|
ulong[] Roles,
|
||||||
DateTimeOffset JoinedAt,
|
DateTimeOffset JoinedAt,
|
||||||
DateTimeOffset? PremiumSince,
|
DateTimeOffset? PremiumSince,
|
||||||
GuildMemberFlags Flags,
|
GuildMemberFlags Flags,
|
||||||
|
|
@ -147,7 +153,7 @@ internal record RedisMember(
|
||||||
RedisUser.FromIUser(member.User.Value),
|
RedisUser.FromIUser(member.User.Value),
|
||||||
member.Nickname.OrDefault(null),
|
member.Nickname.OrDefault(null),
|
||||||
member.Avatar.OrDefault(null)?.Value,
|
member.Avatar.OrDefault(null)?.Value,
|
||||||
member.Roles.ToArray(),
|
member.Roles.Select(id => id.Value).ToArray(),
|
||||||
member.JoinedAt,
|
member.JoinedAt,
|
||||||
member.PremiumSince.OrDefault(null),
|
member.PremiumSince.OrDefault(null),
|
||||||
member.Flags,
|
member.Flags,
|
||||||
|
|
@ -160,7 +166,7 @@ internal record RedisMember(
|
||||||
User.ToRemoraUser(),
|
User.ToRemoraUser(),
|
||||||
Nickname,
|
Nickname,
|
||||||
Avatar != null ? new ImageHash(Avatar) : null,
|
Avatar != null ? new ImageHash(Avatar) : null,
|
||||||
Roles,
|
Roles.Select(DiscordSnowflake.New).ToList(),
|
||||||
JoinedAt,
|
JoinedAt,
|
||||||
PremiumSince,
|
PremiumSince,
|
||||||
false,
|
false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue