Skip to content

Commit 97b0d09

Browse files
r10slink2xtHocuri
authored
feat: get contact-id for info messages (#6714)
instead of showing addresses in info message, provide an API to get the contact-id. UI can then make the info message tappable and open the contact profile in scope the corresponding iOS PR - incl. **screencast** - is at deltachat/deltachat-ios#2652 ; jsonrpc can come in a subsequent PR when things are settled on android/ios the number of parameters in `add_info_msg_with_cmd` gets bigger and bigger, however, i did not want to refactor this in this PR. it is also not really adding complexity closes #6702 --------- Co-authored-by: link2xt <link2xt@testrun.org> Co-authored-by: Hocuri <hocuri@gmx.de>
1 parent e2f9c80 commit 97b0d09

File tree

18 files changed

+311
-62
lines changed

18 files changed

+311
-62
lines changed

deltachat-ffi/deltachat.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4483,13 +4483,22 @@ int dc_msg_is_info (const dc_msg_t* msg);
44834483
* UIs can display e.g. an icon based upon the type.
44844484
*
44854485
* Currently, the following types are defined:
4486+
* - DC_INFO_GROUP_NAME_CHANGED (2) - "Group name changd from OLD to BY by CONTACT"
4487+
* - DC_INFO_GROUP_IMAGE_CHANGED (3) - "Group image changd by CONTACT"
4488+
* - DC_INFO_MEMBER_ADDED_TO_GROUP (4) - "Member CONTACT added by OTHER_CONTACT"
4489+
* - DC_INFO_MEMBER_REMOVED_FROM_GROUP (5) - "Member CONTACT removed by OTHER_CONTACT"
4490+
* - DC_INFO_EPHEMERAL_TIMER_CHANGED (10) - "Disappearing messages CHANGED_TO by CONTACT"
44864491
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected"
44874492
* - DC_INFO_PROTECTION_DISABLED (12) - Info-message for "Chat is no longer protected"
44884493
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
44894494
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
44904495
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
44914496
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
44924497
*
4498+
* For the messages that refer to a CONTACT,
4499+
* dc_msg_get_info_contact_id() returns the contact ID.
4500+
* The UI should open the contact's profile when tapping the info message.
4501+
*
44934502
* Even when you display an icon,
44944503
* you should still display the text of the informational message using dc_msg_get_text()
44954504
*
@@ -4502,6 +4511,29 @@ int dc_msg_is_info (const dc_msg_t* msg);
45024511
int dc_msg_get_info_type (const dc_msg_t* msg);
45034512

45044513

4514+
/**
4515+
* Return the contact ID of the profile to open when tapping the info message.
4516+
*
4517+
* - For DC_INFO_MEMBER_ADDED_TO_GROUP and DC_INFO_MEMBER_REMOVED_FROM_GROUP,
4518+
* this is the contact being added/removed.
4519+
* The contact that did the adding/removal is usually only a tap away
4520+
* (as introducer and/or atop of the memberlist),
4521+
* and usually more known anyways.
4522+
* - For DC_INFO_GROUP_NAME_CHANGED, DC_INFO_GROUP_IMAGE_CHANGED and DC_INFO_EPHEMERAL_TIMER_CHANGED
4523+
* this is the contact who did the change.
4524+
*
4525+
* No need to check additionally for dc_msg_get_info_type(),
4526+
* unless you e.g. want to show the info message in another style.
4527+
*
4528+
* @memberof dc_msg_t
4529+
* @param msg The message object.
4530+
* @return If the info message refers to a contact,
4531+
* this contact ID or DC_CONTACT_ID_SELF is returned.
4532+
* Otherwise 0.
4533+
*/
4534+
uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg);
4535+
4536+
45054537
// DC_INFO* uses the same values as SystemMessage in rust-land
45064538
#define DC_INFO_UNKNOWN 0
45074539
#define DC_INFO_GROUP_NAME_CHANGED 2

deltachat-ffi/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3730,6 +3730,20 @@ pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int
37303730
ffi_msg.message.get_info_type() as libc::c_int
37313731
}
37323732

3733+
#[no_mangle]
3734+
pub unsafe extern "C" fn dc_msg_get_info_contact_id(msg: *mut dc_msg_t) -> u32 {
3735+
if msg.is_null() {
3736+
eprintln!("ignoring careless call to dc_msg_get_info_contact_id()");
3737+
return 0;
3738+
}
3739+
let ffi_msg = &*msg;
3740+
let context = &*ffi_msg.context;
3741+
block_on(ffi_msg.message.get_info_contact_id(context))
3742+
.unwrap_or_default()
3743+
.map(|id| id.to_u32())
3744+
.unwrap_or_default()
3745+
}
3746+
37333747
#[no_mangle]
37343748
pub unsafe extern "C" fn dc_msg_get_webxdc_href(msg: *mut dc_msg_t) -> *mut libc::c_char {
37353749
if msg.is_null() {

deltachat-rpc-client/tests/test_securejoin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def test_qr_securejoin(acfactory, protect):
8989
assert alice_contact_bob_snapshot.is_verified
9090

9191
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
92-
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
92+
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
9393
assert snapshot.chat.get_basic_snapshot().is_protected == protect
9494

9595
# Test that Bob verified Alice's profile.
@@ -563,7 +563,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
563563

564564
# ac1 waits for member added message and creates a QR code.
565565
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
566-
assert snapshot.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr"))
566+
assert snapshot.text == "Member Me added by {}.".format(ac3.get_config("addr"))
567567
ac1_qr_code = snapshot.chat.get_qr_code()
568568

569569
# ac2 verifies ac1
@@ -646,7 +646,7 @@ def test_withdraw_securejoin_qr(acfactory):
646646
alice.clear_all_events()
647647

648648
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
649-
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
649+
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
650650
assert snapshot.chat.get_basic_snapshot().is_protected
651651
bob_chat.leave()
652652

python/tests/test_1_online.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,7 @@ def test_qr_email_capitalization(acfactory, lp):
13461346
lp.sec("ac1 joins a verified group via a QR code")
13471347
ac1_chat = ac1.qr_join_chat(qr)
13481348
msg = ac1._evtracker.wait_next_incoming_message()
1349-
assert msg.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr"))
1349+
assert msg.text == "Member Me added by {}.".format(ac3.get_config("addr"))
13501350
assert len(ac1_chat.get_contacts()) == 2
13511351

13521352
lp.sec("ac2 joins a verified group via a QR code")

src/chat.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,18 @@ impl ChatId {
582582
ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
583583
ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled,
584584
};
585-
add_info_msg_with_cmd(context, self, &text, cmd, timestamp_sort, None, None, None).await?;
585+
add_info_msg_with_cmd(
586+
context,
587+
self,
588+
&text,
589+
cmd,
590+
timestamp_sort,
591+
None,
592+
None,
593+
None,
594+
None,
595+
)
596+
.await?;
586597

587598
Ok(())
588599
}
@@ -1791,6 +1802,7 @@ impl Chat {
17911802
Some(now),
17921803
None,
17931804
None,
1805+
None,
17941806
)
17951807
.await?;
17961808
context.emit_event(EventType::ChatModified(self.id));
@@ -3942,6 +3954,8 @@ pub(crate) async fn add_contact_to_chat_ex(
39423954
msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
39433955
msg.param.set(Param::Arg, contact_addr);
39443956
msg.param.set_int(Param::Arg2, from_handshake.into());
3957+
msg.param
3958+
.set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
39453959
send_msg(context, chat_id, &mut msg).await?;
39463960

39473961
sync = Nosync;
@@ -4139,6 +4153,8 @@ pub async fn remove_contact_from_chat(
41394153
}
41404154
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
41414155
msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4156+
msg.param
4157+
.set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
41424158
let res = send_msg(context, chat_id, &mut msg).await;
41434159
if contact_id == ContactId::SELF {
41444160
res?;
@@ -4737,13 +4753,17 @@ pub(crate) async fn add_info_msg_with_cmd(
47374753
timestamp_sent_rcvd: Option<i64>,
47384754
parent: Option<&Message>,
47394755
from_id: Option<ContactId>,
4756+
added_removed_id: Option<ContactId>,
47404757
) -> Result<MsgId> {
47414758
let rfc724_mid = create_outgoing_rfc724_mid();
47424759
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
47434760

47444761
let mut param = Params::new();
47454762
if cmd != SystemMessage::Unknown {
4746-
param.set_cmd(cmd)
4763+
param.set_cmd(cmd);
4764+
}
4765+
if let Some(contact_id) = added_removed_id {
4766+
param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
47474767
}
47484768

47494769
let row_id =
@@ -4791,6 +4811,7 @@ pub(crate) async fn add_info_msg(
47914811
None,
47924812
None,
47934813
None,
4814+
None,
47944815
)
47954816
.await
47964817
}

src/chat/chat_tests.rs

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::*;
22
use crate::chatlist::get_archived_cnt;
33
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
4+
use crate::ephemeral::Timer;
45
use crate::headerdef::HeaderDef;
56
use crate::imex::{has_backup, imex, ImexMode};
67
use crate::message::{delete_msgs, MessengerMessage};
@@ -331,11 +332,12 @@ async fn test_member_add_remove() -> Result<()> {
331332
// Alice adds Bob to the chat.
332333
add_contact_to_chat(&alice, alice_chat_id, alice_bob_contact_id).await?;
333334
let sent = alice.pop_sent_msg().await;
335+
334336
// Locally set name "robert" should not leak.
335337
assert!(!sent.payload.contains("robert"));
336338
assert_eq!(
337339
sent.load_from_db().await.get_text(),
338-
"You added member robert (bob@example.net)."
340+
"You added member robert."
339341
);
340342

341343
// Alice removes Bob from the chat.
@@ -344,7 +346,7 @@ async fn test_member_add_remove() -> Result<()> {
344346
assert!(!sent.payload.contains("robert"));
345347
assert_eq!(
346348
sent.load_from_db().await.get_text(),
347-
"You removed member robert (bob@example.net)."
349+
"You removed member robert."
348350
);
349351

350352
// Alice leaves the chat.
@@ -412,7 +414,7 @@ async fn test_parallel_member_remove() -> Result<()> {
412414
// Test that remove message is rewritten.
413415
assert_eq!(
414416
bob_received_remove_msg.get_text(),
415-
"Member Me (bob@example.net) removed by alice@example.org."
417+
"Member Me removed by alice@example.org."
416418
);
417419

418420
Ok(())
@@ -521,6 +523,14 @@ async fn test_modify_chat_multi_device() -> Result<()> {
521523
assert!(a2_msg.is_system_message());
522524
assert_eq!(a1_msg.get_info_type(), SystemMessage::GroupNameChanged);
523525
assert_eq!(a2_msg.get_info_type(), SystemMessage::GroupNameChanged);
526+
assert_eq!(
527+
a1_msg.get_info_contact_id(&a1).await?,
528+
Some(ContactId::SELF)
529+
);
530+
assert_eq!(
531+
a2_msg.get_info_contact_id(&a2).await?,
532+
Some(ContactId::SELF)
533+
);
524534
assert_eq!(Chat::load_from_db(&a1, a1_chat_id).await?.name, "bar");
525535
assert_eq!(Chat::load_from_db(&a2, a2_chat_id).await?.name, "bar");
526536

@@ -1574,6 +1584,7 @@ async fn test_add_info_msg_with_cmd() -> Result<()> {
15741584
None,
15751585
None,
15761586
None,
1587+
None,
15771588
)
15781589
.await?;
15791590

@@ -3332,6 +3343,128 @@ async fn test_do_not_overwrite_draft() -> Result<()> {
33323343
Ok(())
33333344
}
33343345

3346+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3347+
async fn test_info_contact_id() -> Result<()> {
3348+
let mut tcm = TestContextManager::new();
3349+
let alice = &tcm.alice().await;
3350+
let alice2 = &tcm.alice().await;
3351+
let bob = &tcm.bob().await;
3352+
3353+
async fn pop_recv_and_check(
3354+
alice: &TestContext,
3355+
alice2: &TestContext,
3356+
bob: &TestContext,
3357+
expected_type: SystemMessage,
3358+
expected_alice_id: ContactId,
3359+
expected_bob_id: ContactId,
3360+
) -> Result<()> {
3361+
let sent_msg = alice.pop_sent_msg().await;
3362+
let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?;
3363+
assert_eq!(msg.get_info_type(), expected_type);
3364+
assert_eq!(
3365+
msg.get_info_contact_id(alice).await?,
3366+
Some(expected_alice_id)
3367+
);
3368+
3369+
let msg = alice2.recv_msg(&sent_msg).await;
3370+
assert_eq!(msg.get_info_type(), expected_type);
3371+
assert_eq!(
3372+
msg.get_info_contact_id(alice2).await?,
3373+
Some(expected_alice_id)
3374+
);
3375+
3376+
let msg = bob.recv_msg(&sent_msg).await;
3377+
assert_eq!(msg.get_info_type(), expected_type);
3378+
assert_eq!(msg.get_info_contact_id(bob).await?, Some(expected_bob_id));
3379+
3380+
Ok(())
3381+
}
3382+
3383+
// Alice creates group, Bob receives group
3384+
let alice_chat_id = alice
3385+
.create_group_with_members(ProtectionStatus::Unprotected, "play", &[bob])
3386+
.await;
3387+
let sent_msg1 = alice.send_text(alice_chat_id, "moin").await;
3388+
3389+
let msg = bob.recv_msg(&sent_msg1).await;
3390+
let bob_alice_id = msg.from_id;
3391+
assert!(!bob_alice_id.is_special());
3392+
3393+
// Alice does group changes, Bob receives them
3394+
set_chat_name(alice, alice_chat_id, "games").await?;
3395+
pop_recv_and_check(
3396+
alice,
3397+
alice2,
3398+
bob,
3399+
SystemMessage::GroupNameChanged,
3400+
ContactId::SELF,
3401+
bob_alice_id,
3402+
)
3403+
.await?;
3404+
3405+
let file = alice.get_blobdir().join("avatar.png");
3406+
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
3407+
tokio::fs::write(&file, bytes).await?;
3408+
set_chat_profile_image(alice, alice_chat_id, file.to_str().unwrap()).await?;
3409+
pop_recv_and_check(
3410+
alice,
3411+
alice2,
3412+
bob,
3413+
SystemMessage::GroupImageChanged,
3414+
ContactId::SELF,
3415+
bob_alice_id,
3416+
)
3417+
.await?;
3418+
3419+
alice_chat_id
3420+
.set_ephemeral_timer(alice, Timer::Enabled { duration: 60 })
3421+
.await?;
3422+
pop_recv_and_check(
3423+
alice,
3424+
alice2,
3425+
bob,
3426+
SystemMessage::EphemeralTimerChanged,
3427+
ContactId::SELF,
3428+
bob_alice_id,
3429+
)
3430+
.await?;
3431+
3432+
let fiona_id = alice.add_or_lookup_contact_id(&tcm.fiona().await).await; // contexts are in sync, fiona_id is same everywhere
3433+
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
3434+
pop_recv_and_check(
3435+
alice,
3436+
alice2,
3437+
bob,
3438+
SystemMessage::MemberAddedToGroup,
3439+
fiona_id,
3440+
fiona_id,
3441+
)
3442+
.await?;
3443+
3444+
remove_contact_from_chat(alice, alice_chat_id, fiona_id).await?;
3445+
pop_recv_and_check(
3446+
alice,
3447+
alice2,
3448+
bob,
3449+
SystemMessage::MemberRemovedFromGroup,
3450+
fiona_id,
3451+
fiona_id,
3452+
)
3453+
.await?;
3454+
3455+
// When fiona_id is deleted, get_info_contact_id() returns None.
3456+
// We raw delete in db as Contact::delete() leaves a tombstone (which is great as the tap works longer then)
3457+
alice
3458+
.sql
3459+
.execute("DELETE FROM contacts WHERE id=?", (fiona_id,))
3460+
.await?;
3461+
let msg = alice.get_last_msg().await;
3462+
assert_eq!(msg.get_info_type(), SystemMessage::MemberRemovedFromGroup);
3463+
assert!(msg.get_info_contact_id(alice).await?.is_none());
3464+
3465+
Ok(())
3466+
}
3467+
33353468
/// Test group consistency.
33363469
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33373470
async fn test_add_member_bug() -> Result<()> {

src/contact.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,16 +1406,13 @@ impl Contact {
14061406
&self.addr
14071407
}
14081408

1409-
/// Get a summary of authorized name and address.
1410-
///
1411-
/// The returned string is either "Name (email@domain.com)" or just
1412-
/// "email@domain.com" if the name is unset.
1409+
/// Get authorized name or address.
14131410
///
14141411
/// This string is suitable for sending over email
14151412
/// as it does not leak the locally set name.
1416-
pub fn get_authname_n_addr(&self) -> String {
1413+
pub(crate) fn get_authname_or_addr(&self) -> String {
14171414
if !self.authname.is_empty() {
1418-
format!("{} ({})", self.authname, self.addr)
1415+
(&self.authname).into()
14191416
} else {
14201417
(&self.addr).into()
14211418
}

0 commit comments

Comments
 (0)