diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 31dc25b51..d6ce461ac 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -4483,6 +4483,11 @@ int dc_msg_is_info (const dc_msg_t* msg); * UIs can display e.g. an icon based upon the type. * * Currently, the following types are defined: + * - DC_INFO_GROUP_NAME_CHANGED (2) - "Group name changd from OLD to BY by CONTACT" + * - DC_INFO_GROUP_IMAGE_CHANGED (3) - "Group image changd by CONTACT" + * - DC_INFO_MEMBER_ADDED_TO_GROUP (4) - "Member CONTACT added by OTHER_CONTACT" + * - DC_INFO_MEMBER_REMOVED_FROM_GROUP (5) - "Member CONTACT removed by OTHER_CONTACT" + * - DC_INFO_EPHEMERAL_TIMER_CHANGED (10) - "Disappearing messages CHANGED_TO by CONTACT" * - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected" * - DC_INFO_PROTECTION_DISABLED (12) - Info-message for "Chat is no longer protected" * - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet", @@ -4490,6 +4495,10 @@ int dc_msg_is_info (const dc_msg_t* msg); * and also offer a way to fix the encryption, eg. by a button offering a QR scan * - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info` * + * For the messages that refer to a CONTACT, + * dc_msg_get_info_contact_id() returns the contact ID. + * The UI should open the contact's profile when tapping the info message. + * * Even when you display an icon, * you should still display the text of the informational message using dc_msg_get_text() * @@ -4502,6 +4511,29 @@ int dc_msg_is_info (const dc_msg_t* msg); int dc_msg_get_info_type (const dc_msg_t* msg); +/** + * Return the contact ID of the profile to open when tapping the info message. + * + * - For DC_INFO_MEMBER_ADDED_TO_GROUP and DC_INFO_MEMBER_REMOVED_FROM_GROUP, + * this is the contact being added/removed. + * The contact that did the adding/removal is usually only a tap away + * (as introducer and/or atop of the memberlist), + * and usually more known anyways. + * - For DC_INFO_GROUP_NAME_CHANGED, DC_INFO_GROUP_IMAGE_CHANGED and DC_INFO_EPHEMERAL_TIMER_CHANGED + * this is the contact who did the change. + * + * No need to check additionally for dc_msg_get_info_type(), + * unless you e.g. want to show the info message in another style. + * + * @memberof dc_msg_t + * @param msg The message object. + * @return If the info message refers to a contact, + * this contact ID or DC_CONTACT_ID_SELF is returned. + * Otherwise 0. + */ +uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg); + + // DC_INFO* uses the same values as SystemMessage in rust-land #define DC_INFO_UNKNOWN 0 #define DC_INFO_GROUP_NAME_CHANGED 2 diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 8fedbc062..622443b45 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -3730,6 +3730,20 @@ pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int ffi_msg.message.get_info_type() as libc::c_int } +#[no_mangle] +pub unsafe extern "C" fn dc_msg_get_info_contact_id(msg: *mut dc_msg_t) -> u32 { + if msg.is_null() { + eprintln!("ignoring careless call to dc_msg_get_info_contact_id()"); + return 0; + } + let ffi_msg = &*msg; + let context = &*ffi_msg.context; + block_on(ffi_msg.message.get_info_contact_id(context)) + .unwrap_or_default() + .map(|id| id.to_u32()) + .unwrap_or_default() +} + #[no_mangle] pub unsafe extern "C" fn dc_msg_get_webxdc_href(msg: *mut dc_msg_t) -> *mut libc::c_char { if msg.is_null() { diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index 909ca5349..03ade4d04 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -89,7 +89,7 @@ def test_qr_securejoin(acfactory, protect): assert alice_contact_bob_snapshot.is_verified snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot() - assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr")) + assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr")) assert snapshot.chat.get_basic_snapshot().is_protected == protect # Test that Bob verified Alice's profile. @@ -563,7 +563,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None: # ac1 waits for member added message and creates a QR code. snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot() - assert snapshot.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr")) + assert snapshot.text == "Member Me added by {}.".format(ac3.get_config("addr")) ac1_qr_code = snapshot.chat.get_qr_code() # ac2 verifies ac1 @@ -646,7 +646,7 @@ def test_withdraw_securejoin_qr(acfactory): alice.clear_all_events() snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot() - assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr")) + assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr")) assert snapshot.chat.get_basic_snapshot().is_protected bob_chat.leave() diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 9238a8ad9..7b3d74ead 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -1346,7 +1346,7 @@ def test_qr_email_capitalization(acfactory, lp): lp.sec("ac1 joins a verified group via a QR code") ac1_chat = ac1.qr_join_chat(qr) msg = ac1._evtracker.wait_next_incoming_message() - assert msg.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr")) + assert msg.text == "Member Me added by {}.".format(ac3.get_config("addr")) assert len(ac1_chat.get_contacts()) == 2 lp.sec("ac2 joins a verified group via a QR code") diff --git a/src/chat.rs b/src/chat.rs index 28c9785a1..cfa7e01c6 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -582,7 +582,18 @@ impl ChatId { ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled, ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled, }; - add_info_msg_with_cmd(context, self, &text, cmd, timestamp_sort, None, None, None).await?; + add_info_msg_with_cmd( + context, + self, + &text, + cmd, + timestamp_sort, + None, + None, + None, + None, + ) + .await?; Ok(()) } @@ -1791,6 +1802,7 @@ impl Chat { Some(now), None, None, + None, ) .await?; context.emit_event(EventType::ChatModified(self.id)); @@ -3942,6 +3954,8 @@ pub(crate) async fn add_contact_to_chat_ex( msg.param.set_cmd(SystemMessage::MemberAddedToGroup); msg.param.set(Param::Arg, contact_addr); msg.param.set_int(Param::Arg2, from_handshake.into()); + msg.param + .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32); send_msg(context, chat_id, &mut msg).await?; sync = Nosync; @@ -4139,6 +4153,8 @@ pub async fn remove_contact_from_chat( } msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup); msg.param.set(Param::Arg, contact.get_addr().to_lowercase()); + msg.param + .set(Param::ContactAddedRemoved, contact.id.to_u32() as i32); let res = send_msg(context, chat_id, &mut msg).await; if contact_id == ContactId::SELF { res?; @@ -4737,13 +4753,17 @@ pub(crate) async fn add_info_msg_with_cmd( timestamp_sent_rcvd: Option, parent: Option<&Message>, from_id: Option, + added_removed_id: Option, ) -> Result { let rfc724_mid = create_outgoing_rfc724_mid(); let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?; let mut param = Params::new(); if cmd != SystemMessage::Unknown { - param.set_cmd(cmd) + param.set_cmd(cmd); + } + if let Some(contact_id) = added_removed_id { + param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string()); } let row_id = @@ -4791,6 +4811,7 @@ pub(crate) async fn add_info_msg( None, None, None, + None, ) .await } diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index ed316b2dd..aa1dad892 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -1,6 +1,7 @@ use super::*; use crate::chatlist::get_archived_cnt; use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS}; +use crate::ephemeral::Timer; use crate::headerdef::HeaderDef; use crate::imex::{has_backup, imex, ImexMode}; use crate::message::{delete_msgs, MessengerMessage}; @@ -331,11 +332,12 @@ async fn test_member_add_remove() -> Result<()> { // Alice adds Bob to the chat. add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?; let sent = alice.pop_sent_msg().await; + // Locally set name "robert" should not leak. assert!(!sent.payload.contains("robert")); assert_eq!( sent.load_from_db().await.get_text(), - "You added member robert (bob@example.net)." + "You added member robert." ); // Alice removes Bob from the chat. @@ -344,7 +346,7 @@ async fn test_member_add_remove() -> Result<()> { assert!(!sent.payload.contains("robert")); assert_eq!( sent.load_from_db().await.get_text(), - "You removed member robert (bob@example.net)." + "You removed member robert." ); // Alice leaves the chat. @@ -412,7 +414,7 @@ async fn test_parallel_member_remove() -> Result<()> { // Test that remove message is rewritten. assert_eq!( bob_received_remove_msg.get_text(), - "Member Me (bob@example.net) removed by alice@example.org." + "Member Me removed by alice@example.org." ); Ok(()) @@ -521,6 +523,14 @@ async fn test_modify_chat_multi_device() -> Result<()> { assert!(a2_msg.is_system_message()); assert_eq!(a1_msg.get_info_type(), SystemMessage::GroupNameChanged); assert_eq!(a2_msg.get_info_type(), SystemMessage::GroupNameChanged); + assert_eq!( + a1_msg.get_info_contact_id(&a1).await?, + Some(ContactId::SELF) + ); + assert_eq!( + a2_msg.get_info_contact_id(&a2).await?, + Some(ContactId::SELF) + ); assert_eq!(Chat::load_from_db(&a1, a1_chat_id).await?.name, "bar"); assert_eq!(Chat::load_from_db(&a2, a2_chat_id).await?.name, "bar"); @@ -1574,6 +1584,7 @@ async fn test_add_info_msg_with_cmd() -> Result<()> { None, None, None, + None, ) .await?; @@ -3332,6 +3343,128 @@ async fn test_do_not_overwrite_draft() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_info_contact_id() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let alice2 = &tcm.alice().await; + let bob = &tcm.bob().await; + + async fn pop_recv_and_check( + alice: &TestContext, + alice2: &TestContext, + bob: &TestContext, + expected_type: SystemMessage, + expected_alice_id: ContactId, + expected_bob_id: ContactId, + ) -> Result<()> { + let sent_msg = alice.pop_sent_msg().await; + let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?; + assert_eq!(msg.get_info_type(), expected_type); + assert_eq!( + msg.get_info_contact_id(alice).await?, + Some(expected_alice_id) + ); + + let msg = alice2.recv_msg(&sent_msg).await; + assert_eq!(msg.get_info_type(), expected_type); + assert_eq!( + msg.get_info_contact_id(alice2).await?, + Some(expected_alice_id) + ); + + let msg = bob.recv_msg(&sent_msg).await; + assert_eq!(msg.get_info_type(), expected_type); + assert_eq!(msg.get_info_contact_id(bob).await?, Some(expected_bob_id)); + + Ok(()) + } + + // Alice creates group, Bob receives group + let alice_chat_id = alice + .create_group_with_members(ProtectionStatus::Unprotected, "play", &[bob]) + .await; + let sent_msg1 = alice.send_text(alice_chat_id, "moin").await; + + let msg = bob.recv_msg(&sent_msg1).await; + let bob_alice_id = msg.from_id; + assert!(!bob_alice_id.is_special()); + + // Alice does group changes, Bob receives them + set_chat_name(alice, alice_chat_id, "games").await?; + pop_recv_and_check( + alice, + alice2, + bob, + SystemMessage::GroupNameChanged, + ContactId::SELF, + bob_alice_id, + ) + .await?; + + let file = alice.get_blobdir().join("avatar.png"); + let bytes = include_bytes!("../../test-data/image/avatar64x64.png"); + tokio::fs::write(&file, bytes).await?; + set_chat_profile_image(alice, alice_chat_id, file.to_str().unwrap()).await?; + pop_recv_and_check( + alice, + alice2, + bob, + SystemMessage::GroupImageChanged, + ContactId::SELF, + bob_alice_id, + ) + .await?; + + alice_chat_id + .set_ephemeral_timer(alice, Timer::Enabled { duration: 60 }) + .await?; + pop_recv_and_check( + alice, + alice2, + bob, + SystemMessage::EphemeralTimerChanged, + ContactId::SELF, + bob_alice_id, + ) + .await?; + + let fiona_id = alice.add_or_lookup_contact_id(&tcm.fiona().await).await; // contexts are in sync, fiona_id is same everywhere + add_contact_to_chat(alice, alice_chat_id, fiona_id).await?; + pop_recv_and_check( + alice, + alice2, + bob, + SystemMessage::MemberAddedToGroup, + fiona_id, + fiona_id, + ) + .await?; + + remove_contact_from_chat(alice, alice_chat_id, fiona_id).await?; + pop_recv_and_check( + alice, + alice2, + bob, + SystemMessage::MemberRemovedFromGroup, + fiona_id, + fiona_id, + ) + .await?; + + // When fiona_id is deleted, get_info_contact_id() returns None. + // We raw delete in db as Contact::delete() leaves a tombstone (which is great as the tap works longer then) + alice + .sql + .execute("DELETE FROM contacts WHERE id=?", (fiona_id,)) + .await?; + let msg = alice.get_last_msg().await; + assert_eq!(msg.get_info_type(), SystemMessage::MemberRemovedFromGroup); + assert!(msg.get_info_contact_id(alice).await?.is_none()); + + Ok(()) +} + /// Test group consistency. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_add_member_bug() -> Result<()> { diff --git a/src/contact.rs b/src/contact.rs index 7fa9771d0..08174f9e0 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1406,16 +1406,13 @@ impl Contact { &self.addr } - /// Get a summary of authorized name and address. - /// - /// The returned string is either "Name (email@domain.com)" or just - /// "email@domain.com" if the name is unset. + /// Get authorized name or address. /// /// This string is suitable for sending over email /// as it does not leak the locally set name. - pub fn get_authname_n_addr(&self) -> String { + pub(crate) fn get_authname_or_addr(&self) -> String { if !self.authname.is_empty() { - format!("{} ({})", self.authname, self.addr) + (&self.authname).into() } else { (&self.addr).into() } diff --git a/src/message.rs b/src/message.rs index 91f18e644..67601d618 100644 --- a/src/message.rs +++ b/src/message.rs @@ -936,6 +936,51 @@ impl Message { self.param.get_cmd() } + /// Return the contact ID of the profile to open when tapping the info message. + pub async fn get_info_contact_id(&self, context: &Context) -> Result> { + match self.param.get_cmd() { + SystemMessage::GroupNameChanged + | SystemMessage::GroupImageChanged + | SystemMessage::EphemeralTimerChanged => { + if self.from_id != ContactId::INFO { + Ok(Some(self.from_id)) + } else { + Ok(None) + } + } + + SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => { + if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) { + let contact_id = ContactId::new(contact_i32.try_into()?); + if contact_id == ContactId::SELF + || Contact::real_exists_by_id(context, contact_id).await? + { + Ok(Some(contact_id)) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + + SystemMessage::AutocryptSetupMessage + | SystemMessage::SecurejoinMessage + | SystemMessage::LocationStreamingEnabled + | SystemMessage::LocationOnly + | SystemMessage::ChatProtectionEnabled + | SystemMessage::ChatProtectionDisabled + | SystemMessage::InvalidUnencryptedMail + | SystemMessage::SecurejoinWait + | SystemMessage::SecurejoinWaitTimeout + | SystemMessage::MultiDeviceSync + | SystemMessage::WebxdcStatusUpdate + | SystemMessage::WebxdcInfoMessage + | SystemMessage::IrohNodeAddr + | SystemMessage::Unknown => Ok(None), + } + } + /// Returns true if the message is a system message. pub fn is_system_message(&self) -> bool { let cmd = self.param.get_cmd(); diff --git a/src/param.rs b/src/param.rs index 31d799803..9500778a2 100644 --- a/src/param.rs +++ b/src/param.rs @@ -215,6 +215,9 @@ pub enum Param { /// For messages: Message text was edited. IsEdited = b'L', + + /// For info messages: Contact ID in added or removed to a group. + ContactAddedRemoved = b'5', } /// An object for handling key=value parameter lists. diff --git a/src/peerstate.rs b/src/peerstate.rs index 6de7fe8d9..d0074e5fe 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -748,6 +748,7 @@ impl Peerstate { Some(timestamp), None, None, + None, ) .await?; } diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 6d2e1f480..55db0d9c5 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1451,13 +1451,13 @@ async fn add_parts( None => better_msg = Some(m), Some(_) => { if !m.is_empty() { - group_changes.extra_msgs.push((m, is_system_message)) + group_changes.extra_msgs.push((m, is_system_message, None)) } } } } - for (group_changes_msg, cmd) in group_changes.extra_msgs { + for (group_changes_msg, cmd, added_removed_id) in group_changes.extra_msgs { chat::add_info_msg_with_cmd( context, chat_id, @@ -1467,6 +1467,7 @@ async fn add_parts( None, None, None, + added_removed_id, ) .await?; } @@ -1550,6 +1551,10 @@ async fn add_parts( let part_is_empty = typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none(); + if let Some(contact_id) = group_changes.added_removed_id { + param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string()); + } + save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden; let save_mime_modified = save_mime_modified && parts.peek().is_none(); @@ -2334,10 +2339,12 @@ struct GroupChangesInfo { /// Optional: A better message that should replace the original system message. /// If this is an empty string, the original system message should be trashed. better_msg: Option, + /// Added/removed contact `better_msg` refers to. + added_removed_id: Option, /// If true, the user should not be notified about the group change. silent: bool, /// A list of additional group changes messages that should be shown in the chat. - extra_msgs: Vec<(String, SystemMessage)>, + extra_msgs: Vec<(String, SystemMessage, Option)>, } /// Apply group member list, name, avatar and protection status changes from the MIME message. @@ -2654,6 +2661,11 @@ async fn apply_group_changes( } Ok(GroupChangesInfo { better_msg, + added_removed_id: if added_id.is_some() { + added_id + } else { + removed_id + }, silent, extra_msgs: group_changes_msgs, }) @@ -2665,7 +2677,7 @@ async fn group_changes_msgs( added_ids: &HashSet, removed_ids: &HashSet, chat_id: ChatId, -) -> Result> { +) -> Result)>> { let mut group_changes_msgs = Vec::new(); if !added_ids.is_empty() { warn!( @@ -2686,6 +2698,7 @@ async fn group_changes_msgs( stock_str::msg_add_member_local(context, contact.get_addr(), ContactId::UNDEFINED) .await, SystemMessage::MemberAddedToGroup, + Some(contact.id), )); } for contact_id in removed_ids { @@ -2694,6 +2707,7 @@ async fn group_changes_msgs( stock_str::msg_del_member_local(context, contact.get_addr(), ContactId::UNDEFINED) .await, SystemMessage::MemberRemovedFromGroup, + Some(contact.id), )); } diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs index 7e3dd46f1..60177808b 100644 --- a/src/securejoin/bob.rs +++ b/src/securejoin/bob.rs @@ -126,6 +126,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul Some(ts_start), None, None, + None, ) .await?; chat_id.spawn_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT); diff --git a/src/smtp.rs b/src/smtp.rs index 6fe94479f..89afc00b5 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -439,6 +439,7 @@ pub(crate) async fn send_msg_to_smtp( None, None, None, + None, ) .await?; }; diff --git a/src/stock_str.rs b/src/stock_str.rs index 98828aa49..fceef6131 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -537,14 +537,6 @@ trait StockStringMods: AsRef + Sized { } impl ContactId { - /// Get contact name and address for stock string, e.g. `Bob (bob@example.net)` - async fn get_stock_name_n_addr(self, context: &Context) -> String { - Contact::get_by_id(context, self) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_else(|_| self.to_string()) - } - /// Get contact name, e.g. `Bob`, or `bob@example.net` if no name is set. async fn get_stock_name(self, context: &Context) -> String { Contact::get_by_id(context, self) @@ -613,7 +605,7 @@ pub(crate) async fn msg_grp_name( .await .replace1(from_group) .replace2(to_group) - .replace3(&by_contact.get_stock_name_n_addr(context).await) + .replace3(&by_contact.get_stock_name(context).await) } } @@ -623,7 +615,7 @@ pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId } else { translated(context, StockMessage::MsgGrpImgChangedBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -637,7 +629,7 @@ pub(crate) async fn msg_add_member_remote(context: &Context, added_member_addr: let whom = &match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) .await - .map(|contact| contact.get_authname_n_addr()) + .map(|contact| contact.get_authname_or_addr()) .unwrap_or_else(|_| addr.to_string()), _ => addr.to_string(), }; @@ -659,7 +651,7 @@ pub(crate) async fn msg_add_member_local( let whom = &match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) .await - .map(|contact| contact.get_name_n_addr()) + .map(|contact| contact.get_display_name().to_string()) .unwrap_or_else(|_| addr.to_string()), _ => addr.to_string(), }; @@ -675,7 +667,7 @@ pub(crate) async fn msg_add_member_local( translated(context, StockMessage::MsgAddMemberBy) .await .replace1(whom) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -688,7 +680,7 @@ pub(crate) async fn msg_del_member_remote(context: &Context, removed_member_addr let whom = &match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) .await - .map(|contact| contact.get_authname_n_addr()) + .map(|contact| contact.get_authname_or_addr()) .unwrap_or_else(|_| addr.to_string()), _ => addr.to_string(), }; @@ -710,7 +702,7 @@ pub(crate) async fn msg_del_member_local( let whom = &match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) .await - .map(|contact| contact.get_name_n_addr()) + .map(|contact| contact.get_display_name().to_string()) .unwrap_or_else(|_| addr.to_string()), _ => addr.to_string(), }; @@ -726,7 +718,7 @@ pub(crate) async fn msg_del_member_local( translated(context, StockMessage::MsgDelMemberBy) .await .replace1(whom) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -742,7 +734,7 @@ pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactI } else { translated(context, StockMessage::MsgGroupLeftBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -804,7 +796,7 @@ pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ContactId } else { translated(context, StockMessage::MsgGrpImgDeletedBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -821,7 +813,7 @@ pub(crate) async fn secure_join_started( if let Ok(contact) = Contact::get_by_id(context, inviter_contact_id).await { translated(context, StockMessage::SecureJoinStarted) .await - .replace1(&contact.get_name_n_addr()) + .replace1(contact.get_display_name()) .replace2(contact.get_display_name()) } else { format!("secure_join_started: unknown contact {inviter_contact_id}") @@ -871,7 +863,7 @@ pub(crate) async fn secure_join_group_qr_description(context: &Context, chat: &C /// Stock string: `%1$s verified.`. #[allow(dead_code)] pub(crate) async fn contact_verified(context: &Context, contact: &Contact) -> String { - let addr = &contact.get_name_n_addr(); + let addr = contact.get_display_name(); translated(context, StockMessage::ContactVerified) .await .replace1(addr) @@ -879,7 +871,7 @@ pub(crate) async fn contact_verified(context: &Context, contact: &Contact) -> St /// Stock string: `Cannot establish guaranteed end-to-end encryption with %1$s`. pub(crate) async fn contact_not_verified(context: &Context, contact: &Contact) -> String { - let addr = &contact.get_name_n_addr(); + let addr = contact.get_display_name(); translated(context, StockMessage::ContactNotVerified) .await .replace1(addr) @@ -936,7 +928,7 @@ pub(crate) async fn msg_location_enabled_by(context: &Context, contact: ContactI } else { translated(context, StockMessage::MsgLocationEnabledBy) .await - .replace1(&contact.get_stock_name_n_addr(context).await) + .replace1(&contact.get_stock_name(context).await) } } @@ -998,7 +990,7 @@ pub(crate) async fn msg_ephemeral_timer_disabled( } else { translated(context, StockMessage::MsgEphemeralTimerDisabledBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -1016,7 +1008,7 @@ pub(crate) async fn msg_ephemeral_timer_enabled( translated(context, StockMessage::MsgEphemeralTimerEnabledBy) .await .replace1(timer) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -1027,7 +1019,7 @@ pub(crate) async fn msg_ephemeral_timer_minute(context: &Context, by_contact: Co } else { translated(context, StockMessage::MsgEphemeralTimerMinuteBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -1038,7 +1030,7 @@ pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: Cont } else { translated(context, StockMessage::MsgEphemeralTimerHourBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -1049,7 +1041,7 @@ pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: Conta } else { translated(context, StockMessage::MsgEphemeralTimerDayBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -1060,7 +1052,7 @@ pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: Cont } else { translated(context, StockMessage::MsgEphemeralTimerWeekBy) .await - .replace1(&by_contact.get_stock_name_n_addr(context).await) + .replace1(&by_contact.get_stock_name(context).await) } } @@ -1142,7 +1134,7 @@ pub(crate) async fn msg_ephemeral_timer_minutes( translated(context, StockMessage::MsgEphemeralTimerMinutesBy) .await .replace1(minutes) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -1160,7 +1152,7 @@ pub(crate) async fn msg_ephemeral_timer_hours( translated(context, StockMessage::MsgEphemeralTimerHoursBy) .await .replace1(hours) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -1178,7 +1170,7 @@ pub(crate) async fn msg_ephemeral_timer_days( translated(context, StockMessage::MsgEphemeralTimerDaysBy) .await .replace1(days) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } @@ -1196,7 +1188,7 @@ pub(crate) async fn msg_ephemeral_timer_weeks( translated(context, StockMessage::MsgEphemeralTimerWeeksBy) .await .replace1(weeks) - .replace2(&by_contact.get_stock_name_n_addr(context).await) + .replace2(&by_contact.get_stock_name(context).await) } } diff --git a/src/stock_str/stock_str_tests.rs b/src/stock_str/stock_str_tests.rs index f63d4673d..9b3fa018c 100644 --- a/src/stock_str/stock_str_tests.rs +++ b/src/stock_str/stock_str_tests.rs @@ -54,10 +54,7 @@ async fn test_stock_string_repl_str() { .unwrap(); let contact = Contact::get_by_id(&t.ctx, contact_id).await.unwrap(); // uses %1$s substitution - assert_eq!( - contact_verified(&t, &contact).await, - "Someone (someone@example.org) verified." - ); + assert_eq!(contact_verified(&t, &contact).await, "Someone verified."); // We have no string using %1$d to test... } @@ -95,7 +92,7 @@ async fn test_stock_system_msg_add_member_by_me_with_displayname() { ); assert_eq!( msg_add_member_local(&t, "alice@example.org", ContactId::SELF).await, - "You added member Alice (alice@example.org)." + "You added member Alice." ); } @@ -112,7 +109,7 @@ async fn test_stock_system_msg_add_member_by_other_with_displayname() { }; assert_eq!( msg_add_member_local(&t, "alice@example.org", contact_id,).await, - "Member Alice (alice@example.org) added by Bob (bob@example.com)." + "Member Alice added by Bob." ); } diff --git a/src/sync.rs b/src/sync.rs index 0687f1778..dfcb2542e 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -743,10 +743,7 @@ mod tests { let fiona = &tcm.fiona().await; tcm.exec_securejoin_qr(fiona, alice2, &qr).await; let msg = fiona.get_last_msg().await; - assert_eq!( - msg.text, - "Member Me (fiona@example.net) added by alice@example.org." - ); + assert_eq!(msg.text, "Member Me added by alice@example.org."); Ok(()) } } diff --git a/src/webxdc.rs b/src/webxdc.rs index a4ea27116..50f6ccba0 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -388,6 +388,7 @@ impl Context { None, Some(&instance), Some(from_id), + None, ) .await?; } diff --git a/test-data/golden/two_group_securejoins b/test-data/golden/two_group_securejoins index 5f66198ef..f93d0bbe6 100644 --- a/test-data/golden/two_group_securejoins +++ b/test-data/golden/two_group_securejoins @@ -5,5 +5,5 @@ Msg#11: info (Contact#Contact#Info): alice@example.org invited you to join this Waiting for the device of alice@example.org to reply… [NOTICED][INFO] Msg#13: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO] Msg#17: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️] -Msg#18🔒: (Contact#Contact#10): Member Me (fiona@example.net) added by alice@example.org. [FRESH][INFO] +Msg#18🔒: (Contact#Contact#10): Member Me added by alice@example.org. [FRESH][INFO] --------------------------------------------------------------------------------