From 6f8680597e95b595d9cdac708e8cede2bd641c37 Mon Sep 17 00:00:00 2001 From: The Major Date: Sun, 23 Jun 2024 20:33:47 +0000 Subject: [PATCH] Make member cache updates work like user cache updates --- lib/nostrum/cache/member_cache/ets.ex | 13 +++++++++++-- lib/nostrum/cache/member_cache/mnesia.ex | 21 ++++++++++++++++----- lib/nostrum/struct/guild/member.ex | 17 +++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/lib/nostrum/cache/member_cache/ets.ex b/lib/nostrum/cache/member_cache/ets.ex index 4cd096d2e..4a93d6d35 100644 --- a/lib/nostrum/cache/member_cache/ets.ex +++ b/lib/nostrum/cache/member_cache/ets.ex @@ -12,6 +12,7 @@ defmodule Nostrum.Cache.MemberCache.ETS do @table_name :nostrum_guild_members alias Nostrum.Cache.MemberCache + alias Nostrum.Snowflake alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.Member alias Nostrum.Util @@ -76,14 +77,22 @@ defmodule Nostrum.Cache.MemberCache.ETS do @impl MemberCache @spec update(Guild.id(), map()) :: {Guild.id(), Member.t() | nil, Member.t()} def update(guild_id, payload) do - new_member = Util.cast(payload, {:struct, Member}) + # Force keys to be atoms before casting just to simplify finding the user_id + # because of the atom/string ambiguity issues from the gateway that Discord + # won't fix. - case :ets.lookup(@table_name, {guild_id, new_member.user_id}) do + member_payload = Map.new(payload, fn {k, v} -> {Util.maybe_to_atom(k), v} end) + + member_id = Util.cast(member_payload[:user][:id], Snowflake) + + case :ets.lookup(@table_name, {guild_id, member_id}) do [{key, old_member}] -> + new_member = Member.to_struct(member_payload, old_member) true = :ets.update_element(@table_name, key, {2, new_member}) {guild_id, old_member, new_member} [] -> + new_member = Util.cast(member_payload, {:struct, Member}) {guild_id, nil, new_member} end end diff --git a/lib/nostrum/cache/member_cache/mnesia.ex b/lib/nostrum/cache/member_cache/mnesia.ex index 5a8367df6..89802a0ee 100644 --- a/lib/nostrum/cache/member_cache/mnesia.ex +++ b/lib/nostrum/cache/member_cache/mnesia.ex @@ -17,6 +17,7 @@ if Code.ensure_loaded?(:mnesia) do @behaviour Nostrum.Cache.MemberCache alias Nostrum.Cache.MemberCache + alias Nostrum.Snowflake alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.Member alias Nostrum.Util @@ -76,18 +77,28 @@ if Code.ensure_loaded?(:mnesia) do @doc "Update the given member for the given guild in the cache." @spec update(Guild.id(), map()) :: {Guild.id(), Member.t() | nil, Member.t()} def update(guild_id, payload) do - new_member = Util.cast(payload, {:struct, Member}) - key = {guild_id, new_member.user_id} + # Force keys to be atoms before casting just to simplify finding the user_id + # because of the atom/string ambiguity issues from the gateway that Discord + # won't fix. - old_member = + member_payload = Map.new(payload, fn {k, v} -> {Util.maybe_to_atom(k), v} end) + + member_id = Util.cast(member_payload[:user][:id], Snowflake) + + key = {guild_id, member_id} + + {old_member, new_member} = :mnesia.activity(:sync_transaction, fn -> case :mnesia.read(@table_name, key, :write) do [{_tag, _key, _guild_id, _user_id, old_member} = entry] -> + new_member = Member.to_struct(member_payload, old_member) + :mnesia.write(put_elem(entry, 4, new_member)) - old_member + {old_member, new_member} [] -> - nil + new_member = Member.to_struct(member_payload) + {nil, new_member} end end) diff --git a/lib/nostrum/struct/guild/member.ex b/lib/nostrum/struct/guild/member.ex index e09bddd98..0701b478b 100644 --- a/lib/nostrum/struct/guild/member.ex +++ b/lib/nostrum/struct/guild/member.ex @@ -308,4 +308,21 @@ defmodule Nostrum.Struct.Guild.Member do struct(__MODULE__, new) end + + @doc false + @spec to_struct(map(), nil | __MODULE__.t()) :: __MODULE__.t() + def to_struct(map, nil), do: to_struct(map) + + def to_struct(map, old_user) do + new = + map + |> Map.new(fn {k, v} -> {Util.maybe_to_atom(k), v} end) + |> Util.map_update_if_present(:roles, &Util.cast(&1, {:list, Snowflake})) + |> Util.map_update_if_present(:communication_disabled_until, &Util.maybe_to_datetime/1) + |> Util.map_update_if_present(:premium_since, &Util.maybe_to_datetime/1) + |> Util.map_update_if_present(:joined_at, &Util.maybe_to_unixtime/1) + |> Map.put(:user_id, Util.cast(map[:user][:id], Snowflake)) + + struct(old_user, new) + end end