feat: Show broadcast channels in their own, proper "Channel" chat (#6901)

Part of #6884 

----

- [x] Add new chat type `InBroadcastChannel` and `OutBroadcastChannel`
for incoming / outgoing channels, where the former is similar to a
`Mailinglist` and the latter is similar to a `Broadcast` (which is
removed)
- Consideration for naming: `InChannel`/`OutChannel` (without
"broadcast") would be shorter, but less greppable because we already
have a lot of occurences of `channel` in the code. Consistently calling
them `BcChannel`/`bc_channel` in the code would be both short and
greppable, but a bit arcane when reading it at first. Opinions are
welcome; if I hear none, I'll keep with `BroadcastChannel`.
- [x] api: Add create_broadcast_channel(), deprecate
create_broadcast_list() (or `create_channel()` / `create_bc_channel()`
if we decide to switch)
  - Adjust code comments to match the new behavior.
- [x] Ask Desktop developers what they use `is_broadcast` field for, and
whether it should be true for both outgoing & incoming channels (or look
it up myself)
- I added `is_out_broadcast_channel`, and deprecated `is_broadcast`, for
now
- [x] When the user changes the broadcast channel name, immediately show
this change on receiving devices
- [x] Allow to change brodacast channel avatar, and immediately apply it
on the receiving device
- [x] Make it possible to block InBroadcastChannel
- [x] Make it possible to set the avatar of an OutgoingChannel, and
apply it on the receiving side
- [x] DECIDE whether we still want to use the broadcast icon as the
default icon or whether we want to use the letter-in-a-circle
- We decided to use the letter-in-a-circle for now, because it's easier
to implement, and I need to stay in the time plan
- [x] chat.rs: Return an error if the user tries to modify a
`InBroadcastChannel`
- [x] Add automated regression tests
- [x] Grep for `broadcast` and see whether there is any other work I
need to do
- [x] Bug: Don't show `~` in front of the sender's same in broadcast
lists

----

Note that I removed the following guard:

```rust
        if !new_chat_contacts.contains(&ContactId::SELF) {
            warn!(
                context,
                "Received group avatar update for group chat {} we are not a member of.", chat.id
            );
        } else if !new_chat_contacts.contains(&from_id) {
            warn!(
                context,
                "Contact {from_id} attempts to modify group chat {} avatar without being a member.",
                chat.id,
            );
        } else [...]
```

i.e. with this change, non-members will be able to modify the avatar.
Things were slightly easier this way, and I think that this is in line
with non-members being able to modify the group name and memberlist
(they need to know the Group-Chat-Id, anyway), but I can also change it
back.
This commit is contained in:
Hocuri
2025-07-02 22:40:30 +02:00
committed by GitHub
parent 2ee3f58b69
commit 0a73c2b7ab
23 changed files with 744 additions and 319 deletions

View File

@@ -80,7 +80,7 @@ pub struct MimeFactory {
/// because in case of "member removed" message
/// removed member is in the recipient list,
/// but not in the `To` header.
/// In case of broadcast lists there are multiple recipients,
/// In case of broadcast channels there are multiple recipients,
/// but the `To` header has no members.
///
/// If `bcc_self` configuration is enabled,
@@ -98,7 +98,7 @@ pub struct MimeFactory {
/// Vector of pairs of recipient name and address that goes into the `To` field.
///
/// The list of actual message recipient addresses may be different,
/// e.g. if members are hidden for broadcast lists
/// e.g. if members are hidden for broadcast channels
/// or if the keys for some recipients are missing
/// and encrypted message cannot be sent to them.
to: Vec<(String, String)>,
@@ -178,7 +178,7 @@ impl MimeFactory {
let now = time();
let chat = Chat::load_from_db(context, msg.chat_id).await?;
let attach_profile_data = Self::should_attach_profile_data(&msg);
let undisclosed_recipients = chat.typ == Chattype::Broadcast;
let undisclosed_recipients = chat.typ == Chattype::OutBroadcast;
let from_addr = context.get_primary_self_addr().await?;
let config_displayname = context
@@ -599,7 +599,7 @@ impl MimeFactory {
return Ok(msg.subject.clone());
}
if (chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast)
if (chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast)
&& quoted_msg_subject.is_none_or_empty()
{
let re = if self.in_reply_to.is_empty() {
@@ -791,7 +791,7 @@ impl MimeFactory {
}
if let Loaded::Message { chat, .. } = &self.loaded {
if chat.typ == Chattype::Broadcast {
if chat.typ == Chattype::OutBroadcast {
headers.push((
"List-ID",
mail_builder::headers::text::Text::new(format!(
@@ -1035,7 +1035,7 @@ impl MimeFactory {
match &self.loaded {
Loaded::Message { chat, msg } => {
if chat.typ != Chattype::Broadcast {
if chat.typ != Chattype::OutBroadcast {
for (addr, key) in &encryption_keys {
let fingerprint = key.dc_fingerprint().hex();
let cmd = msg.param.get_cmd();
@@ -1300,9 +1300,9 @@ impl MimeFactory {
let send_verified_headers = match chat.typ {
Chattype::Single => true,
Chattype::Group => true,
// Mailinglists and broadcast lists can actually never be verified:
// Mailinglists and broadcast channels can actually never be verified:
Chattype::Mailinglist => false,
Chattype::Broadcast => false,
Chattype::OutBroadcast | Chattype::InBroadcast => false,
};
if chat.is_protected() && send_verified_headers {
headers.push((
@@ -1311,7 +1311,7 @@ impl MimeFactory {
));
}
if chat.typ == Chattype::Group {
if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
// Send group ID unless it is an ad hoc group that has no ID.
if !chat.grpid.is_empty() {
headers.push((