twilight_cache_inmemory/event/
guild.rs

1use crate::{config::ResourceType, CacheableGuild, CacheableModels, InMemoryCache, UpdateCache};
2use dashmap::DashMap;
3use std::{collections::HashSet, hash::Hash, mem};
4use twilight_model::{
5    gateway::payload::incoming::{GuildCreate, GuildDelete, GuildUpdate},
6    guild::Guild,
7    id::{marker::GuildMarker, Id},
8};
9
10impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
11    #[allow(clippy::too_many_lines)]
12    fn cache_guild(&self, mut guild: Guild) {
13        // The map and set creation needs to occur first, so caching states and
14        // objects always has a place to put them.
15        if self.wants(ResourceType::CHANNEL) {
16            self.guild_channels.insert(guild.id, HashSet::new());
17
18            let mut channels = mem::take(&mut guild.channels);
19            let mut threads = mem::take(&mut guild.threads);
20
21            for channel in &mut channels {
22                channel.guild_id = Some(guild.id);
23            }
24
25            for channel in &mut threads {
26                channel.guild_id = Some(guild.id);
27            }
28
29            self.cache_channels(channels);
30            self.cache_channels(threads);
31        }
32
33        if self.wants(ResourceType::EMOJI) {
34            self.guild_emojis.insert(guild.id, HashSet::new());
35            self.cache_emojis(guild.id, mem::take(&mut guild.emojis));
36        }
37
38        if self.wants(ResourceType::MEMBER) {
39            self.guild_members.insert(guild.id, HashSet::new());
40            self.cache_members(guild.id, mem::take(&mut guild.members));
41        }
42
43        if self.wants(ResourceType::PRESENCE) {
44            self.guild_presences.insert(guild.id, HashSet::new());
45            self.cache_presences(guild.id, mem::take(&mut guild.presences));
46        }
47
48        if self.wants(ResourceType::ROLE) {
49            self.guild_roles.insert(guild.id, HashSet::new());
50            self.cache_roles(guild.id, mem::take(&mut guild.roles));
51        }
52
53        if self.wants(ResourceType::STICKER) {
54            self.guild_stickers.insert(guild.id, HashSet::new());
55            self.cache_stickers(guild.id, mem::take(&mut guild.stickers));
56        }
57
58        if self.wants(ResourceType::VOICE_STATE) {
59            self.voice_state_guilds.insert(guild.id, HashSet::new());
60            self.cache_voice_states(mem::take(&mut guild.voice_states));
61        }
62
63        if self.wants(ResourceType::STAGE_INSTANCE) {
64            self.guild_stage_instances.insert(guild.id, HashSet::new());
65            self.cache_stage_instances(guild.id, mem::take(&mut guild.stage_instances));
66        }
67
68        if self.wants(ResourceType::GUILD_SCHEDULED_EVENT) {
69            self.guild_scheduled_events.insert(guild.id, HashSet::new());
70            self.cache_guild_scheduled_events(
71                guild.id,
72                mem::take(&mut guild.guild_scheduled_events),
73            );
74        }
75
76        if self.wants(ResourceType::GUILD) {
77            let guild = CacheModels::Guild::from(guild);
78            self.unavailable_guilds.remove(&guild.id());
79            self.guilds.insert(guild.id(), guild);
80        }
81    }
82
83    pub(crate) fn delete_guild(&self, id: Id<GuildMarker>, unavailable: bool) {
84        fn remove_ids<T: Eq + Hash, U>(
85            guild_map: &DashMap<Id<GuildMarker>, HashSet<T>>,
86            container: &DashMap<T, U>,
87            guild_id: Id<GuildMarker>,
88        ) {
89            if let Some((_, ids)) = guild_map.remove(&guild_id) {
90                for id in ids {
91                    container.remove(&id);
92                }
93            }
94        }
95
96        if self.wants(ResourceType::GUILD) {
97            if unavailable {
98                if let Some(mut guild) = self.guilds.get_mut(&id) {
99                    guild.set_unavailable(Some(true));
100                }
101            } else {
102                self.guilds.remove(&id);
103            }
104        }
105
106        if self.wants(ResourceType::CHANNEL) {
107            remove_ids(&self.guild_channels, &self.channels, id);
108        }
109
110        if self.wants(ResourceType::EMOJI) {
111            remove_ids(&self.guild_emojis, &self.emojis, id);
112        }
113
114        if self.wants(ResourceType::ROLE) {
115            remove_ids(&self.guild_roles, &self.roles, id);
116        }
117
118        if self.wants(ResourceType::STICKER) {
119            remove_ids(&self.guild_stickers, &self.stickers, id);
120        }
121
122        if self.wants(ResourceType::VOICE_STATE) {
123            // Clear out a guilds voice states when a guild leaves
124            self.voice_state_guilds.remove(&id);
125        }
126
127        if self.wants(ResourceType::MEMBER) {
128            if let Some((_, ids)) = self.guild_members.remove(&id) {
129                for user_id in ids {
130                    self.members.remove(&(id, user_id));
131                }
132            }
133        }
134
135        if self.wants(ResourceType::PRESENCE) {
136            if let Some((_, ids)) = self.guild_presences.remove(&id) {
137                for user_id in ids {
138                    self.presences.remove(&(id, user_id));
139                }
140            }
141        }
142    }
143}
144
145impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildCreate {
146    fn update(&self, cache: &InMemoryCache<CacheModels>) {
147        match self {
148            GuildCreate::Available(g) => cache.cache_guild(g.clone()),
149            GuildCreate::Unavailable(g) => {
150                cache.unavailable_guild(g.id);
151            }
152        }
153    }
154}
155
156impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildDelete {
157    fn update(&self, cache: &InMemoryCache<CacheModels>) {
158        cache.delete_guild(self.id, false);
159    }
160}
161
162impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildUpdate {
163    fn update(&self, cache: &InMemoryCache<CacheModels>) {
164        if !cache.wants(ResourceType::GUILD) {
165            return;
166        }
167
168        if let Some(mut guild) = cache.guilds.get_mut(&self.0.id) {
169            guild.update_with_guild_update(self);
170        };
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use crate::{test, DefaultInMemoryCache};
177    use std::str::FromStr;
178    use twilight_model::{
179        channel::{
180            thread::{AutoArchiveDuration, ThreadMember, ThreadMetadata},
181            Channel, ChannelType,
182        },
183        gateway::payload::incoming::{
184            GuildCreate, GuildUpdate, MemberAdd, MemberRemove, UnavailableGuild,
185        },
186        guild::{
187            AfkTimeout, DefaultMessageNotificationLevel, ExplicitContentFilter, Guild, MfaLevel,
188            NSFWLevel, PartialGuild, Permissions, PremiumTier, SystemChannelFlags,
189            VerificationLevel,
190        },
191        id::Id,
192        util::datetime::{Timestamp, TimestampParseError},
193    };
194
195    #[allow(clippy::too_many_lines)]
196    #[test]
197    fn guild_create_channels_have_guild_ids() -> Result<(), TimestampParseError> {
198        const DATETIME: &str = "2021-09-19T14:17:32.000000+00:00";
199
200        let timestamp = Timestamp::from_str(DATETIME)?;
201
202        let channels = Vec::from([Channel {
203            application_id: None,
204            applied_tags: None,
205            available_tags: None,
206            bitrate: None,
207            default_auto_archive_duration: None,
208            default_forum_layout: None,
209            default_reaction_emoji: None,
210            default_sort_order: None,
211            default_thread_rate_limit_per_user: None,
212            flags: None,
213            guild_id: None,
214            icon: None,
215            id: Id::new(111),
216            invitable: None,
217            kind: ChannelType::GuildText,
218            last_message_id: None,
219            last_pin_timestamp: None,
220            managed: None,
221            member: None,
222            member_count: None,
223            message_count: None,
224            name: Some("guild channel with no guild id".to_owned()),
225            newly_created: None,
226            nsfw: Some(true),
227            owner_id: None,
228            parent_id: None,
229            permission_overwrites: Some(Vec::new()),
230            position: Some(1),
231            rate_limit_per_user: None,
232            recipients: None,
233            rtc_region: None,
234            thread_metadata: None,
235            topic: None,
236            user_limit: None,
237            video_quality_mode: None,
238        }]);
239
240        let threads = Vec::from([Channel {
241            application_id: None,
242            applied_tags: None,
243            available_tags: None,
244            bitrate: None,
245            default_auto_archive_duration: None,
246            default_forum_layout: None,
247            default_reaction_emoji: None,
248            default_sort_order: None,
249            default_thread_rate_limit_per_user: None,
250            flags: None,
251            guild_id: None,
252            icon: None,
253            id: Id::new(222),
254            invitable: None,
255            kind: ChannelType::PublicThread,
256            last_message_id: None,
257            last_pin_timestamp: None,
258            managed: Some(true),
259            member: Some(ThreadMember {
260                flags: 0,
261                id: Some(Id::new(1)),
262                join_timestamp: timestamp,
263                member: None,
264                presence: None,
265                user_id: Some(Id::new(2)),
266            }),
267            member_count: Some(0),
268            message_count: Some(0),
269            name: Some("guild thread with no guild id".to_owned()),
270            newly_created: None,
271            nsfw: None,
272            owner_id: None,
273            parent_id: None,
274            permission_overwrites: None,
275            position: None,
276            rate_limit_per_user: None,
277            recipients: None,
278            rtc_region: None,
279            thread_metadata: Some(ThreadMetadata {
280                archived: false,
281                auto_archive_duration: AutoArchiveDuration::Hour,
282                archive_timestamp: timestamp,
283                create_timestamp: Some(timestamp),
284                invitable: None,
285                locked: false,
286            }),
287            topic: None,
288            user_limit: None,
289            video_quality_mode: None,
290        }]);
291
292        let guild = Guild {
293            afk_channel_id: None,
294            afk_timeout: AfkTimeout::FIFTEEN_MINUTES,
295            application_id: None,
296            approximate_member_count: None,
297            approximate_presence_count: None,
298            banner: None,
299            channels,
300            default_message_notifications: DefaultMessageNotificationLevel::Mentions,
301            description: None,
302            discovery_splash: None,
303            emojis: Vec::new(),
304            explicit_content_filter: ExplicitContentFilter::AllMembers,
305            features: vec![],
306            guild_scheduled_events: Vec::new(),
307            icon: None,
308            id: Id::new(123),
309            joined_at: Some(Timestamp::from_secs(1_632_072_645).expect("non zero")),
310            large: false,
311            max_members: Some(50),
312            max_presences: Some(100),
313            max_stage_video_channel_users: Some(10),
314            max_video_channel_users: None,
315            member_count: Some(25),
316            members: Vec::new(),
317            mfa_level: MfaLevel::Elevated,
318            name: "this is a guild".to_owned(),
319            nsfw_level: NSFWLevel::AgeRestricted,
320            owner_id: Id::new(456),
321            owner: Some(false),
322            permissions: Some(Permissions::SEND_MESSAGES),
323            preferred_locale: "en-GB".to_owned(),
324            premium_progress_bar_enabled: true,
325            premium_subscription_count: Some(0),
326            premium_tier: PremiumTier::None,
327            presences: Vec::new(),
328            public_updates_channel_id: None,
329            roles: Vec::new(),
330            rules_channel_id: None,
331            safety_alerts_channel_id: Some(Id::new(789)),
332            splash: None,
333            stage_instances: Vec::new(),
334            stickers: Vec::new(),
335            system_channel_flags: SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
336            system_channel_id: None,
337            threads,
338            unavailable: Some(false),
339            vanity_url_code: None,
340            verification_level: VerificationLevel::VeryHigh,
341            voice_states: Vec::new(),
342            widget_channel_id: None,
343            widget_enabled: None,
344        };
345
346        let cache = DefaultInMemoryCache::new();
347        cache.cache_guild(guild);
348
349        let channel = cache.channel(Id::new(111)).unwrap();
350
351        let thread = cache.channel(Id::new(222)).unwrap();
352
353        // The channel was given to the cache without a guild ID, but because
354        // it's part of a guild create, the cache can automatically attach the
355        // guild ID to it. So now, the channel's guild ID is present with the
356        // correct value.
357        assert_eq!(Some(Id::new(123)), channel.guild_id);
358        assert_eq!(Some(Id::new(123)), thread.guild_id);
359
360        Ok(())
361    }
362
363    #[test]
364    fn unavailable_available_guild() {
365        let cache = DefaultInMemoryCache::new();
366        let guild = test::guild(Id::new(1), None);
367
368        cache.update(&GuildCreate::Unavailable(
369            twilight_model::guild::UnavailableGuild {
370                id: guild.id,
371                unavailable: true,
372            },
373        ));
374        assert!(cache.unavailable_guilds.get(&guild.id).is_some());
375
376        cache.update(&GuildCreate::Available(guild.clone()));
377        assert_eq!(*cache.guilds.get(&guild.id).unwrap(), guild);
378        assert!(cache.unavailable_guilds.get(&guild.id).is_none());
379
380        cache.update(&GuildCreate::Unavailable(
381            twilight_model::guild::UnavailableGuild {
382                id: guild.id,
383                unavailable: true,
384            },
385        ));
386        assert!(cache.unavailable_guilds.get(&guild.id).is_some());
387        assert!(cache.guilds.get(&guild.id).unwrap().unavailable.unwrap());
388
389        cache.update(&GuildCreate::Available(guild.clone()));
390        assert!(!cache
391            .guilds
392            .get(&guild.id)
393            .unwrap()
394            .unavailable
395            .unwrap_or(false));
396        assert!(cache.unavailable_guilds.get(&guild.id).is_none());
397    }
398
399    #[test]
400    fn guild_update() {
401        let cache = DefaultInMemoryCache::new();
402        let guild = test::guild(Id::new(1), None);
403
404        cache.update(&GuildCreate::Available(guild.clone()));
405
406        let mutation = PartialGuild {
407            id: guild.id,
408            afk_channel_id: guild.afk_channel_id,
409            afk_timeout: guild.afk_timeout,
410            application_id: guild.application_id,
411            banner: guild.banner,
412            default_message_notifications: guild.default_message_notifications,
413            description: guild.description,
414            discovery_splash: guild.discovery_splash,
415            emojis: guild.emojis,
416            explicit_content_filter: guild.explicit_content_filter,
417            features: guild.features,
418            icon: guild.icon,
419            max_members: guild.max_members,
420            max_presences: guild.max_presences,
421            member_count: guild.member_count,
422            mfa_level: guild.mfa_level,
423            name: "test2222".to_owned(),
424            nsfw_level: guild.nsfw_level,
425            owner_id: Id::new(2),
426            owner: guild.owner,
427            permissions: guild.permissions,
428            preferred_locale: guild.preferred_locale,
429            premium_progress_bar_enabled: guild.premium_progress_bar_enabled,
430            premium_subscription_count: guild.premium_subscription_count,
431            premium_tier: guild.premium_tier,
432            public_updates_channel_id: None,
433            roles: guild.roles,
434            rules_channel_id: guild.rules_channel_id,
435            splash: guild.splash,
436            system_channel_flags: guild.system_channel_flags,
437            system_channel_id: guild.system_channel_id,
438            verification_level: guild.verification_level,
439            vanity_url_code: guild.vanity_url_code,
440            widget_channel_id: guild.widget_channel_id,
441            widget_enabled: guild.widget_enabled,
442        };
443
444        cache.update(&GuildUpdate(mutation.clone()));
445
446        assert_eq!(cache.guild(guild.id).unwrap().name, mutation.name);
447        assert_eq!(cache.guild(guild.id).unwrap().owner_id, mutation.owner_id);
448        assert_eq!(cache.guild(guild.id).unwrap().id, mutation.id);
449    }
450
451    #[test]
452    fn guild_member_count() {
453        let user_id = Id::new(2);
454        let guild_id = Id::new(1);
455        let cache = DefaultInMemoryCache::new();
456        let user = test::user(user_id);
457        let member = test::member(user_id);
458        let guild = test::guild(guild_id, Some(1));
459
460        cache.update(&GuildCreate::Available(guild));
461        cache.update(&MemberAdd { guild_id, member });
462
463        assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(2));
464
465        cache.update(&MemberRemove { guild_id, user });
466
467        assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(1));
468    }
469
470    #[test]
471    fn guild_members_size_after_unavailable() {
472        let user_id = Id::new(2);
473        let guild_id = Id::new(1);
474        let cache = DefaultInMemoryCache::new();
475        let member = test::member(user_id);
476        let mut guild = test::guild(guild_id, Some(1));
477        guild.members.push(member);
478
479        cache.update(&GuildCreate::Available(guild.clone()));
480
481        assert_eq!(
482            1,
483            cache
484                .guild_members(guild_id)
485                .map(|members| members.len())
486                .unwrap_or_default()
487        );
488
489        cache.update(&UnavailableGuild { id: guild_id });
490
491        assert_eq!(
492            0,
493            cache
494                .guild_members(guild_id)
495                .map(|members| members.len())
496                .unwrap_or_default()
497        );
498        assert!(cache.guild(guild_id).unwrap().unavailable.unwrap());
499
500        cache.update(&GuildCreate::Available(guild));
501
502        assert_eq!(
503            1,
504            cache
505                .guild_members(guild_id)
506                .map(|members| members.len())
507                .unwrap_or_default()
508        );
509        assert!(!cache.guild(guild_id).unwrap().unavailable.unwrap_or(false));
510    }
511}