feat: member avatar/name logging, timeout logging
This commit is contained in:
parent
c906a4d6b6
commit
d445b5ba44
10 changed files with 350 additions and 17 deletions
|
|
@ -144,6 +144,11 @@ public class ChannelCommands(
|
||||||
PrettyChannelString(guildConfig.Channels.GuildMemberAvatarUpdate),
|
PrettyChannelString(guildConfig.Channels.GuildMemberAvatarUpdate),
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
|
new EmbedField(
|
||||||
|
"Timeouts",
|
||||||
|
PrettyChannelString(guildConfig.Channels.GuildMemberTimeout),
|
||||||
|
true
|
||||||
|
),
|
||||||
new EmbedField(
|
new EmbedField(
|
||||||
"Kicks",
|
"Kicks",
|
||||||
PrettyChannelString(guildConfig.Channels.GuildMemberKick),
|
PrettyChannelString(guildConfig.Channels.GuildMemberKick),
|
||||||
|
|
@ -306,12 +311,24 @@ public class ChannelCommands(
|
||||||
),
|
),
|
||||||
new ButtonComponent(
|
new ButtonComponent(
|
||||||
ButtonComponentStyle.Primary,
|
ButtonComponentStyle.Primary,
|
||||||
Label: "Members avatar changes",
|
Label: "Member avatar changes",
|
||||||
CustomID: CustomIDHelpers.CreateButtonIDWithState(
|
CustomID: CustomIDHelpers.CreateButtonIDWithState(
|
||||||
"config-channels",
|
"config-channels",
|
||||||
nameof(LogChannelType.GuildMemberAvatarUpdate)
|
nameof(LogChannelType.GuildMemberAvatarUpdate)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
new ButtonComponent(
|
||||||
|
ButtonComponentStyle.Primary,
|
||||||
|
Label: "Timeouts",
|
||||||
|
CustomID: CustomIDHelpers.CreateButtonIDWithState(
|
||||||
|
"config-channels",
|
||||||
|
nameof(LogChannelType.GuildMemberTimeout)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new ActionRowComponent(
|
||||||
|
[
|
||||||
new ButtonComponent(
|
new ButtonComponent(
|
||||||
ButtonComponentStyle.Primary,
|
ButtonComponentStyle.Primary,
|
||||||
Label: "Kicks",
|
Label: "Kicks",
|
||||||
|
|
@ -320,10 +337,6 @@ public class ChannelCommands(
|
||||||
nameof(LogChannelType.GuildMemberKick)
|
nameof(LogChannelType.GuildMemberKick)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
|
||||||
),
|
|
||||||
new ActionRowComponent(
|
|
||||||
[
|
|
||||||
new ButtonComponent(
|
new ButtonComponent(
|
||||||
ButtonComponentStyle.Primary,
|
ButtonComponentStyle.Primary,
|
||||||
Label: "Bans",
|
Label: "Bans",
|
||||||
|
|
@ -356,6 +369,10 @@ public class ChannelCommands(
|
||||||
nameof(LogChannelType.InviteDelete)
|
nameof(LogChannelType.InviteDelete)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new ActionRowComponent(
|
||||||
|
[
|
||||||
new ButtonComponent(
|
new ButtonComponent(
|
||||||
ButtonComponentStyle.Primary,
|
ButtonComponentStyle.Primary,
|
||||||
Label: "Edited messages",
|
Label: "Edited messages",
|
||||||
|
|
@ -364,10 +381,6 @@ public class ChannelCommands(
|
||||||
nameof(LogChannelType.MessageUpdate)
|
nameof(LogChannelType.MessageUpdate)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
|
||||||
),
|
|
||||||
new ActionRowComponent(
|
|
||||||
[
|
|
||||||
new ButtonComponent(
|
new ButtonComponent(
|
||||||
ButtonComponentStyle.Primary,
|
ButtonComponentStyle.Primary,
|
||||||
Label: "Deleted messages",
|
Label: "Deleted messages",
|
||||||
|
|
@ -420,10 +433,11 @@ public class ChannelCommands(
|
||||||
LogChannelType.ChannelUpdate => "Edited channels",
|
LogChannelType.ChannelUpdate => "Edited channels",
|
||||||
LogChannelType.ChannelDelete => "Deleted channels",
|
LogChannelType.ChannelDelete => "Deleted channels",
|
||||||
LogChannelType.GuildMemberAdd => "Members joining",
|
LogChannelType.GuildMemberAdd => "Members joining",
|
||||||
LogChannelType.GuildMemberUpdate => "Members leaving",
|
LogChannelType.GuildMemberUpdate => "Member role changes",
|
||||||
LogChannelType.GuildKeyRoleUpdate => "Key role changes",
|
LogChannelType.GuildKeyRoleUpdate => "Key role changes",
|
||||||
LogChannelType.GuildMemberNickUpdate => "Member name changes",
|
LogChannelType.GuildMemberNickUpdate => "Member name changes",
|
||||||
LogChannelType.GuildMemberAvatarUpdate => "Member avatar changes",
|
LogChannelType.GuildMemberAvatarUpdate => "Member avatar changes",
|
||||||
|
LogChannelType.GuildMemberTimeout => "Timeouts",
|
||||||
LogChannelType.GuildMemberRemove => "Members leaving",
|
LogChannelType.GuildMemberRemove => "Members leaving",
|
||||||
LogChannelType.GuildMemberKick => "Kicks",
|
LogChannelType.GuildMemberKick => "Kicks",
|
||||||
LogChannelType.GuildBanAdd => "Bans",
|
LogChannelType.GuildBanAdd => "Bans",
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,9 @@ public class ChannelCommandsComponents(
|
||||||
case LogChannelType.GuildMemberRemove:
|
case LogChannelType.GuildMemberRemove:
|
||||||
guildConfig.Channels.GuildMemberRemove = 0;
|
guildConfig.Channels.GuildMemberRemove = 0;
|
||||||
break;
|
break;
|
||||||
|
case LogChannelType.GuildMemberTimeout:
|
||||||
|
guildConfig.Channels.GuildMemberTimeout = 0;
|
||||||
|
break;
|
||||||
case LogChannelType.GuildMemberKick:
|
case LogChannelType.GuildMemberKick:
|
||||||
guildConfig.Channels.GuildMemberKick = 0;
|
guildConfig.Channels.GuildMemberKick = 0;
|
||||||
break;
|
break;
|
||||||
|
|
@ -305,6 +308,9 @@ public class ChannelCommandsComponents(
|
||||||
case LogChannelType.GuildMemberRemove:
|
case LogChannelType.GuildMemberRemove:
|
||||||
guildConfig.Channels.GuildMemberRemove = channelId;
|
guildConfig.Channels.GuildMemberRemove = channelId;
|
||||||
break;
|
break;
|
||||||
|
case LogChannelType.GuildMemberTimeout:
|
||||||
|
guildConfig.Channels.GuildMemberTimeout = channelId;
|
||||||
|
break;
|
||||||
case LogChannelType.GuildMemberKick:
|
case LogChannelType.GuildMemberKick:
|
||||||
guildConfig.Channels.GuildMemberKick = channelId;
|
guildConfig.Channels.GuildMemberKick = channelId;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
|
using Remora.Discord.API;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
using Remora.Discord.Gateway.Responders;
|
using Remora.Discord.Gateway.Responders;
|
||||||
using Remora.Results;
|
using Remora.Results;
|
||||||
|
|
||||||
|
|
@ -12,9 +14,36 @@ public class AuditLogResponder(AuditLogCache auditLogCache, ILogger logger)
|
||||||
|
|
||||||
public Task<Result> RespondAsync(IGuildAuditLogEntryCreate evt, CancellationToken ct = default)
|
public Task<Result> RespondAsync(IGuildAuditLogEntryCreate evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
_logger.Debug("type: {ActionType}", evt.ActionType);
|
if (evt.TargetID == null || evt.UserID == null)
|
||||||
_logger.Debug("{Id}, {Reason}", evt.ID, evt.Reason);
|
return Task.FromResult(Result.Success);
|
||||||
|
|
||||||
throw new NotImplementedException();
|
switch (evt.ActionType)
|
||||||
|
{
|
||||||
|
case AuditLogEvent.MemberKick:
|
||||||
|
auditLogCache.SetKick(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
||||||
|
break;
|
||||||
|
case AuditLogEvent.MemberBanAdd:
|
||||||
|
auditLogCache.SetBan(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
||||||
|
break;
|
||||||
|
case AuditLogEvent.MemberBanRemove:
|
||||||
|
auditLogCache.SetUnban(evt.GuildID, evt.TargetID, evt.UserID.Value, evt.Reason);
|
||||||
|
break;
|
||||||
|
case AuditLogEvent.MemberUpdate:
|
||||||
|
auditLogCache.SetMemberUpdate(
|
||||||
|
evt.GuildID,
|
||||||
|
evt.TargetID,
|
||||||
|
evt.UserID.Value,
|
||||||
|
evt.Reason
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_logger.Debug(
|
||||||
|
"Received audit log event {Id} that we don't care about, ignoring",
|
||||||
|
evt.ID
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(Result.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ public class GuildMemberAddResponder(
|
||||||
await memberCache.SetAsync(member.GuildID, member);
|
await memberCache.SetAsync(member.GuildID, member);
|
||||||
|
|
||||||
var user = member.User.GetOrThrow();
|
var user = member.User.GetOrThrow();
|
||||||
|
userCache.UpdateUser(user);
|
||||||
|
|
||||||
var builder = new EmbedBuilder()
|
var builder = new EmbedBuilder()
|
||||||
.WithTitle("Member joined")
|
.WithTitle("Member joined")
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Queries;
|
||||||
|
using Catalogger.Backend.Extensions;
|
||||||
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
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.Discord.Gateway.Responders;
|
||||||
using Remora.Results;
|
using Remora.Results;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Bot.Responders.MemberUpdate;
|
namespace Catalogger.Backend.Bot.Responders.MemberUpdate;
|
||||||
|
|
||||||
public class GuildMemberUpdateResponder(ILogger logger, IMemberCache memberCache)
|
public class GuildMemberUpdateResponder(
|
||||||
: IResponder<IGuildMemberUpdate>
|
ILogger logger,
|
||||||
|
DatabaseContext db,
|
||||||
|
UserCache userCache,
|
||||||
|
IMemberCache memberCache,
|
||||||
|
WebhookExecutorService webhookExecutor,
|
||||||
|
AuditLogCache auditLogCache
|
||||||
|
) : IResponder<IGuildMemberUpdate>
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger = logger.ForContext<GuildMemberUpdateResponder>();
|
private readonly ILogger _logger = logger.ForContext<GuildMemberUpdateResponder>();
|
||||||
|
|
||||||
|
|
@ -27,12 +40,198 @@ public class GuildMemberUpdateResponder(ILogger logger, IMemberCache memberCache
|
||||||
);
|
);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldUser = oldMember.User.GetOrThrow();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Equals(oldMember.Avatar.OrDefault(), newMember.Avatar.OrDefault())
|
||||||
|
|| !Equals(oldUser.Avatar, newMember.User.Avatar)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return await HandleAvatarUpdateAsync(newMember, oldMember, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
newMember.Nickname.OrDefault() != oldMember.Nickname.OrDefault()
|
||||||
|
|| newMember.User.Tag() != oldUser.Tag()
|
||||||
|
|| newMember.User.GlobalName.OrDefault() != oldUser.GlobalName.OrDefault()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return await HandleNameUpdateAsync(newMember, oldMember, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
newMember.CommunicationDisabledUntil.OrDefault()
|
||||||
|
!= oldMember.CommunicationDisabledUntil.OrDefault()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return await HandleTimeoutAsync(newMember, ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await memberCache.UpdateAsync(newMember);
|
await memberCache.UpdateAsync(newMember);
|
||||||
|
userCache.UpdateUser(newMember.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Result> HandleAvatarUpdateAsync(
|
||||||
|
IGuildMemberUpdate newMember,
|
||||||
|
IGuildMember oldMember,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
IEmbed embed;
|
||||||
|
|
||||||
|
if (!Equals(oldMember.Avatar.OrDefault(), newMember.Avatar.OrDefault()))
|
||||||
|
{
|
||||||
|
var builder = new EmbedBuilder()
|
||||||
|
.WithAuthor(newMember.User.Tag(), null, newMember.User.AvatarUrl())
|
||||||
|
.WithColour(DiscordUtils.Green)
|
||||||
|
.WithFooter($"User ID: {newMember.User.ID}")
|
||||||
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
|
if (newMember.Avatar.IsDefined())
|
||||||
|
{
|
||||||
|
builder = builder
|
||||||
|
.WithTitle("Changed server avatar")
|
||||||
|
.WithThumbnailUrl(newMember.AvatarUrl(1024)!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder = builder.WithTitle("Removed server avatar");
|
||||||
|
}
|
||||||
|
|
||||||
|
embed = builder.Build().GetOrThrow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
embed = new EmbedBuilder()
|
||||||
|
.WithAuthor(newMember.User.Tag(), null, newMember.User.AvatarUrl())
|
||||||
|
.WithTitle("Changed avatar")
|
||||||
|
.WithThumbnailUrl(newMember.User.AvatarUrl(1024))
|
||||||
|
.WithColour(DiscordUtils.Green)
|
||||||
|
.WithFooter($"User ID: {newMember.User.ID}")
|
||||||
|
.WithCurrentTimestamp()
|
||||||
|
.Build()
|
||||||
|
.GetOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
var guildConfig = await db.GetGuildAsync(newMember.GuildID, ct);
|
||||||
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildMemberAvatarUpdate, embed);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Result> HandleNameUpdateAsync(
|
||||||
|
IGuildMemberUpdate newMember,
|
||||||
|
IGuildMember oldMember,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var oldUser = oldMember.User.GetOrThrow();
|
||||||
|
|
||||||
|
var builder = new EmbedBuilder()
|
||||||
|
.WithAuthor(newMember.User.Tag(), null, newMember.User.AvatarUrl())
|
||||||
|
.WithColour(DiscordUtils.Green)
|
||||||
|
.WithFooter($"User ID: {newMember.User.ID}")
|
||||||
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
|
if (newMember.Nickname.OrDefault() != oldMember.Nickname.OrDefault())
|
||||||
|
{
|
||||||
|
builder.AddField(
|
||||||
|
"Changed nickname",
|
||||||
|
$"""
|
||||||
|
**Before:** {oldMember.Nickname.OrDefault("*(none)*")}
|
||||||
|
**After:** {newMember.Nickname.OrDefault("*(none)*")}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMember.User.GlobalName.OrDefault() != oldUser.GlobalName.OrDefault())
|
||||||
|
{
|
||||||
|
builder.AddField(
|
||||||
|
"Changed display name",
|
||||||
|
$"""
|
||||||
|
**Before:** {oldUser.GlobalName.OrDefault("*(none)*")}
|
||||||
|
**After:** {newMember.User.GlobalName.OrDefault("*(none)*")}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMember.User.Tag() != oldUser.Tag())
|
||||||
|
{
|
||||||
|
builder.AddField(
|
||||||
|
"Changed username",
|
||||||
|
$"""
|
||||||
|
**Before:** {oldUser.Tag()}
|
||||||
|
**After:** {newMember.User.Tag()}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var guildConfig = await db.GetGuildAsync(newMember.GuildID, ct);
|
||||||
|
webhookExecutor.QueueLog(
|
||||||
|
guildConfig,
|
||||||
|
LogChannelType.GuildMemberNickUpdate,
|
||||||
|
builder.Build().GetOrThrow()
|
||||||
|
);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Result> HandleTimeoutAsync(
|
||||||
|
IGuildMemberUpdate member,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Delay 2 seconds to make sure the timeout audit log got cached
|
||||||
|
await Task.Delay(2000, ct);
|
||||||
|
|
||||||
|
var timeoutUntil = member.CommunicationDisabledUntil.OrDefault();
|
||||||
|
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithAuthor(member.User.Tag(), null, member.User.AvatarUrl())
|
||||||
|
.WithTitle(
|
||||||
|
timeoutUntil != null ? "Member timed out" : "Member removed from timeout early"
|
||||||
|
)
|
||||||
|
.WithDescription($"<@{member.User.ID}>")
|
||||||
|
.WithColour(DiscordUtils.Red)
|
||||||
|
.WithFooter($"User ID: {member.User.ID}")
|
||||||
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
|
if (timeoutUntil != null)
|
||||||
|
{
|
||||||
|
embed.AddField(
|
||||||
|
"Until",
|
||||||
|
$"<t:{timeoutUntil.Value.ToUnixTimeSeconds()}>\nin {timeoutUntil.Value.AddSeconds(5).Prettify()}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auditLogCache.TryGetMemberUpdate(member.GuildID, member.User.ID, out var actionData))
|
||||||
|
{
|
||||||
|
var moderator = await userCache.GetUserAsync(actionData.ModeratorId);
|
||||||
|
embed.AddField(
|
||||||
|
"Responsible moderator",
|
||||||
|
moderator == null
|
||||||
|
? $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"
|
||||||
|
: $"{moderator.Tag()} <@{moderator.ID}>"
|
||||||
|
);
|
||||||
|
|
||||||
|
embed.AddField("Reason", actionData.Reason ?? "No reason given");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
embed.AddField("Responsible moderator", "*(unknown)*");
|
||||||
|
embed.AddField("Reason", "*(unknown)*");
|
||||||
|
}
|
||||||
|
|
||||||
|
var guildConfig = await db.GetGuildAsync(member.GuildID, ct);
|
||||||
|
webhookExecutor.QueueLog(
|
||||||
|
guildConfig,
|
||||||
|
LogChannelType.GuildMemberTimeout,
|
||||||
|
embed.Build().GetOrThrow()
|
||||||
|
);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,22 @@ public class AuditLogCache
|
||||||
(Snowflake GuildId, Snowflake TargetId),
|
(Snowflake GuildId, Snowflake TargetId),
|
||||||
ActionData
|
ActionData
|
||||||
> _kicks = new();
|
> _kicks = new();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<
|
private readonly ConcurrentDictionary<
|
||||||
(Snowflake GuildId, Snowflake TargetId),
|
(Snowflake GuildId, Snowflake TargetId),
|
||||||
ActionData
|
ActionData
|
||||||
> _bans = new();
|
> _bans = new();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<
|
||||||
|
(Snowflake GuildId, Snowflake TargetId),
|
||||||
|
ActionData
|
||||||
|
> _unbans = new();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<
|
||||||
|
(Snowflake GuildId, Snowflake TargetId),
|
||||||
|
ActionData
|
||||||
|
> _memberUpdates = new();
|
||||||
|
|
||||||
public void SetKick(
|
public void SetKick(
|
||||||
Snowflake guildId,
|
Snowflake guildId,
|
||||||
string targetId,
|
string targetId,
|
||||||
|
|
@ -47,5 +58,40 @@ public class AuditLogCache
|
||||||
public bool TryGetBan(Snowflake guildId, Snowflake targetId, out ActionData data) =>
|
public bool TryGetBan(Snowflake guildId, Snowflake targetId, out ActionData data) =>
|
||||||
_bans.TryGetValue((guildId, targetId), out data);
|
_bans.TryGetValue((guildId, targetId), out data);
|
||||||
|
|
||||||
|
public void SetUnban(
|
||||||
|
Snowflake guildId,
|
||||||
|
string targetId,
|
||||||
|
Snowflake moderatorId,
|
||||||
|
Optional<string> reason
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!DiscordSnowflake.TryParse(targetId, out var targetUser))
|
||||||
|
throw new CataloggerError("Target ID was not a valid snowflake");
|
||||||
|
|
||||||
|
_unbans[(guildId, targetUser.Value)] = new ActionData(moderatorId, reason.OrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetUnban(Snowflake guildId, Snowflake targetId, out ActionData data) =>
|
||||||
|
_unbans.TryGetValue((guildId, targetId), out data);
|
||||||
|
|
||||||
|
public void SetMemberUpdate(
|
||||||
|
Snowflake guildId,
|
||||||
|
string targetId,
|
||||||
|
Snowflake moderatorId,
|
||||||
|
Optional<string> reason
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!DiscordSnowflake.TryParse(targetId, out var targetUser))
|
||||||
|
throw new CataloggerError("Target ID was not a valid snowflake");
|
||||||
|
|
||||||
|
_memberUpdates[(guildId, targetUser.Value)] = new ActionData(
|
||||||
|
moderatorId,
|
||||||
|
reason.OrDefault()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetMemberUpdate(Snowflake guildId, Snowflake targetId, out ActionData data) =>
|
||||||
|
_memberUpdates.TryGetValue((guildId, targetId), out data);
|
||||||
|
|
||||||
public record struct ActionData(Snowflake ModeratorId, string? Reason);
|
public record struct ActionData(Snowflake ModeratorId, string? Reason);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ public class Guild
|
||||||
public ulong GuildKeyRoleUpdate { get; set; }
|
public ulong GuildKeyRoleUpdate { get; set; }
|
||||||
public ulong GuildMemberNickUpdate { get; set; }
|
public ulong GuildMemberNickUpdate { get; set; }
|
||||||
public ulong GuildMemberAvatarUpdate { get; set; }
|
public ulong GuildMemberAvatarUpdate { get; set; }
|
||||||
|
public ulong GuildMemberTimeout { get; set; }
|
||||||
public ulong GuildMemberRemove { get; set; }
|
public ulong GuildMemberRemove { get; set; }
|
||||||
public ulong GuildMemberKick { get; set; }
|
public ulong GuildMemberKick { get; set; }
|
||||||
public ulong GuildBanAdd { get; set; }
|
public ulong GuildBanAdd { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
using Remora.Discord.API.Abstractions.Rest;
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
using Remora.Discord.API.Objects;
|
using Remora.Discord.API.Objects;
|
||||||
|
|
@ -35,6 +36,40 @@ public static class DiscordExtensions
|
||||||
return $"https://cdn.discordapp.com/embed/avatars/{avatarIndex}.png?size={size}";
|
return $"https://cdn.discordapp.com/embed/avatars/{avatarIndex}.png?size={size}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string? AvatarUrl(this IGuildMemberUpdate member, int size = 256) =>
|
||||||
|
GuildAvatarUrl(
|
||||||
|
member.GuildID,
|
||||||
|
member.User.ID,
|
||||||
|
member.Avatar.OrDefault()?.Value,
|
||||||
|
isAnimated: member.Avatar.OrDefault()?.HasGif,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
|
||||||
|
public static string? AvatarUrl(this IGuildMember member, Snowflake guildId, int size = 256) =>
|
||||||
|
GuildAvatarUrl(
|
||||||
|
guildId,
|
||||||
|
member.User.GetOrThrow().ID,
|
||||||
|
member.Avatar.OrDefault()?.Value,
|
||||||
|
isAnimated: member.Avatar.OrDefault()?.HasGif,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
|
||||||
|
private static string? GuildAvatarUrl(
|
||||||
|
Snowflake guildId,
|
||||||
|
Snowflake userId,
|
||||||
|
string? hash,
|
||||||
|
bool? isAnimated,
|
||||||
|
int size = 256
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (hash == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var ext = isAnimated == true ? ".gif" : ".webp";
|
||||||
|
|
||||||
|
return $"https://cdn.discordapp.com/guilds/{guildId}/users/{userId}/avatars/{hash}{ext}?size={size}";
|
||||||
|
}
|
||||||
|
|
||||||
public static string? IconUrl(this IGuild guild, int size = 256)
|
public static string? IconUrl(this IGuild guild, int size = 256)
|
||||||
{
|
{
|
||||||
if (guild.Icon == null)
|
if (guild.Icon == null)
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@ builder
|
||||||
.Configure<DiscordGatewayClientOptions>(g =>
|
.Configure<DiscordGatewayClientOptions>(g =>
|
||||||
g.Intents =
|
g.Intents =
|
||||||
GatewayIntents.Guilds
|
GatewayIntents.Guilds
|
||||||
| GatewayIntents.GuildBans
|
| GatewayIntents.GuildBans // Actually GUILD_MODERATION
|
||||||
| GatewayIntents.GuildInvites
|
| GatewayIntents.GuildInvites
|
||||||
| GatewayIntents.GuildMembers
|
| GatewayIntents.GuildMembers
|
||||||
| GatewayIntents.GuildMessages
|
| GatewayIntents.GuildMessages
|
||||||
| GatewayIntents.GuildWebhooks
|
| GatewayIntents.GuildWebhooks
|
||||||
| GatewayIntents.MessageContents
|
| GatewayIntents.MessageContents
|
||||||
| GatewayIntents.GuildEmojisAndStickers
|
| GatewayIntents.GuildEmojisAndStickers // Actually GUILD_EXPRESSIONS
|
||||||
)
|
)
|
||||||
.AddDiscordCaching()
|
.AddDiscordCaching()
|
||||||
.AddDiscordCommands(enableSlash: true, useDefaultCommandResponder: false)
|
.AddDiscordCommands(enableSlash: true, useDefaultCommandResponder: false)
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ public class WebhookExecutorService(
|
||||||
LogChannelType.GuildKeyRoleUpdate => guild.Channels.GuildKeyRoleUpdate,
|
LogChannelType.GuildKeyRoleUpdate => guild.Channels.GuildKeyRoleUpdate,
|
||||||
LogChannelType.GuildMemberNickUpdate => guild.Channels.GuildMemberNickUpdate,
|
LogChannelType.GuildMemberNickUpdate => guild.Channels.GuildMemberNickUpdate,
|
||||||
LogChannelType.GuildMemberAvatarUpdate => guild.Channels.GuildMemberAvatarUpdate,
|
LogChannelType.GuildMemberAvatarUpdate => guild.Channels.GuildMemberAvatarUpdate,
|
||||||
|
LogChannelType.GuildMemberTimeout => guild.Channels.GuildMemberTimeout,
|
||||||
LogChannelType.GuildMemberRemove => guild.Channels.GuildMemberRemove,
|
LogChannelType.GuildMemberRemove => guild.Channels.GuildMemberRemove,
|
||||||
LogChannelType.GuildMemberKick => guild.Channels.GuildMemberKick,
|
LogChannelType.GuildMemberKick => guild.Channels.GuildMemberKick,
|
||||||
LogChannelType.GuildBanAdd => guild.Channels.GuildBanAdd,
|
LogChannelType.GuildBanAdd => guild.Channels.GuildBanAdd,
|
||||||
|
|
@ -302,6 +303,7 @@ public enum LogChannelType
|
||||||
GuildKeyRoleUpdate,
|
GuildKeyRoleUpdate,
|
||||||
GuildMemberNickUpdate,
|
GuildMemberNickUpdate,
|
||||||
GuildMemberAvatarUpdate,
|
GuildMemberAvatarUpdate,
|
||||||
|
GuildMemberTimeout,
|
||||||
GuildMemberRemove,
|
GuildMemberRemove,
|
||||||
GuildMemberKick,
|
GuildMemberKick,
|
||||||
GuildBanAdd,
|
GuildBanAdd,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue