diff --git a/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs new file mode 100644 index 0000000..98d5dbc --- /dev/null +++ b/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs @@ -0,0 +1,14 @@ +using Catalogger.Backend.Cache.InMemoryCache; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.Gateway.Responders; +using Remora.Results; + +namespace Catalogger.Backend.Bot.Responders.Guilds; + +public class AuditLogResponder(AuditLogCache auditLogCache) : IResponder +{ + public Task RespondAsync(IGuildAuditLogEntryCreate evt, CancellationToken ct = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildMemberRemoveResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildMemberRemoveResponder.cs new file mode 100644 index 0000000..898df85 --- /dev/null +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildMemberRemoveResponder.cs @@ -0,0 +1,49 @@ +using Catalogger.Backend.Cache; +using Catalogger.Backend.Cache.InMemoryCache; +using Catalogger.Backend.Database; +using Catalogger.Backend.Extensions; +using Catalogger.Backend.Services; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Extensions.Embeds; +using Remora.Discord.Gateway.Responders; +using Remora.Results; + +namespace Catalogger.Backend.Bot.Responders.Guilds; + +public class GuildMemberRemoveResponder( + ILogger logger, + DatabaseContext db, + IMemberCache memberCache, + UserCache userCache, + WebhookExecutorService webhookExecutor, + IDiscordRestGuildAPI guildApi) : IResponder +{ + private readonly ILogger _logger = logger.ForContext(); + + public async Task RespondAsync(IGuildMemberRemove evt, CancellationToken ct = default) + { + try + { + var embed = new EmbedBuilder() + .WithTitle("Member left") + .WithAuthor(evt.User.Tag(), iconUrl: evt.User.AvatarUrl()) + .WithColour(DiscordUtils.Orange) + .WithDescription($"<@{evt.User.ID}>") + .WithFooter($"ID: {evt.User.ID}") + .WithCurrentTimestamp(); + + var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID); + if (member != null) + { + + } + + return Result.Success; + } + finally + { + await memberCache.RemoveAsync(evt.GuildID, evt.User.ID); + } + } +} \ No newline at end of file diff --git a/Catalogger.Backend/Cache/IMemberCache.cs b/Catalogger.Backend/Cache/IMemberCache.cs index 17edc57..72fa463 100644 --- a/Catalogger.Backend/Cache/IMemberCache.cs +++ b/Catalogger.Backend/Cache/IMemberCache.cs @@ -8,6 +8,7 @@ public interface IMemberCache public Task TryGetAsync(Snowflake guildId, Snowflake userId); public Task SetAsync(Snowflake guildId, IGuildMember member); public Task SetManyAsync(Snowflake guildId, IReadOnlyList members); + public Task RemoveAsync(Snowflake guildId, Snowflake userId); public Task IsGuildCachedAsync(Snowflake guildId); public Task MarkAsCachedAsync(Snowflake guildId); public Task MarkAsUncachedAsync(Snowflake guildId); diff --git a/Catalogger.Backend/Cache/InMemoryCache/AuditLogCache.cs b/Catalogger.Backend/Cache/InMemoryCache/AuditLogCache.cs new file mode 100644 index 0000000..ca904b3 --- /dev/null +++ b/Catalogger.Backend/Cache/InMemoryCache/AuditLogCache.cs @@ -0,0 +1,21 @@ +using System.Collections.Concurrent; +using Remora.Discord.API; +using Remora.Rest.Core; + +namespace Catalogger.Backend.Cache.InMemoryCache; + +public class AuditLogCache +{ + private readonly ConcurrentDictionary<(Snowflake, Snowflake), ModactionData> _kicks = new(); + private readonly ConcurrentDictionary<(Snowflake, Snowflake), ModactionData> _bans = new(); + + public void SetKick(Snowflake guildId, string targetId, Snowflake moderatorId, Optional reason) + { + if (!DiscordSnowflake.TryParse(targetId, out var targetUser)) + throw new CataloggerError("Target ID was not a valid snowflake"); + + _kicks[(guildId, targetUser.Value)] = new ModactionData(moderatorId, reason.OrDefault()); + } + + public record struct ModactionData(Snowflake ModeratorId, string? Reason); +} \ No newline at end of file diff --git a/Catalogger.Backend/Cache/InMemoryCache/InMemoryMemberCache.cs b/Catalogger.Backend/Cache/InMemoryCache/InMemoryMemberCache.cs index a3100d4..4eae1d0 100644 --- a/Catalogger.Backend/Cache/InMemoryCache/InMemoryMemberCache.cs +++ b/Catalogger.Backend/Cache/InMemoryCache/InMemoryMemberCache.cs @@ -19,7 +19,7 @@ public class InMemoryMemberCache : IMemberCache public Task SetAsync(Snowflake guildId, IGuildMember member) { if (!member.User.IsDefined()) - throw new CataloggerError("Member with undefined User passed to RedisMemberCache.SetAsync"); + throw new CataloggerError("Member with undefined User passed to InMemoryMemberCache.SetAsync"); _members[(guildId, member.User.Value.ID)] = member; return Task.CompletedTask; } @@ -30,6 +30,12 @@ public class InMemoryMemberCache : IMemberCache await SetAsync(guildId, member); } + public Task RemoveAsync(Snowflake guildId, Snowflake userId) + { + _members.Remove((guildId, userId), out _); + return Task.CompletedTask; + } + public Task IsGuildCachedAsync(Snowflake guildId) => Task.FromResult(_guilds.ContainsKey(guildId)); public Task MarkAsCachedAsync(Snowflake guildId) diff --git a/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs b/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs index d97c0c8..3856225 100644 --- a/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs +++ b/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs @@ -31,6 +31,9 @@ public class RedisMemberCache(RedisService redisService) : IMemberCache await redisService.SetHashAsync(GuildMembersKey(guildId), redisMembers, m => m.User.Id.ToString()); } + public async Task RemoveAsync(Snowflake guildId, Snowflake userId) => + await redisService.GetDatabase().HashDeleteAsync(GuildMembersKey(guildId), userId.ToString()); + public async Task IsGuildCachedAsync(Snowflake guildId) => await redisService.GetDatabase().SetContainsAsync(GuildCacheKey, guildId.ToString());