From 9891c2a5318fb26c698f69dc51e870f77aa14180 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Tue, 10 Mar 2026 10:28:51 -0300 Subject: [PATCH] fix: Add "member added" messages to OutBroadcast when executing SetPgpContacts sync message (#7952) If one of broadcast owner's devices didn't add a new subscriber for any reason, e.g. because of missing SecureJoin messages, this device shall add "member added" messages when syncing the member list from the `SetPgpContacts` message. --- src/chat.rs | 31 +++++++++++++++++++++++++------ src/chat/chat_tests.rs | 33 ++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 307aa6d12..e71845531 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,7 +1,7 @@ //! # Chat module. use std::cmp; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt; use std::io::Cursor; use std::marker::Sync; @@ -5031,18 +5031,18 @@ async fn set_contacts_by_fingerprints( matches!(chat.typ, Chattype::Group | Chattype::OutBroadcast), "{id} is not a group or broadcast", ); - let mut contacts = HashSet::new(); + let mut contacts = BTreeSet::new(); for (fingerprint, addr) in fingerprint_addrs { let contact = Contact::add_or_lookup_ex(context, "", addr, fingerprint, Origin::Hidden) .await? .0; contacts.insert(contact); } - let contacts_old = HashSet::::from_iter(get_chat_contacts(context, id).await?); + let contacts_old = BTreeSet::::from_iter(get_chat_contacts(context, id).await?); if contacts == contacts_old { return Ok(()); } - context + let broadcast_contacts_added = context .sql .transaction(move |transaction| { // For broadcast channels, we only add members, @@ -5059,12 +5059,31 @@ async fn set_contacts_by_fingerprints( let mut statement = transaction.prepare( "INSERT OR IGNORE INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)", )?; + let mut broadcast_contacts_added = Vec::new(); for contact_id in &contacts { - statement.execute((id, contact_id))?; + if statement.execute((id, contact_id))? > 0 && chat.typ == Chattype::OutBroadcast { + broadcast_contacts_added.push(*contact_id); + } } - Ok(()) + Ok(broadcast_contacts_added) }) .await?; + let timestamp = smeared_time(context); + for added_id in broadcast_contacts_added { + let msg = stock_str::msg_add_member_local(context, added_id, ContactId::UNDEFINED).await; + add_info_msg_with_cmd( + context, + id, + &msg, + SystemMessage::MemberAddedToGroup, + Some(timestamp), + timestamp, + None, + Some(ContactId::SELF), + Some(added_id), + ) + .await?; + } context.emit_event(EventType::ChatModified(id)); Ok(()) } diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index 4980794fd..ead38e747 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -2997,18 +2997,41 @@ async fn test_broadcast_recipients_sync1() -> Result<()> { alice2.recv_msg_trash(&request_with_auth).await; let member_added = alice1.pop_sent_msg().await; - let a2_member_added = alice2.recv_msg(&member_added).await; + let a2_charlie_added = alice2.recv_msg(&member_added).await; let _c_member_added = charlie.recv_msg(&member_added).await; + let a2_chatlist = Chatlist::try_load(alice2, 0, Some("Channel"), None).await?; + assert_eq!(a2_chatlist.get_msg_id(0)?.unwrap(), a2_charlie_added.id); // Alice1 will now sync the full member list to Alice2: sync(alice1, alice2).await; - let a2_chatlist = Chatlist::try_load(alice2, 0, Some("Channel"), None).await?; - assert_eq!(a2_chatlist.get_msg_id(0)?.unwrap(), a2_member_added.id); - let a2_bob_contact = alice2.add_or_lookup_contact_id(bob).await; let a2_charlie_contact = alice2.add_or_lookup_contact_id(charlie).await; + let a2_chatlist = Chatlist::try_load(alice2, 0, Some("Channel"), None).await?; + let msg_id = a2_chatlist.get_msg_id(0)?.unwrap(); + let a2_bob_added = Message::load_from_db(alice2, msg_id).await?; + assert_ne!(a2_bob_added.id, a2_charlie_added.id); + assert_eq!( + a2_bob_added.text, + stock_str::msg_add_member_local(alice2, a2_bob_contact, ContactId::UNDEFINED).await + ); + assert_eq!(a2_bob_added.from_id, ContactId::SELF); + assert_eq!( + a2_bob_added.param.get_cmd(), + SystemMessage::MemberAddedToGroup + ); + assert_eq!( + ContactId::new( + a2_bob_added + .param + .get_int(Param::ContactAddedRemoved) + .unwrap() + .try_into() + .unwrap() + ), + a2_bob_contact + ); - let a2_chat_members = get_chat_contacts(alice2, a2_member_added.chat_id).await?; + let a2_chat_members = get_chat_contacts(alice2, a2_charlie_added.chat_id).await?; assert!(a2_chat_members.contains(&a2_bob_contact)); assert!(a2_chat_members.contains(&a2_charlie_contact)); assert_eq!(a2_chat_members.len(), 2);