1use std::ops::Deref;
2
3use serde::{Deserialize, Serialize};
4use twilight_model::{
5 application::interaction::InteractionMember,
6 gateway::payload::incoming::MemberUpdate,
7 guild::{Member, MemberFlags, PartialMember},
8 id::{
9 marker::{GuildMarker, RoleMarker, UserMarker},
10 Id,
11 },
12 util::{ImageHash, Timestamp},
13};
14
15use crate::{Cache, Error};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct ComputedInteractionMember {
21 pub avatar: Option<ImageHash>,
22 pub deaf: Option<bool>,
23 pub interaction_member: InteractionMember,
24 pub mute: Option<bool>,
25 pub user_id: Id<UserMarker>,
26}
27
28impl Deref for ComputedInteractionMember {
29 type Target = InteractionMember;
30
31 fn deref(&self) -> &Self::Target {
32 &self.interaction_member
33 }
34}
35
36#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
37pub struct CachedMember {
38 pub avatar: Option<ImageHash>,
39 pub communication_disabled_until: Option<Timestamp>,
40 pub deaf: Option<bool>,
41 pub flags: MemberFlags,
42 pub joined_at: Option<Timestamp>,
43 pub mute: Option<bool>,
44 pub nick: Option<String>,
45 pub pending: bool,
46 pub premium_since: Option<Timestamp>,
47 pub roles: Vec<Id<RoleMarker>>,
48 pub user_id: Id<UserMarker>,
49}
50
51impl CachedMember {
52 pub(crate) fn update_with_member_update(&mut self, member_update: &MemberUpdate) {
53 self.avatar = member_update.avatar;
54 self.deaf = member_update.deaf.or(self.deaf);
55 self.mute = member_update.mute.or(self.mute);
56 self.nick.clone_from(&member_update.nick);
57 self.roles.clone_from(&member_update.roles);
58 self.joined_at = member_update.joined_at;
59 self.pending = member_update.pending;
60 self.communication_disabled_until = member_update.communication_disabled_until;
61 }
62}
63
64impl From<Member> for CachedMember {
65 fn from(member: Member) -> Self {
66 let Member {
67 avatar,
68 communication_disabled_until,
69 deaf,
70 flags,
71 joined_at,
72 mute,
73 nick,
74 pending,
75 premium_since,
76 roles,
77 user,
78 } = member;
79
80 Self {
81 avatar,
82 communication_disabled_until,
83 deaf: Some(deaf),
84 flags,
85 joined_at,
86 mute: Some(mute),
87 nick,
88 pending,
89 premium_since,
90 roles,
91 user_id: user.id,
92 }
93 }
94}
95
96impl From<(Id<UserMarker>, PartialMember)> for CachedMember {
97 fn from((user_id, member): (Id<UserMarker>, PartialMember)) -> Self {
98 #[expect(
99 clippy::unneeded_field_pattern,
100 reason = "clearer that we're explicitly skipping those fields"
101 )]
102 let PartialMember {
103 avatar,
104 communication_disabled_until,
105 deaf,
106 flags,
107 joined_at,
108 mute,
109 nick,
110 permissions: _,
111 premium_since,
112 roles,
113 user,
114 } = member;
115
116 Self {
117 avatar,
118 communication_disabled_until,
119 deaf: Some(deaf),
120 flags,
121 joined_at,
122 mute: Some(mute),
123 nick,
124 pending: false,
125 premium_since,
126 roles,
127 user_id: user.map_or(user_id, |user| user.id),
128 }
129 }
130}
131
132impl From<ComputedInteractionMember> for CachedMember {
133 fn from(member: ComputedInteractionMember) -> Self {
134 let ComputedInteractionMember {
135 avatar,
136 deaf,
137 mute,
138 user_id,
139 interaction_member,
140 } = member;
141
142 #[expect(
143 clippy::unneeded_field_pattern,
144 reason = "clearer that we're explicitly skipping those fields"
145 )]
146 let InteractionMember {
147 avatar: _,
148 communication_disabled_until,
149 flags,
150 joined_at,
151 nick,
152 pending,
153 permissions: _,
154 premium_since,
155 roles,
156 } = interaction_member;
157
158 Self {
159 avatar,
160 communication_disabled_until,
161 deaf,
162 flags,
163 joined_at,
164 mute,
165 nick,
166 pending,
167 premium_since,
168 roles,
169 user_id,
170 }
171 }
172}
173
174impl PartialEq<Member> for CachedMember {
175 fn eq(&self, other: &Member) -> bool {
176 self.avatar == other.avatar
177 && self.communication_disabled_until == other.communication_disabled_until
178 && self.deaf == Some(other.deaf)
179 && self.joined_at == other.joined_at
180 && self.mute == Some(other.mute)
181 && self.nick == other.nick
182 && self.pending == other.pending
183 && self.premium_since == other.premium_since
184 && self.roles == other.roles
185 && self.user_id == other.user.id
186 }
187}
188
189impl PartialEq<PartialMember> for CachedMember {
190 fn eq(&self, other: &PartialMember) -> bool {
191 self.communication_disabled_until == other.communication_disabled_until
192 && self.deaf == Some(other.deaf)
193 && self.joined_at == other.joined_at
194 && self.mute == Some(other.mute)
195 && self.nick == other.nick
196 && self.premium_since == other.premium_since
197 && self.roles == other.roles
198 }
199}
200
201impl PartialEq<InteractionMember> for CachedMember {
202 fn eq(&self, other: &InteractionMember) -> bool {
203 self.joined_at == other.joined_at
204 && self.nick == other.nick
205 && self.premium_since == other.premium_since
206 && self.roles == other.roles
207 }
208}
209
210impl Cache {
211 pub(crate) async fn cache_members(
212 &self,
213 guild_id: Id<GuildMarker>,
214 members: impl IntoIterator<Item = Member>,
215 ) -> Result<(), Error> {
216 for member in members {
217 self.cache_member(guild_id, member).await?;
218 }
219
220 Ok(())
221 }
222
223 pub(crate) async fn cache_member(
224 &self,
225 guild_id: Id<GuildMarker>,
226 member: Member,
227 ) -> Result<(), Error> {
228 let member_id = member.user.id;
229 let id = (guild_id, member_id);
230
231 if self.members.get(&id).await?.is_some_and(|m| m == member) {
232 return Ok(());
233 }
234
235 self.cache_user(&member.user, Some(guild_id)).await?;
236 self.members
237 .insert(&id, &CachedMember::from(member.clone()))
238 .await?;
239 self.guild_members.insert(&guild_id, &member_id).await?;
240
241 Ok(())
242 }
243
244 pub(crate) async fn cache_borrowed_partial_member(
245 &self,
246 guild_id: Id<GuildMarker>,
247 member: &PartialMember,
248 user_id: Id<UserMarker>,
249 ) -> Result<(), Error> {
250 let id = (guild_id, user_id);
251
252 if self.members.get(&id).await?.is_some_and(|m| m == *member) {
253 return Ok(());
254 }
255
256 self.guild_members.insert(&guild_id, &user_id).await?;
257
258 self.members
259 .insert(&id, &CachedMember::from((user_id, member.clone())))
260 .await?;
261
262 Ok(())
263 }
264
265 pub(crate) async fn cache_borrowed_interaction_member(
266 &self,
267 guild_id: Id<GuildMarker>,
268 member: &InteractionMember,
269 user_id: Id<UserMarker>,
270 ) -> Result<(), Error> {
271 let id = (guild_id, user_id);
272
273 let (avatar, deaf, mute) = match self.members.get(&id).await? {
274 Some(m) if &m == member => return Ok(()),
275 Some(m) => (m.avatar, m.deaf, m.mute),
276 None => (None, None, None),
277 };
278
279 self.guild_members.insert(&guild_id, &user_id).await?;
280
281 let cached = CachedMember::from(ComputedInteractionMember {
282 avatar,
283 deaf,
284 interaction_member: member.clone(),
285 mute,
286 user_id,
287 });
288
289 self.members.insert(&id, &cached).await?;
290
291 Ok(())
292 }
293}