twilight_cache_inmemory/event/
message.rs

1use crate::{config::ResourceType, CacheableModels, InMemoryCache, UpdateCache};
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            if let Some(popped_id) = channel_messages.pop_back() {
33                cache.messages.remove(&popped_id);
34            }
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            if let Some(popped_id) = channel_messages.pop_back() {
117                cache.messages.remove(&popped_id);
118            }
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::{image_hash::ImageHashParseError, ImageHash, Timestamp},
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                public_flags: None,
170                system: None,
171                verified: None,
172            },
173            call: None,
174            channel_id: Id::new(2),
175            components: Vec::new(),
176            content: "ping".to_owned(),
177            edited_timestamp: None,
178            embeds: Vec::new(),
179            flags: Some(MessageFlags::empty()),
180            guild_id: Some(Id::new(1)),
181            id: Id::new(4),
182            interaction: None,
183            interaction_metadata: None,
184            kind: MessageType::Regular,
185            member: Some(PartialMember {
186                avatar: None,
187                communication_disabled_until: None,
188                deaf: false,
189                flags,
190                joined_at,
191                mute: false,
192                nick: Some("member nick".to_owned()),
193                permissions: None,
194                premium_since: None,
195                roles: Vec::new(),
196                user: None,
197            }),
198            mention_channels: Vec::new(),
199            mention_everyone: false,
200            mention_roles: Vec::new(),
201            mentions: Vec::new(),
202            message_snapshots: Vec::new(),
203            pinned: false,
204            poll: None,
205            reactions: Vec::new(),
206            reference: None,
207            referenced_message: None,
208            role_subscription_data: None,
209            sticker_items: Vec::new(),
210            timestamp: Timestamp::from_secs(1_632_072_645).expect("non zero"),
211            thread: None,
212            tts: false,
213            webhook_id: None,
214        };
215
216        cache.update(&MessageCreate(msg.clone()));
217        msg.id = Id::new(5);
218        cache.update(&MessageCreate(msg));
219
220        {
221            let entry = cache.user_guilds(Id::new(3)).unwrap();
222            assert_eq!(entry.value().len(), 1);
223        }
224        assert_eq!(
225            cache.member(Id::new(1), Id::new(3)).unwrap().user_id,
226            Id::new(3),
227        );
228        {
229            let entry = cache.channel_messages.get(&Id::new(2)).unwrap();
230            assert_eq!(entry.value().len(), 2);
231        }
232
233        let messages = cache
234            .channel_messages(Id::new(2))
235            .expect("channel is in cache");
236
237        let mut iter = messages.iter();
238        // messages are iterated over in descending order from insertion
239        assert_eq!(Some(&Id::new(5)), iter.next());
240        assert_eq!(Some(&Id::new(4)), iter.next());
241        assert!(iter.next().is_none());
242
243        Ok(())
244    }
245}