1use crate::CacheableVoiceState;
2use crate::{config::ResourceType, CacheableModels, InMemoryCache, UpdateCache};
3use twilight_model::gateway::payload::incoming::VoiceStateUpdate;
4use twilight_model::voice::VoiceState;
5
6impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
7 pub(crate) fn cache_voice_states(&self, voice_states: impl IntoIterator<Item = VoiceState>) {
8 for voice_state in voice_states {
9 self.cache_voice_state(voice_state);
10 }
11 }
12
13 fn cache_voice_state(&self, voice_state: VoiceState) {
14 let Some(guild_id) = voice_state.guild_id else {
16 return;
17 };
18
19 let user_id = voice_state.user_id;
20
21 if let Some(voice_state) = self.voice_states.get(&(guild_id, user_id)) {
23 let remove_channel_mapping = self
24 .voice_state_channels
25 .get_mut(&voice_state.channel_id())
26 .is_some_and(|mut channel_voice_states| {
27 channel_voice_states.remove(&(guild_id, user_id));
28
29 channel_voice_states.is_empty()
30 });
31
32 if remove_channel_mapping {
33 self.voice_state_channels.remove(&voice_state.channel_id());
34 }
35 }
36
37 if let Some(channel_id) = voice_state.channel_id {
38 let cached_voice_state =
39 CacheModels::VoiceState::from((channel_id, guild_id, voice_state));
40
41 self.voice_states
42 .insert((guild_id, user_id), cached_voice_state);
43
44 self.voice_state_guilds
45 .entry(guild_id)
46 .or_default()
47 .insert(user_id);
48
49 self.voice_state_channels
50 .entry(channel_id)
51 .or_default()
52 .insert((guild_id, user_id));
53 } else {
54 {
56 let remove_guild =
57 self.voice_state_guilds
58 .get_mut(&guild_id)
59 .is_some_and(|mut guild_users| {
60 guild_users.remove(&user_id);
61
62 guild_users.is_empty()
63 });
64
65 if remove_guild {
66 self.voice_state_guilds.remove(&guild_id);
67 }
68 }
69
70 self.voice_states.remove(&(guild_id, user_id));
71 }
72 }
73}
74
75impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for VoiceStateUpdate {
76 fn update(&self, cache: &InMemoryCache<CacheModels>) {
77 if !cache.wants(ResourceType::VOICE_STATE) {
78 return;
79 }
80
81 cache.cache_voice_state(self.0.clone());
82
83 if let (Some(guild_id), Some(member)) = (self.0.guild_id, &self.0.member) {
84 cache.cache_member(guild_id, member.clone());
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::{model::CachedVoiceState, test, DefaultInMemoryCache, ResourceType};
92 use std::str::FromStr;
93 use twilight_model::{
94 gateway::payload::incoming::VoiceStateUpdate,
95 guild::{Member, MemberFlags},
96 id::{
97 marker::{ChannelMarker, GuildMarker, UserMarker},
98 Id,
99 },
100 user::User,
101 util::{image_hash::ImageHashParseError, ImageHash, Timestamp},
102 voice::VoiceState,
103 };
104
105 #[test]
106 fn voice_state_inserts_and_removes() {
107 let cache = DefaultInMemoryCache::new();
108
109 {
115 let (guild_id, channel_id, user_id) = (Id::new(1), Id::new(11), Id::new(1));
117 cache.cache_voice_state(test::voice_state(guild_id, Some(channel_id), user_id));
118
119 assert!(cache.voice_states.contains_key(&(guild_id, user_id)));
121 assert_eq!(1, cache.voice_states.len());
123
124 assert!(cache.voice_state_channels.contains_key(&channel_id));
126 assert_eq!(1, cache.voice_state_channels.len());
127
128 assert!(cache.voice_state_guilds.contains_key(&guild_id));
130 assert_eq!(1, cache.voice_state_guilds.len());
131 }
132
133 {
135 let (guild_id, channel_id, user_id) = (Id::new(2), Id::new(21), Id::new(2));
137 cache.cache_voice_state(test::voice_state(guild_id, Some(channel_id), user_id));
138
139 assert!(cache.voice_states.contains_key(&(guild_id, user_id)));
141 assert_eq!(2, cache.voice_states.len());
143
144 assert!(cache.voice_state_channels.contains_key(&channel_id));
146 assert_eq!(2, cache.voice_state_channels.len());
147
148 assert!(cache.voice_state_guilds.contains_key(&guild_id));
150 assert_eq!(2, cache.voice_state_guilds.len());
151 }
152
153 {
155 let (guild_id, channel_id, user_id) = (Id::new(1), Id::new(12), Id::new(3));
157 cache.cache_voice_state(test::voice_state(guild_id, Some(channel_id), user_id));
158
159 assert!(cache.voice_states.contains_key(&(guild_id, user_id)));
161 assert_eq!(3, cache.voice_states.len());
162
163 assert!(cache.voice_state_channels.contains_key(&channel_id));
165 assert_eq!(3, cache.voice_state_channels.len());
166
167 assert!(cache.voice_state_guilds.contains_key(&guild_id));
169 assert_eq!(2, cache.voice_state_guilds.len());
172 }
173
174 {
176 let (guild_id, channel_id, user_id) = (Id::new(1), Id::new(11), Id::new(3));
178 cache.cache_voice_state(test::voice_state(guild_id, Some(channel_id), user_id));
179
180 assert!(cache.voice_states.contains_key(&(guild_id, user_id)));
182 assert_eq!(3, cache.voice_states.len());
184
185 assert!(cache.voice_state_channels.contains_key(&channel_id));
187 assert_eq!(2, cache.voice_state_channels.len());
189
190 assert!(cache.voice_state_guilds.contains_key(&guild_id));
192 assert_eq!(2, cache.voice_state_guilds.len());
193 }
194
195 {
197 let (guild_id, channel_id, user_id) = (Id::new(1), Id::new(11), Id::new(3));
198 cache.cache_voice_state(test::voice_state(guild_id, None, user_id));
199
200 assert!(!cache.voice_states.contains_key(&(guild_id, user_id)));
202 assert_eq!(2, cache.voice_states.len());
203
204 assert!(cache.voice_state_channels.contains_key(&channel_id));
206 assert!(cache.voice_state_guilds.contains_key(&guild_id));
208 assert_eq!(2, cache.voice_state_guilds.len());
209 }
210
211 {
213 let (guild_id, channel_id, user_id) = (Id::new(2), Id::new(21), Id::new(2));
214 cache.cache_voice_state(test::voice_state(guild_id, None, user_id));
215
216 assert!(!cache.voice_states.contains_key(&(guild_id, user_id)));
218 assert_eq!(1, cache.voice_states.len());
219
220 assert!(!cache.voice_state_channels.contains_key(&channel_id));
222 assert_eq!(1, cache.voice_state_channels.len());
223
224 assert!(!cache.voice_state_guilds.contains_key(&guild_id));
226 assert_eq!(1, cache.voice_state_guilds.len());
227 }
228
229 {
231 let (guild_id, _channel_id, user_id) =
232 (Id::new(1), Id::<ChannelMarker>::new(11), Id::new(1));
233 cache.cache_voice_state(test::voice_state(guild_id, None, user_id));
234
235 assert!(cache.voice_states.is_empty());
237 assert!(cache.voice_state_channels.is_empty());
238 assert!(cache.voice_state_guilds.is_empty());
239 }
240 }
241
242 #[test]
243 fn voice_states() {
244 let cache = DefaultInMemoryCache::new();
245 cache.cache_voice_state(test::voice_state(Id::new(1), Some(Id::new(2)), Id::new(3)));
246 cache.cache_voice_state(test::voice_state(Id::new(1), Some(Id::new(2)), Id::new(4)));
247
248 assert_eq!(2, cache.voice_channel_states(Id::new(2)).unwrap().count());
250
251 assert!(cache.voice_channel_states(Id::new(1)).is_none());
253 }
254
255 #[test]
256 fn voice_states_with_no_cached_guilds() {
257 let cache = DefaultInMemoryCache::builder()
258 .resource_types(ResourceType::VOICE_STATE)
259 .build();
260
261 cache.update(&VoiceStateUpdate(VoiceState {
262 channel_id: None,
263 deaf: false,
264 guild_id: Some(Id::new(1)),
265 member: None,
266 mute: false,
267 self_deaf: false,
268 self_mute: false,
269 self_stream: false,
270 self_video: false,
271 session_id: "38fj3jfkh3pfho3prh2".to_string(),
272 suppress: false,
273 user_id: Id::new(1),
274 request_to_speak_timestamp: Some(
275 Timestamp::from_str("2021-04-21T22:16:50+00:00").expect("proper datetime"),
276 ),
277 }));
278 }
279
280 #[test]
281 fn voice_states_members() -> Result<(), ImageHashParseError> {
282 let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
283
284 let cache = DefaultInMemoryCache::new();
285
286 let avatar = ImageHash::parse(b"169280485ba78d541a9090e7ea35a14e")?;
287 let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
288
289 let mutation = VoiceStateUpdate(VoiceState {
290 channel_id: Some(Id::new(4)),
291 deaf: false,
292 guild_id: Some(Id::new(2)),
293 member: Some(Member {
294 avatar: None,
295 communication_disabled_until: None,
296 deaf: false,
297 flags,
298 joined_at,
299 mute: false,
300 nick: None,
301 pending: false,
302 premium_since: None,
303 roles: Vec::new(),
304 user: User {
305 accent_color: None,
306 avatar: Some(avatar),
307 avatar_decoration: None,
308 avatar_decoration_data: None,
309 banner: None,
310 bot: false,
311 discriminator: 1,
312 email: None,
313 flags: None,
314 global_name: Some("test".to_owned()),
315 id: Id::new(3),
316 locale: None,
317 mfa_enabled: None,
318 name: "test".to_owned(),
319 premium_type: None,
320 public_flags: None,
321 system: None,
322 verified: None,
323 },
324 }),
325 mute: false,
326 self_deaf: false,
327 self_mute: false,
328 self_stream: false,
329 self_video: false,
330 session_id: String::new(),
331 suppress: false,
332 user_id: Id::new(3),
333 request_to_speak_timestamp: Some(
334 Timestamp::from_str("2021-04-21T22:16:50+00:00").expect("proper datetime"),
335 ),
336 });
337
338 cache.update(&mutation);
339
340 assert_eq!(cache.members.len(), 1);
341 {
342 let entry = cache.user_guilds(Id::new(3)).unwrap();
343 assert_eq!(entry.value().len(), 1);
344 }
345 assert_eq!(
346 cache.member(Id::new(2), Id::new(3)).unwrap().user_id,
347 Id::new(3),
348 );
349
350 Ok(())
351 }
352
353 #[test]
356 fn uses_cached_variant() {
357 const CHANNEL_ID: Id<ChannelMarker> = Id::new(2);
358 const GUILD_ID: Id<GuildMarker> = Id::new(1);
359 const USER_ID: Id<UserMarker> = Id::new(3);
360
361 let cache = DefaultInMemoryCache::new();
362 let voice_state = test::voice_state(GUILD_ID, Some(CHANNEL_ID), USER_ID);
363 cache.update(&VoiceStateUpdate(voice_state.clone()));
364
365 let cached = CachedVoiceState::from((CHANNEL_ID, GUILD_ID, voice_state));
366 let in_cache = cache.voice_state(USER_ID, GUILD_ID).unwrap();
367 assert_eq!(in_cache.value(), &cached);
368 }
369}