1use std::cmp::Ordering;
18use std::collections::BTreeMap;
19use std::fmt;
20
21use anyhow::Result;
22use serde::{Deserialize, Serialize};
23
24use crate::chat::{send_msg, Chat, ChatId};
25use crate::chatlist_events;
26use crate::contact::ContactId;
27use crate::context::Context;
28use crate::events::EventType;
29use crate::message::{rfc724_mid_exists, Message, MsgId};
30use crate::param::Param;
31
32#[derive(Debug, Default, Clone, Deserialize, Eq, PartialEq, Serialize)]
36pub struct Reaction {
37 reaction: String,
39}
40
41impl From<&str> for Reaction {
45 fn from(reaction: &str) -> Self {
60 let mut emojis: Vec<&str> = reaction
61 .split_ascii_whitespace()
62 .filter(|&emoji| emoji.len() < 30)
63 .collect();
64 emojis.sort_unstable();
65 emojis.dedup();
66 let reaction = emojis.join(" ");
67 Self { reaction }
68 }
69}
70
71impl Reaction {
72 pub fn is_empty(&self) -> bool {
74 self.reaction.is_empty()
75 }
76
77 pub fn emojis(&self) -> Vec<&str> {
79 self.reaction.split(' ').collect()
80 }
81
82 pub fn as_str(&self) -> &str {
84 &self.reaction
85 }
86
87 pub fn add(&self, other: Self) -> Self {
89 let mut emojis: Vec<&str> = self.emojis();
90 emojis.append(&mut other.emojis());
91 emojis.sort_unstable();
92 emojis.dedup();
93 let reaction = emojis.join(" ");
94 Self { reaction }
95 }
96}
97
98#[derive(Debug)]
100pub struct Reactions {
101 reactions: BTreeMap<ContactId, Reaction>,
103}
104
105impl Reactions {
106 pub fn contacts(&self) -> Vec<ContactId> {
108 self.reactions.keys().copied().collect()
109 }
110
111 pub fn get(&self, contact_id: ContactId) -> Reaction {
116 self.reactions.get(&contact_id).cloned().unwrap_or_default()
117 }
118
119 pub fn is_empty(&self) -> bool {
121 self.reactions.is_empty()
122 }
123
124 pub fn emoji_frequencies(&self) -> BTreeMap<String, usize> {
126 let mut emoji_frequencies: BTreeMap<String, usize> = BTreeMap::new();
127 for reaction in self.reactions.values() {
128 for emoji in reaction.emojis() {
129 emoji_frequencies
130 .entry(emoji.to_string())
131 .and_modify(|x| *x += 1)
132 .or_insert(1);
133 }
134 }
135 emoji_frequencies
136 }
137
138 pub fn emoji_sorted_by_frequency(&self) -> Vec<(String, usize)> {
144 let mut emoji_frequencies: Vec<(String, usize)> =
145 self.emoji_frequencies().into_iter().collect();
146 emoji_frequencies.sort_by(|(a, a_count), (b, b_count)| {
147 match a_count.cmp(b_count).reverse() {
148 Ordering::Equal => a.cmp(b),
149 other => other,
150 }
151 });
152 emoji_frequencies
153 }
154}
155
156impl fmt::Display for Reactions {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 let emoji_frequencies = self.emoji_sorted_by_frequency();
159 let mut first = true;
160 for (emoji, frequency) in emoji_frequencies {
161 if !first {
162 write!(f, " ")?;
163 }
164 first = false;
165 write!(f, "{emoji}{frequency}")?;
166 }
167 Ok(())
168 }
169}
170
171async fn set_msg_id_reaction(
172 context: &Context,
173 msg_id: MsgId,
174 chat_id: ChatId,
175 contact_id: ContactId,
176 timestamp: i64,
177 reaction: &Reaction,
178) -> Result<()> {
179 if reaction.is_empty() {
180 context
182 .sql
183 .execute(
184 "DELETE FROM reactions
185 WHERE msg_id = ?1
186 AND contact_id = ?2",
187 (msg_id, contact_id),
188 )
189 .await?;
190 } else {
191 context
192 .sql
193 .execute(
194 "INSERT INTO reactions (msg_id, contact_id, reaction)
195 VALUES (?1, ?2, ?3)
196 ON CONFLICT(msg_id, contact_id)
197 DO UPDATE SET reaction=excluded.reaction",
198 (msg_id, contact_id, reaction.as_str()),
199 )
200 .await?;
201 let mut chat = Chat::load_from_db(context, chat_id).await?;
202 if chat
203 .param
204 .update_timestamp(Param::LastReactionTimestamp, timestamp)?
205 {
206 chat.param
207 .set_i64(Param::LastReactionMsgId, i64::from(msg_id.to_u32()));
208 chat.param
209 .set_i64(Param::LastReactionContactId, i64::from(contact_id.to_u32()));
210 chat.update_param(context).await?;
211 }
212 }
213
214 context.emit_event(EventType::ReactionsChanged {
215 chat_id,
216 msg_id,
217 contact_id,
218 });
219 chatlist_events::emit_chatlist_item_changed(context, chat_id);
220 Ok(())
221}
222
223pub async fn send_reaction(context: &Context, msg_id: MsgId, reaction: &str) -> Result<MsgId> {
228 let msg = Message::load_from_db(context, msg_id).await?;
229 let chat_id = msg.chat_id;
230
231 let reaction: Reaction = reaction.into();
232 let mut reaction_msg = Message::new_text(reaction.as_str().to_string());
233 reaction_msg.set_reaction();
234 reaction_msg.in_reply_to = Some(msg.rfc724_mid);
235 reaction_msg.hidden = true;
236
237 let reaction_msg_id = send_msg(context, chat_id, &mut reaction_msg).await?;
239
240 set_msg_id_reaction(
242 context,
243 msg_id,
244 msg.chat_id,
245 ContactId::SELF,
246 reaction_msg.timestamp_sort,
247 &reaction,
248 )
249 .await?;
250 Ok(reaction_msg_id)
251}
252
253pub async fn add_reaction(context: &Context, msg_id: MsgId, reaction: &str) -> Result<MsgId> {
260 let self_reaction = get_self_reaction(context, msg_id).await?;
261 let reaction = self_reaction.add(Reaction::from(reaction));
262 send_reaction(context, msg_id, reaction.as_str()).await
263}
264
265pub(crate) async fn set_msg_reaction(
272 context: &Context,
273 in_reply_to: &str,
274 chat_id: ChatId,
275 contact_id: ContactId,
276 timestamp: i64,
277 reaction: Reaction,
278 is_incoming_fresh: bool,
279) -> Result<()> {
280 if let Some((msg_id, _)) = rfc724_mid_exists(context, in_reply_to).await? {
281 set_msg_id_reaction(context, msg_id, chat_id, contact_id, timestamp, &reaction).await?;
282
283 if is_incoming_fresh
284 && !reaction.is_empty()
285 && msg_id.get_state(context).await?.is_outgoing()
286 {
287 context.emit_event(EventType::IncomingReaction {
288 chat_id,
289 contact_id,
290 msg_id,
291 reaction,
292 });
293 }
294 } else {
295 info!(
296 context,
297 "Can't assign reaction to unknown message with Message-ID {}", in_reply_to
298 );
299 }
300 Ok(())
301}
302
303async fn get_self_reaction(context: &Context, msg_id: MsgId) -> Result<Reaction> {
305 let reaction_str: Option<String> = context
306 .sql
307 .query_get_value(
308 "SELECT reaction
309 FROM reactions
310 WHERE msg_id=? AND contact_id=?",
311 (msg_id, ContactId::SELF),
312 )
313 .await?;
314 Ok(reaction_str
315 .as_deref()
316 .map(Reaction::from)
317 .unwrap_or_default())
318}
319
320pub async fn get_msg_reactions(context: &Context, msg_id: MsgId) -> Result<Reactions> {
322 let reactions = context
323 .sql
324 .query_map(
325 "SELECT contact_id, reaction FROM reactions WHERE msg_id=?",
326 (msg_id,),
327 |row| {
328 let contact_id: ContactId = row.get(0)?;
329 let reaction: String = row.get(1)?;
330 Ok((contact_id, reaction))
331 },
332 |rows| {
333 let mut reactions = Vec::new();
334 for row in rows {
335 let (contact_id, reaction) = row?;
336 reactions.push((contact_id, Reaction::from(reaction.as_str())));
337 }
338 Ok(reactions)
339 },
340 )
341 .await?
342 .into_iter()
343 .collect();
344 Ok(Reactions { reactions })
345}
346
347impl Chat {
348 pub async fn get_last_reaction_if_newer_than(
352 &self,
353 context: &Context,
354 timestamp: i64,
355 ) -> Result<Option<(Message, ContactId, String)>> {
356 if self
357 .param
358 .get_i64(Param::LastReactionTimestamp)
359 .filter(|&reaction_timestamp| reaction_timestamp > timestamp)
360 .is_none()
361 {
362 return Ok(None);
363 };
364 let reaction_msg_id = MsgId::new(
365 self.param
366 .get_int(Param::LastReactionMsgId)
367 .unwrap_or_default() as u32,
368 );
369 let Some(reaction_msg) = Message::load_from_db_optional(context, reaction_msg_id).await?
370 else {
371 return Ok(None);
375 };
376 let reaction_contact_id = ContactId::new(
377 self.param
378 .get_int(Param::LastReactionContactId)
379 .unwrap_or_default() as u32,
380 );
381 if let Some(reaction) = context
382 .sql
383 .query_get_value(
384 "SELECT reaction FROM reactions WHERE msg_id=? AND contact_id=?",
385 (reaction_msg.id, reaction_contact_id),
386 )
387 .await?
388 {
389 Ok(Some((reaction_msg, reaction_contact_id, reaction)))
390 } else {
391 Ok(None)
392 }
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use deltachat_contact_tools::ContactAddress;
399
400 use super::*;
401 use crate::chat::{forward_msgs, get_chat_msgs, marknoticed_chat, send_text_msg};
402 use crate::chatlist::Chatlist;
403 use crate::config::Config;
404 use crate::contact::{Contact, Origin};
405 use crate::download::DownloadState;
406 use crate::message::{delete_msgs, MessageState};
407 use crate::receive_imf::{receive_imf, receive_imf_from_inbox};
408 use crate::sql::housekeeping;
409 use crate::test_utils::TestContext;
410 use crate::test_utils::TestContextManager;
411 use crate::tools::SystemTime;
412 use std::time::Duration;
413
414 #[test]
415 fn test_parse_reaction() {
416 assert_eq!(Reaction::from("๐").emojis(), vec!["๐"]);
418 assert_eq!(Reaction::from("๐").emojis(), vec!["๐"]);
419 assert_eq!(Reaction::from("๐").emojis(), vec!["๐"]);
420 assert_eq!(Reaction::from("โน").emojis(), vec!["โน"]);
421 assert_eq!(Reaction::from("๐ข").emojis(), vec!["๐ข"]);
422
423 assert!(Reaction::from("").is_empty());
425
426 assert_eq!(Reaction::from(":deltacat:").emojis(), vec![":deltacat:"]);
429
430 assert!(
432 Reaction::from(":foobarbazquuxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:").is_empty()
433 );
434
435 assert_eq!(Reaction::from("๐ โค").emojis(), vec!["โค", "๐"]);
437 assert_eq!(Reaction::from("๐\tโค").emojis(), vec!["โค", "๐"]);
438
439 assert_eq!(
441 Reaction::from("๐\t:foo: โค").emojis(),
442 vec![":foo:", "โค", "๐"]
443 );
444 assert_eq!(Reaction::from("๐\t:foo: โค").as_str(), ":foo: โค ๐");
445
446 assert_eq!(Reaction::from("๐ ๐").emojis(), vec!["๐"]);
448 }
449
450 #[test]
451 fn test_add_reaction() {
452 let reaction1 = Reaction::from("๐ ๐");
453 let reaction2 = Reaction::from("โค");
454 let reaction_sum = reaction1.add(reaction2);
455
456 assert_eq!(reaction_sum.emojis(), vec!["โค", "๐", "๐"]);
457 }
458
459 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
460 async fn test_receive_reaction() -> Result<()> {
461 let alice = TestContext::new_alice().await;
462
463 receive_imf(
465 &alice,
466 "To: bob@example.net\n\
467From: alice@example.org\n\
468Date: Today, 29 February 2021 00:00:00 -800\n\
469Message-ID: 12345@example.org\n\
470Subject: Meeting\n\
471\n\
472Can we chat at 1pm pacific, today?"
473 .as_bytes(),
474 false,
475 )
476 .await?;
477 let msg = alice.get_last_msg().await;
478 assert_eq!(msg.state, MessageState::OutDelivered);
479 let reactions = get_msg_reactions(&alice, msg.id).await?;
480 let contacts = reactions.contacts();
481 assert_eq!(contacts.len(), 0);
482
483 let bob_id = Contact::add_or_lookup(
484 &alice,
485 "",
486 &ContactAddress::new("bob@example.net")?,
487 Origin::ManuallyCreated,
488 )
489 .await?
490 .0;
491 let bob_reaction = reactions.get(bob_id);
492 assert!(bob_reaction.is_empty()); receive_imf(
496 &alice,
497 "To: alice@example.org\n\
498From: bob@example.net\n\
499Date: Today, 29 February 2021 00:00:10 -800\n\
500Message-ID: 56789@example.net\n\
501In-Reply-To: 12345@example.org\n\
502Subject: Meeting\n\
503Mime-Version: 1.0 (1.0)\n\
504Content-Type: text/plain; charset=utf-8\n\
505Content-Disposition: reaction\n\
506\n\
507\u{1F44D}"
508 .as_bytes(),
509 false,
510 )
511 .await?;
512
513 let reactions = get_msg_reactions(&alice, msg.id).await?;
514 assert_eq!(reactions.to_string(), "๐1");
515
516 let contacts = reactions.contacts();
517 assert_eq!(contacts.len(), 1);
518
519 assert_eq!(contacts.first(), Some(&bob_id));
520 let bob_reaction = reactions.get(bob_id);
521 assert_eq!(bob_reaction.is_empty(), false);
522 assert_eq!(bob_reaction.emojis(), vec!["๐"]);
523 assert_eq!(bob_reaction.as_str(), "๐");
524
525 receive_imf(
527 &alice,
528 "To: alice@example.org\n\
529From: bob@example.net\n\
530Date: Today, 29 February 2021 00:00:10 -800\n\
531Message-ID: 56790@example.net\n\
532In-Reply-To: 12345@example.org\n\
533Subject: Meeting\n\
534Mime-Version: 1.0 (1.0)\n\
535Content-Type: text/plain; charset=utf-8\n\
536Content-Disposition: reaction\n\
537\n\
538๐\n\
539\n\
540--\n\
541_______________________________________________\n\
542Here's my footer -- bob@example.net"
543 .as_bytes(),
544 false,
545 )
546 .await?;
547
548 let reactions = get_msg_reactions(&alice, msg.id).await?;
549 assert_eq!(reactions.to_string(), "๐1");
550
551 Ok(())
552 }
553
554 async fn expect_reactions_changed_event(
555 t: &TestContext,
556 expected_chat_id: ChatId,
557 expected_msg_id: MsgId,
558 expected_contact_id: ContactId,
559 ) -> Result<()> {
560 let event = t
561 .evtracker
562 .get_matching(|evt| {
563 matches!(
564 evt,
565 EventType::ReactionsChanged { .. } | EventType::IncomingMsg { .. }
566 )
567 })
568 .await;
569 match event {
570 EventType::ReactionsChanged {
571 chat_id,
572 msg_id,
573 contact_id,
574 } => {
575 assert_eq!(chat_id, expected_chat_id);
576 assert_eq!(msg_id, expected_msg_id);
577 assert_eq!(contact_id, expected_contact_id);
578 }
579 _ => panic!("Unexpected event {event:?}."),
580 }
581 Ok(())
582 }
583
584 async fn expect_incoming_reactions_event(
585 t: &TestContext,
586 expected_chat_id: ChatId,
587 expected_msg_id: MsgId,
588 expected_contact_id: ContactId,
589 expected_reaction: &str,
590 ) -> Result<()> {
591 let event = t
592 .evtracker
593 .get_matching(|evt| {
596 matches!(
597 evt,
598 EventType::IncomingReaction { .. } | EventType::IncomingMsg { .. }
599 )
600 })
601 .await;
602 match event {
603 EventType::IncomingReaction {
604 chat_id,
605 msg_id,
606 contact_id,
607 reaction,
608 } => {
609 assert_eq!(chat_id, expected_chat_id);
610 assert_eq!(msg_id, expected_msg_id);
611 assert_eq!(contact_id, expected_contact_id);
612 assert_eq!(reaction, Reaction::from(expected_reaction));
613 }
614 _ => panic!("Unexpected event {event:?}."),
615 }
616 Ok(())
617 }
618
619 async fn expect_no_unwanted_events(t: &TestContext) {
621 let ev = t
622 .evtracker
623 .get_matching_opt(t, |evt| {
624 matches!(
625 evt,
626 EventType::IncomingReaction { .. }
627 | EventType::IncomingMsg { .. }
628 | EventType::MsgsChanged { .. }
629 )
630 })
631 .await;
632 if let Some(ev) = ev {
633 panic!("Unwanted event {ev:?}.")
634 }
635 }
636
637 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
638 async fn test_send_reaction() -> Result<()> {
639 let alice = TestContext::new_alice().await;
640 let bob = TestContext::new_bob().await;
641
642 alice
644 .set_config(
645 Config::Selfstatus,
646 Some("Buy Delta Chat today and make this banner go away!"),
647 )
648 .await?;
649 bob.set_config(Config::Selfstatus, Some("Sent from my Delta Chat Pro. ๐"))
650 .await?;
651
652 let chat_alice = alice.create_chat(&bob).await;
653 let alice_msg = alice.send_text(chat_alice.id, "Hi!").await;
654 let bob_msg = bob.recv_msg(&alice_msg).await;
655 assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 1);
656 assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 1);
657
658 let alice_msg2 = alice.send_text(chat_alice.id, "Hi again!").await;
659 bob.recv_msg(&alice_msg2).await;
660 assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 2);
661 assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
662
663 bob_msg.chat_id.accept(&bob).await?;
664
665 bob.evtracker.clear_events();
666 send_reaction(&bob, bob_msg.id, "๐").await.unwrap();
667 expect_reactions_changed_event(&bob, bob_msg.chat_id, bob_msg.id, ContactId::SELF).await?;
668 expect_no_unwanted_events(&bob).await;
669 assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
670
671 let bob_reaction_msg = bob.pop_sent_msg().await;
672 let alice_reaction_msg = alice.recv_msg_hidden(&bob_reaction_msg).await;
673 assert_eq!(alice_reaction_msg.state, MessageState::InFresh);
674 assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 2);
675
676 let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
677 assert_eq!(reactions.to_string(), "๐1");
678 let contacts = reactions.contacts();
679 assert_eq!(contacts.len(), 1);
680 let bob_id = contacts.first().unwrap();
681 let bob_reaction = reactions.get(*bob_id);
682 assert_eq!(bob_reaction.is_empty(), false);
683 assert_eq!(bob_reaction.emojis(), vec!["๐"]);
684 assert_eq!(bob_reaction.as_str(), "๐");
685 expect_reactions_changed_event(&alice, chat_alice.id, alice_msg.sender_msg_id, *bob_id)
686 .await?;
687 expect_incoming_reactions_event(
688 &alice,
689 chat_alice.id,
690 alice_msg.sender_msg_id,
691 *bob_id,
692 "๐",
693 )
694 .await?;
695 expect_no_unwanted_events(&alice).await;
696
697 marknoticed_chat(&alice, chat_alice.id).await?;
698 assert_eq!(
699 alice_reaction_msg.id.get_state(&alice).await?,
700 MessageState::InSeen
701 );
702 assert_eq!(
704 alice
705 .sql
706 .count("SELECT COUNT(*) FROM smtp_mdns", ())
707 .await?,
708 0
709 );
710
711 send_reaction(&alice, alice_msg.sender_msg_id, "๐ ๐")
713 .await
714 .unwrap();
715 let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
716 assert_eq!(reactions.to_string(), "๐2 ๐1");
717
718 assert_eq!(
719 reactions.emoji_sorted_by_frequency(),
720 vec![("๐".to_string(), 2), ("๐".to_string(), 1)]
721 );
722
723 Ok(())
724 }
725
726 async fn assert_summary(t: &TestContext, expected: &str) {
727 let chatlist = Chatlist::try_load(t, 0, None, None).await.unwrap();
728 let summary = chatlist.get_summary(t, 0, None).await.unwrap();
729 assert_eq!(summary.text, expected);
730 }
731
732 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
733 async fn test_reaction_summary() -> Result<()> {
734 let mut tcm = TestContextManager::new();
735 let alice = tcm.alice().await;
736 let bob = tcm.bob().await;
737 alice.set_config(Config::Displayname, Some("ALICE")).await?;
738 bob.set_config(Config::Displayname, Some("BOB")).await?;
739 let alice_bob_id = alice.add_or_lookup_contact_id(&bob).await;
740
741 let alice_chat = alice.create_chat(&bob).await;
743 let alice_msg1 = alice.send_text(alice_chat.id, "Party?").await;
744 let bob_msg1 = bob.recv_msg(&alice_msg1).await;
745
746 SystemTime::shift(Duration::from_secs(10));
748 bob_msg1.chat_id.accept(&bob).await?;
749 send_reaction(&bob, bob_msg1.id, "๐").await?;
750 let bob_send_reaction = bob.pop_sent_msg().await;
751 alice.recv_msg_hidden(&bob_send_reaction).await;
752 expect_incoming_reactions_event(
753 &alice,
754 alice_chat.id,
755 alice_msg1.sender_msg_id,
756 alice_bob_id,
757 "๐",
758 )
759 .await?;
760 expect_no_unwanted_events(&alice).await;
761
762 let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
763 let summary = chatlist.get_summary(&bob, 0, None).await?;
764 assert_eq!(summary.text, "You reacted ๐ to \"Party?\"");
765 assert_eq!(summary.timestamp, bob_msg1.get_timestamp()); assert_eq!(summary.state, MessageState::InFresh); assert!(summary.prefix.is_none());
768 assert!(summary.thumbnail_path.is_none());
769 assert_summary(&alice, "BOB reacted ๐ to \"Party?\"").await;
770
771 SystemTime::shift(Duration::from_secs(10));
773 send_reaction(&alice, alice_msg1.sender_msg_id, "๐ฟ").await?;
774 let alice_send_reaction = alice.pop_sent_msg().await;
775 bob.evtracker.clear_events();
776 bob.recv_msg_opt(&alice_send_reaction).await;
777 expect_no_unwanted_events(&bob).await;
778
779 assert_summary(&alice, "You reacted ๐ฟ to \"Party?\"").await;
780 assert_summary(&bob, "ALICE reacted ๐ฟ to \"Party?\"").await;
781
782 SystemTime::shift(Duration::from_secs(10));
784 let alice_msg2 = alice.send_text(alice_chat.id, "kewl").await;
785 bob.recv_msg(&alice_msg2).await;
786
787 assert_summary(&alice, "kewl").await;
788 assert_summary(&bob, "kewl").await;
789
790 SystemTime::shift(Duration::from_secs(10));
792 send_reaction(&alice, alice_msg1.sender_msg_id, "๐ค").await?;
793 let alice_send_reaction = alice.pop_sent_msg().await;
794 bob.recv_msg_opt(&alice_send_reaction).await;
795
796 assert_summary(&alice, "You reacted ๐ค to \"Party?\"").await;
797 assert_summary(&bob, "ALICE reacted ๐ค to \"Party?\"").await;
798
799 SystemTime::shift(Duration::from_secs(10));
801 send_reaction(&alice, alice_msg1.sender_msg_id, "").await?;
802 let alice_remove_reaction = alice.pop_sent_msg().await;
803 bob.recv_msg_opt(&alice_remove_reaction).await;
804
805 assert_summary(&alice, "kewl").await;
806 assert_summary(&bob, "kewl").await;
807
808 SystemTime::shift(Duration::from_secs(10));
810 send_reaction(&alice, alice_msg1.sender_msg_id, "๐งน").await?;
811 assert_summary(&alice, "You reacted ๐งน to \"Party?\"").await;
812
813 delete_msgs(&alice, &[alice_msg1.sender_msg_id]).await?; assert_summary(&alice, "kewl").await;
815 housekeeping(&alice).await?; assert_summary(&alice, "kewl").await;
817
818 Ok(())
819 }
820
821 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
822 async fn test_reaction_forwarded_summary() -> Result<()> {
823 let alice = TestContext::new_alice().await;
824
825 let self_chat = alice.get_self_chat().await;
827 let msg_id = send_text_msg(&alice, self_chat.id, "foo".to_string()).await?;
828 assert_summary(&alice, "foo").await;
829
830 SystemTime::shift(Duration::from_secs(10));
832 send_reaction(&alice, msg_id, "๐ซ").await?;
833 assert_summary(&alice, "You reacted ๐ซ to \"foo\"").await;
834 let reactions = get_msg_reactions(&alice, msg_id).await?;
835 assert_eq!(reactions.reactions.len(), 1);
836
837 let bob_id = Contact::create(&alice, "", "bob@example.net").await?;
839 let bob_chat_id = ChatId::create_for_contact(&alice, bob_id).await?;
840 forward_msgs(&alice, &[msg_id], bob_chat_id).await?;
841 assert_summary(&alice, "Forwarded: foo").await; let chatlist = Chatlist::try_load(&alice, 0, None, None).await.unwrap();
843 let forwarded_msg_id = chatlist.get_msg_id(0)?.unwrap();
844 let reactions = get_msg_reactions(&alice, forwarded_msg_id).await?;
845 assert!(reactions.reactions.is_empty()); SystemTime::shift(Duration::from_secs(10));
850 send_reaction(&alice, forwarded_msg_id, "๐ณ").await?;
851 assert_summary(&alice, "You reacted ๐ณ to \"foo\"").await;
852 let reactions = get_msg_reactions(&alice, msg_id).await?;
853 assert_eq!(reactions.reactions.len(), 1);
854
855 Ok(())
856 }
857
858 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
859 async fn test_reaction_self_chat_multidevice_summary() -> Result<()> {
860 let alice0 = TestContext::new_alice().await;
861 let alice1 = TestContext::new_alice().await;
862 let chat = alice0.get_self_chat().await;
863
864 let msg_id = send_text_msg(&alice0, chat.id, "mom's birthday!".to_string()).await?;
865 alice1.recv_msg(&alice0.pop_sent_msg().await).await;
866
867 SystemTime::shift(Duration::from_secs(10));
868 send_reaction(&alice0, msg_id, "๐").await?;
869 let sync = alice0.pop_sent_msg().await;
870 receive_imf(&alice1, sync.payload().as_bytes(), false).await?;
871
872 assert_summary(&alice0, "You reacted ๐ to \"mom's birthday!\"").await;
873 assert_summary(&alice1, "You reacted ๐ to \"mom's birthday!\"").await;
874
875 Ok(())
876 }
877
878 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
879 async fn test_partial_download_and_reaction() -> Result<()> {
880 let alice = TestContext::new_alice().await;
881 let bob = TestContext::new_bob().await;
882
883 alice
884 .create_chat_with_contact("Bob", "bob@example.net")
885 .await;
886
887 let msg_header = "From: Bob <bob@example.net>\n\
888 To: Alice <alice@example.org>\n\
889 Chat-Version: 1.0\n\
890 Subject: subject\n\
891 Message-ID: <first@example.org>\n\
892 Date: Sun, 14 Nov 2021 00:10:00 +0000\
893 Content-Type: text/plain";
894 let msg_full = format!("{msg_header}\n\n100k text...");
895
896 let alice_received_message = receive_imf_from_inbox(
898 &alice,
899 "first@example.org",
900 msg_header.as_bytes(),
901 false,
902 Some(100000),
903 )
904 .await?
905 .unwrap();
906 let alice_msg_id = *alice_received_message.msg_ids.first().unwrap();
907
908 let bob_received_message = receive_imf(&bob, msg_full.as_bytes(), false)
910 .await?
911 .unwrap();
912 let bob_msg_id = *bob_received_message.msg_ids.first().unwrap();
913
914 send_reaction(&bob, bob_msg_id, "๐").await.unwrap();
916 let bob_reaction_msg = bob.pop_sent_msg().await;
917
918 alice.recv_msg_hidden(&bob_reaction_msg).await;
920
921 let reactions = get_msg_reactions(&alice, alice_msg_id).await?;
922 assert_eq!(reactions.to_string(), "๐1");
923 let msg = Message::load_from_db(&alice, alice_msg_id).await?;
924 assert_eq!(msg.download_state(), DownloadState::Available);
925
926 receive_imf_from_inbox(
928 &alice,
929 "first@example.org",
930 msg_full.as_bytes(),
931 false,
932 None,
933 )
934 .await?;
935
936 let msg = Message::load_from_db(&alice, alice_msg_id).await?;
938 assert_eq!(msg.download_state(), DownloadState::Done);
939 let reactions = get_msg_reactions(&alice, alice_msg_id).await?;
940 assert_eq!(reactions.to_string(), "๐1");
941
942 Ok(())
943 }
944
945 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
950 async fn test_reaction_status_multidevice() -> Result<()> {
951 let mut tcm = TestContextManager::new();
952 let alice1 = tcm.alice().await;
953 let alice2 = tcm.alice().await;
954
955 alice1
956 .set_config(Config::Selfstatus, Some("New status"))
957 .await?;
958
959 let alice2_msg = tcm.send_recv(&alice1, &alice2, "Hi!").await;
960 assert_eq!(
961 alice2.get_config(Config::Selfstatus).await?.as_deref(),
962 Some("New status")
963 );
964
965 {
968 send_reaction(&alice2, alice2_msg.id, "๐").await?;
969 let msg = alice2.pop_sent_msg().await;
970 alice1.recv_msg_hidden(&msg).await;
971 }
972
973 assert_eq!(
975 alice1.get_config(Config::Selfstatus).await?.as_deref(),
976 Some("New status")
977 );
978 Ok(())
979 }
980
981 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
982 async fn test_send_reaction_multidevice() -> Result<()> {
983 let alice0 = TestContext::new_alice().await;
984 let alice1 = TestContext::new_alice().await;
985 let bob_id = Contact::create(&alice0, "", "bob@example.net").await?;
986 let chat_id = ChatId::create_for_contact(&alice0, bob_id).await?;
987
988 let alice0_msg_id = send_text_msg(&alice0, chat_id, "foo".to_string()).await?;
989 let alice1_msg = alice1.recv_msg(&alice0.pop_sent_msg().await).await;
990
991 send_reaction(&alice0, alice0_msg_id, "๐").await?;
992 alice1.recv_msg_hidden(&alice0.pop_sent_msg().await).await;
993
994 expect_reactions_changed_event(&alice0, chat_id, alice0_msg_id, ContactId::SELF).await?;
995 expect_reactions_changed_event(&alice1, alice1_msg.chat_id, alice1_msg.id, ContactId::SELF)
996 .await?;
997 for a in [&alice0, &alice1] {
998 expect_no_unwanted_events(a).await;
999 }
1000 Ok(())
1001 }
1002}