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:
|
||||
auditLogCache.SetUnban(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
||||
break;
|
||||
case AuditLogEvent.MemberRoleUpdate:
|
||||
case AuditLogEvent.MemberUpdate:
|
||||
auditLogCache.SetMemberUpdate(
|
||||
evt.GuildID,
|
||||
|
|
@ -38,8 +39,9 @@ public class AuditLogResponder(AuditLogCache auditLogCache, ILogger logger)
|
|||
break;
|
||||
default:
|
||||
_logger.Debug(
|
||||
"Received audit log event {Id} that we don't care about, ignoring",
|
||||
evt.ID
|
||||
"Received audit log event {Id} with type {Type} that we don't care about, ignoring",
|
||||
evt.ID,
|
||||
evt.ActionType
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Remora.Discord.API.Abstractions.Gateway.Events;
|
|||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.Extensions.Embeds;
|
||||
using Remora.Discord.Gateway.Responders;
|
||||
using Remora.Rest.Core;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Catalogger.Backend.Bot.Responders.MemberUpdate;
|
||||
|
|
@ -16,6 +17,7 @@ public class GuildMemberUpdateResponder(
|
|||
ILogger logger,
|
||||
DatabaseContext db,
|
||||
UserCache userCache,
|
||||
RoleCache roleCache,
|
||||
IMemberCache memberCache,
|
||||
WebhookExecutorService webhookExecutor,
|
||||
AuditLogCache auditLogCache
|
||||
|
|
@ -67,6 +69,14 @@ public class GuildMemberUpdateResponder(
|
|||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -234,4 +244,120 @@ public class GuildMemberUpdateResponder(
|
|||
);
|
||||
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)
|
||||
{
|
||||
_logger.Debug(
|
||||
"Updating member {MemberId} in {GuildId}",
|
||||
newMember.User.ID,
|
||||
newMember.GuildID
|
||||
);
|
||||
|
||||
var oldMember = await TryGetInnerAsync(newMember.GuildID, newMember.User.ID);
|
||||
if (oldMember == null)
|
||||
{
|
||||
|
|
@ -109,7 +115,7 @@ public class RedisMemberCache(
|
|||
User = RedisUser.FromIUser(newMember.User),
|
||||
Nickname = newMember.Nickname.HasValue ? newMember.Nickname.Value : oldMember.Nickname,
|
||||
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,
|
||||
PremiumSince = newMember.PremiumSince.HasValue
|
||||
? newMember.PremiumSince.Value
|
||||
|
|
@ -134,7 +140,7 @@ internal record RedisMember(
|
|||
RedisUser User,
|
||||
string? Nickname,
|
||||
string? Avatar,
|
||||
Snowflake[] Roles,
|
||||
ulong[] Roles,
|
||||
DateTimeOffset JoinedAt,
|
||||
DateTimeOffset? PremiumSince,
|
||||
GuildMemberFlags Flags,
|
||||
|
|
@ -147,7 +153,7 @@ internal record RedisMember(
|
|||
RedisUser.FromIUser(member.User.Value),
|
||||
member.Nickname.OrDefault(null),
|
||||
member.Avatar.OrDefault(null)?.Value,
|
||||
member.Roles.ToArray(),
|
||||
member.Roles.Select(id => id.Value).ToArray(),
|
||||
member.JoinedAt,
|
||||
member.PremiumSince.OrDefault(null),
|
||||
member.Flags,
|
||||
|
|
@ -160,7 +166,7 @@ internal record RedisMember(
|
|||
User.ToRemoraUser(),
|
||||
Nickname,
|
||||
Avatar != null ? new ImageHash(Avatar) : null,
|
||||
Roles,
|
||||
Roles.Select(DiscordSnowflake.New).ToList(),
|
||||
JoinedAt,
|
||||
PremiumSince,
|
||||
false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue