twilight_cache_inmemory/event/
reaction.rs1use crate::{
2 config::ResourceType,
3 traits::{CacheableCurrentUser, CacheableMessage},
4 CacheableModels, InMemoryCache, UpdateCache,
5};
6use twilight_model::{
7 channel::message::{EmojiReactionType, Reaction, ReactionCountDetails},
8 gateway::payload::incoming::{
9 ReactionAdd, ReactionRemove, ReactionRemoveAll, ReactionRemoveEmoji,
10 },
11};
12
13impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for ReactionAdd {
14 fn update(&self, cache: &InMemoryCache<CacheModels>) {
15 if !cache.wants(ResourceType::REACTION) {
16 return;
17 }
18
19 let key = self.0.message_id;
20
21 let Some(mut message) = cache.messages.get_mut(&key) else {
22 return;
23 };
24
25 if let Some(reaction) = message
26 .reactions_mut()
27 .iter_mut()
28 .find(|r| reactions_eq(&r.emoji, &self.0.emoji))
29 {
30 if !reaction.me {
31 if let Some(current_user) = cache.current_user() {
32 if current_user.id() == self.0.user_id {
33 reaction.me = true;
34 }
35 }
36 }
37
38 reaction.count += 1;
39 } else {
40 let me = cache
41 .current_user()
42 .is_some_and(|user| user.id() == self.0.user_id);
43
44 message.add_reaction(Reaction {
45 burst_colors: Vec::new(),
46 count: 1,
47 count_details: ReactionCountDetails {
48 burst: 0,
49 normal: 1,
50 },
51 emoji: self.0.emoji.clone(),
52 me,
53 me_burst: false,
54 });
55 }
56 }
57}
58
59impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for ReactionRemove {
60 fn update(&self, cache: &InMemoryCache<CacheModels>) {
61 if !cache.wants(ResourceType::REACTION) {
62 return;
63 }
64
65 let Some(mut message) = cache.messages.get_mut(&self.0.message_id) else {
66 return;
67 };
68
69 if let Some(reaction) = message
70 .reactions_mut()
71 .iter_mut()
72 .find(|r| reactions_eq(&r.emoji, &self.0.emoji))
73 {
74 if reaction.me {
75 if let Some(current_user) = cache.current_user() {
76 if current_user.id() == self.0.user_id {
77 reaction.me = false;
78 }
79 }
80 }
81
82 if reaction.count > 1 {
83 reaction.count -= 1;
84 } else {
85 message.retain_reactions(|e| !(reactions_eq(&e.emoji, &self.0.emoji)));
86 }
87 }
88 }
89}
90
91impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for ReactionRemoveAll {
92 fn update(&self, cache: &InMemoryCache<CacheModels>) {
93 if !cache.wants(ResourceType::REACTION) {
94 return;
95 }
96
97 let Some(mut message) = cache.messages.get_mut(&self.message_id) else {
98 return;
99 };
100
101 message.clear_reactions();
102 }
103}
104
105impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for ReactionRemoveEmoji {
106 fn update(&self, cache: &InMemoryCache<CacheModels>) {
107 if !cache.wants(ResourceType::REACTION) {
108 return;
109 }
110
111 let Some(mut message) = cache.messages.get_mut(&self.message_id) else {
112 return;
113 };
114
115 let maybe_index = message
116 .reactions()
117 .iter()
118 .position(|r| reactions_eq(&r.emoji, &self.emoji));
119
120 if let Some(index) = maybe_index {
121 message.remove_reaction(index);
122 }
123 }
124}
125
126fn reactions_eq(a: &EmojiReactionType, b: &EmojiReactionType) -> bool {
127 match (a, b) {
128 (
129 EmojiReactionType::Custom { id: id_a, .. },
130 EmojiReactionType::Custom { id: id_b, .. },
131 ) => id_a == id_b,
132 (
133 EmojiReactionType::Unicode { name: name_a },
134 EmojiReactionType::Unicode { name: name_b },
135 ) => name_a == name_b,
136 _ => false,
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::reactions_eq;
143 use crate::{model::CachedMessage, test};
144 use twilight_model::{
145 channel::message::{EmojiReactionType, Reaction},
146 gateway::{
147 payload::incoming::{ReactionRemove, ReactionRemoveAll, ReactionRemoveEmoji},
148 GatewayReaction,
149 },
150 id::Id,
151 };
152
153 fn find_custom_react(msg: &CachedMessage) -> Option<&Reaction> {
154 msg.reactions.iter().find(|&r| {
155 reactions_eq(
156 &r.emoji,
157 &EmojiReactionType::Custom {
158 animated: false,
159 id: Id::new(6),
160 name: None,
161 },
162 )
163 })
164 }
165
166 #[test]
167 fn reaction_add() {
168 let cache = test::cache_with_message_and_reactions();
169 let msg = cache.message(Id::new(4)).unwrap();
170
171 assert_eq!(msg.reactions.len(), 3);
172
173 let world_react = msg
174 .reactions
175 .iter()
176 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "πΊοΈ"));
177 let smiley_react = msg
178 .reactions
179 .iter()
180 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "π"));
181 let custom_react = find_custom_react(&msg);
182
183 assert!(world_react.is_some());
184 assert_eq!(world_react.unwrap().count, 1);
185 assert!(smiley_react.is_some());
186 assert_eq!(smiley_react.unwrap().count, 2);
187 assert!(custom_react.is_some());
188 assert_eq!(custom_react.unwrap().count, 1);
189 }
190
191 #[test]
192 fn reaction_remove() {
193 let cache = test::cache_with_message_and_reactions();
194 cache.update(&ReactionRemove(GatewayReaction {
195 burst: false,
196 burst_colors: Vec::new(),
197 channel_id: Id::new(2),
198 emoji: EmojiReactionType::Unicode {
199 name: "π".to_owned(),
200 },
201 guild_id: Some(Id::new(1)),
202 member: None,
203 message_author_id: None,
204 message_id: Id::new(4),
205 user_id: Id::new(5),
206 }));
207 cache.update(&ReactionRemove(GatewayReaction {
208 burst: false,
209 burst_colors: Vec::new(),
210 channel_id: Id::new(2),
211 emoji: EmojiReactionType::Custom {
212 animated: false,
213 id: Id::new(6),
214 name: None,
215 },
216 guild_id: Some(Id::new(1)),
217 member: None,
218 message_author_id: None,
219 message_id: Id::new(4),
220 user_id: Id::new(5),
221 }));
222
223 let msg = cache.message(Id::new(4)).unwrap();
224
225 assert_eq!(msg.reactions.len(), 2);
226
227 let world_react = msg
228 .reactions
229 .iter()
230 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "πΊοΈ"));
231 let smiley_react = msg
232 .reactions
233 .iter()
234 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "π"));
235 let custom_react = find_custom_react(&msg);
236
237 assert!(world_react.is_some());
238 assert_eq!(world_react.unwrap().count, 1);
239 assert!(smiley_react.is_some());
240 assert_eq!(smiley_react.unwrap().count, 1);
241 assert!(custom_react.is_none());
242 }
243
244 #[test]
245 fn reaction_remove_all() {
246 let cache = test::cache_with_message_and_reactions();
247 cache.update(&ReactionRemoveAll {
248 channel_id: Id::new(2),
249 message_id: Id::new(4),
250 guild_id: Some(Id::new(1)),
251 });
252
253 let msg = cache.message(Id::new(4)).unwrap();
254
255 assert_eq!(msg.reactions.len(), 0);
256 }
257
258 #[test]
259 fn reaction_remove_emoji() {
260 let cache = test::cache_with_message_and_reactions();
261 cache.update(&ReactionRemoveEmoji {
262 channel_id: Id::new(2),
263 emoji: EmojiReactionType::Unicode {
264 name: "π".to_owned(),
265 },
266 guild_id: Id::new(1),
267 message_id: Id::new(4),
268 });
269 cache.update(&ReactionRemoveEmoji {
270 channel_id: Id::new(2),
271 emoji: EmojiReactionType::Custom {
272 animated: false,
273 id: Id::new(6),
274 name: None,
275 },
276 guild_id: Id::new(1),
277 message_id: Id::new(4),
278 });
279
280 let msg = cache.message(Id::new(4)).unwrap();
281
282 assert_eq!(msg.reactions.len(), 1);
283
284 let world_react = msg
285 .reactions
286 .iter()
287 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "πΊοΈ"));
288 let smiley_react = msg
289 .reactions
290 .iter()
291 .find(|&r| matches!(&r.emoji, EmojiReactionType::Unicode {name} if name == "π"));
292 let custom_react = find_custom_react(&msg);
293
294 assert!(world_react.is_some());
295 assert_eq!(world_react.unwrap().count, 1);
296 assert!(smiley_react.is_none());
297 assert!(custom_react.is_none());
298 }
299}