diff --git a/CHANGELOG.md b/CHANGELOG.md index 5afe6ef06..d9c47ceb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - BREAKING: jsonrpc: - `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries` - `get_chatlist_entries` now returns `Vec` of chatids instead of `ChatListEntries` + - `JSONRPCReactions.reactions` is now a `Vec` with unique reactions and their count, sorted in descending order. - `Event`: `context_id` property is now called `contextId` - jsonrpc: expand `MessageSearchResult`: - always include `chat_name`(not an option anymore) diff --git a/deltachat-jsonrpc/src/api/types/reactions.rs b/deltachat-jsonrpc/src/api/types/reactions.rs index 8717ebdc2..d068b23c5 100644 --- a/deltachat-jsonrpc/src/api/types/reactions.rs +++ b/deltachat-jsonrpc/src/api/types/reactions.rs @@ -1,23 +1,37 @@ use std::collections::BTreeMap; +use deltachat::contact::ContactId; use deltachat::reaction::Reactions; use serde::Serialize; use typescript_type_def::TypeDef; +/// A single reaction emoji. +#[derive(Serialize, TypeDef)] +#[serde(rename = "Reaction", rename_all = "camelCase")] +pub struct JSONRPCReaction { + /// Emoji. + emoji: String, + + /// Emoji frequency. + count: usize, + + /// True if we reacted with this emoji. + is_from_self: bool, +} + /// Structure representing all reactions to a particular message. #[derive(Serialize, TypeDef)] #[serde(rename = "Reactions", rename_all = "camelCase")] pub struct JSONRPCReactions { /// Map from a contact to it's reaction to message. reactions_by_contact: BTreeMap>, - /// Unique reactions and their count - reactions: BTreeMap, + /// Unique reactions and their count, sorted in descending order. + reactions: Vec, } impl From for JSONRPCReactions { fn from(reactions: Reactions) -> Self { let mut reactions_by_contact: BTreeMap> = BTreeMap::new(); - let mut unique_reactions: BTreeMap = BTreeMap::new(); for contact_id in reactions.contacts() { let reaction = reactions.get(contact_id); @@ -30,18 +44,29 @@ impl From for JSONRPCReactions { .map(|emoji| emoji.to_owned()) .collect(); reactions_by_contact.insert(contact_id.to_u32(), emojis.clone()); - for emoji in emojis { - if let Some(x) = unique_reactions.get_mut(&emoji) { - *x += 1; - } else { - unique_reactions.insert(emoji, 1); - } - } + } + + let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32()); + + let mut reactions_v = Vec::new(); + for (emoji, count) in reactions.emoji_sorted_by_frequency() { + let is_from_self = if let Some(self_reactions) = self_reactions { + self_reactions.contains(&emoji) + } else { + false + }; + + let reaction = JSONRPCReaction { + emoji, + count, + is_from_self, + }; + reactions_v.push(reaction) } JSONRPCReactions { reactions_by_contact, - reactions: unique_reactions, + reactions: reactions_v, } } } diff --git a/src/reaction.rs b/src/reaction.rs index 1af05b0c9..530619519 100644 --- a/src/reaction.rs +++ b/src/reaction.rs @@ -14,6 +14,7 @@ //! possible to remove all reactions by sending an empty string as a reaction, //! even though RFC 9078 requires at least one emoji to be sent. +use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt; @@ -116,10 +117,9 @@ impl Reactions { pub fn is_empty(&self) -> bool { self.reactions.is_empty() } -} -impl fmt::Display for Reactions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// Returns a map from emojis to their frequencies. + pub fn emoji_frequencies(&self) -> BTreeMap { let mut emoji_frequencies: BTreeMap = BTreeMap::new(); for reaction in self.reactions.values() { for emoji in reaction.emojis() { @@ -129,6 +129,30 @@ impl fmt::Display for Reactions { .or_insert(1); } } + emoji_frequencies + } + + /// Returns a vector of emojis + /// sorted in descending order of frequencies. + /// + /// This function can be used to display the reactions in + /// the message bubble in the UIs. + pub fn emoji_sorted_by_frequency(&self) -> Vec<(String, usize)> { + let mut emoji_frequencies: Vec<(String, usize)> = + self.emoji_frequencies().into_iter().collect(); + emoji_frequencies.sort_by(|(a, a_count), (b, b_count)| { + match a_count.cmp(b_count).reverse() { + Ordering::Equal => a.cmp(b), + other => other, + } + }); + emoji_frequencies + } +} + +impl fmt::Display for Reactions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let emoji_frequencies = self.emoji_sorted_by_frequency(); let mut first = true; for (emoji, frequency) in emoji_frequencies { if !first { @@ -481,6 +505,11 @@ Content-Disposition: reaction\n\ let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?; assert_eq!(reactions.to_string(), "👍2 😀1"); + assert_eq!( + reactions.emoji_sorted_by_frequency(), + vec![("👍".to_string(), 2), ("😀".to_string(), 1)] + ); + Ok(()) }