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