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