// Copyright (C) 2021-present sam (starshines.gay) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using System.Collections.Concurrent; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Objects; using Remora.Rest.Core; namespace Catalogger.Backend.Cache.InMemoryCache; public class InMemoryMemberCache(IDiscordRestGuildAPI guildApi, ILogger logger) : IMemberCache { private readonly ILogger _logger = logger.ForContext(); private readonly ConcurrentDictionary< (Snowflake GuildId, Snowflake UserId), IGuildMember > _members = new(); private readonly ConcurrentDictionary _guilds = new(); #pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. public Task TryGetAsync(Snowflake guildId, Snowflake userId) => _members.TryGetValue((guildId, userId), out var member) ? Task.FromResult(member) : Task.FromResult(null); #pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. public Task SetAsync(Snowflake guildId, IGuildMember member) { if (!member.User.IsDefined()) throw new CataloggerError( "Member with undefined User passed to InMemoryMemberCache.SetAsync" ); _members[(guildId, member.User.Value.ID)] = member; return Task.CompletedTask; } public async Task SetManyAsync(Snowflake guildId, IReadOnlyList members) { foreach (var member in members) 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) { _guilds[guildId] = 1; return Task.CompletedTask; } public Task MarkAsUncachedAsync(Snowflake guildId) { _guilds.Remove(guildId, out _); return Task.CompletedTask; } public async Task UpdateAsync(IGuildMemberUpdate newMember) { if (!_members.TryGetValue((newMember.GuildID, newMember.User.ID), out var oldMember)) { _logger.Warning( "Received member update event for {MemberId} in {GuildId}, but member wasn't found in cache. Fetching them and storing.", newMember.User.ID, newMember.GuildID ); var memberResult = await guildApi.GetGuildMemberAsync( newMember.GuildID, newMember.User.ID ); if (!memberResult.IsSuccess) { _logger.Error( "Could not get uncached member {MemberId} in {GuildId} via REST: {Error}", newMember.User.ID, newMember.GuildID, memberResult.Error ); return; } _members[(newMember.GuildID, newMember.User.ID)] = memberResult.Entity; return; } // Unwrapping the interface because fuck it, we ball var member = (GuildMember)oldMember with { User = new Optional(newMember.User), Nickname = newMember.Nickname.HasValue ? newMember.Nickname.Value : oldMember.Nickname, Avatar = newMember.Avatar.HasValue ? newMember.Avatar : oldMember.Avatar, Roles = newMember.Roles.ToArray(), JoinedAt = newMember.JoinedAt ?? oldMember.JoinedAt, PremiumSince = newMember.PremiumSince.HasValue ? newMember.PremiumSince.Value : oldMember.PremiumSince, IsPending = newMember.IsPending.HasValue ? newMember.IsPending.Value : oldMember.IsPending, CommunicationDisabledUntil = newMember.CommunicationDisabledUntil.HasValue ? newMember.CommunicationDisabledUntil.Value : oldMember.CommunicationDisabledUntil, }; _members[(newMember.GuildID, newMember.User.ID)] = member; } public Task SetMemberNamesAsync(Snowflake guildId, IEnumerable members) => Task.CompletedTask; public Task UpdateMemberNameAsync( Snowflake guildId, Snowflake userId, string prevName, string newName ) => Task.CompletedTask; public Task> GetMemberNamesAsync( Snowflake guildId, string prefix ) => Task.FromResult>([]); public Task TryRemoveMemberNameAsync(Snowflake guildId, string username) => Task.CompletedTask; }