using Catalogger.Backend.Database.Redis; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Objects; using Remora.Rest.Core; namespace Catalogger.Backend.Cache.RedisCache; public class RedisMemberCache(RedisService redisService) : IMemberCache { public async Task TryGetAsync(Snowflake guildId, Snowflake userId) { var redisMember = await redisService.GetHashAsync( GuildMembersKey(guildId), userId.ToString() ); return redisMember?.ToRemoraMember(); } public async Task SetAsync(Snowflake guildId, IGuildMember member) { if (!member.User.IsDefined()) throw new CataloggerError( "Member with undefined User passed to RedisMemberCache.SetAsync" ); await redisService.SetHashAsync( GuildMembersKey(guildId), member.User.Value.ID.ToString(), RedisMember.FromIGuildMember(member) ); } public async Task SetManyAsync(Snowflake guildId, IReadOnlyList members) { if (members.Any(m => !m.User.IsDefined())) throw new CataloggerError( "Member with undefined User passed to RedisMemberCache.SetAsync" ); var redisMembers = members.Select(RedisMember.FromIGuildMember).ToList(); 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()); public async Task MarkAsCachedAsync(Snowflake guildId) => await redisService.GetDatabase().SetAddAsync(GuildCacheKey, guildId.ToString()); public async Task MarkAsUncachedAsync(Snowflake guildId) => await redisService.GetDatabase().SetRemoveAsync(GuildCacheKey, guildId.ToString()); private const string GuildCacheKey = "cached-guilds"; private static string GuildMembersKey(Snowflake guildId) => $"guild-members:{guildId}"; } internal record RedisMember( RedisUser User, string? Nickname, string? Avatar, Snowflake[] Roles, DateTimeOffset JoinedAt, DateTimeOffset? PremiumSince, GuildMemberFlags Flags, bool? IsPending, DateTimeOffset? CommunicationDisabledUntil ) { public static RedisMember FromIGuildMember(IGuildMember member) => new( RedisUser.FromIUser(member.User.Value), member.Nickname.OrDefault(null), member.Avatar.OrDefault(null)?.Value, member.Roles.ToArray(), member.JoinedAt, member.PremiumSince.OrDefault(null), member.Flags, member.IsPending.OrDefault(null), member.CommunicationDisabledUntil.OrDefault(null) ); public GuildMember ToRemoraMember() => new( User.ToRemoraUser(), Nickname, Avatar != null ? new ImageHash(Avatar) : null, Roles, JoinedAt, PremiumSince, false, false, Flags, IsPending, default, CommunicationDisabledUntil ); } internal record RedisUser( ulong Id, string Username, ushort Discriminator, string? GlobalName, string? Avatar, bool IsBot, bool IsSystem, string? Banner ) { public static RedisUser FromIUser(IUser user) => new( user.ID.Value, user.Username, user.Discriminator, user.GlobalName.OrDefault(null), user.Avatar?.Value, user.IsBot.OrDefault(false), user.IsSystem.OrDefault(false), user.Banner.OrDefault(null)?.Value ); public User ToRemoraUser() => new( DiscordSnowflake.New(Id), Username, Discriminator, GlobalName, Avatar != null ? new ImageHash(Avatar) : null, IsBot, IsSystem, Banner: Banner != null ? new ImageHash(Banner) : null ); }