From 0ce40379c31bb106870b8dcd9c993a3cb51b501c Mon Sep 17 00:00:00 2001 From: iequidoo Date: Sun, 26 May 2024 16:09:39 -0300 Subject: [PATCH] feat: Display vCard contact name in the message summary --- deltachat-ffi/deltachat.h | 3 --- src/chat.rs | 3 +++ src/message.rs | 24 ++++++++++++++++++++++-- src/mimeparser.rs | 37 ++++++++++++++++++++++++------------- src/param.rs | 3 +++ src/receive_imf/tests.rs | 1 + src/stock_str.rs | 8 -------- src/summary.rs | 26 ++++++++++++++++++-------- 8 files changed, 71 insertions(+), 34 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 8d12301618..d205763013 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -7363,9 +7363,6 @@ void dc_event_unref(dc_event_t* event); /// Used as info message. #define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191 -/// "Contact" -#define DC_STR_CONTACT 200 - /** * @} */ diff --git a/src/chat.rs b/src/chat.rs index 33818350b2..bdb8568e2f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -887,6 +887,7 @@ impl ChatId { bail!("No text and no quote in draft"); } } + Viewtype::Vcard => msg.try_set_vcard(context).await?, _ => { let blob = msg .param @@ -2643,6 +2644,8 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { context .ensure_sendable_webxdc_file(&blob.to_abs_path()) .await?; + } else if msg.viewtype == Viewtype::Vcard { + msg.try_set_vcard(context).await?; } let mut maybe_sticker = msg.viewtype == Viewtype::Sticker; diff --git a/src/message.rs b/src/message.rs index 9f6ee2c5dc..6ce4f8dcc5 100644 --- a/src/message.rs +++ b/src/message.rs @@ -24,7 +24,7 @@ use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer}; use crate::events::EventType; use crate::imap::markseen_on_imap_table; use crate::location::delete_poi_location; -use crate::mimeparser::{parse_message_id, SystemMessage}; +use crate::mimeparser::{get_vcard_summary, parse_message_id, SystemMessage}; use crate::param::{Param, Params}; use crate::pgp::split_armored_data; use crate::reaction::get_msg_reactions; @@ -1057,7 +1057,9 @@ impl Message { /// the file will only be used when the message is prepared /// for sending. pub fn set_file(&mut self, file: impl ToString, filemime: Option<&str>) { - if let Some(name) = Path::new(&file.to_string()).file_name() { + let file = file.to_string(); + let path = Path::new(&file); + if let Some(name) = path.file_name() { if let Some(name) = name.to_str() { self.param.set(Param::Filename, name); } @@ -1081,6 +1083,24 @@ impl Message { Ok(()) } + /// Updates message state from the vCard attachment. + pub(crate) async fn try_set_vcard(&mut self, context: &Context) -> Result<()> { + let blob = self + .param + .get_blob(Param::File, context, !self.is_increation()) + .await? + .context("Attachment missing")?; + let path = blob.to_abs_path(); + let vcard = fs::read(path).await.context("Could not read {path}")?; + if let Some(summary) = get_vcard_summary(&vcard) { + self.param.set(Param::Summary1, summary); + } else { + warn!(context, "try_set_vcard: Not a valid DeltaChat vCard."); + self.viewtype = Viewtype::File; + } + Ok(()) + } + /// Set different sender name for a message. /// This overrides the name set by the `set_config()`-option `displayname`. pub fn set_override_sender_name(&mut self, name: Option) { diff --git a/src/mimeparser.rs b/src/mimeparser.rs index e0a1b4594e..db00b3460a 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -1233,6 +1233,7 @@ impl MimeMessage { return Ok(()); } } + let mut param = Params::new(); let msg_type = if context .is_webxdc_file(filename, decoded_data) .await @@ -1276,6 +1277,13 @@ impl MimeMessage { .unwrap_or_default(); self.webxdc_status_update = Some(serialized); return Ok(()); + } else if msg_type == Viewtype::Vcard { + if let Some(summary) = get_vcard_summary(decoded_data) { + param.set(Param::Summary1, summary); + msg_type + } else { + Viewtype::File + } } else { msg_type }; @@ -1299,8 +1307,8 @@ impl MimeMessage { let mut part = Part::default(); if mime_type.type_() == mime::IMAGE { if let Ok((width, height)) = get_filemeta(decoded_data) { - part.param.set_int(Param::Width, width as i32); - part.param.set_int(Param::Height, height as i32); + param.set_int(Param::Width, width as i32); + param.set_int(Param::Height, height as i32); } } @@ -1308,9 +1316,10 @@ impl MimeMessage { part.org_filename = Some(filename.to_string()); part.mimetype = Some(mime_type); part.bytes = decoded_data.len(); - part.param.set(Param::File, blob.as_name()); - part.param.set(Param::Filename, filename); - part.param.set(Param::MimeType, raw_mime); + param.set(Param::File, blob.as_name()); + param.set(Param::Filename, filename); + param.set(Param::MimeType, raw_mime); + part.param = param; part.is_related = is_related; self.do_add_single_part(part); @@ -1937,7 +1946,7 @@ fn get_mime_type( let viewtype = match mimetype.type_() { mime::TEXT => match mimetype.subtype() { - mime::VCARD if is_valid_deltachat_vcard(mail) => Viewtype::Vcard, + mime::VCARD => Viewtype::Vcard, mime::PLAIN | mime::HTML if !is_attachment_disposition(mail) => Viewtype::Text, _ => Viewtype::File, }, @@ -1988,15 +1997,17 @@ fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool { .any(|(key, _value)| key.starts_with("filename")) } -fn is_valid_deltachat_vcard(mail: &mailparse::ParsedMail) -> bool { - let Ok(body) = &mail.get_body() else { - return false; +/// Returns the 1st part of summary text (i.e. before the dash if any) for a valid DeltaChat vCard. +pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option { + let vcard = str::from_utf8(vcard).ok()?; + let contacts = deltachat_contact_tools::parse_vcard(vcard); + let [c] = &contacts[..] else { + return None; }; - let contacts = deltachat_contact_tools::parse_vcard(body); - if let [c] = &contacts[..] { - return deltachat_contact_tools::may_be_valid_addr(&c.addr); + if !deltachat_contact_tools::may_be_valid_addr(&c.addr) { + return None; } - false + Some(c.display_name().to_string()) } /// Tries to get attachment filename. diff --git a/src/param.rs b/src/param.rs index 4348422617..b65cfd1d39 100644 --- a/src/param.rs +++ b/src/param.rs @@ -88,6 +88,9 @@ pub enum Param { /// For Messages: quoted text. Quote = b'q', + /// For Messages: the 1st part of summary text (i.e. before the dash if any). + Summary1 = b'4', + /// For Messages Cmd = b'S', diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs index 38d0d4eff8..c93bb9ae66 100644 --- a/src/receive_imf/tests.rs +++ b/src/receive_imf/tests.rs @@ -4551,6 +4551,7 @@ async fn test_receive_vcard() -> Result<()> { if vcard_contains_address { assert_eq!(rcvd.viewtype, Viewtype::Vcard); + assert_eq!(rcvd.get_summary_text(&bob).await, "šŸ‘¤ Claire"); } else { // VCards without an email address are not "deltachat contacts", // so they are shown as files diff --git a/src/stock_str.rs b/src/stock_str.rs index 2d7da10437..984a658f82 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -443,9 +443,6 @@ pub enum StockMessage { fallback = "Could not yet establish guaranteed end-to-end encryption, but you may already send a message." ))] SecurejoinWaitTimeout = 191, - - #[strum(props(fallback = "Contact"))] - Contact = 200, } impl StockMessage { @@ -1101,11 +1098,6 @@ pub(crate) async fn videochat_invite_msg_body(context: &Context, url: &str) -> S .replace1(url) } -/// Stock string: `Contact`. -pub(crate) async fn contact(context: &Context) -> String { - translated(context, StockMessage::Contact).await -} - /// Stock string: `Error:\n\nā€œ%1$sā€`. pub(crate) async fn configuration_failed(context: &Context, details: &str) -> String { translated(context, StockMessage::ConfigurationFailed) diff --git a/src/summary.rs b/src/summary.rs index 9fe63530b3..c59edf3790 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -10,6 +10,7 @@ use crate::contact::{Contact, ContactId}; use crate::context::Context; use crate::message::{Message, MessageState, Viewtype}; use crate::mimeparser::SystemMessage; +use crate::param::Param; use crate::stock_str; use crate::stock_str::msg_reacted; use crate::tools::truncate; @@ -149,7 +150,7 @@ impl Summary { impl Message { /// Returns a summary text. - async fn get_summary_text(&self, context: &Context) -> String { + pub(crate) async fn get_summary_text(&self, context: &Context) -> String { let summary = self.get_summary_text_without_prefix(context).await; if self.is_forwarded() { @@ -231,8 +232,8 @@ impl Message { } Viewtype::Vcard => { emoji = Some("šŸ‘¤"); - type_name = Some(stock_str::contact(context).await); - type_file = None; + type_name = None; + type_file = self.param.get(Param::Summary1).map(|s| s.to_string()); append_text = true; } Viewtype::Text | Viewtype::Unknown => { @@ -284,6 +285,7 @@ impl Message { #[cfg(test)] mod tests { use super::*; + use crate::chat::ChatId; use crate::param::Param; use crate::test_utils as test; @@ -296,7 +298,9 @@ mod tests { async fn test_get_summary_text() { let d = test::TestContext::new().await; let ctx = &d.ctx; - + let chat_id = ChatId::create_for_contact(ctx, ContactId::SELF) + .await + .unwrap(); let some_text = " bla \t\n\tbla\n\t".to_string(); let mut msg = Message::new(Viewtype::Text); @@ -367,10 +371,15 @@ mod tests { assert_summary_texts(&msg, ctx, "Video chat invitation").await; // text is not added for videochat invitations let mut msg = Message::new(Viewtype::Vcard); - msg.set_file("foo.vcf", None); - assert_summary_texts(&msg, ctx, "šŸ‘¤ Contact").await; + msg.set_file_from_bytes(ctx, "foo.vcf", b"", None) + .await + .unwrap(); + chat_id.set_draft(ctx, Some(&mut msg)).await.unwrap(); + // If a vCard can't be parsed, the message becomes `Viewtype::File`. + assert_eq!(msg.viewtype, Viewtype::File); + assert_summary_texts(&msg, ctx, "šŸ“Ž foo.vcf").await; msg.set_text(some_text.clone()); - assert_summary_texts(&msg, ctx, "šŸ‘¤ bla bla").await; + assert_summary_texts(&msg, ctx, "šŸ“Ž foo.vcf \u{2013} bla bla").await; let mut msg = Message::new(Viewtype::Vcard); msg.set_file_from_bytes( @@ -385,7 +394,8 @@ mod tests { ) .await .unwrap(); - assert_summary_texts(&msg, ctx, "šŸ‘¤ Contact").await; + chat_id.set_draft(ctx, Some(&mut msg)).await.unwrap(); + assert_summary_texts(&msg, ctx, "šŸ‘¤ Alice Wonderland").await; // Forwarded let mut msg = Message::new(Viewtype::Text);