twilight_cache_inmemory/event/
message.rs

1use crate::{CacheableModels, InMemoryCache, UpdateCache, config::ResourceType};
2use std::borrow::Cow;
3use twilight_model::gateway::payload::incoming::{
4    MessageCreate, MessageDelete, MessageDeleteBulk, MessageUpdate,
5};
6
7impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MessageCreate {
8    fn update(&self, cache: &InMemoryCache<CacheModels>) {
9        if cache.wants(ResourceType::USER) {
10            cache.cache_user(Cow::Borrowed(&self.author), self.guild_id);
11        }
12
13        if let (Some(member), Some(guild_id), true) = (
14            &self.member,
15            self.guild_id,
16            cache.wants(ResourceType::MEMBER),
17        ) {
18            cache.cache_borrowed_partial_member(guild_id, member, self.author.id);
19        }
20
21        if !cache.wants(ResourceType::MESSAGE) {
22            return;
23        }
24
25        let mut channel_messages = cache.channel_messages.entry(self.0.channel_id).or_default();
26
27        // If the channel has more messages than the cache size the user has
28        // requested then we pop a message ID out. Once we have the popped ID we
29        // can remove it from the message cache. This prevents the cache from
30        // filling up with old messages that aren't in any channel cache.
31        if channel_messages.len() >= cache.config.message_cache_size()
32            && let Some(popped_id) = channel_messages.pop_back()
33        {
34            cache.messages.remove(&popped_id);
35        }
36
37        channel_messages.push_front(self.0.id);
38        cache
39            .messages
40            .insert(self.0.id, CacheModels::Message::from(self.0.clone()));
41    }
42}
43
44impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MessageDelete {
45    fn update(&self, cache: &InMemoryCache<CacheModels>) {
46        if !cache.wants(ResourceType::MESSAGE) {
47            return;
48        }
49
50        cache.messages.remove(&self.id);
51
52        let mut channel_messages = cache.channel_messages.entry(self.channel_id).or_default();
53
54        if let Some(idx) = channel_messages.iter().position(|id| *id == self.id) {
55            channel_messages.remove(idx);
56        }
57    }
58}
59
60impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MessageDeleteBulk {
61    fn update(&self, cache: &InMemoryCache<CacheModels>) {
62        if !cache.wants(ResourceType::MESSAGE) {
63            return;
64        }
65
66        let mut channel_messages = cache.channel_messages.entry(self.channel_id).or_default();
67
68        for id in &self.ids {
69            cache.messages.remove(id);
70
71            if let Some(idx) = channel_messages
72                .iter()
73                .position(|message_id| message_id == id)
74            {
75                channel_messages.remove(idx);
76            }
77        }
78    }
79}
80
81impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MessageUpdate {
82    fn update(&self, cache: &InMemoryCache<CacheModels>) {
83        if cache.wants(ResourceType::USER) {
84            cache.cache_user(Cow::Borrowed(&self.author), self.guild_id);
85        }
86
87        if let (Some(member), Some(guild_id), true) = (
88            &self.member,
89            self.guild_id,
90            cache.wants(ResourceType::MEMBER),
91        ) {
92            cache.cache_borrowed_partial_member(guild_id, member, self.author.id);
93        }
94
95        if !cache.wants(ResourceType::MESSAGE) {
96            return;
97        }
98
99        // In special cases, this message was popped out due to the limitation
100        // of the message cache capacity, or its Event::MessageCreate was missed.
101        // If that is the case, we do not only add it to the message cache but
102        // also add its ID to the channel messages cache.
103        if cache
104            .messages
105            .insert(self.id, CacheModels::Message::from(self.0.clone()))
106            .is_some()
107        {
108            return;
109        }
110
111        let mut channel_messages = cache.channel_messages.entry(self.0.channel_id).or_default();
112
113        // If this channel cache is full, we pop an message ID out of
114        // the channel cache and also remove it from the message cache.
115        if channel_messages.len() >= cache.config.message_cache_size()
116            && let Some(popped_id) = channel_messages.pop_back()
117        {
118            cache.messages.remove(&popped_id);
119        }
120
121        channel_messages.push_front(self.0.id);
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use crate::{DefaultInMemoryCache, ResourceType};
128    use twilight_model::{
129        channel::message::{Message, MessageFlags, MessageType},
130        gateway::payload::incoming::MessageCreate,
131        guild::{MemberFlags, PartialMember},
132        id::Id,
133        user::User,
134        util::{ImageHash, Timestamp, image_hash::ImageHashParseError},
135    };
136
137    #[allow(deprecated)]
138    #[test]
139    fn message_create() -> Result<(), ImageHashParseError> {
140        let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
141        let cache = DefaultInMemoryCache::builder()
142            .resource_types(ResourceType::MESSAGE | ResourceType::MEMBER | ResourceType::USER)
143            .message_cache_size(2)
144            .build();
145
146        let avatar = ImageHash::parse(b"e91c75bc7656063cc745f4e79d0b7664")?;
147        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
148        let mut msg = Message {
149            activity: None,
150            application: None,
151            application_id: None,
152            attachments: Vec::new(),
153            author: User {
154                accent_color: None,
155                avatar: Some(avatar),
156                avatar_decoration: None,
157                avatar_decoration_data: None,
158                banner: None,
159                bot: false,
160                discriminator: 1,
161                email: None,
162                flags: None,
163                global_name: Some("test".to_owned()),
164                id: Id::new(3),
165                locale: None,
166                mfa_enabled: None,
167                name: "test".to_owned(),
168                premium_type: None,
169                primary_guild: None,
170                public_flags: None,
171                system: None,
172                verified: None,
173            },
174            call: None,
175            channel_id: Id::new(2),
176            components: Vec::new(),
177            content: "ping".to_owned(),
178            edited_timestamp: None,
179            embeds: Vec::new(),
180            flags: Some(MessageFlags::empty()),
181            guild_id: Some(Id::new(1)),
182            id: Id::new(4),
183            interaction: None,
184            interaction_metadata: None,
185            kind: MessageType::Regular,
186            member: Some(PartialMember {
187                avatar: None,
188                avatar_decoration_data: None,
189                banner: None,
190                communication_disabled_until: None,
191                deaf: false,
192                flags,
193                joined_at,
194                mute: false,
195                nick: Some("member nick".to_owned()),
196                permissions: None,
197                premium_since: None,
198                roles: Vec::new(),
199                user: None,
200            }),
201            mention_channels: Vec::new(),
202            mention_everyone: false,
203            mention_roles: Vec::new(),
204            mentions: Vec::new(),
205            message_snapshots: Vec::new(),
206            pinned: false,
207            poll: None,
208            reactions: Vec::new(),
209            reference: None,
210            referenced_message: None,
211            role_subscription_data: None,
212            sticker_items: Vec::new(),
213            timestamp: Timestamp::from_secs(1_632_072_645).expect("non zero"),
214            thread: None,
215            tts: false,
216            webhook_id: None,
217        };
218
219        cache.update(&MessageCreate(msg.clone()));
220        msg.id = Id::new(5);
221        cache.update(&MessageCreate(msg));
222
223        {
224            let entry = cache.user_guilds(Id::new(3)).unwrap();
225            assert_eq!(entry.value().len(), 1);
226        }
227        assert_eq!(
228            cache.member(Id::new(1), Id::new(3)).unwrap().user_id,
229            Id::new(3),
230        );
231        {
232            let entry = cache.channel_messages.get(&Id::new(2)).unwrap();
233            assert_eq!(entry.value().len(), 2);
234        }
235
236        let messages = cache
237            .channel_messages(Id::new(2))
238            .expect("channel is in cache");
239
240        let mut iter = messages.iter();
241        // messages are iterated over in descending order from insertion
242        assert_eq!(Some(&Id::new(5)), iter.next());
243        assert_eq!(Some(&Id::new(4)), iter.next());
244        assert!(iter.next().is_none());
245
246        Ok(())
247    }
248}