diff --git a/deltachat-contact-tools/src/vcard.rs b/deltachat-contact-tools/src/vcard.rs index 0ccd81104..faa1cb463 100644 --- a/deltachat-contact-tools/src/vcard.rs +++ b/deltachat-contact-tools/src/vcard.rs @@ -20,6 +20,8 @@ pub struct VcardContact { pub key: Option, /// The contact's profile image (=avatar) in Base64, vcard property `photo` pub profile_image: Option, + /// The biography, stored in the vcard property `note` + pub biography: Option, /// The timestamp when the vcard was created / last updated, vcard property `rev` pub timestamp: Result, } @@ -60,6 +62,9 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String { if let Some(profile_image) = &c.profile_image { res += &format!("PHOTO:data:image/jpeg;base64,{profile_image}\r\n"); } + if let Some(biography) = &c.biography { + res += &format!("NOTE:{biography}\r\n"); + } if let Some(timestamp) = format_timestamp(c) { res += &format!("REV:{timestamp}\r\n"); } @@ -186,6 +191,7 @@ pub fn parse_vcard(vcard: &str) -> Vec { let mut addr = None; let mut key = None; let mut photo = None; + let mut biography = None; let mut datetime = None; for mut line in lines.by_ref() { @@ -205,6 +211,8 @@ pub fn parse_vcard(vcard: &str) -> Vec { key.get_or_insert(k); } else if let Some(p) = base64_photo(line) { photo.get_or_insert(p); + } else if let Some((_params, bio)) = vcard_property(line, "note") { + biography.get_or_insert(bio); } else if let Some((_params, rev)) = vcard_property(line, "rev") { datetime.get_or_insert(rev); } else if line.eq_ignore_ascii_case("END:VCARD") { @@ -216,6 +224,7 @@ pub fn parse_vcard(vcard: &str) -> Vec { addr, key: key.map(|s| s.to_string()), profile_image: photo.map(|s| s.to_string()), + biography: biography.map(|b| b.to_owned()), timestamp: datetime .context("No timestamp in vcard") .and_then(parse_datetime), diff --git a/deltachat-contact-tools/src/vcard/vcard_tests.rs b/deltachat-contact-tools/src/vcard/vcard_tests.rs index cd2742b48..60d2b0bdb 100644 --- a/deltachat-contact-tools/src/vcard/vcard_tests.rs +++ b/deltachat-contact-tools/src/vcard/vcard_tests.rs @@ -91,6 +91,7 @@ fn test_make_and_parse_vcard() { authname: "Alice Wonderland".to_string(), key: Some("[base64-data]".to_string()), profile_image: Some("image in Base64".to_string()), + biography: Some("Hi, I'm Alice".to_string()), timestamp: Ok(1713465762), }, VcardContact { @@ -98,6 +99,7 @@ fn test_make_and_parse_vcard() { authname: "".to_string(), key: None, profile_image: None, + biography: None, timestamp: Ok(0), }, ]; @@ -108,6 +110,7 @@ fn test_make_and_parse_vcard() { FN:Alice Wonderland\r\n\ KEY:data:application/pgp-keys;base64,[base64-data]\r\n\ PHOTO:data:image/jpeg;base64,image in Base64\r\n\ + NOTE:Hi, I'm Alice\r\n\ REV:20240418T184242Z\r\n\ END:VCARD\r\n", "BEGIN:VCARD\r\n\ diff --git a/src/contact.rs b/src/contact.rs index 7f923348f..bb683bd8c 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -287,6 +287,7 @@ pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result Resu ); } } + if let Some(biography) = &contact.biography { + if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await { + warn!( + context, + "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr + ); + } + } Ok(id) } diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index 98724cc9d..2acf699f1 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -1054,6 +1054,8 @@ async fn test_make_n_import_vcard() -> Result<()> { let alice = &tcm.alice().await; let bob = &tcm.bob().await; bob.set_config(Config::Displayname, Some("Bob")).await?; + bob.set_config(Config::Selfstatus, Some("It's me, bob")) + .await?; let avatar_path = bob.dir.path().join("avatar.png"); let avatar_bytes = include_bytes!("../../test-data/image/avatar64x64.png"); let avatar_base64 = base64::engine::general_purpose::STANDARD.encode(avatar_bytes); @@ -1061,6 +1063,7 @@ async fn test_make_n_import_vcard() -> Result<()> { bob.set_config(Config::Selfavatar, Some(avatar_path.to_str().unwrap())) .await?; let bob_addr = bob.get_config(Config::Addr).await?.unwrap(); + let bob_biography = bob.get_config(Config::Selfstatus).await?.unwrap(); let chat = bob.create_chat(alice).await; let sent_msg = bob.send_text(chat.id, "moin").await; alice.recv_msg(&sent_msg).await; @@ -1086,12 +1089,14 @@ async fn test_make_n_import_vcard() -> Result<()> { assert_eq!(contacts[0].authname, "Bob".to_string()); assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64); assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64); + assert_eq!(*contacts[0].biography.as_ref().unwrap(), bob_biography); let timestamp = *contacts[0].timestamp.as_ref().unwrap(); assert!(t0 <= timestamp && timestamp <= t1); assert_eq!(contacts[1].addr, "fiona@example.net".to_string()); assert_eq!(contacts[1].authname, "".to_string()); assert_eq!(contacts[1].key, None); assert_eq!(contacts[1].profile_image, None); + assert_eq!(contacts[1].biography, None); let timestamp = *contacts[1].timestamp.as_ref().unwrap(); assert!(t0 <= timestamp && timestamp <= t1); @@ -1114,6 +1119,7 @@ async fn test_make_n_import_vcard() -> Result<()> { assert_eq!(contacts[0].authname, "Bob".to_string()); assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64); assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64); + assert_eq!(*contacts[0].biography.as_ref().unwrap(), bob_biography); assert!(contacts[0].timestamp.is_ok()); assert_eq!(contacts[1].addr, "fiona@example.net".to_string()); @@ -1145,6 +1151,7 @@ async fn test_make_n_import_vcard() -> Result<()> { assert_eq!(contacts[0].authname, "".to_string()); assert_eq!(contacts[0].key, None); assert_eq!(contacts[0].profile_image, None); + assert_eq!(contacts[0].biography, None); assert!(contacts[0].timestamp.is_ok()); Ok(())