Skip to content

Commit 16f7c39

Browse files
committed
feat: Make it possible to leave broadcast channels
1 parent aad8f69 commit 16f7c39

File tree

4 files changed

+125
-22
lines changed

4 files changed

+125
-22
lines changed

src/chat.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,7 +2858,9 @@ async fn prepare_send_msg(
28582858
// Allow to send "Member removed" messages so we can leave the group.
28592859
// Necessary checks should be made anyway before removing contact
28602860
// from the chat.
2861-
CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2861+
CantSendReason::NotAMember | CantSendReason::InBroadcast => {
2862+
msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup
2863+
}
28622864
CantSendReason::MissingKey => msg
28632865
.param
28642866
.get_bool(Param::ForcePlaintext)
@@ -4035,8 +4037,6 @@ pub async fn remove_contact_from_chat(
40354037
"Cannot remove special contact"
40364038
);
40374039

4038-
let mut msg = Message::new(Viewtype::default());
4039-
40404040
let chat = Chat::load_from_db(context, chat_id).await?;
40414041
if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
40424042
if !chat.is_self_in_chat(context).await? {
@@ -4066,19 +4066,10 @@ pub async fn remove_contact_from_chat(
40664066
// in case of the database becoming inconsistent due to a bug.
40674067
if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
40684068
if chat.typ == Chattype::Group && chat.is_promoted() {
4069-
msg.viewtype = Viewtype::Text;
4070-
if contact_id == ContactId::SELF {
4071-
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4072-
} else {
4073-
msg.text =
4074-
stock_str::msg_del_member_local(context, contact_id, ContactId::SELF)
4075-
.await;
4076-
}
4077-
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4078-
msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4079-
msg.param
4080-
.set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4081-
let res = send_msg(context, chat_id, &mut msg).await;
4069+
let addr = contact.get_addr();
4070+
4071+
let res = send_member_removal_msg(context, chat_id, contact_id, addr).await;
4072+
40824073
if contact_id == ContactId::SELF {
40834074
res?;
40844075
set_group_explicitly_left(context, &chat.grpid).await?;
@@ -4097,13 +4088,40 @@ pub async fn remove_contact_from_chat(
40974088
chat.sync_contacts(context).await.log_err(context).ok();
40984089
}
40994090
}
4091+
} else if chat.typ == Chattype::InBroadcast && contact_id == ContactId::SELF {
4092+
// For incoming broadcast channels, it's not possible to remove members,
4093+
// but it's possible to leave:
4094+
let self_addr = context.get_primary_self_addr().await?;
4095+
send_member_removal_msg(context, chat_id, contact_id, &self_addr).await?;
41004096
} else {
41014097
bail!("Cannot remove members from non-group chats.");
41024098
}
41034099

41044100
Ok(())
41054101
}
41064102

4103+
async fn send_member_removal_msg(
4104+
context: &Context,
4105+
chat_id: ChatId,
4106+
contact_id: ContactId,
4107+
addr: &str,
4108+
) -> Result<MsgId> {
4109+
let mut msg = Message::new(Viewtype::Text);
4110+
4111+
if contact_id == ContactId::SELF {
4112+
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4113+
} else {
4114+
msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
4115+
}
4116+
4117+
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4118+
msg.param.set(Param::Arg, addr.to_lowercase());
4119+
msg.param
4120+
.set(Param::ContactAddedRemoved, contact_id.to_u32() as i32);
4121+
4122+
send_msg(context, chat_id, &mut msg).await
4123+
}
4124+
41074125
async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
41084126
if !is_group_explicitly_left(context, grpid).await? {
41094127
context

src/chat/chat_tests.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2889,6 +2889,61 @@ async fn test_block_broadcast() -> Result<()> {
28892889
Ok(())
28902890
}
28912891

2892+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2893+
async fn test_leave_broadcast() -> Result<()> {
2894+
let mut tcm = TestContextManager::new();
2895+
let alice = tcm.alice().await;
2896+
let bob = tcm.bob().await;
2897+
2898+
tcm.section("Alice creates broadcast channel with Bob.");
2899+
let alice_chat_id = create_broadcast(&alice, "foo".to_string()).await?;
2900+
let bob_contact = alice.add_or_lookup_contact(&bob).await.id;
2901+
add_contact_to_chat(&alice, alice_chat_id, bob_contact).await?;
2902+
2903+
tcm.section("Alice sends first message to broadcast.");
2904+
let sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
2905+
let bob_msg = bob.recv_msg(&sent_msg).await;
2906+
2907+
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 1);
2908+
2909+
// Clear events so that we can later check
2910+
// that the 'Broadcast channel left' message didn't trigger IncomingMsg:
2911+
alice.evtracker.clear_events();
2912+
2913+
// Shift the time so that we can later check the 'Broadcast channel left' message's timestamp:
2914+
SystemTime::shift(Duration::from_secs(60));
2915+
2916+
tcm.section("Bob leaves the broadcast channel.");
2917+
let bob_chat_id = bob_msg.chat_id;
2918+
bob_chat_id.accept(&bob).await?;
2919+
remove_contact_from_chat(&bob, bob_chat_id, ContactId::SELF).await?;
2920+
2921+
let leave_msg = bob.pop_sent_msg().await;
2922+
alice.recv_msg_trash(&leave_msg).await;
2923+
2924+
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 0);
2925+
2926+
alice.emit_event(EventType::Test);
2927+
alice
2928+
.evtracker
2929+
.get_matching(|ev| match ev {
2930+
EventType::Test => true,
2931+
EventType::IncomingMsg { .. } => {
2932+
panic!("'Brodcast channel left' message should be silent")
2933+
}
2934+
EventType::MsgsNoticed(..) => {
2935+
panic!("'Broadcast channel left' message shouldn't clear notifications")
2936+
}
2937+
EventType::MsgsChanged { .. } => {
2938+
panic!("Broadcast channels should be left silently, without any message");
2939+
}
2940+
_ => false,
2941+
})
2942+
.await;
2943+
2944+
Ok(())
2945+
}
2946+
28922947
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
28932948
async fn test_create_for_contact_with_blocked() -> Result<()> {
28942949
let t = TestContext::new().await;

src/mimefactory.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ impl MimeFactory {
791791
}
792792

793793
if let Loaded::Message { chat, .. } = &self.loaded {
794-
if chat.typ == Chattype::OutBroadcast {
794+
if chat.typ == Chattype::OutBroadcast || chat.typ == Chattype::InBroadcast {
795795
headers.push((
796796
"List-ID",
797797
mail_builder::headers::text::Text::new(format!(
@@ -1321,7 +1321,10 @@ impl MimeFactory {
13211321
}
13221322
}
13231323

1324-
if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
1324+
if chat.typ == Chattype::Group
1325+
|| chat.typ == Chattype::OutBroadcast
1326+
|| chat.typ == Chattype::InBroadcast
1327+
{
13251328
headers.push((
13261329
"Chat-Group-Name",
13271330
mail_builder::headers::text::Text::new(chat.name.to_string()).into(),

src/receive_imf.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use mailparse::SingleInfo;
1414
use num_traits::FromPrimitive;
1515
use regex::Regex;
1616

17-
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
17+
use crate::chat::{
18+
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, remove_from_chat_contacts_table,
19+
};
1820
use crate::config::Config;
1921
use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
2022
use crate::contact::{Contact, ContactId, Origin, mark_contact_id_as_verified};
@@ -1670,7 +1672,9 @@ async fn add_parts(
16701672
_ if chat.id.is_special() => GroupChangesInfo::default(),
16711673
Chattype::Single => GroupChangesInfo::default(),
16721674
Chattype::Mailinglist => GroupChangesInfo::default(),
1673-
Chattype::OutBroadcast => GroupChangesInfo::default(),
1675+
Chattype::OutBroadcast => {
1676+
apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1677+
}
16741678
Chattype::Group => {
16751679
apply_group_changes(
16761680
context,
@@ -1684,7 +1688,7 @@ async fn add_parts(
16841688
.await?
16851689
}
16861690
Chattype::InBroadcast => {
1687-
apply_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
1691+
apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
16881692
}
16891693
};
16901694

@@ -3421,7 +3425,30 @@ async fn apply_mailinglist_changes(
34213425
Ok(())
34223426
}
34233427

3424-
async fn apply_broadcast_changes(
3428+
async fn apply_out_broadcast_changes(
3429+
context: &Context,
3430+
mime_parser: &MimeMessage,
3431+
chat: &mut Chat,
3432+
from_id: ContactId,
3433+
) -> Result<GroupChangesInfo> {
3434+
ensure!(chat.typ == Chattype::OutBroadcast);
3435+
3436+
if let Some(_removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
3437+
// The sender of the message left the broadcast channel
3438+
remove_from_chat_contacts_table(context, chat.id, from_id).await?;
3439+
3440+
return Ok(GroupChangesInfo {
3441+
better_msg: Some("".to_string()),
3442+
added_removed_id: None,
3443+
silent: true,
3444+
extra_msgs: vec![],
3445+
});
3446+
}
3447+
3448+
Ok(GroupChangesInfo::default())
3449+
}
3450+
3451+
async fn apply_in_broadcast_changes(
34253452
context: &Context,
34263453
mime_parser: &MimeMessage,
34273454
chat: &mut Chat,

0 commit comments

Comments
 (0)