deltachat/
chat.rs

1//! # Chat module.
2
3use std::cmp;
4use std::collections::{HashMap, HashSet};
5use std::fmt;
6use std::io::Cursor;
7use std::marker::Sync;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::time::Duration;
11
12use anyhow::{Context as _, Result, anyhow, bail, ensure};
13use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line};
14use deltachat_derive::{FromSql, ToSql};
15use mail_builder::mime::MimePart;
16use serde::{Deserialize, Serialize};
17use strum_macros::EnumIter;
18
19use crate::blob::BlobObject;
20use crate::chatlist::Chatlist;
21use crate::color::str_to_color;
22use crate::config::Config;
23use crate::constants::{
24    Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_LAST_SPECIAL,
25    DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS, EDITED_PREFIX, TIMESTAMP_SENT_TOLERANCE,
26};
27use crate::contact::{self, Contact, ContactId, Origin};
28use crate::context::Context;
29use crate::debug_logging::maybe_set_logging_xdc;
30use crate::download::DownloadState;
31use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
32use crate::events::EventType;
33use crate::location;
34use crate::log::{LogExt, error, info, warn};
35use crate::message::{self, Message, MessageState, MsgId, Viewtype};
36use crate::mimefactory::MimeFactory;
37use crate::mimeparser::SystemMessage;
38use crate::param::{Param, Params};
39use crate::receive_imf::ReceivedMsg;
40use crate::smtp::send_msg_to_smtp;
41use crate::stock_str;
42use crate::sync::{self, Sync::*, SyncData};
43use crate::tools::{
44    IsNoneOrEmpty, SystemTime, buf_compress, create_id, create_outgoing_rfc724_mid,
45    create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset,
46    smeared_time, time, truncate_msg_text,
47};
48use crate::webxdc::StatusUpdateSerial;
49use crate::{chatlist_events, imap};
50
51/// An chat item, such as a message or a marker.
52#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53pub enum ChatItem {
54    /// Chat message stored in the database.
55    Message {
56        /// Database ID of the message.
57        msg_id: MsgId,
58    },
59
60    /// Day marker, separating messages that correspond to different
61    /// days according to local time.
62    DayMarker {
63        /// Marker timestamp, for day markers
64        timestamp: i64,
65    },
66}
67
68/// Chat protection status.
69#[derive(
70    Debug,
71    Default,
72    Display,
73    Clone,
74    Copy,
75    PartialEq,
76    Eq,
77    FromPrimitive,
78    ToPrimitive,
79    FromSql,
80    ToSql,
81    IntoStaticStr,
82    Serialize,
83    Deserialize,
84)]
85#[repr(u32)]
86pub enum ProtectionStatus {
87    /// Chat is not protected.
88    #[default]
89    Unprotected = 0,
90
91    /// Chat is protected.
92    ///
93    /// All members of the chat must be verified.
94    Protected = 1,
95
96    /// The chat was protected, but now a new message came in
97    /// which was not encrypted / signed correctly.
98    /// The user has to confirm that this is OK.
99    ///
100    /// We only do this in 1:1 chats; in group chats, the chat just
101    /// stays protected.
102    ProtectionBroken = 3, // `2` was never used as a value.
103}
104
105/// The reason why messages cannot be sent to the chat.
106///
107/// The reason is mainly for logging and displaying in debug REPL, thus not translated.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub(crate) enum CantSendReason {
110    /// Special chat.
111    SpecialChat,
112
113    /// The chat is a device chat.
114    DeviceChat,
115
116    /// The chat is a contact request, it needs to be accepted before sending a message.
117    ContactRequest,
118
119    /// The chat was protected, but now a new message came in
120    /// which was not encrypted / signed correctly.
121    ProtectionBroken,
122
123    /// Mailing list without known List-Post header.
124    ReadOnlyMailingList,
125
126    /// Incoming broadcast channel where the user can't send messages.
127    InBroadcast,
128
129    /// Not a member of the chat.
130    NotAMember,
131
132    /// State for 1:1 chat with a key-contact that does not have a key.
133    MissingKey,
134}
135
136impl fmt::Display for CantSendReason {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Self::SpecialChat => write!(f, "the chat is a special chat"),
140            Self::DeviceChat => write!(f, "the chat is a device chat"),
141            Self::ContactRequest => write!(
142                f,
143                "contact request chat should be accepted before sending messages"
144            ),
145            Self::ProtectionBroken => write!(
146                f,
147                "accept that the encryption isn't verified anymore before sending messages"
148            ),
149            Self::ReadOnlyMailingList => {
150                write!(f, "mailing list does not have a know post address")
151            }
152            Self::InBroadcast => {
153                write!(f, "Broadcast channel is read-only")
154            }
155            Self::NotAMember => write!(f, "not a member of the chat"),
156            Self::MissingKey => write!(f, "key is missing"),
157        }
158    }
159}
160
161/// Chat ID, including reserved IDs.
162///
163/// Some chat IDs are reserved to identify special chat types.  This
164/// type can represent both the special as well as normal chats.
165#[derive(
166    Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
167)]
168pub struct ChatId(u32);
169
170impl ChatId {
171    /// Create a new [ChatId].
172    pub const fn new(id: u32) -> ChatId {
173        ChatId(id)
174    }
175
176    /// An unset ChatId
177    ///
178    /// This is transitional and should not be used in new code.
179    pub fn is_unset(self) -> bool {
180        self.0 == 0
181    }
182
183    /// Whether the chat ID signifies a special chat.
184    ///
185    /// This kind of chat ID can not be used for real chats.
186    pub fn is_special(self) -> bool {
187        (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
188    }
189
190    /// Chat ID for messages which need to be deleted.
191    ///
192    /// Messages which should be deleted get this chat ID and are
193    /// deleted later.  Deleted messages need to stay around as long
194    /// as they are not deleted on the server so that their rfc724_mid
195    /// remains known and downloading them again can be avoided.
196    pub fn is_trash(self) -> bool {
197        self == DC_CHAT_ID_TRASH
198    }
199
200    /// Chat ID signifying there are **any** number of archived chats.
201    ///
202    /// This chat ID can be returned in a [`Chatlist`] and signals to
203    /// the UI to include a link to the archived chats.
204    ///
205    /// [`Chatlist`]: crate::chatlist::Chatlist
206    pub fn is_archived_link(self) -> bool {
207        self == DC_CHAT_ID_ARCHIVED_LINK
208    }
209
210    /// Virtual chat ID signalling there are **only** archived chats.
211    ///
212    /// This can be included in the chatlist if the
213    /// [`DC_GCL_ADD_ALLDONE_HINT`] flag is used to build the
214    /// [`Chatlist`].
215    ///
216    /// [`DC_GCL_ADD_ALLDONE_HINT`]: crate::constants::DC_GCL_ADD_ALLDONE_HINT
217    /// [`Chatlist`]: crate::chatlist::Chatlist
218    pub fn is_alldone_hint(self) -> bool {
219        self == DC_CHAT_ID_ALLDONE_HINT
220    }
221
222    /// Returns [`ChatId`] of a chat that `msg` belongs to.
223    pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
224        if msg.chat_id == DC_CHAT_ID_TRASH {
225            return None;
226        }
227        if msg.download_state == DownloadState::Undecipherable {
228            return None;
229        }
230        Some(msg.chat_id)
231    }
232
233    /// Returns the [`ChatId`] for the 1:1 chat with `contact_id`
234    /// if it exists and is not blocked.
235    ///
236    /// If the chat does not exist or is blocked, `None` is returned.
237    pub async fn lookup_by_contact(
238        context: &Context,
239        contact_id: ContactId,
240    ) -> Result<Option<Self>> {
241        let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
242        else {
243            return Ok(None);
244        };
245
246        let chat_id = match chat_id_blocked.blocked {
247            Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
248            Blocked::Yes => None,
249        };
250        Ok(chat_id)
251    }
252
253    /// Returns the [`ChatId`] for the 1:1 chat with `contact_id`.
254    ///
255    /// If the chat does not yet exist an unblocked chat ([`Blocked::Not`]) is created.
256    ///
257    /// This is an internal API, if **a user action** needs to get a chat
258    /// [`ChatId::create_for_contact`] should be used as this also scales up the
259    /// [`Contact`]'s origin.
260    pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
261        ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
262            .await
263            .map(|chat| chat.id)
264    }
265
266    /// Returns the unblocked 1:1 chat with `contact_id`.
267    ///
268    /// This should be used when **a user action** creates a chat 1:1, it ensures the chat
269    /// exists, is unblocked and scales the [`Contact`]'s origin.
270    pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
271        ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
272    }
273
274    /// Same as `create_for_contact()` with an additional `create_blocked` parameter
275    /// that is used in case the chat does not exist or to unblock existing chats.
276    /// `create_blocked` won't block already unblocked chats again.
277    pub(crate) async fn create_for_contact_with_blocked(
278        context: &Context,
279        contact_id: ContactId,
280        create_blocked: Blocked,
281    ) -> Result<Self> {
282        let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
283            Some(chat) => {
284                if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
285                    return Ok(chat.id);
286                }
287                chat.id.set_blocked(context, Blocked::Not).await?;
288                chat.id
289            }
290            None => {
291                if Contact::real_exists_by_id(context, contact_id).await?
292                    || contact_id == ContactId::SELF
293                {
294                    let chat_id =
295                        ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
296                            .await
297                            .map(|chat| chat.id)?;
298                    ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
299                    chat_id
300                } else {
301                    warn!(
302                        context,
303                        "Cannot create chat, contact {contact_id} does not exist."
304                    );
305                    bail!("Can not create chat for non-existing contact");
306                }
307            }
308        };
309        context.emit_msgs_changed_without_ids();
310        chatlist_events::emit_chatlist_changed(context);
311        chatlist_events::emit_chatlist_item_changed(context, chat_id);
312        Ok(chat_id)
313    }
314
315    /// Create a group or mailinglist raw database record with the given parameters.
316    /// The function does not add SELF nor checks if the record already exists.
317    #[expect(clippy::too_many_arguments)]
318    pub(crate) async fn create_multiuser_record(
319        context: &Context,
320        chattype: Chattype,
321        grpid: &str,
322        grpname: &str,
323        create_blocked: Blocked,
324        create_protected: ProtectionStatus,
325        param: Option<String>,
326        timestamp: i64,
327    ) -> Result<Self> {
328        let grpname = sanitize_single_line(grpname);
329        let timestamp = cmp::min(timestamp, smeared_time(context));
330        let row_id =
331            context.sql.insert(
332                "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
333                (
334                    chattype,
335                    &grpname,
336                    grpid,
337                    create_blocked,
338                    timestamp,
339                    create_protected,
340                    param.unwrap_or_default(),
341                ),
342            ).await?;
343
344        let chat_id = ChatId::new(u32::try_from(row_id)?);
345
346        if create_protected == ProtectionStatus::Protected {
347            chat_id
348                .add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
349                .await?;
350        }
351
352        info!(
353            context,
354            "Created group/mailinglist '{}' grpid={} as {}, blocked={}, protected={create_protected}.",
355            &grpname,
356            grpid,
357            chat_id,
358            create_blocked,
359        );
360
361        Ok(chat_id)
362    }
363
364    async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
365        context
366            .sql
367            .execute(
368                "UPDATE contacts
369                 SET selfavatar_sent=?
370                 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
371                (timestamp, self),
372            )
373            .await?;
374        Ok(())
375    }
376
377    /// Updates chat blocked status.
378    ///
379    /// Returns true if the value was modified.
380    pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
381        if self.is_special() {
382            bail!("ignoring setting of Block-status for {}", self);
383        }
384        let count = context
385            .sql
386            .execute(
387                "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
388                (new_blocked, self),
389            )
390            .await?;
391        Ok(count > 0)
392    }
393
394    /// Blocks the chat as a result of explicit user action.
395    pub async fn block(self, context: &Context) -> Result<()> {
396        self.block_ex(context, Sync).await
397    }
398
399    pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
400        let chat = Chat::load_from_db(context, self).await?;
401        let mut delete = false;
402
403        match chat.typ {
404            Chattype::OutBroadcast => {
405                bail!("Can't block chat of type {:?}", chat.typ)
406            }
407            Chattype::Single => {
408                for contact_id in get_chat_contacts(context, self).await? {
409                    if contact_id != ContactId::SELF {
410                        info!(
411                            context,
412                            "Blocking the contact {contact_id} to block 1:1 chat."
413                        );
414                        contact::set_blocked(context, Nosync, contact_id, true).await?;
415                    }
416                }
417            }
418            Chattype::Group => {
419                info!(context, "Can't block groups yet, deleting the chat.");
420                delete = true;
421            }
422            Chattype::Mailinglist | Chattype::InBroadcast => {
423                if self.set_blocked(context, Blocked::Yes).await? {
424                    context.emit_event(EventType::ChatModified(self));
425                }
426            }
427        }
428        chatlist_events::emit_chatlist_changed(context);
429
430        if sync.into() {
431            // NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices.
432            chat.sync(context, SyncAction::Block)
433                .await
434                .log_err(context)
435                .ok();
436        }
437        if delete {
438            self.delete_ex(context, Nosync).await?;
439        }
440        Ok(())
441    }
442
443    /// Unblocks the chat.
444    pub async fn unblock(self, context: &Context) -> Result<()> {
445        self.unblock_ex(context, Sync).await
446    }
447
448    pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
449        self.set_blocked(context, Blocked::Not).await?;
450
451        chatlist_events::emit_chatlist_changed(context);
452
453        if sync.into() {
454            let chat = Chat::load_from_db(context, self).await?;
455            // TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices.
456            // Maybe we should unblock the contact locally too, this would also resolve discrepancy
457            // with `block()` which also blocks the contact.
458            chat.sync(context, SyncAction::Unblock)
459                .await
460                .log_err(context)
461                .ok();
462        }
463
464        Ok(())
465    }
466
467    /// Accept the contact request.
468    ///
469    /// Unblocks the chat and scales up origin of contacts.
470    pub async fn accept(self, context: &Context) -> Result<()> {
471        self.accept_ex(context, Sync).await
472    }
473
474    pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
475        let chat = Chat::load_from_db(context, self).await?;
476
477        match chat.typ {
478            Chattype::Single
479                if chat.blocked == Blocked::Not
480                    && chat.protected == ProtectionStatus::ProtectionBroken =>
481            {
482                // The protection was broken, then the user clicked 'Accept'/'OK',
483                // so, now we want to set the status to Unprotected again:
484                chat.id
485                    .inner_set_protection(context, ProtectionStatus::Unprotected)
486                    .await?;
487            }
488            Chattype::Single | Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast => {
489                // User has "created a chat" with all these contacts.
490                //
491                // Previously accepting a chat literally created a chat because unaccepted chats
492                // went to "contact requests" list rather than normal chatlist.
493                for contact_id in get_chat_contacts(context, self).await? {
494                    if contact_id != ContactId::SELF {
495                        ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
496                            .await?;
497                    }
498                }
499            }
500            Chattype::Mailinglist => {
501                // If the message is from a mailing list, the contacts are not counted as "known"
502            }
503        }
504
505        if self.set_blocked(context, Blocked::Not).await? {
506            context.emit_event(EventType::ChatModified(self));
507            chatlist_events::emit_chatlist_item_changed(context, self);
508        }
509
510        if sync.into() {
511            chat.sync(context, SyncAction::Accept)
512                .await
513                .log_err(context)
514                .ok();
515        }
516        Ok(())
517    }
518
519    /// Sets protection without sending a message.
520    ///
521    /// Returns whether the protection status was actually modified.
522    pub(crate) async fn inner_set_protection(
523        self,
524        context: &Context,
525        protect: ProtectionStatus,
526    ) -> Result<bool> {
527        ensure!(!self.is_special(), "Invalid chat-id {self}.");
528
529        let chat = Chat::load_from_db(context, self).await?;
530
531        if protect == chat.protected {
532            info!(context, "Protection status unchanged for {}.", self);
533            return Ok(false);
534        }
535
536        match protect {
537            ProtectionStatus::Protected => match chat.typ {
538                Chattype::Single
539                | Chattype::Group
540                | Chattype::OutBroadcast
541                | Chattype::InBroadcast => {}
542                Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
543            },
544            ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {}
545        };
546
547        context
548            .sql
549            .execute("UPDATE chats SET protected=? WHERE id=?;", (protect, self))
550            .await?;
551
552        context.emit_event(EventType::ChatModified(self));
553        chatlist_events::emit_chatlist_item_changed(context, self);
554
555        // make sure, the receivers will get all keys
556        self.reset_gossiped_timestamp(context).await?;
557
558        Ok(true)
559    }
560
561    /// Adds an info message to the chat, telling the user that the protection status changed.
562    ///
563    /// Params:
564    ///
565    /// * `contact_id`: In a 1:1 chat, pass the chat partner's contact id.
566    /// * `timestamp_sort` is used as the timestamp of the added message
567    ///   and should be the timestamp of the change happening.
568    pub(crate) async fn add_protection_msg(
569        self,
570        context: &Context,
571        protect: ProtectionStatus,
572        contact_id: Option<ContactId>,
573        timestamp_sort: i64,
574    ) -> Result<()> {
575        if contact_id == Some(ContactId::SELF) {
576            // Do not add protection messages to Saved Messages chat.
577            // This chat never gets protected and unprotected,
578            // we do not want the first message
579            // to be a protection message with an arbitrary timestamp.
580            return Ok(());
581        }
582
583        let text = context.stock_protection_msg(protect, contact_id).await;
584        let cmd = match protect {
585            ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
586            ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
587            ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled,
588        };
589        add_info_msg_with_cmd(
590            context,
591            self,
592            &text,
593            cmd,
594            timestamp_sort,
595            None,
596            None,
597            None,
598            None,
599        )
600        .await?;
601
602        Ok(())
603    }
604
605    /// Sets protection and adds a message.
606    ///
607    /// `timestamp_sort` is used as the timestamp of the added message
608    /// and should be the timestamp of the change happening.
609    async fn set_protection_for_timestamp_sort(
610        self,
611        context: &Context,
612        protect: ProtectionStatus,
613        timestamp_sort: i64,
614        contact_id: Option<ContactId>,
615    ) -> Result<()> {
616        let protection_status_modified = self
617            .inner_set_protection(context, protect)
618            .await
619            .with_context(|| format!("Cannot set protection for {self}"))?;
620        if protection_status_modified {
621            self.add_protection_msg(context, protect, contact_id, timestamp_sort)
622                .await?;
623            chatlist_events::emit_chatlist_item_changed(context, self);
624        }
625        Ok(())
626    }
627
628    /// Sets protection and sends or adds a message.
629    ///
630    /// `timestamp_sent` is the "sent" timestamp of a message caused the protection state change.
631    pub(crate) async fn set_protection(
632        self,
633        context: &Context,
634        protect: ProtectionStatus,
635        timestamp_sent: i64,
636        contact_id: Option<ContactId>,
637    ) -> Result<()> {
638        let sort_to_bottom = true;
639        let (received, incoming) = (false, false);
640        let ts = self
641            .calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
642            .await?
643            // Always sort protection messages below `SystemMessage::SecurejoinWait{,Timeout}` ones
644            // in case of race conditions.
645            .saturating_add(1);
646        self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
647            .await
648    }
649
650    /// Sets the 1:1 chat with the given address to ProtectionStatus::Protected,
651    /// and posts a `SystemMessage::ChatProtectionEnabled` into it.
652    ///
653    /// If necessary, creates a hidden chat for this.
654    pub(crate) async fn set_protection_for_contact(
655        context: &Context,
656        contact_id: ContactId,
657        timestamp: i64,
658    ) -> Result<()> {
659        let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
660            .await
661            .with_context(|| format!("can't create chat for {contact_id}"))?;
662        chat_id
663            .set_protection(
664                context,
665                ProtectionStatus::Protected,
666                timestamp,
667                Some(contact_id),
668            )
669            .await?;
670        Ok(())
671    }
672
673    /// Archives or unarchives a chat.
674    pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
675        self.set_visibility_ex(context, Sync, visibility).await
676    }
677
678    pub(crate) async fn set_visibility_ex(
679        self,
680        context: &Context,
681        sync: sync::Sync,
682        visibility: ChatVisibility,
683    ) -> Result<()> {
684        ensure!(
685            !self.is_special(),
686            "bad chat_id, can not be special chat: {}",
687            self
688        );
689
690        context
691            .sql
692            .transaction(move |transaction| {
693                if visibility == ChatVisibility::Archived {
694                    transaction.execute(
695                        "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
696                        (MessageState::InNoticed, self, MessageState::InFresh),
697                    )?;
698                }
699                transaction.execute(
700                    "UPDATE chats SET archived=? WHERE id=?;",
701                    (visibility, self),
702                )?;
703                Ok(())
704            })
705            .await?;
706
707        if visibility == ChatVisibility::Archived {
708            start_chat_ephemeral_timers(context, self).await?;
709        }
710
711        context.emit_msgs_changed_without_ids();
712        chatlist_events::emit_chatlist_changed(context);
713        chatlist_events::emit_chatlist_item_changed(context, self);
714
715        if sync.into() {
716            let chat = Chat::load_from_db(context, self).await?;
717            chat.sync(context, SyncAction::SetVisibility(visibility))
718                .await
719                .log_err(context)
720                .ok();
721        }
722        Ok(())
723    }
724
725    /// Unarchives a chat that is archived and not muted.
726    /// Needed after a message is added to a chat so that the chat gets a normal visibility again.
727    /// `msg_state` is the state of the message. Matters only for incoming messages currently. For
728    /// multiple outgoing messages the function may be called once with MessageState::Undefined.
729    /// Sending an appropriate event is up to the caller.
730    /// Also emits DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived
731    /// chats with unread messages increases (which is possible if the chat is muted).
732    pub async fn unarchive_if_not_muted(
733        self,
734        context: &Context,
735        msg_state: MessageState,
736    ) -> Result<()> {
737        if msg_state != MessageState::InFresh {
738            context
739                .sql
740                .execute(
741                    "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
742                AND NOT(muted_until=-1 OR muted_until>?)",
743                    (self, time()),
744                )
745                .await?;
746            return Ok(());
747        }
748        let chat = Chat::load_from_db(context, self).await?;
749        if chat.visibility != ChatVisibility::Archived {
750            return Ok(());
751        }
752        if chat.is_muted() {
753            let unread_cnt = context
754                .sql
755                .count(
756                    "SELECT COUNT(*)
757                FROM msgs
758                WHERE state=?
759                AND hidden=0
760                AND chat_id=?",
761                    (MessageState::InFresh, self),
762                )
763                .await?;
764            if unread_cnt == 1 {
765                // Added the first unread message in the chat.
766                context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
767            }
768            return Ok(());
769        }
770        context
771            .sql
772            .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
773            .await?;
774        Ok(())
775    }
776
777    /// Emits an appropriate event for a message. `important` is whether a notification should be
778    /// shown.
779    pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
780        if important {
781            debug_assert!(!msg_id.is_unset());
782
783            context.emit_incoming_msg(self, msg_id);
784        } else {
785            context.emit_msgs_changed(self, msg_id);
786        }
787    }
788
789    /// Deletes a chat.
790    pub async fn delete(self, context: &Context) -> Result<()> {
791        self.delete_ex(context, Sync).await
792    }
793
794    pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
795        ensure!(
796            !self.is_special(),
797            "bad chat_id, can not be a special chat: {}",
798            self
799        );
800
801        let chat = Chat::load_from_db(context, self).await?;
802        let delete_msgs_target = context.get_delete_msgs_target().await?;
803        let sync_id = match sync {
804            Nosync => None,
805            Sync => chat.get_sync_id(context).await?,
806        };
807
808        context
809            .sql
810            .transaction(|transaction| {
811                transaction.execute(
812                    "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
813                    (delete_msgs_target, self,),
814                )?;
815                transaction.execute(
816                    "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
817                    (self,),
818                )?;
819                transaction.execute(
820                    "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
821                    (self,),
822                )?;
823                transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
824                transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
825                transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
826                Ok(())
827            })
828            .await?;
829
830        context.emit_event(EventType::ChatDeleted { chat_id: self });
831        context.emit_msgs_changed_without_ids();
832
833        if let Some(id) = sync_id {
834            self::sync(context, id, SyncAction::Delete)
835                .await
836                .log_err(context)
837                .ok();
838        }
839
840        if chat.is_self_talk() {
841            let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
842            add_device_msg(context, None, Some(&mut msg)).await?;
843        }
844        chatlist_events::emit_chatlist_changed(context);
845
846        context
847            .set_config_internal(Config::LastHousekeeping, None)
848            .await?;
849        context.scheduler.interrupt_inbox().await;
850
851        Ok(())
852    }
853
854    /// Sets draft message.
855    ///
856    /// Passing `None` as message just deletes the draft
857    pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
858        if self.is_special() {
859            return Ok(());
860        }
861
862        let changed = match &mut msg {
863            None => self.maybe_delete_draft(context).await?,
864            Some(msg) => self.do_set_draft(context, msg).await?,
865        };
866
867        if changed {
868            if msg.is_some() {
869                match self.get_draft_msg_id(context).await? {
870                    Some(msg_id) => context.emit_msgs_changed(self, msg_id),
871                    None => context.emit_msgs_changed_without_msg_id(self),
872                }
873            } else {
874                context.emit_msgs_changed_without_msg_id(self)
875            }
876        }
877
878        Ok(())
879    }
880
881    /// Returns ID of the draft message, if there is one.
882    async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
883        let msg_id: Option<MsgId> = context
884            .sql
885            .query_get_value(
886                "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
887                (self, MessageState::OutDraft),
888            )
889            .await?;
890        Ok(msg_id)
891    }
892
893    /// Returns draft message, if there is one.
894    pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
895        if self.is_special() {
896            return Ok(None);
897        }
898        match self.get_draft_msg_id(context).await? {
899            Some(draft_msg_id) => {
900                let msg = Message::load_from_db(context, draft_msg_id).await?;
901                Ok(Some(msg))
902            }
903            None => Ok(None),
904        }
905    }
906
907    /// Deletes draft message, if there is one.
908    ///
909    /// Returns `true`, if message was deleted, `false` otherwise.
910    async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
911        Ok(context
912            .sql
913            .execute(
914                "DELETE FROM msgs WHERE chat_id=? AND state=?",
915                (self, MessageState::OutDraft),
916            )
917            .await?
918            > 0)
919    }
920
921    /// Set provided message as draft message for specified chat.
922    /// Returns true if the draft was added or updated in place.
923    async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
924        match msg.viewtype {
925            Viewtype::Unknown => bail!("Can not set draft of unknown type."),
926            Viewtype::Text => {
927                if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
928                    bail!("No text and no quote in draft");
929                }
930            }
931            _ => {
932                if msg.viewtype == Viewtype::File {
933                    if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
934                        // We do not do an automatic conversion to other viewtypes here so that
935                        // users can send images as "files" to preserve the original quality
936                        // (usually we compress images). The remaining conversions are done by
937                        // `prepare_msg_blob()` later.
938                        .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
939                    {
940                        msg.viewtype = better_type;
941                    }
942                }
943                if msg.viewtype == Viewtype::Vcard {
944                    let blob = msg
945                        .param
946                        .get_file_blob(context)?
947                        .context("no file stored in params")?;
948                    msg.try_set_vcard(context, &blob.to_abs_path()).await?;
949                }
950            }
951        }
952
953        // set back draft information to allow identifying the draft later on -
954        // no matter if message object is reused or reloaded from db
955        msg.state = MessageState::OutDraft;
956        msg.chat_id = self;
957
958        // if possible, replace existing draft and keep id
959        if !msg.id.is_special() {
960            if let Some(old_draft) = self.get_draft(context).await? {
961                if old_draft.id == msg.id
962                    && old_draft.chat_id == self
963                    && old_draft.state == MessageState::OutDraft
964                {
965                    let affected_rows = context
966                        .sql.execute(
967                                "UPDATE msgs
968                                SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
969                                WHERE id=?7
970                                AND (type <> ?2 
971                                    OR txt <> ?3 
972                                    OR txt_normalized <> ?4
973                                    OR param <> ?5
974                                    OR mime_in_reply_to <> ?6);",
975                                (
976                                    time(),
977                                    msg.viewtype,
978                                    &msg.text,
979                                    message::normalize_text(&msg.text),
980                                    msg.param.to_string(),
981                                    msg.in_reply_to.as_deref().unwrap_or_default(),
982                                    msg.id,
983                                ),
984                            ).await?;
985                    return Ok(affected_rows > 0);
986                }
987            }
988        }
989
990        let row_id = context
991            .sql
992            .transaction(|transaction| {
993                // Delete existing draft if it exists.
994                transaction.execute(
995                    "DELETE FROM msgs WHERE chat_id=? AND state=?",
996                    (self, MessageState::OutDraft),
997                )?;
998
999                // Insert new draft.
1000                transaction.execute(
1001                    "INSERT INTO msgs (
1002                 chat_id,
1003                 rfc724_mid,
1004                 from_id,
1005                 timestamp,
1006                 type,
1007                 state,
1008                 txt,
1009                 txt_normalized,
1010                 param,
1011                 hidden,
1012                 mime_in_reply_to)
1013         VALUES (?,?,?,?,?,?,?,?,?,?,?);",
1014                    (
1015                        self,
1016                        &msg.rfc724_mid,
1017                        ContactId::SELF,
1018                        time(),
1019                        msg.viewtype,
1020                        MessageState::OutDraft,
1021                        &msg.text,
1022                        message::normalize_text(&msg.text),
1023                        msg.param.to_string(),
1024                        1,
1025                        msg.in_reply_to.as_deref().unwrap_or_default(),
1026                    ),
1027                )?;
1028
1029                Ok(transaction.last_insert_rowid())
1030            })
1031            .await?;
1032        msg.id = MsgId::new(row_id.try_into()?);
1033        Ok(true)
1034    }
1035
1036    /// Returns number of messages in a chat.
1037    pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
1038        let count = context
1039            .sql
1040            .count(
1041                "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
1042                (self,),
1043            )
1044            .await?;
1045        Ok(count)
1046    }
1047
1048    /// Returns the number of fresh messages in the chat.
1049    pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
1050        // this function is typically used to show a badge counter beside _each_ chatlist item.
1051        // to make this as fast as possible, esp. on older devices, we added an combined index over the rows used for querying.
1052        // so if you alter the query here, you may want to alter the index over `(state, hidden, chat_id)` in `sql.rs`.
1053        //
1054        // the impact of the index is significant once the database grows:
1055        // - on an older android4 with 18k messages, query-time decreased from 110ms to 2ms
1056        // - on an mid-class moto-g or iphone7 with 50k messages, query-time decreased from 26ms or 6ms to 0-1ms
1057        // the times are average, no matter if there are fresh messages or not -
1058        // and have to be multiplied by the number of items shown at once on the chatlist,
1059        // so savings up to 2 seconds are possible on older devices - newer ones will feel "snappier" :)
1060        let count = if self.is_archived_link() {
1061            context
1062                .sql
1063                .count(
1064                    "SELECT COUNT(DISTINCT(m.chat_id))
1065                    FROM msgs m
1066                    LEFT JOIN chats c ON m.chat_id=c.id
1067                    WHERE m.state=10
1068                    and m.hidden=0
1069                    AND m.chat_id>9
1070                    AND c.blocked=0
1071                    AND c.archived=1
1072                    ",
1073                    (),
1074                )
1075                .await?
1076        } else {
1077            context
1078                .sql
1079                .count(
1080                    "SELECT COUNT(*)
1081                FROM msgs
1082                WHERE state=?
1083                AND hidden=0
1084                AND chat_id=?;",
1085                    (MessageState::InFresh, self),
1086                )
1087                .await?
1088        };
1089        Ok(count)
1090    }
1091
1092    pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
1093        Ok(context
1094            .sql
1095            .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
1096            .await?
1097            .unwrap_or(0))
1098    }
1099
1100    /// Returns timestamp of the latest message in the chat,
1101    /// including hidden messages or a draft if there is one.
1102    pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
1103        let timestamp = context
1104            .sql
1105            .query_get_value(
1106                "SELECT MAX(timestamp)
1107                 FROM msgs
1108                 WHERE chat_id=?
1109                 HAVING COUNT(*) > 0",
1110                (self,),
1111            )
1112            .await?;
1113        Ok(timestamp)
1114    }
1115
1116    /// Returns a list of active similar chat IDs sorted by similarity metric.
1117    ///
1118    /// Jaccard similarity coefficient is used to estimate similarity of chat member sets.
1119    ///
1120    /// Chat is considered active if something was posted there within the last 42 days.
1121    pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
1122        // Count number of common members in this and other chats.
1123        let intersection: Vec<(ChatId, f64)> = context
1124            .sql
1125            .query_map(
1126                "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
1127                 FROM chats_contacts as x
1128                 JOIN chats_contacts as y
1129                 WHERE x.contact_id > 9
1130                   AND y.contact_id > 9
1131                   AND x.add_timestamp >= x.remove_timestamp
1132                   AND y.add_timestamp >= y.remove_timestamp
1133                   AND x.chat_id=?
1134                   AND y.chat_id<>x.chat_id
1135                   AND y.chat_id>?
1136                 GROUP BY y.chat_id",
1137                (self, DC_CHAT_ID_LAST_SPECIAL),
1138                |row| {
1139                    let chat_id: ChatId = row.get(0)?;
1140                    let intersection: f64 = row.get(1)?;
1141                    Ok((chat_id, intersection))
1142                },
1143                |rows| {
1144                    rows.collect::<std::result::Result<Vec<_>, _>>()
1145                        .map_err(Into::into)
1146                },
1147            )
1148            .await
1149            .context("failed to calculate member set intersections")?;
1150
1151        let chat_size: HashMap<ChatId, f64> = context
1152            .sql
1153            .query_map(
1154                "SELECT chat_id, count(*) AS n
1155                 FROM chats_contacts
1156                 WHERE contact_id > ? AND chat_id > ?
1157                 AND add_timestamp >= remove_timestamp
1158                 GROUP BY chat_id",
1159                (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
1160                |row| {
1161                    let chat_id: ChatId = row.get(0)?;
1162                    let size: f64 = row.get(1)?;
1163                    Ok((chat_id, size))
1164                },
1165                |rows| {
1166                    rows.collect::<std::result::Result<HashMap<ChatId, f64>, _>>()
1167                        .map_err(Into::into)
1168                },
1169            )
1170            .await
1171            .context("failed to count chat member sizes")?;
1172
1173        let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
1174        let mut chats_with_metrics = Vec::new();
1175        for (chat_id, intersection_size) in intersection {
1176            if intersection_size > 0.0 {
1177                let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
1178                let union_size = our_chat_size + other_chat_size - intersection_size;
1179                let metric = intersection_size / union_size;
1180                chats_with_metrics.push((chat_id, metric))
1181            }
1182        }
1183        chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
1184            metric2
1185                .partial_cmp(metric1)
1186                .unwrap_or(chat_id2.cmp(chat_id1))
1187        });
1188
1189        // Select up to five similar active chats.
1190        let mut res = Vec::new();
1191        let now = time();
1192        for (chat_id, metric) in chats_with_metrics {
1193            if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
1194                if now > chat_timestamp + 42 * 24 * 3600 {
1195                    // Chat was inactive for 42 days, skip.
1196                    continue;
1197                }
1198            }
1199
1200            if metric < 0.1 {
1201                // Chat is unrelated.
1202                break;
1203            }
1204
1205            let chat = Chat::load_from_db(context, chat_id).await?;
1206            if chat.typ != Chattype::Group {
1207                continue;
1208            }
1209
1210            match chat.visibility {
1211                ChatVisibility::Normal | ChatVisibility::Pinned => {}
1212                ChatVisibility::Archived => continue,
1213            }
1214
1215            res.push((chat_id, metric));
1216            if res.len() >= 5 {
1217                break;
1218            }
1219        }
1220
1221        Ok(res)
1222    }
1223
1224    /// Returns similar chats as a [`Chatlist`].
1225    ///
1226    /// [`Chatlist`]: crate::chatlist::Chatlist
1227    pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1228        let chat_ids: Vec<ChatId> = self
1229            .get_similar_chat_ids(context)
1230            .await
1231            .context("failed to get similar chat IDs")?
1232            .into_iter()
1233            .map(|(chat_id, _metric)| chat_id)
1234            .collect();
1235        let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1236        Ok(chatlist)
1237    }
1238
1239    pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1240        let res: Option<String> = context
1241            .sql
1242            .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1243            .await?;
1244        Ok(res
1245            .map(|s| s.parse().unwrap_or_default())
1246            .unwrap_or_default())
1247    }
1248
1249    /// Returns true if the chat is not promoted.
1250    pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1251        let param = self.get_param(context).await?;
1252        let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1253        Ok(unpromoted)
1254    }
1255
1256    /// Returns true if the chat is promoted.
1257    pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1258        let promoted = !self.is_unpromoted(context).await?;
1259        Ok(promoted)
1260    }
1261
1262    /// Returns true if chat is a saved messages chat.
1263    pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1264        Ok(self.get_param(context).await?.exists(Param::Selftalk))
1265    }
1266
1267    /// Returns true if chat is a device chat.
1268    pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1269        Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1270    }
1271
1272    async fn parent_query<T, F>(
1273        self,
1274        context: &Context,
1275        fields: &str,
1276        state_out_min: MessageState,
1277        f: F,
1278    ) -> Result<Option<T>>
1279    where
1280        F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1281        T: Send + 'static,
1282    {
1283        let sql = &context.sql;
1284        let query = format!(
1285            "SELECT {fields} \
1286             FROM msgs \
1287             WHERE chat_id=? \
1288             AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1289             AND NOT hidden \
1290             AND download_state={} \
1291             AND from_id != {} \
1292             ORDER BY timestamp DESC, id DESC \
1293             LIMIT 1;",
1294            MessageState::InFresh as u32,
1295            MessageState::InSeen as u32,
1296            state_out_min as u32,
1297            // Do not reply to not fully downloaded messages. Such a message could be a group chat
1298            // message that we assigned to 1:1 chat.
1299            DownloadState::Done as u32,
1300            // Do not reference info messages, they are not actually sent out
1301            // and have Message-IDs unknown to other chat members.
1302            ContactId::INFO.to_u32(),
1303        );
1304        sql.query_row_optional(&query, (self,), f).await
1305    }
1306
1307    async fn get_parent_mime_headers(
1308        self,
1309        context: &Context,
1310        state_out_min: MessageState,
1311    ) -> Result<Option<(String, String, String)>> {
1312        self.parent_query(
1313            context,
1314            "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1315            state_out_min,
1316            |row: &rusqlite::Row| {
1317                let rfc724_mid: String = row.get(0)?;
1318                let mime_in_reply_to: String = row.get(1)?;
1319                let mime_references: String = row.get(2)?;
1320                Ok((rfc724_mid, mime_in_reply_to, mime_references))
1321            },
1322        )
1323        .await
1324    }
1325
1326    /// Returns multi-line text summary of encryption preferences of all chat contacts.
1327    ///
1328    /// This can be used to find out if encryption is not available because
1329    /// keys for some users are missing or simply because the majority of the users in a group
1330    /// prefer plaintext emails.
1331    ///
1332    /// To get more verbose summary for a contact, including its key fingerprint, use [`Contact::get_encrinfo`].
1333    pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1334        let chat = Chat::load_from_db(context, self).await?;
1335        if !chat.is_encrypted(context).await? {
1336            return Ok(stock_str::encr_none(context).await);
1337        }
1338
1339        let mut ret = stock_str::e2e_available(context).await + "\n";
1340
1341        for contact_id in get_chat_contacts(context, self)
1342            .await?
1343            .iter()
1344            .filter(|&contact_id| !contact_id.is_special())
1345        {
1346            let contact = Contact::get_by_id(context, *contact_id).await?;
1347            let addr = contact.get_addr();
1348            debug_assert!(contact.is_key_contact());
1349            let fingerprint = contact
1350                .fingerprint()
1351                .context("Contact does not have a fingerprint in encrypted chat")?;
1352            if contact.public_key(context).await?.is_some() {
1353                ret += &format!("\n{addr}\n{fingerprint}\n");
1354            } else {
1355                ret += &format!("\n{addr}\n(key missing)\n{fingerprint}\n");
1356            }
1357        }
1358
1359        Ok(ret.trim().to_string())
1360    }
1361
1362    /// Bad evil escape hatch.
1363    ///
1364    /// Avoid using this, eventually types should be cleaned up enough
1365    /// that it is no longer necessary.
1366    pub fn to_u32(self) -> u32 {
1367        self.0
1368    }
1369
1370    pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1371        context
1372            .sql
1373            .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1374            .await?;
1375        Ok(())
1376    }
1377
1378    /// Returns true if the chat is protected.
1379    pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
1380        let protection_status = context
1381            .sql
1382            .query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
1383            .await?
1384            .unwrap_or_default();
1385        Ok(protection_status)
1386    }
1387
1388    /// Returns the sort timestamp for a new message in the chat.
1389    ///
1390    /// `message_timestamp` should be either the message "sent" timestamp or a timestamp of the
1391    /// corresponding event in case of a system message (usually the current system time).
1392    /// `always_sort_to_bottom` makes this adjust the returned timestamp up so that the message goes
1393    /// to the chat bottom.
1394    /// `received` -- whether the message is received. Otherwise being sent.
1395    /// `incoming` -- whether the message is incoming.
1396    pub(crate) async fn calc_sort_timestamp(
1397        self,
1398        context: &Context,
1399        message_timestamp: i64,
1400        always_sort_to_bottom: bool,
1401        received: bool,
1402        incoming: bool,
1403    ) -> Result<i64> {
1404        let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1405
1406        let last_msg_time: Option<i64> = if always_sort_to_bottom {
1407            // get newest message for this chat
1408
1409            // Let hidden messages also be ordered with protection messages because hidden messages
1410            // also can be or not be verified, so let's preserve this information -- even it's not
1411            // used currently, it can be useful in the future versions.
1412            context
1413                .sql
1414                .query_get_value(
1415                    "SELECT MAX(timestamp)
1416                     FROM msgs
1417                     WHERE chat_id=? AND state!=?
1418                     HAVING COUNT(*) > 0",
1419                    (self, MessageState::OutDraft),
1420                )
1421                .await?
1422        } else if received {
1423            // Received messages shouldn't mingle with just sent ones and appear somewhere in the
1424            // middle of the chat, so we go after the newest non fresh message.
1425            //
1426            // But if a received outgoing message is older than some seen message, better sort the
1427            // received message purely by timestamp. We could place it just before that seen
1428            // message, but anyway the user may not notice it.
1429            //
1430            // NB: Received outgoing messages may break sorting of fresh incoming ones, but this
1431            // shouldn't happen frequently. Seen incoming messages don't really break sorting of
1432            // fresh ones, they rather mean that older incoming messages are actually seen as well.
1433            context
1434                .sql
1435                .query_row_optional(
1436                    "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1437                     FROM msgs
1438                     WHERE chat_id=? AND hidden=0 AND state>?
1439                     HAVING COUNT(*) > 0",
1440                    (MessageState::InSeen, self, MessageState::InFresh),
1441                    |row| {
1442                        let ts: i64 = row.get(0)?;
1443                        let ts_sent_seen: i64 = row.get(1)?;
1444                        Ok((ts, ts_sent_seen))
1445                    },
1446                )
1447                .await?
1448                .and_then(|(ts, ts_sent_seen)| {
1449                    match incoming || ts_sent_seen <= message_timestamp {
1450                        true => Some(ts),
1451                        false => None,
1452                    }
1453                })
1454        } else {
1455            None
1456        };
1457
1458        if let Some(last_msg_time) = last_msg_time {
1459            if last_msg_time > sort_timestamp {
1460                sort_timestamp = last_msg_time;
1461            }
1462        }
1463
1464        Ok(sort_timestamp)
1465    }
1466}
1467
1468impl std::fmt::Display for ChatId {
1469    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1470        if self.is_trash() {
1471            write!(f, "Chat#Trash")
1472        } else if self.is_archived_link() {
1473            write!(f, "Chat#ArchivedLink")
1474        } else if self.is_alldone_hint() {
1475            write!(f, "Chat#AlldoneHint")
1476        } else if self.is_special() {
1477            write!(f, "Chat#Special{}", self.0)
1478        } else {
1479            write!(f, "Chat#{}", self.0)
1480        }
1481    }
1482}
1483
1484/// Allow converting [ChatId] to an SQLite type.
1485///
1486/// This allows you to directly store [ChatId] into the database as
1487/// well as query for a [ChatId].
1488impl rusqlite::types::ToSql for ChatId {
1489    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
1490        let val = rusqlite::types::Value::Integer(i64::from(self.0));
1491        let out = rusqlite::types::ToSqlOutput::Owned(val);
1492        Ok(out)
1493    }
1494}
1495
1496/// Allow converting an SQLite integer directly into [ChatId].
1497impl rusqlite::types::FromSql for ChatId {
1498    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1499        i64::column_result(value).and_then(|val| {
1500            if 0 <= val && val <= i64::from(u32::MAX) {
1501                Ok(ChatId::new(val as u32))
1502            } else {
1503                Err(rusqlite::types::FromSqlError::OutOfRange(val))
1504            }
1505        })
1506    }
1507}
1508
1509/// An object representing a single chat in memory.
1510/// Chat objects are created using eg. `Chat::load_from_db`
1511/// and are not updated on database changes;
1512/// if you want an update, you have to recreate the object.
1513#[derive(Debug, Clone, Deserialize, Serialize)]
1514pub struct Chat {
1515    /// Database ID.
1516    pub id: ChatId,
1517
1518    /// Chat type, e.g. 1:1 chat, group chat, mailing list.
1519    pub typ: Chattype,
1520
1521    /// Chat name.
1522    pub name: String,
1523
1524    /// Whether the chat is archived or pinned.
1525    pub visibility: ChatVisibility,
1526
1527    /// Group ID. For [`Chattype::Mailinglist`] -- mailing list address. Empty for 1:1 chats and
1528    /// ad-hoc groups.
1529    pub grpid: String,
1530
1531    /// Whether the chat is blocked, unblocked or a contact request.
1532    pub blocked: Blocked,
1533
1534    /// Additional chat parameters stored in the database.
1535    pub param: Params,
1536
1537    /// If location streaming is enabled in the chat.
1538    is_sending_locations: bool,
1539
1540    /// Duration of the chat being muted.
1541    pub mute_duration: MuteDuration,
1542
1543    /// If the chat is protected (verified).
1544    pub(crate) protected: ProtectionStatus,
1545}
1546
1547impl Chat {
1548    /// Loads chat from the database by its ID.
1549    pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1550        let mut chat = context
1551            .sql
1552            .query_row(
1553                "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1554                    c.blocked, c.locations_send_until, c.muted_until, c.protected
1555             FROM chats c
1556             WHERE c.id=?;",
1557                (chat_id,),
1558                |row| {
1559                    let c = Chat {
1560                        id: chat_id,
1561                        typ: row.get(0)?,
1562                        name: row.get::<_, String>(1)?,
1563                        grpid: row.get::<_, String>(2)?,
1564                        param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1565                        visibility: row.get(4)?,
1566                        blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1567                        is_sending_locations: row.get(6)?,
1568                        mute_duration: row.get(7)?,
1569                        protected: row.get(8)?,
1570                    };
1571                    Ok(c)
1572                },
1573            )
1574            .await
1575            .context(format!("Failed loading chat {chat_id} from database"))?;
1576
1577        if chat.id.is_archived_link() {
1578            chat.name = stock_str::archived_chats(context).await;
1579        } else {
1580            if chat.typ == Chattype::Single && chat.name.is_empty() {
1581                // chat.name is set to contact.display_name on changes,
1582                // however, if things went wrong somehow, we do this here explicitly.
1583                let mut chat_name = "Err [Name not found]".to_owned();
1584                match get_chat_contacts(context, chat.id).await {
1585                    Ok(contacts) => {
1586                        if let Some(contact_id) = contacts.first() {
1587                            if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1588                                contact.get_display_name().clone_into(&mut chat_name);
1589                            }
1590                        }
1591                    }
1592                    Err(err) => {
1593                        error!(
1594                            context,
1595                            "Failed to load contacts for {}: {:#}.", chat.id, err
1596                        );
1597                    }
1598                }
1599                chat.name = chat_name;
1600            }
1601            if chat.param.exists(Param::Selftalk) {
1602                chat.name = stock_str::saved_messages(context).await;
1603            } else if chat.param.exists(Param::Devicetalk) {
1604                chat.name = stock_str::device_messages(context).await;
1605            }
1606        }
1607
1608        Ok(chat)
1609    }
1610
1611    /// Returns whether this is the `saved messages` chat
1612    pub fn is_self_talk(&self) -> bool {
1613        self.param.exists(Param::Selftalk)
1614    }
1615
1616    /// Returns true if chat is a device chat.
1617    pub fn is_device_talk(&self) -> bool {
1618        self.param.exists(Param::Devicetalk)
1619    }
1620
1621    /// Returns true if chat is a mailing list.
1622    pub fn is_mailing_list(&self) -> bool {
1623        self.typ == Chattype::Mailinglist
1624    }
1625
1626    /// Returns None if user can send messages to this chat.
1627    ///
1628    /// Otherwise returns a reason useful for logging.
1629    pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1630        self.why_cant_send_ex(context, &|_| false).await
1631    }
1632
1633    pub(crate) async fn why_cant_send_ex(
1634        &self,
1635        context: &Context,
1636        skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1637    ) -> Result<Option<CantSendReason>> {
1638        use CantSendReason::*;
1639        // NB: Don't forget to update Chatlist::try_load() when changing this function!
1640
1641        if self.id.is_special() {
1642            let reason = SpecialChat;
1643            if !skip_fn(&reason) {
1644                return Ok(Some(reason));
1645            }
1646        }
1647        if self.is_device_talk() {
1648            let reason = DeviceChat;
1649            if !skip_fn(&reason) {
1650                return Ok(Some(reason));
1651            }
1652        }
1653        if self.is_contact_request() {
1654            let reason = ContactRequest;
1655            if !skip_fn(&reason) {
1656                return Ok(Some(reason));
1657            }
1658        }
1659        if self.is_protection_broken() {
1660            let reason = ProtectionBroken;
1661            if !skip_fn(&reason) {
1662                return Ok(Some(reason));
1663            }
1664        }
1665        if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1666            let reason = ReadOnlyMailingList;
1667            if !skip_fn(&reason) {
1668                return Ok(Some(reason));
1669            }
1670        }
1671        if self.typ == Chattype::InBroadcast {
1672            let reason = InBroadcast;
1673            if !skip_fn(&reason) {
1674                return Ok(Some(reason));
1675            }
1676        }
1677
1678        // Do potentially slow checks last and after calls to `skip_fn` which should be fast.
1679        let reason = NotAMember;
1680        if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1681            return Ok(Some(reason));
1682        }
1683
1684        let reason = MissingKey;
1685        if !skip_fn(&reason) && self.typ == Chattype::Single {
1686            let contact_ids = get_chat_contacts(context, self.id).await?;
1687            if let Some(contact_id) = contact_ids.first() {
1688                let contact = Contact::get_by_id(context, *contact_id).await?;
1689                if contact.is_key_contact() && contact.public_key(context).await?.is_none() {
1690                    return Ok(Some(reason));
1691                }
1692            }
1693        }
1694
1695        Ok(None)
1696    }
1697
1698    /// Returns true if can send to the chat.
1699    ///
1700    /// This function can be used by the UI to decide whether to display the input box.
1701    pub async fn can_send(&self, context: &Context) -> Result<bool> {
1702        Ok(self.why_cant_send(context).await?.is_none())
1703    }
1704
1705    /// Checks if the user is part of a chat
1706    /// and has basically the permissions to edit the chat therefore.
1707    /// The function does not check if the chat type allows editing of concrete elements.
1708    pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1709        match self.typ {
1710            Chattype::Single | Chattype::OutBroadcast | Chattype::Mailinglist => Ok(true),
1711            Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1712            Chattype::InBroadcast => Ok(false),
1713        }
1714    }
1715
1716    pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1717        context
1718            .sql
1719            .execute(
1720                "UPDATE chats SET param=? WHERE id=?",
1721                (self.param.to_string(), self.id),
1722            )
1723            .await?;
1724        Ok(())
1725    }
1726
1727    /// Returns chat ID.
1728    pub fn get_id(&self) -> ChatId {
1729        self.id
1730    }
1731
1732    /// Returns chat type.
1733    pub fn get_type(&self) -> Chattype {
1734        self.typ
1735    }
1736
1737    /// Returns chat name.
1738    pub fn get_name(&self) -> &str {
1739        &self.name
1740    }
1741
1742    /// Returns mailing list address where messages are sent to.
1743    pub fn get_mailinglist_addr(&self) -> Option<&str> {
1744        self.param.get(Param::ListPost)
1745    }
1746
1747    /// Returns profile image path for the chat.
1748    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1749        if self.id.is_archived_link() {
1750            // This is not a real chat, but the "Archive" button
1751            // that is shown at the top of the chats list
1752            return Ok(Some(get_archive_icon(context).await?));
1753        } else if self.is_device_talk() {
1754            return Ok(Some(get_device_icon(context).await?));
1755        } else if self.is_self_talk() {
1756            return Ok(Some(get_saved_messages_icon(context).await?));
1757        } else if self.typ == Chattype::Single {
1758            // For 1:1 chats, we always use the same avatar as for the contact
1759            // This is before the `self.is_encrypted()` check, because that function
1760            // has two database calls, i.e. it's slow
1761            let contacts = get_chat_contacts(context, self.id).await?;
1762            if let Some(contact_id) = contacts.first() {
1763                let contact = Contact::get_by_id(context, *contact_id).await?;
1764                return contact.get_profile_image(context).await;
1765            }
1766        } else if !self.is_encrypted(context).await? {
1767            // This is an address-contact chat, show a special avatar that marks it as such
1768            return Ok(Some(get_abs_path(
1769                context,
1770                Path::new(&get_address_contact_icon(context).await?),
1771            )));
1772        } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1773            // Load the group avatar, or the device-chat / saved-messages icon
1774            if !image_rel.is_empty() {
1775                return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1776            }
1777        }
1778        Ok(None)
1779    }
1780
1781    /// Returns chat avatar color.
1782    ///
1783    /// For 1:1 chats, the color is calculated from the contact's address.
1784    /// For group chats the color is calculated from the chat name.
1785    pub async fn get_color(&self, context: &Context) -> Result<u32> {
1786        let mut color = 0;
1787
1788        if self.typ == Chattype::Single {
1789            let contacts = get_chat_contacts(context, self.id).await?;
1790            if let Some(contact_id) = contacts.first() {
1791                if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1792                    color = contact.get_color();
1793                }
1794            }
1795        } else {
1796            color = str_to_color(&self.name);
1797        }
1798
1799        Ok(color)
1800    }
1801
1802    /// Returns a struct describing the current state of the chat.
1803    ///
1804    /// This is somewhat experimental, even more so than the rest of
1805    /// deltachat, and the data returned is still subject to change.
1806    pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1807        let draft = match self.id.get_draft(context).await? {
1808            Some(message) => message.text,
1809            _ => String::new(),
1810        };
1811        Ok(ChatInfo {
1812            id: self.id,
1813            type_: self.typ as u32,
1814            name: self.name.clone(),
1815            archived: self.visibility == ChatVisibility::Archived,
1816            param: self.param.to_string(),
1817            is_sending_locations: self.is_sending_locations,
1818            color: self.get_color(context).await?,
1819            profile_image: self
1820                .get_profile_image(context)
1821                .await?
1822                .unwrap_or_else(std::path::PathBuf::new),
1823            draft,
1824            is_muted: self.is_muted(),
1825            ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1826        })
1827    }
1828
1829    /// Returns chat visibilitiy, e.g. whether it is archived or pinned.
1830    pub fn get_visibility(&self) -> ChatVisibility {
1831        self.visibility
1832    }
1833
1834    /// Returns true if chat is a contact request.
1835    ///
1836    /// Messages cannot be sent to such chat and read receipts are not
1837    /// sent until the chat is manually unblocked.
1838    pub fn is_contact_request(&self) -> bool {
1839        self.blocked == Blocked::Request
1840    }
1841
1842    /// Returns true if the chat is not promoted.
1843    pub fn is_unpromoted(&self) -> bool {
1844        self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1845    }
1846
1847    /// Returns true if the chat is promoted.
1848    /// This means a message has been sent to it and it _not_ only exists on the users device.
1849    pub fn is_promoted(&self) -> bool {
1850        !self.is_unpromoted()
1851    }
1852
1853    /// Returns true if chat protection is enabled.
1854    ///
1855    /// UI should display a green checkmark
1856    /// in the chat title,
1857    /// in the chat profile title and
1858    /// in the chatlist item
1859    /// if chat protection is enabled.
1860    /// UI should also display a green checkmark
1861    /// in the contact profile
1862    /// if 1:1 chat with this contact exists and is protected.
1863    pub fn is_protected(&self) -> bool {
1864        self.protected == ProtectionStatus::Protected
1865    }
1866
1867    /// Returns true if the chat is encrypted.
1868    pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
1869        let is_encrypted = self.is_protected()
1870            || match self.typ {
1871                Chattype::Single => {
1872                    let chat_contact_ids = get_chat_contacts(context, self.id).await?;
1873                    if let Some(contact_id) = chat_contact_ids.first() {
1874                        if *contact_id == ContactId::DEVICE {
1875                            true
1876                        } else {
1877                            let contact = Contact::get_by_id(context, *contact_id).await?;
1878                            contact.is_key_contact()
1879                        }
1880                    } else {
1881                        true
1882                    }
1883                }
1884                Chattype::Group => {
1885                    // Do not encrypt ad-hoc groups.
1886                    !self.grpid.is_empty()
1887                }
1888                Chattype::Mailinglist => false,
1889                Chattype::OutBroadcast | Chattype::InBroadcast => true,
1890            };
1891        Ok(is_encrypted)
1892    }
1893
1894    /// Returns true if the chat was protected, and then an incoming message broke this protection.
1895    ///
1896    /// This function is only useful if the UI enabled the `verified_one_on_one_chats` feature flag,
1897    /// otherwise it will return false for all chats.
1898    ///
1899    /// 1:1 chats are automatically set as protected when a contact is verified.
1900    /// When a message comes in that is not encrypted / signed correctly,
1901    /// the chat is automatically set as unprotected again.
1902    /// `is_protection_broken()` will return true until `chat_id.accept()` is called.
1903    ///
1904    /// The UI should let the user confirm that this is OK with a message like
1905    /// `Bob sent a message from another device. Tap to learn more`
1906    /// and then call `chat_id.accept()`.
1907    pub fn is_protection_broken(&self) -> bool {
1908        match self.protected {
1909            ProtectionStatus::Protected => false,
1910            ProtectionStatus::Unprotected => false,
1911            ProtectionStatus::ProtectionBroken => true,
1912        }
1913    }
1914
1915    /// Returns true if location streaming is enabled in the chat.
1916    pub fn is_sending_locations(&self) -> bool {
1917        self.is_sending_locations
1918    }
1919
1920    /// Returns true if the chat is currently muted.
1921    pub fn is_muted(&self) -> bool {
1922        match self.mute_duration {
1923            MuteDuration::NotMuted => false,
1924            MuteDuration::Forever => true,
1925            MuteDuration::Until(when) => when > SystemTime::now(),
1926        }
1927    }
1928
1929    /// Returns chat member list timestamp.
1930    pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1931        if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1932            Ok(member_list_timestamp)
1933        } else {
1934            Ok(self.id.created_timestamp(context).await?)
1935        }
1936    }
1937
1938    /// Returns true if member list is stale,
1939    /// i.e. has not been updated for 60 days.
1940    ///
1941    /// This is used primarily to detect the case
1942    /// where the user just restored an old backup.
1943    pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1944        let now = time();
1945        let member_list_ts = self.member_list_timestamp(context).await?;
1946        let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1947            >= member_list_ts.saturating_add(60 * 24 * 3600);
1948        Ok(is_stale)
1949    }
1950
1951    /// Adds missing values to the msg object,
1952    /// writes the record to the database and returns its msg_id.
1953    ///
1954    /// If `update_msg_id` is set, that record is reused;
1955    /// if `update_msg_id` is None, a new record is created.
1956    async fn prepare_msg_raw(
1957        &mut self,
1958        context: &Context,
1959        msg: &mut Message,
1960        update_msg_id: Option<MsgId>,
1961        timestamp: i64,
1962    ) -> Result<MsgId> {
1963        let mut to_id = 0;
1964        let mut location_id = 0;
1965
1966        if msg.rfc724_mid.is_empty() {
1967            msg.rfc724_mid = create_outgoing_rfc724_mid();
1968        }
1969
1970        if self.typ == Chattype::Single {
1971            if let Some(id) = context
1972                .sql
1973                .query_get_value(
1974                    "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
1975                    (self.id,),
1976                )
1977                .await?
1978            {
1979                to_id = id;
1980            } else {
1981                error!(
1982                    context,
1983                    "Cannot send message, contact for {} not found.", self.id,
1984                );
1985                bail!("Cannot set message, contact for {} not found.", self.id);
1986            }
1987        } else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
1988            && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
1989        {
1990            msg.param.set_int(Param::AttachGroupImage, 1);
1991            self.param
1992                .remove(Param::Unpromoted)
1993                .set_i64(Param::GroupNameTimestamp, timestamp);
1994            self.update_param(context).await?;
1995            // TODO: Remove this compat code needed because Core <= v1.143:
1996            // - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also
1997            //   send them when the group is promoted.
1998            // - doesn't sync QR code tokens for unpromoted groups and the group might be created
1999            //   before an upgrade.
2000            context
2001                .sync_qr_code_tokens(Some(self.grpid.as_str()))
2002                .await
2003                .log_err(context)
2004                .ok();
2005        }
2006
2007        let is_bot = context.get_config_bool(Config::Bot).await?;
2008        msg.param
2009            .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2010
2011        // Set "In-Reply-To:" to identify the message to which the composed message is a reply.
2012        // Set "References:" to identify the "thread" of the conversation.
2013        // Both according to [RFC 5322 3.6.4, page 25](https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4).
2014        let new_references;
2015        if self.is_self_talk() {
2016            // As self-talks are mainly used to transfer data between devices,
2017            // we do not set In-Reply-To/References in this case.
2018            new_references = String::new();
2019        } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2020            // We don't filter `OutPending` and `OutFailed` messages because the new message for
2021            // which `parent_query()` is done may assume that it will be received in a context
2022            // affected by those messages, e.g. they could add new members to a group and the
2023            // new message will contain them in "To:". Anyway recipients must be prepared to
2024            // orphaned references.
2025            self
2026                .id
2027                .get_parent_mime_headers(context, MessageState::OutPending)
2028                .await?
2029        {
2030            // "In-Reply-To:" is not changed if it is set manually.
2031            // This does not affect "References:" header, it will contain "default parent" (the
2032            // latest message in the thread) anyway.
2033            if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2034                msg.in_reply_to = Some(parent_rfc724_mid.clone());
2035            }
2036
2037            // Use parent `In-Reply-To` as a fallback
2038            // in case parent message has no `References` header
2039            // as specified in RFC 5322:
2040            // > If the parent message does not contain
2041            // > a "References:" field but does have an "In-Reply-To:" field
2042            // > containing a single message identifier, then the "References:" field
2043            // > will contain the contents of the parent's "In-Reply-To:" field
2044            // > followed by the contents of the parent's "Message-ID:" field (if
2045            // > any).
2046            let parent_references = if parent_references.is_empty() {
2047                parent_in_reply_to
2048            } else {
2049                parent_references
2050            };
2051
2052            // The whole list of messages referenced may be huge.
2053            // Only take 2 recent references and add third from `In-Reply-To`.
2054            let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2055            references_vec.reverse();
2056
2057            if !parent_rfc724_mid.is_empty()
2058                && !references_vec.contains(&parent_rfc724_mid.as_str())
2059            {
2060                references_vec.push(&parent_rfc724_mid)
2061            }
2062
2063            if references_vec.is_empty() {
2064                // As a fallback, use our Message-ID,
2065                // same as in the case of top-level message.
2066                new_references = msg.rfc724_mid.clone();
2067            } else {
2068                new_references = references_vec.join(" ");
2069            }
2070        } else {
2071            // This is a top-level message.
2072            // Add our Message-ID as first references.
2073            // This allows us to identify replies to our message even if
2074            // email server such as Outlook changes `Message-ID:` header.
2075            // MUAs usually keep the first Message-ID in `References:` header unchanged.
2076            new_references = msg.rfc724_mid.clone();
2077        }
2078
2079        // add independent location to database
2080        if msg.param.exists(Param::SetLatitude) {
2081            if let Ok(row_id) = context
2082                .sql
2083                .insert(
2084                    "INSERT INTO locations \
2085                     (timestamp,from_id,chat_id, latitude,longitude,independent)\
2086                     VALUES (?,?,?, ?,?,1);",
2087                    (
2088                        timestamp,
2089                        ContactId::SELF,
2090                        self.id,
2091                        msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2092                        msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2093                    ),
2094                )
2095                .await
2096            {
2097                location_id = row_id;
2098            }
2099        }
2100
2101        let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2102            EphemeralTimer::Disabled
2103        } else {
2104            self.id.get_ephemeral_timer(context).await?
2105        };
2106        let ephemeral_timestamp = match ephemeral_timer {
2107            EphemeralTimer::Disabled => 0,
2108            EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2109        };
2110
2111        let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2112        let new_mime_headers = if msg.has_html() {
2113            if msg.param.exists(Param::Forwarded) {
2114                msg.get_id().get_html(context).await?
2115            } else {
2116                msg.param.get(Param::SendHtml).map(|s| s.to_string())
2117            }
2118        } else {
2119            None
2120        };
2121        let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2122            let html_part = MimePart::new("text/html", s);
2123            let mut buffer = Vec::new();
2124            let cursor = Cursor::new(&mut buffer);
2125            html_part.write_part(cursor).ok();
2126            String::from_utf8_lossy(&buffer).to_string()
2127        });
2128        let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2129            // We need to add some headers so that they are stripped before formatting HTML by
2130            // `MsgId::get_html()`, not a part of the actual text. Let's add "Content-Type", it's
2131            // anyway a useful metadata about the stored text.
2132            true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2133            false => None,
2134        });
2135        let new_mime_headers = match new_mime_headers {
2136            Some(h) => Some(tokio::task::block_in_place(move || {
2137                buf_compress(h.as_bytes())
2138            })?),
2139            None => None,
2140        };
2141
2142        msg.chat_id = self.id;
2143        msg.from_id = ContactId::SELF;
2144        msg.timestamp_sort = timestamp;
2145
2146        // add message to the database
2147        if let Some(update_msg_id) = update_msg_id {
2148            context
2149                .sql
2150                .execute(
2151                    "UPDATE msgs
2152                     SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2153                         state=?, txt=?, txt_normalized=?, subject=?, param=?,
2154                         hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2155                         mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2156                         ephemeral_timestamp=?
2157                     WHERE id=?;",
2158                    params_slice![
2159                        msg.rfc724_mid,
2160                        msg.chat_id,
2161                        msg.from_id,
2162                        to_id,
2163                        msg.timestamp_sort,
2164                        msg.viewtype,
2165                        msg.state,
2166                        msg_text,
2167                        message::normalize_text(&msg_text),
2168                        &msg.subject,
2169                        msg.param.to_string(),
2170                        msg.hidden,
2171                        msg.in_reply_to.as_deref().unwrap_or_default(),
2172                        new_references,
2173                        new_mime_headers.is_some(),
2174                        new_mime_headers.unwrap_or_default(),
2175                        location_id as i32,
2176                        ephemeral_timer,
2177                        ephemeral_timestamp,
2178                        update_msg_id
2179                    ],
2180                )
2181                .await?;
2182            msg.id = update_msg_id;
2183        } else {
2184            let raw_id = context
2185                .sql
2186                .insert(
2187                    "INSERT INTO msgs (
2188                        rfc724_mid,
2189                        chat_id,
2190                        from_id,
2191                        to_id,
2192                        timestamp,
2193                        type,
2194                        state,
2195                        txt,
2196                        txt_normalized,
2197                        subject,
2198                        param,
2199                        hidden,
2200                        mime_in_reply_to,
2201                        mime_references,
2202                        mime_modified,
2203                        mime_headers,
2204                        mime_compressed,
2205                        location_id,
2206                        ephemeral_timer,
2207                        ephemeral_timestamp)
2208                        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2209                    params_slice![
2210                        msg.rfc724_mid,
2211                        msg.chat_id,
2212                        msg.from_id,
2213                        to_id,
2214                        msg.timestamp_sort,
2215                        msg.viewtype,
2216                        msg.state,
2217                        msg_text,
2218                        message::normalize_text(&msg_text),
2219                        &msg.subject,
2220                        msg.param.to_string(),
2221                        msg.hidden,
2222                        msg.in_reply_to.as_deref().unwrap_or_default(),
2223                        new_references,
2224                        new_mime_headers.is_some(),
2225                        new_mime_headers.unwrap_or_default(),
2226                        location_id as i32,
2227                        ephemeral_timer,
2228                        ephemeral_timestamp
2229                    ],
2230                )
2231                .await?;
2232            context.new_msgs_notify.notify_one();
2233            msg.id = MsgId::new(u32::try_from(raw_id)?);
2234
2235            maybe_set_logging_xdc(context, msg, self.id).await?;
2236            context
2237                .update_webxdc_integration_database(msg, context)
2238                .await?;
2239        }
2240        context.scheduler.interrupt_ephemeral_task().await;
2241        Ok(msg.id)
2242    }
2243
2244    /// Sends a `SyncAction` synchronising chat contacts to other devices.
2245    pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2246        if self.is_encrypted(context).await? {
2247            let fingerprint_addrs = context
2248                .sql
2249                .query_map(
2250                    "SELECT c.fingerprint, c.addr
2251                     FROM contacts c INNER JOIN chats_contacts cc
2252                     ON c.id=cc.contact_id
2253                     WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2254                    (self.id,),
2255                    |row| {
2256                        let fingerprint = row.get(0)?;
2257                        let addr = row.get(1)?;
2258                        Ok((fingerprint, addr))
2259                    },
2260                    |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2261                )
2262                .await?;
2263            self.sync(context, SyncAction::SetPgpContacts(fingerprint_addrs))
2264                .await?;
2265        } else {
2266            let addrs = context
2267                .sql
2268                .query_map(
2269                    "SELECT c.addr \
2270                    FROM contacts c INNER JOIN chats_contacts cc \
2271                    ON c.id=cc.contact_id \
2272                    WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2273                    (self.id,),
2274                    |row| row.get::<_, String>(0),
2275                    |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2276                )
2277                .await?;
2278            self.sync(context, SyncAction::SetContacts(addrs)).await?;
2279        }
2280        Ok(())
2281    }
2282
2283    /// Returns chat id for the purpose of synchronisation across devices.
2284    async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2285        match self.typ {
2286            Chattype::Single => {
2287                if self.is_device_talk() {
2288                    return Ok(Some(SyncId::Device));
2289                }
2290
2291                let mut r = None;
2292                for contact_id in get_chat_contacts(context, self.id).await? {
2293                    if contact_id == ContactId::SELF && !self.is_self_talk() {
2294                        continue;
2295                    }
2296                    if r.is_some() {
2297                        return Ok(None);
2298                    }
2299                    let contact = Contact::get_by_id(context, contact_id).await?;
2300                    if let Some(fingerprint) = contact.fingerprint() {
2301                        r = Some(SyncId::ContactFingerprint(fingerprint.hex()));
2302                    } else {
2303                        r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2304                    }
2305                }
2306                Ok(r)
2307            }
2308            Chattype::OutBroadcast
2309            | Chattype::InBroadcast
2310            | Chattype::Group
2311            | Chattype::Mailinglist => {
2312                if !self.grpid.is_empty() {
2313                    return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2314                }
2315
2316                let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2317                    .id
2318                    .get_parent_mime_headers(context, MessageState::OutDelivered)
2319                    .await?
2320                else {
2321                    warn!(
2322                        context,
2323                        "Chat::get_sync_id({}): No good message identifying the chat found.",
2324                        self.id
2325                    );
2326                    return Ok(None);
2327                };
2328                Ok(Some(SyncId::Msgids(vec![
2329                    parent_in_reply_to,
2330                    parent_rfc724_mid,
2331                ])))
2332            }
2333        }
2334    }
2335
2336    /// Synchronises a chat action to other devices.
2337    pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2338        if let Some(id) = self.get_sync_id(context).await? {
2339            sync(context, id, action).await?;
2340        }
2341        Ok(())
2342    }
2343}
2344
2345pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2346    context
2347        .add_sync_item(SyncData::AlterChat { id, action })
2348        .await?;
2349    context.scheduler.interrupt_inbox().await;
2350    Ok(())
2351}
2352
2353/// Whether the chat is pinned or archived.
2354#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2355#[repr(i8)]
2356pub enum ChatVisibility {
2357    /// Chat is neither archived nor pinned.
2358    Normal = 0,
2359
2360    /// Chat is archived.
2361    Archived = 1,
2362
2363    /// Chat is pinned to the top of the chatlist.
2364    Pinned = 2,
2365}
2366
2367impl rusqlite::types::ToSql for ChatVisibility {
2368    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
2369        let val = rusqlite::types::Value::Integer(*self as i64);
2370        let out = rusqlite::types::ToSqlOutput::Owned(val);
2371        Ok(out)
2372    }
2373}
2374
2375impl rusqlite::types::FromSql for ChatVisibility {
2376    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2377        i64::column_result(value).map(|val| {
2378            match val {
2379                2 => ChatVisibility::Pinned,
2380                1 => ChatVisibility::Archived,
2381                0 => ChatVisibility::Normal,
2382                // fallback to Normal for unknown values, may happen eg. on imports created by a newer version.
2383                _ => ChatVisibility::Normal,
2384            }
2385        })
2386    }
2387}
2388
2389/// The current state of a chat.
2390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2391#[non_exhaustive]
2392pub struct ChatInfo {
2393    /// The chat ID.
2394    pub id: ChatId,
2395
2396    /// The type of chat as a `u32` representation of [Chattype].
2397    ///
2398    /// On the C API this number is one of the
2399    /// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
2400    /// or `DC_CHAT_TYPE_GROUP`
2401    /// constants.
2402    #[serde(rename = "type")]
2403    pub type_: u32,
2404
2405    /// The name of the chat.
2406    pub name: String,
2407
2408    /// Whether the chat is archived.
2409    pub archived: bool,
2410
2411    /// The "params" of the chat.
2412    ///
2413    /// This is the string-serialised version of `Params` currently.
2414    pub param: String,
2415
2416    /// Whether this chat is currently sending location-stream messages.
2417    pub is_sending_locations: bool,
2418
2419    /// Colour this chat should be represented in by the UI.
2420    ///
2421    /// Yes, spelling colour is hard.
2422    pub color: u32,
2423
2424    /// The path to the profile image.
2425    ///
2426    /// If there is no profile image set this will be an empty string
2427    /// currently.
2428    pub profile_image: std::path::PathBuf,
2429
2430    /// The draft message text.
2431    ///
2432    /// If the chat has not draft this is an empty string.
2433    ///
2434    /// TODO: This doesn't seem rich enough, it can not handle drafts
2435    ///       which contain non-text parts.  Perhaps it should be a
2436    ///       simple `has_draft` bool instead.
2437    pub draft: String,
2438
2439    /// Whether the chat is muted
2440    ///
2441    /// The exact time its muted can be found out via the `chat.mute_duration` property
2442    pub is_muted: bool,
2443
2444    /// Ephemeral message timer.
2445    pub ephemeral_timer: EphemeralTimer,
2446    // ToDo:
2447    // - [ ] summary,
2448    // - [ ] lastUpdated,
2449    // - [ ] freshMessageCounter,
2450    // - [ ] email
2451}
2452
2453async fn get_asset_icon(context: &Context, name: &str, bytes: &[u8]) -> Result<PathBuf> {
2454    ensure!(name.starts_with("icon-"));
2455    if let Some(icon) = context.sql.get_raw_config(name).await? {
2456        return Ok(get_abs_path(context, Path::new(&icon)));
2457    }
2458
2459    let blob =
2460        BlobObject::create_and_deduplicate_from_bytes(context, bytes, &format!("{name}.png"))?;
2461    let icon = blob.as_name().to_string();
2462    context.sql.set_raw_config(name, Some(&icon)).await?;
2463
2464    Ok(get_abs_path(context, Path::new(&icon)))
2465}
2466
2467pub(crate) async fn get_saved_messages_icon(context: &Context) -> Result<PathBuf> {
2468    get_asset_icon(
2469        context,
2470        "icon-saved-messages",
2471        include_bytes!("../assets/icon-saved-messages.png"),
2472    )
2473    .await
2474}
2475
2476pub(crate) async fn get_device_icon(context: &Context) -> Result<PathBuf> {
2477    get_asset_icon(
2478        context,
2479        "icon-device",
2480        include_bytes!("../assets/icon-device.png"),
2481    )
2482    .await
2483}
2484
2485pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
2486    get_asset_icon(
2487        context,
2488        "icon-archive",
2489        include_bytes!("../assets/icon-archive.png"),
2490    )
2491    .await
2492}
2493
2494pub(crate) async fn get_address_contact_icon(context: &Context) -> Result<PathBuf> {
2495    get_asset_icon(
2496        context,
2497        "icon-address-contact",
2498        include_bytes!("../assets/icon-address-contact.png"),
2499    )
2500    .await
2501}
2502
2503async fn update_special_chat_name(
2504    context: &Context,
2505    contact_id: ContactId,
2506    name: String,
2507) -> Result<()> {
2508    if let Some(ChatIdBlocked { id: chat_id, .. }) =
2509        ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2510    {
2511        // the `!= name` condition avoids unneeded writes
2512        context
2513            .sql
2514            .execute(
2515                "UPDATE chats SET name=? WHERE id=? AND name!=?",
2516                (&name, chat_id, &name),
2517            )
2518            .await?;
2519    }
2520    Ok(())
2521}
2522
2523pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2524    update_special_chat_name(
2525        context,
2526        ContactId::DEVICE,
2527        stock_str::device_messages(context).await,
2528    )
2529    .await?;
2530    update_special_chat_name(
2531        context,
2532        ContactId::SELF,
2533        stock_str::saved_messages(context).await,
2534    )
2535    .await?;
2536    Ok(())
2537}
2538
2539/// Handle a [`ChatId`] and its [`Blocked`] status at once.
2540///
2541/// This struct is an optimisation to read a [`ChatId`] and its [`Blocked`] status at once
2542/// from the database.  It [`Deref`]s to [`ChatId`] so it can be used as an extension to
2543/// [`ChatId`].
2544///
2545/// [`Deref`]: std::ops::Deref
2546#[derive(Debug)]
2547pub(crate) struct ChatIdBlocked {
2548    /// Chat ID.
2549    pub id: ChatId,
2550
2551    /// Whether the chat is blocked, unblocked or a contact request.
2552    pub blocked: Blocked,
2553}
2554
2555impl ChatIdBlocked {
2556    /// Searches the database for the 1:1 chat with this contact.
2557    ///
2558    /// If no chat is found `None` is returned.
2559    pub async fn lookup_by_contact(
2560        context: &Context,
2561        contact_id: ContactId,
2562    ) -> Result<Option<Self>> {
2563        ensure!(context.sql.is_open().await, "Database not available");
2564        ensure!(
2565            contact_id != ContactId::UNDEFINED,
2566            "Invalid contact id requested"
2567        );
2568
2569        context
2570            .sql
2571            .query_row_optional(
2572                "SELECT c.id, c.blocked
2573                   FROM chats c
2574                  INNER JOIN chats_contacts j
2575                          ON c.id=j.chat_id
2576                  WHERE c.type=100  -- 100 = Chattype::Single
2577                    AND c.id>9      -- 9 = DC_CHAT_ID_LAST_SPECIAL
2578                    AND j.contact_id=?;",
2579                (contact_id,),
2580                |row| {
2581                    let id: ChatId = row.get(0)?;
2582                    let blocked: Blocked = row.get(1)?;
2583                    Ok(ChatIdBlocked { id, blocked })
2584                },
2585            )
2586            .await
2587    }
2588
2589    /// Returns the chat for the 1:1 chat with this contact.
2590    ///
2591    /// If the chat does not yet exist a new one is created, using the provided [`Blocked`]
2592    /// state.
2593    pub async fn get_for_contact(
2594        context: &Context,
2595        contact_id: ContactId,
2596        create_blocked: Blocked,
2597    ) -> Result<Self> {
2598        ensure!(context.sql.is_open().await, "Database not available");
2599        ensure!(
2600            contact_id != ContactId::UNDEFINED,
2601            "Invalid contact id requested"
2602        );
2603
2604        if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2605            // Already exists, no need to create.
2606            return Ok(res);
2607        }
2608
2609        let contact = Contact::get_by_id(context, contact_id).await?;
2610        let chat_name = contact.get_display_name().to_string();
2611        let mut params = Params::new();
2612        match contact_id {
2613            ContactId::SELF => {
2614                params.set_int(Param::Selftalk, 1);
2615            }
2616            ContactId::DEVICE => {
2617                params.set_int(Param::Devicetalk, 1);
2618            }
2619            _ => (),
2620        }
2621
2622        let protected = contact_id == ContactId::SELF || contact.is_verified(context).await?;
2623        let smeared_time = create_smeared_timestamp(context);
2624
2625        let chat_id = context
2626            .sql
2627            .transaction(move |transaction| {
2628                transaction.execute(
2629                    "INSERT INTO chats
2630                     (type, name, param, blocked, created_timestamp, protected)
2631                     VALUES(?, ?, ?, ?, ?, ?)",
2632                    (
2633                        Chattype::Single,
2634                        chat_name,
2635                        params.to_string(),
2636                        create_blocked as u8,
2637                        smeared_time,
2638                        if protected {
2639                            ProtectionStatus::Protected
2640                        } else {
2641                            ProtectionStatus::Unprotected
2642                        },
2643                    ),
2644                )?;
2645                let chat_id = ChatId::new(
2646                    transaction
2647                        .last_insert_rowid()
2648                        .try_into()
2649                        .context("chat table rowid overflows u32")?,
2650                );
2651
2652                transaction.execute(
2653                    "INSERT INTO chats_contacts
2654                 (chat_id, contact_id)
2655                 VALUES((SELECT last_insert_rowid()), ?)",
2656                    (contact_id,),
2657                )?;
2658
2659                Ok(chat_id)
2660            })
2661            .await?;
2662
2663        if protected {
2664            chat_id
2665                .add_protection_msg(
2666                    context,
2667                    ProtectionStatus::Protected,
2668                    Some(contact_id),
2669                    smeared_time,
2670                )
2671                .await?;
2672        }
2673
2674        Ok(Self {
2675            id: chat_id,
2676            blocked: create_blocked,
2677        })
2678    }
2679}
2680
2681async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2682    if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
2683        // the caller should check if the message text is empty
2684    } else if msg.viewtype.has_file() {
2685        let mut blob = msg
2686            .param
2687            .get_file_blob(context)?
2688            .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2689        let send_as_is = msg.viewtype == Viewtype::File;
2690
2691        if msg.viewtype == Viewtype::File
2692            || msg.viewtype == Viewtype::Image
2693            || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2694        {
2695            // Correct the type, take care not to correct already very special
2696            // formats as GIF or VOICE.
2697            //
2698            // Typical conversions:
2699            // - from FILE to AUDIO/VIDEO/IMAGE
2700            // - from FILE/IMAGE to GIF */
2701            if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2702                if msg.viewtype == Viewtype::Sticker {
2703                    if better_type != Viewtype::Image {
2704                        // UIs don't want conversions of `Sticker` to anything other than `Image`.
2705                        msg.param.set_int(Param::ForceSticker, 1);
2706                    }
2707                } else if better_type != Viewtype::Webxdc
2708                    || context
2709                        .ensure_sendable_webxdc_file(&blob.to_abs_path())
2710                        .await
2711                        .is_ok()
2712                {
2713                    msg.viewtype = better_type;
2714                }
2715            }
2716        } else if msg.viewtype == Viewtype::Webxdc {
2717            context
2718                .ensure_sendable_webxdc_file(&blob.to_abs_path())
2719                .await?;
2720        }
2721
2722        if msg.viewtype == Viewtype::Vcard {
2723            msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2724        }
2725
2726        let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
2727        if !send_as_is
2728            && (msg.viewtype == Viewtype::Image
2729                || maybe_sticker && !msg.param.exists(Param::ForceSticker))
2730        {
2731            let new_name = blob
2732                .recode_to_image_size(context, msg.get_filename(), &mut maybe_sticker)
2733                .await?;
2734            msg.param.set(Param::Filename, new_name);
2735            msg.param.set(Param::File, blob.as_name());
2736
2737            if !maybe_sticker {
2738                msg.viewtype = Viewtype::Image;
2739            }
2740        }
2741
2742        if !msg.param.exists(Param::MimeType) {
2743            if let Some((_, mime)) = message::guess_msgtype_from_suffix(msg) {
2744                msg.param.set(Param::MimeType, mime);
2745            }
2746        }
2747
2748        msg.try_calc_and_set_dimensions(context).await?;
2749
2750        info!(
2751            context,
2752            "Attaching \"{}\" for message type #{}.",
2753            blob.to_abs_path().display(),
2754            msg.viewtype
2755        );
2756    } else {
2757        bail!("Cannot send messages of type #{}.", msg.viewtype);
2758    }
2759    Ok(())
2760}
2761
2762/// Returns whether a contact is in a chat or not.
2763pub async fn is_contact_in_chat(
2764    context: &Context,
2765    chat_id: ChatId,
2766    contact_id: ContactId,
2767) -> Result<bool> {
2768    // this function works for group and for normal chats, however, it is more useful
2769    // for group chats.
2770    // ContactId::SELF may be used to check, if the user itself is in a group
2771    // chat (ContactId::SELF is not added to normal chats)
2772
2773    let exists = context
2774        .sql
2775        .exists(
2776            "SELECT COUNT(*) FROM chats_contacts
2777             WHERE chat_id=? AND contact_id=?
2778             AND add_timestamp >= remove_timestamp",
2779            (chat_id, contact_id),
2780        )
2781        .await?;
2782    Ok(exists)
2783}
2784
2785/// Sends a message object to a chat.
2786///
2787/// Sends the event #DC_EVENT_MSGS_CHANGED on success.
2788/// However, this does not imply, the message really reached the recipient -
2789/// sending may be delayed eg. due to network problems. However, from your
2790/// view, you're done with the message. Sooner or later it will find its way.
2791pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2792    ensure!(
2793        !chat_id.is_special(),
2794        "chat_id cannot be a special chat: {chat_id}"
2795    );
2796
2797    if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2798        msg.param.remove(Param::GuaranteeE2ee);
2799        msg.param.remove(Param::ForcePlaintext);
2800        msg.update_param(context).await?;
2801    }
2802
2803    // protect all system messages against RTLO attacks
2804    if msg.is_system_message() {
2805        msg.text = sanitize_bidi_characters(&msg.text);
2806    }
2807
2808    if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2809        if !msg.hidden {
2810            context.emit_msgs_changed(msg.chat_id, msg.id);
2811        }
2812
2813        if msg.param.exists(Param::SetLatitude) {
2814            context.emit_location_changed(Some(ContactId::SELF)).await?;
2815        }
2816
2817        context.scheduler.interrupt_smtp().await;
2818    }
2819
2820    Ok(msg.id)
2821}
2822
2823/// Tries to send a message synchronously.
2824///
2825/// Creates jobs in the `smtp` table, then drectly opens an SMTP connection and sends the
2826/// message. If this fails, the jobs remain in the database for later sending.
2827pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2828    let rowids = prepare_send_msg(context, chat_id, msg).await?;
2829    if rowids.is_empty() {
2830        return Ok(msg.id);
2831    }
2832    let mut smtp = crate::smtp::Smtp::new();
2833    for rowid in rowids {
2834        send_msg_to_smtp(context, &mut smtp, rowid)
2835            .await
2836            .context("failed to send message, queued for later sending")?;
2837    }
2838    context.emit_msgs_changed(msg.chat_id, msg.id);
2839    Ok(msg.id)
2840}
2841
2842/// Prepares a message to be sent out.
2843///
2844/// Returns row ids of the `smtp` table.
2845async fn prepare_send_msg(
2846    context: &Context,
2847    chat_id: ChatId,
2848    msg: &mut Message,
2849) -> Result<Vec<i64>> {
2850    let mut chat = Chat::load_from_db(context, chat_id).await?;
2851
2852    let skip_fn = |reason: &CantSendReason| match reason {
2853        CantSendReason::ProtectionBroken | CantSendReason::ContactRequest => {
2854            // Allow securejoin messages, they are supposed to repair the verification.
2855            // If the chat is a contact request, let the user accept it later.
2856            msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2857        }
2858        // Allow to send "Member removed" messages so we can leave the group.
2859        // Necessary checks should be made anyway before removing contact
2860        // from the chat.
2861        CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2862        CantSendReason::MissingKey => msg
2863            .param
2864            .get_bool(Param::ForcePlaintext)
2865            .unwrap_or_default(),
2866        _ => false,
2867    };
2868    if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2869        bail!("Cannot send to {chat_id}: {reason}");
2870    }
2871
2872    // Check a quote reply is not leaking data from other chats.
2873    // This is meant as a last line of defence, the UI should check that before as well.
2874    // (We allow Chattype::Single in general for "Reply Privately";
2875    // checking for exact contact_id will produce false positives when ppl just left the group)
2876    if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2877        if let Some(quoted_message) = msg.quoted_message(context).await? {
2878            if quoted_message.chat_id != chat_id {
2879                bail!(
2880                    "Quote of message from {} cannot be sent to {chat_id}",
2881                    quoted_message.chat_id
2882                );
2883            }
2884        }
2885    }
2886
2887    // check current MessageState for drafts (to keep msg_id) ...
2888    let update_msg_id = if msg.state == MessageState::OutDraft {
2889        msg.hidden = false;
2890        if !msg.id.is_special() && msg.chat_id == chat_id {
2891            Some(msg.id)
2892        } else {
2893            None
2894        }
2895    } else {
2896        None
2897    };
2898
2899    // ... then change the MessageState in the message object
2900    msg.state = MessageState::OutPending;
2901
2902    prepare_msg_blob(context, msg).await?;
2903    if !msg.hidden {
2904        chat_id.unarchive_if_not_muted(context, msg.state).await?;
2905    }
2906    msg.id = chat
2907        .prepare_msg_raw(
2908            context,
2909            msg,
2910            update_msg_id,
2911            create_smeared_timestamp(context),
2912        )
2913        .await?;
2914    msg.chat_id = chat_id;
2915
2916    let row_ids = create_send_msg_jobs(context, msg)
2917        .await
2918        .context("Failed to create send jobs")?;
2919    Ok(row_ids)
2920}
2921
2922/// Constructs jobs for sending a message and inserts them into the appropriate table.
2923///
2924/// Returns row ids if `smtp` table jobs were created or an empty `Vec` otherwise.
2925///
2926/// The caller has to interrupt SMTP loop or otherwise process new rows.
2927pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2928    if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2929        msg.chat_id
2930            .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2931            .await?;
2932    }
2933
2934    let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2935    let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
2936    let attach_selfavatar = mimefactory.attach_selfavatar;
2937    let mut recipients = mimefactory.recipients();
2938
2939    let from = context.get_primary_self_addr().await?;
2940    let lowercase_from = from.to_lowercase();
2941
2942    // Send BCC to self if it is enabled.
2943    //
2944    // Previous versions of Delta Chat did not send BCC self
2945    // if DeleteServerAfter was set to immediately delete messages
2946    // from the server. This is not the case anymore
2947    // because BCC-self messages are also used to detect
2948    // that message was sent if SMTP server is slow to respond
2949    // and connection is frequently lost
2950    // before receiving status line. NB: This is not a problem for chatmail servers, so `BccSelf`
2951    // disabled by default is fine.
2952    //
2953    // `from` must be the last addr, see `receive_imf_inner()` why.
2954    recipients.retain(|x| x.to_lowercase() != lowercase_from);
2955    if (context.get_config_bool(Config::BccSelf).await?
2956        || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
2957        && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
2958    {
2959        recipients.push(from);
2960    }
2961
2962    // Default Webxdc integrations are hidden messages and must not be sent out
2963    if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2964        recipients.clear();
2965    }
2966
2967    if recipients.is_empty() {
2968        // may happen eg. for groups with only SELF and bcc_self disabled
2969        info!(
2970            context,
2971            "Message {} has no recipient, skipping smtp-send.", msg.id
2972        );
2973        msg.param.set_int(Param::GuaranteeE2ee, 1);
2974        msg.update_param(context).await?;
2975        msg.id.set_delivered(context).await?;
2976        msg.state = MessageState::OutDelivered;
2977        return Ok(Vec::new());
2978    }
2979
2980    let rendered_msg = match mimefactory.render(context).await {
2981        Ok(res) => Ok(res),
2982        Err(err) => {
2983            message::set_msg_failed(context, msg, &err.to_string()).await?;
2984            Err(err)
2985        }
2986    }?;
2987
2988    if needs_encryption && !rendered_msg.is_encrypted {
2989        /* unrecoverable */
2990        message::set_msg_failed(
2991            context,
2992            msg,
2993            "End-to-end-encryption unavailable unexpectedly.",
2994        )
2995        .await?;
2996        bail!(
2997            "e2e encryption unavailable {} - {:?}",
2998            msg.id,
2999            needs_encryption
3000        );
3001    }
3002
3003    let now = smeared_time(context);
3004
3005    if rendered_msg.last_added_location_id.is_some() {
3006        if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3007            error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3008        }
3009    }
3010
3011    if attach_selfavatar {
3012        if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3013            error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3014        }
3015    }
3016
3017    if rendered_msg.is_encrypted && !needs_encryption {
3018        msg.param.set_int(Param::GuaranteeE2ee, 1);
3019        msg.update_param(context).await?;
3020    }
3021
3022    msg.subject.clone_from(&rendered_msg.subject);
3023    msg.update_subject(context).await?;
3024    let chunk_size = context.get_max_smtp_rcpt_to().await?;
3025    let trans_fn = |t: &mut rusqlite::Transaction| {
3026        let mut row_ids = Vec::<i64>::new();
3027        if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3028            t.execute(
3029                &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3030                (),
3031            )?;
3032            t.execute(
3033                "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3034                (&rendered_msg.message, msg.id),
3035            )?;
3036        } else {
3037            for recipients_chunk in recipients.chunks(chunk_size) {
3038                let recipients_chunk = recipients_chunk.join(" ");
3039                let row_id = t.execute(
3040                    "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3041                    VALUES            (?1,         ?2,         ?3,   ?4)",
3042                    (
3043                        &rendered_msg.rfc724_mid,
3044                        recipients_chunk,
3045                        &rendered_msg.message,
3046                        msg.id,
3047                    ),
3048                )?;
3049                row_ids.push(row_id.try_into()?);
3050            }
3051        }
3052        Ok(row_ids)
3053    };
3054    context.sql.transaction(trans_fn).await
3055}
3056
3057/// Sends a text message to the given chat.
3058///
3059/// Returns database ID of the sent message.
3060pub async fn send_text_msg(
3061    context: &Context,
3062    chat_id: ChatId,
3063    text_to_send: String,
3064) -> Result<MsgId> {
3065    ensure!(
3066        !chat_id.is_special(),
3067        "bad chat_id, can not be a special chat: {}",
3068        chat_id
3069    );
3070
3071    let mut msg = Message::new_text(text_to_send);
3072    send_msg(context, chat_id, &mut msg).await
3073}
3074
3075/// Sends chat members a request to edit the given message's text.
3076pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3077    let mut original_msg = Message::load_from_db(context, msg_id).await?;
3078    ensure!(
3079        original_msg.from_id == ContactId::SELF,
3080        "Can edit only own messages"
3081    );
3082    ensure!(!original_msg.is_info(), "Cannot edit info messages");
3083    ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3084    ensure!(
3085        original_msg.viewtype != Viewtype::VideochatInvitation,
3086        "Cannot edit videochat invitations"
3087    );
3088    ensure!(
3089        !original_msg.text.is_empty(), // avoid complexity in UI element changes. focus is typos and rewordings
3090        "Cannot add text"
3091    );
3092    ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3093    if original_msg.text == new_text {
3094        info!(context, "Text unchanged.");
3095        return Ok(());
3096    }
3097
3098    save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3099
3100    let mut edit_msg = Message::new_text(EDITED_PREFIX.to_owned() + &new_text); // prefix only set for nicer display in Non-Delta-MUAs
3101    edit_msg.set_quote(context, Some(&original_msg)).await?; // quote only set for nicer display in Non-Delta-MUAs
3102    if original_msg.get_showpadlock() {
3103        edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3104    }
3105    edit_msg
3106        .param
3107        .set(Param::TextEditFor, original_msg.rfc724_mid);
3108    edit_msg.hidden = true;
3109    send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3110    Ok(())
3111}
3112
3113pub(crate) async fn save_text_edit_to_db(
3114    context: &Context,
3115    original_msg: &mut Message,
3116    new_text: &str,
3117) -> Result<()> {
3118    original_msg.param.set_int(Param::IsEdited, 1);
3119    context
3120        .sql
3121        .execute(
3122            "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3123            (
3124                new_text,
3125                message::normalize_text(new_text),
3126                original_msg.param.to_string(),
3127                original_msg.id,
3128            ),
3129        )
3130        .await?;
3131    context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3132    Ok(())
3133}
3134
3135/// Sends invitation to a videochat.
3136pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3137    ensure!(
3138        !chat_id.is_special(),
3139        "video chat invitation cannot be sent to special chat: {}",
3140        chat_id
3141    );
3142
3143    let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3144        if !instance.is_empty() {
3145            instance
3146        } else {
3147            bail!("webrtc_instance is empty");
3148        }
3149    } else {
3150        bail!("webrtc_instance not set");
3151    };
3152
3153    let instance = Message::create_webrtc_instance(&instance, &create_id());
3154
3155    let mut msg = Message::new(Viewtype::VideochatInvitation);
3156    msg.param.set(Param::WebrtcRoom, &instance);
3157    msg.text =
3158        stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3159            .await;
3160    send_msg(context, chat_id, &mut msg).await
3161}
3162
3163/// Chat message list request options.
3164#[derive(Debug)]
3165pub struct MessageListOptions {
3166    /// Return only info messages.
3167    pub info_only: bool,
3168
3169    /// Add day markers before each date regarding the local timezone.
3170    pub add_daymarker: bool,
3171}
3172
3173/// Returns all messages belonging to the chat.
3174pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3175    get_chat_msgs_ex(
3176        context,
3177        chat_id,
3178        MessageListOptions {
3179            info_only: false,
3180            add_daymarker: false,
3181        },
3182    )
3183    .await
3184}
3185
3186/// Returns messages belonging to the chat according to the given options.
3187pub async fn get_chat_msgs_ex(
3188    context: &Context,
3189    chat_id: ChatId,
3190    options: MessageListOptions,
3191) -> Result<Vec<ChatItem>> {
3192    let MessageListOptions {
3193        info_only,
3194        add_daymarker,
3195    } = options;
3196    let process_row = if info_only {
3197        |row: &rusqlite::Row| {
3198            // is_info logic taken from Message.is_info()
3199            let params = row.get::<_, String>("param")?;
3200            let (from_id, to_id) = (
3201                row.get::<_, ContactId>("from_id")?,
3202                row.get::<_, ContactId>("to_id")?,
3203            );
3204            let is_info_msg: bool = from_id == ContactId::INFO
3205                || to_id == ContactId::INFO
3206                || match Params::from_str(&params) {
3207                    Ok(p) => {
3208                        let cmd = p.get_cmd();
3209                        cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3210                    }
3211                    _ => false,
3212                };
3213
3214            Ok((
3215                row.get::<_, i64>("timestamp")?,
3216                row.get::<_, MsgId>("id")?,
3217                !is_info_msg,
3218            ))
3219        }
3220    } else {
3221        |row: &rusqlite::Row| {
3222            Ok((
3223                row.get::<_, i64>("timestamp")?,
3224                row.get::<_, MsgId>("id")?,
3225                false,
3226            ))
3227        }
3228    };
3229    let process_rows = |rows: rusqlite::MappedRows<_>| {
3230        // It is faster to sort here rather than
3231        // let sqlite execute an ORDER BY clause.
3232        let mut sorted_rows = Vec::new();
3233        for row in rows {
3234            let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3235            if !exclude_message {
3236                sorted_rows.push((ts, curr_id));
3237            }
3238        }
3239        sorted_rows.sort_unstable();
3240
3241        let mut ret = Vec::new();
3242        let mut last_day = 0;
3243        let cnv_to_local = gm2local_offset();
3244
3245        for (ts, curr_id) in sorted_rows {
3246            if add_daymarker {
3247                let curr_local_timestamp = ts + cnv_to_local;
3248                let curr_day = curr_local_timestamp / 86400;
3249                if curr_day != last_day {
3250                    ret.push(ChatItem::DayMarker {
3251                        timestamp: curr_day * 86400, // Convert day back to Unix timestamp
3252                    });
3253                    last_day = curr_day;
3254                }
3255            }
3256            ret.push(ChatItem::Message { msg_id: curr_id });
3257        }
3258        Ok(ret)
3259    };
3260
3261    let items = if info_only {
3262        context
3263            .sql
3264            .query_map(
3265        // GLOB is used here instead of LIKE because it is case-sensitive
3266                "SELECT m.id AS id, m.timestamp AS timestamp, m.param AS param, m.from_id AS from_id, m.to_id AS to_id
3267               FROM msgs m
3268              WHERE m.chat_id=?
3269                AND m.hidden=0
3270                AND (
3271                    m.param GLOB \"*S=*\"
3272                    OR m.from_id == ?
3273                    OR m.to_id == ?
3274                );",
3275                (chat_id, ContactId::INFO, ContactId::INFO),
3276                process_row,
3277                process_rows,
3278            )
3279            .await?
3280    } else {
3281        context
3282            .sql
3283            .query_map(
3284                "SELECT m.id AS id, m.timestamp AS timestamp
3285               FROM msgs m
3286              WHERE m.chat_id=?
3287                AND m.hidden=0;",
3288                (chat_id,),
3289                process_row,
3290                process_rows,
3291            )
3292            .await?
3293    };
3294    Ok(items)
3295}
3296
3297/// Marks all messages in the chat as noticed.
3298/// If the given chat-id is the archive-link, marks all messages in all archived chats as noticed.
3299pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3300    // "WHERE" below uses the index `(state, hidden, chat_id)`, see get_fresh_msg_cnt() for reasoning
3301    // the additional SELECT statement may speed up things as no write-blocking is needed.
3302    if chat_id.is_archived_link() {
3303        let chat_ids_in_archive = context
3304            .sql
3305            .query_map(
3306                "SELECT DISTINCT(m.chat_id) FROM msgs m
3307                    LEFT JOIN chats c ON m.chat_id=c.id
3308                    WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3309                (),
3310                |row| row.get::<_, ChatId>(0),
3311                |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3312            )
3313            .await?;
3314        if chat_ids_in_archive.is_empty() {
3315            return Ok(());
3316        }
3317
3318        context
3319            .sql
3320            .transaction(|transaction| {
3321                let mut stmt = transaction.prepare(
3322                    "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3323                )?;
3324                for chat_id_in_archive in &chat_ids_in_archive {
3325                    stmt.execute((chat_id_in_archive,))?;
3326                }
3327                Ok(())
3328            })
3329            .await?;
3330
3331        for chat_id_in_archive in chat_ids_in_archive {
3332            start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3333            context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3334            chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3335        }
3336    } else {
3337        start_chat_ephemeral_timers(context, chat_id).await?;
3338
3339        let noticed_msgs_count = context
3340            .sql
3341            .execute(
3342                "UPDATE msgs
3343            SET state=?
3344          WHERE state=?
3345            AND hidden=0
3346            AND chat_id=?;",
3347                (MessageState::InNoticed, MessageState::InFresh, chat_id),
3348            )
3349            .await?;
3350
3351        // This is to trigger emitting `MsgsNoticed` on other devices when reactions are noticed
3352        // locally (i.e. when the chat was opened locally).
3353        let hidden_messages = context
3354            .sql
3355            .query_map(
3356                "SELECT id, rfc724_mid FROM msgs
3357                    WHERE state=?
3358                      AND hidden=1
3359                      AND chat_id=?
3360                    ORDER BY id LIMIT 100", // LIMIT to 100 in order to avoid blocking the UI too long, usually there will be less than 100 messages anyway
3361                (MessageState::InFresh, chat_id), // No need to check for InNoticed messages, because reactions are never InNoticed
3362                |row| {
3363                    let msg_id: MsgId = row.get(0)?;
3364                    let rfc724_mid: String = row.get(1)?;
3365                    Ok((msg_id, rfc724_mid))
3366                },
3367                |rows| {
3368                    rows.collect::<std::result::Result<Vec<_>, _>>()
3369                        .map_err(Into::into)
3370                },
3371            )
3372            .await?;
3373        for (msg_id, rfc724_mid) in &hidden_messages {
3374            message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3375            imap::markseen_on_imap_table(context, rfc724_mid).await?;
3376        }
3377
3378        if noticed_msgs_count == 0 {
3379            return Ok(());
3380        }
3381    }
3382
3383    context.emit_event(EventType::MsgsNoticed(chat_id));
3384    chatlist_events::emit_chatlist_item_changed(context, chat_id);
3385    context.on_archived_chats_maybe_noticed();
3386    Ok(())
3387}
3388
3389/// Marks messages preceding outgoing messages as noticed.
3390///
3391/// In a chat, if there is an outgoing message, it can be assumed that all previous
3392/// messages were noticed. So, this function takes a Vec of messages that were
3393/// just received, and for all the outgoing messages, it marks all
3394/// previous messages as noticed.
3395pub(crate) async fn mark_old_messages_as_noticed(
3396    context: &Context,
3397    mut msgs: Vec<ReceivedMsg>,
3398) -> Result<()> {
3399    msgs.retain(|m| m.state.is_outgoing());
3400    if msgs.is_empty() {
3401        return Ok(());
3402    }
3403
3404    let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3405    for msg in msgs {
3406        let chat_id = msg.chat_id;
3407        if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3408            if msg.sort_timestamp > existing_msg.sort_timestamp {
3409                msgs_by_chat.insert(chat_id, msg);
3410            }
3411        } else {
3412            msgs_by_chat.insert(chat_id, msg);
3413        }
3414    }
3415
3416    let changed_chats = context
3417        .sql
3418        .transaction(|transaction| {
3419            let mut changed_chats = Vec::new();
3420            for (_, msg) in msgs_by_chat {
3421                let changed_rows = transaction.execute(
3422                    "UPDATE msgs
3423            SET state=?
3424          WHERE state=?
3425            AND hidden=0
3426            AND chat_id=?
3427            AND timestamp<=?;",
3428                    (
3429                        MessageState::InNoticed,
3430                        MessageState::InFresh,
3431                        msg.chat_id,
3432                        msg.sort_timestamp,
3433                    ),
3434                )?;
3435                if changed_rows > 0 {
3436                    changed_chats.push(msg.chat_id);
3437                }
3438            }
3439            Ok(changed_chats)
3440        })
3441        .await?;
3442
3443    if !changed_chats.is_empty() {
3444        info!(
3445            context,
3446            "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3447        );
3448        context.on_archived_chats_maybe_noticed();
3449    }
3450
3451    for c in changed_chats {
3452        start_chat_ephemeral_timers(context, c).await?;
3453        context.emit_event(EventType::MsgsNoticed(c));
3454        chatlist_events::emit_chatlist_item_changed(context, c);
3455    }
3456
3457    Ok(())
3458}
3459
3460/// Returns all database message IDs of the given types.
3461///
3462/// If `chat_id` is None, return messages from any chat.
3463///
3464/// `Viewtype::Unknown` can be used for `msg_type2` and `msg_type3`
3465/// if less than 3 viewtypes are requested.
3466pub async fn get_chat_media(
3467    context: &Context,
3468    chat_id: Option<ChatId>,
3469    msg_type: Viewtype,
3470    msg_type2: Viewtype,
3471    msg_type3: Viewtype,
3472) -> Result<Vec<MsgId>> {
3473    let list = if msg_type == Viewtype::Webxdc
3474        && msg_type2 == Viewtype::Unknown
3475        && msg_type3 == Viewtype::Unknown
3476    {
3477        context
3478            .sql
3479            .query_map(
3480                "SELECT id
3481               FROM msgs
3482              WHERE (1=? OR chat_id=?)
3483                AND chat_id != ?
3484                AND type = ?
3485                AND hidden=0
3486              ORDER BY max(timestamp, timestamp_rcvd), id;",
3487                (
3488                    chat_id.is_none(),
3489                    chat_id.unwrap_or_else(|| ChatId::new(0)),
3490                    DC_CHAT_ID_TRASH,
3491                    Viewtype::Webxdc,
3492                ),
3493                |row| row.get::<_, MsgId>(0),
3494                |ids| Ok(ids.flatten().collect()),
3495            )
3496            .await?
3497    } else {
3498        context
3499            .sql
3500            .query_map(
3501                "SELECT id
3502               FROM msgs
3503              WHERE (1=? OR chat_id=?)
3504                AND chat_id != ?
3505                AND type IN (?, ?, ?)
3506                AND hidden=0
3507              ORDER BY timestamp, id;",
3508                (
3509                    chat_id.is_none(),
3510                    chat_id.unwrap_or_else(|| ChatId::new(0)),
3511                    DC_CHAT_ID_TRASH,
3512                    msg_type,
3513                    if msg_type2 != Viewtype::Unknown {
3514                        msg_type2
3515                    } else {
3516                        msg_type
3517                    },
3518                    if msg_type3 != Viewtype::Unknown {
3519                        msg_type3
3520                    } else {
3521                        msg_type
3522                    },
3523                ),
3524                |row| row.get::<_, MsgId>(0),
3525                |ids| Ok(ids.flatten().collect()),
3526            )
3527            .await?
3528    };
3529    Ok(list)
3530}
3531
3532/// Returns a vector of contact IDs for given chat ID.
3533pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3534    // Normal chats do not include SELF.  Group chats do (as it may happen that one is deleted from a
3535    // groupchat but the chats stays visible, moreover, this makes displaying lists easier)
3536
3537    let list = context
3538        .sql
3539        .query_map(
3540            "SELECT cc.contact_id
3541               FROM chats_contacts cc
3542               LEFT JOIN contacts c
3543                      ON c.id=cc.contact_id
3544              WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3545              ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3546            (chat_id,),
3547            |row| row.get::<_, ContactId>(0),
3548            |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3549        )
3550        .await?;
3551
3552    Ok(list)
3553}
3554
3555/// Returns a vector of contact IDs for given chat ID that are no longer part of the group.
3556///
3557/// Members that have been removed recently are in the beginning of the list.
3558pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3559    let now = time();
3560    let list = context
3561        .sql
3562        .query_map(
3563            "SELECT cc.contact_id
3564             FROM chats_contacts cc
3565             LEFT JOIN contacts c
3566                  ON c.id=cc.contact_id
3567             WHERE cc.chat_id=?
3568             AND cc.add_timestamp < cc.remove_timestamp
3569             AND ? < cc.remove_timestamp
3570             ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3571            (chat_id, now.saturating_sub(60 * 24 * 3600)),
3572            |row| row.get::<_, ContactId>(0),
3573            |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3574        )
3575        .await?;
3576
3577    Ok(list)
3578}
3579
3580/// Creates a group chat with a given `name`.
3581pub async fn create_group_chat(
3582    context: &Context,
3583    protect: ProtectionStatus,
3584    chat_name: &str,
3585) -> Result<ChatId> {
3586    let chat_name = sanitize_single_line(chat_name);
3587    ensure!(!chat_name.is_empty(), "Invalid chat name");
3588
3589    let grpid = create_id();
3590
3591    let timestamp = create_smeared_timestamp(context);
3592    let row_id = context
3593        .sql
3594        .insert(
3595            "INSERT INTO chats
3596        (type, name, grpid, param, created_timestamp)
3597        VALUES(?, ?, ?, \'U=1\', ?);",
3598            (Chattype::Group, chat_name, grpid, timestamp),
3599        )
3600        .await?;
3601
3602    let chat_id = ChatId::new(u32::try_from(row_id)?);
3603    add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3604
3605    context.emit_msgs_changed_without_ids();
3606    chatlist_events::emit_chatlist_changed(context);
3607    chatlist_events::emit_chatlist_item_changed(context, chat_id);
3608
3609    if protect == ProtectionStatus::Protected {
3610        chat_id
3611            .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3612            .await?;
3613    }
3614
3615    if !context.get_config_bool(Config::Bot).await?
3616        && !context.get_config_bool(Config::SkipStartMessages).await?
3617    {
3618        let text = stock_str::new_group_send_first_message(context).await;
3619        add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3620    }
3621
3622    Ok(chat_id)
3623}
3624
3625/// Create a new **broadcast channel**
3626/// (called "Channel" in the UI).
3627///
3628/// Broadcast channels are similar to groups on the sending device,
3629/// however, recipients get the messages in a read-only chat
3630/// and will not see who the other members are.
3631///
3632/// Called `broadcast` here rather than `channel`,
3633/// because the word "channel" already appears a lot in the code,
3634/// which would make it hard to grep for it.
3635///
3636/// After creation, the chat contains no recipients and is in _unpromoted_ state;
3637/// see [`create_group_chat`] for more information on the unpromoted state.
3638///
3639/// Returns the created chat's id.
3640pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
3641    let grpid = create_id();
3642    create_broadcast_ex(context, Sync, grpid, chat_name).await
3643}
3644
3645pub(crate) async fn create_broadcast_ex(
3646    context: &Context,
3647    sync: sync::Sync,
3648    grpid: String,
3649    chat_name: String,
3650) -> Result<ChatId> {
3651    let row_id = {
3652        let chat_name = &chat_name;
3653        let grpid = &grpid;
3654        let trans_fn = |t: &mut rusqlite::Transaction| {
3655            let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3656            ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3657            if cnt == 1 {
3658                return Ok(t.query_row(
3659                    "SELECT id FROM chats WHERE grpid=? AND type=?",
3660                    (grpid, Chattype::OutBroadcast),
3661                    |row| {
3662                        let id: isize = row.get(0)?;
3663                        Ok(id)
3664                    },
3665                )?);
3666            }
3667            t.execute(
3668                "INSERT INTO chats \
3669                (type, name, grpid, param, created_timestamp) \
3670                VALUES(?, ?, ?, \'U=1\', ?);",
3671                (
3672                    Chattype::OutBroadcast,
3673                    &chat_name,
3674                    &grpid,
3675                    create_smeared_timestamp(context),
3676                ),
3677            )?;
3678            Ok(t.last_insert_rowid().try_into()?)
3679        };
3680        context.sql.transaction(trans_fn).await?
3681    };
3682    let chat_id = ChatId::new(u32::try_from(row_id)?);
3683
3684    context.emit_msgs_changed_without_ids();
3685    chatlist_events::emit_chatlist_changed(context);
3686
3687    if sync.into() {
3688        let id = SyncId::Grpid(grpid);
3689        let action = SyncAction::CreateBroadcast(chat_name);
3690        self::sync(context, id, action).await.log_err(context).ok();
3691    }
3692
3693    Ok(chat_id)
3694}
3695
3696/// Set chat contacts in the `chats_contacts` table.
3697pub(crate) async fn update_chat_contacts_table(
3698    context: &Context,
3699    timestamp: i64,
3700    id: ChatId,
3701    contacts: &HashSet<ContactId>,
3702) -> Result<()> {
3703    context
3704        .sql
3705        .transaction(move |transaction| {
3706            // Bump `remove_timestamp` to at least `now`
3707            // even for members from `contacts`.
3708            // We add members from `contacts` back below.
3709            transaction.execute(
3710                "UPDATE chats_contacts
3711                 SET remove_timestamp=MAX(add_timestamp+1, ?)
3712                 WHERE chat_id=?",
3713                (timestamp, id),
3714            )?;
3715
3716            if !contacts.is_empty() {
3717                let mut statement = transaction.prepare(
3718                    "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3719                     VALUES                     (?1,      ?2,         ?3)
3720                     ON CONFLICT (chat_id, contact_id)
3721                     DO UPDATE SET add_timestamp=remove_timestamp",
3722                )?;
3723
3724                for contact_id in contacts {
3725                    // We bumped `add_timestamp` for existing rows above,
3726                    // so on conflict it is enough to set `add_timestamp = remove_timestamp`
3727                    // and this guarantees that `add_timestamp` is no less than `timestamp`.
3728                    statement.execute((id, contact_id, timestamp))?;
3729                }
3730            }
3731            Ok(())
3732        })
3733        .await?;
3734    Ok(())
3735}
3736
3737/// Adds contacts to the `chats_contacts` table.
3738pub(crate) async fn add_to_chat_contacts_table(
3739    context: &Context,
3740    timestamp: i64,
3741    chat_id: ChatId,
3742    contact_ids: &[ContactId],
3743) -> Result<()> {
3744    context
3745        .sql
3746        .transaction(move |transaction| {
3747            let mut add_statement = transaction.prepare(
3748                "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3749                 ON CONFLICT (chat_id, contact_id)
3750                 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3751            )?;
3752
3753            for contact_id in contact_ids {
3754                add_statement.execute((chat_id, contact_id, timestamp))?;
3755            }
3756            Ok(())
3757        })
3758        .await?;
3759
3760    Ok(())
3761}
3762
3763/// Removes a contact from the chat
3764/// by updating the `remove_timestamp`.
3765pub(crate) async fn remove_from_chat_contacts_table(
3766    context: &Context,
3767    chat_id: ChatId,
3768    contact_id: ContactId,
3769) -> Result<()> {
3770    let now = time();
3771    context
3772        .sql
3773        .execute(
3774            "UPDATE chats_contacts
3775             SET remove_timestamp=MAX(add_timestamp+1, ?)
3776             WHERE chat_id=? AND contact_id=?",
3777            (now, chat_id, contact_id),
3778        )
3779        .await?;
3780    Ok(())
3781}
3782
3783/// Adds a contact to the chat.
3784/// If the group is promoted, also sends out a system message to all group members
3785pub async fn add_contact_to_chat(
3786    context: &Context,
3787    chat_id: ChatId,
3788    contact_id: ContactId,
3789) -> Result<()> {
3790    add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3791    Ok(())
3792}
3793
3794pub(crate) async fn add_contact_to_chat_ex(
3795    context: &Context,
3796    mut sync: sync::Sync,
3797    chat_id: ChatId,
3798    contact_id: ContactId,
3799    from_handshake: bool,
3800) -> Result<bool> {
3801    ensure!(!chat_id.is_special(), "can not add member to special chats");
3802    let contact = Contact::get_by_id(context, contact_id).await?;
3803    let mut msg = Message::new(Viewtype::default());
3804
3805    chat_id.reset_gossiped_timestamp(context).await?;
3806
3807    // this also makes sure, no contacts are added to special or normal chats
3808    let mut chat = Chat::load_from_db(context, chat_id).await?;
3809    ensure!(
3810        chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
3811        "{} is not a group/broadcast where one can add members",
3812        chat_id
3813    );
3814    ensure!(
3815        Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3816        "invalid contact_id {} for adding to group",
3817        contact_id
3818    );
3819    ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3820    ensure!(
3821        chat.typ != Chattype::OutBroadcast || contact_id != ContactId::SELF,
3822        "Cannot add SELF to broadcast channel."
3823    );
3824    ensure!(
3825        chat.is_encrypted(context).await? == contact.is_key_contact(),
3826        "Only key-contacts can be added to encrypted chats"
3827    );
3828
3829    if !chat.is_self_in_chat(context).await? {
3830        context.emit_event(EventType::ErrorSelfNotInGroup(
3831            "Cannot add contact to group; self not in group.".into(),
3832        ));
3833        bail!("can not add contact because the account is not part of the group/broadcast");
3834    }
3835
3836    let sync_qr_code_tokens;
3837    if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3838        chat.param
3839            .remove(Param::Unpromoted)
3840            .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3841        chat.update_param(context).await?;
3842        sync_qr_code_tokens = true;
3843    } else {
3844        sync_qr_code_tokens = false;
3845    }
3846
3847    if context.is_self_addr(contact.get_addr()).await? {
3848        // ourself is added using ContactId::SELF, do not add this address explicitly.
3849        // if SELF is not in the group, members cannot be added at all.
3850        warn!(
3851            context,
3852            "Invalid attempt to add self e-mail address to group."
3853        );
3854        return Ok(false);
3855    }
3856
3857    if is_contact_in_chat(context, chat_id, contact_id).await? {
3858        if !from_handshake {
3859            return Ok(true);
3860        }
3861    } else {
3862        // else continue and send status mail
3863        if chat.is_protected() && !contact.is_verified(context).await? {
3864            error!(
3865                context,
3866                "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
3867            );
3868            return Ok(false);
3869        }
3870        if is_contact_in_chat(context, chat_id, contact_id).await? {
3871            return Ok(false);
3872        }
3873        add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3874    }
3875    if chat.typ == Chattype::Group && chat.is_promoted() {
3876        msg.viewtype = Viewtype::Text;
3877
3878        let contact_addr = contact.get_addr().to_lowercase();
3879        msg.text = stock_str::msg_add_member_local(context, contact.id, ContactId::SELF).await;
3880        msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3881        msg.param.set(Param::Arg, contact_addr);
3882        msg.param.set_int(Param::Arg2, from_handshake.into());
3883        msg.param
3884            .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3885        send_msg(context, chat_id, &mut msg).await?;
3886
3887        sync = Nosync;
3888        // TODO: Remove this compat code needed because Core <= v1.143:
3889        // - doesn't accept synchronization of QR code tokens for unpromoted groups, so we also send
3890        //   them when the group is promoted.
3891        // - doesn't sync QR code tokens for unpromoted groups and the group might be created before
3892        //   an upgrade.
3893        if sync_qr_code_tokens
3894            && context
3895                .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3896                .await
3897                .log_err(context)
3898                .is_ok()
3899        {
3900            context.scheduler.interrupt_inbox().await;
3901        }
3902    }
3903    context.emit_event(EventType::ChatModified(chat_id));
3904    if sync.into() {
3905        chat.sync_contacts(context).await.log_err(context).ok();
3906    }
3907    Ok(true)
3908}
3909
3910/// Returns true if an avatar should be attached in the given chat.
3911///
3912/// This function does not check if the avatar is set.
3913/// If avatar is not set and this function returns `true`,
3914/// a `Chat-User-Avatar: 0` header should be sent to reset the avatar.
3915pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3916    let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3917    let needs_attach = context
3918        .sql
3919        .query_map(
3920            "SELECT c.selfavatar_sent
3921             FROM chats_contacts cc
3922             LEFT JOIN contacts c ON c.id=cc.contact_id
3923             WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3924            (chat_id, ContactId::SELF),
3925            |row| Ok(row.get::<_, i64>(0)),
3926            |rows| {
3927                let mut needs_attach = false;
3928                for row in rows {
3929                    let row = row?;
3930                    let selfavatar_sent = row?;
3931                    if selfavatar_sent < timestamp_some_days_ago {
3932                        needs_attach = true;
3933                    }
3934                }
3935                Ok(needs_attach)
3936            },
3937        )
3938        .await?;
3939    Ok(needs_attach)
3940}
3941
3942/// Chat mute duration.
3943#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3944pub enum MuteDuration {
3945    /// Chat is not muted.
3946    NotMuted,
3947
3948    /// Chat is muted until the user unmutes the chat.
3949    Forever,
3950
3951    /// Chat is muted for a limited period of time.
3952    Until(std::time::SystemTime),
3953}
3954
3955impl rusqlite::types::ToSql for MuteDuration {
3956    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
3957        let duration: i64 = match &self {
3958            MuteDuration::NotMuted => 0,
3959            MuteDuration::Forever => -1,
3960            MuteDuration::Until(when) => {
3961                let duration = when
3962                    .duration_since(SystemTime::UNIX_EPOCH)
3963                    .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
3964                i64::try_from(duration.as_secs())
3965                    .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
3966            }
3967        };
3968        let val = rusqlite::types::Value::Integer(duration);
3969        let out = rusqlite::types::ToSqlOutput::Owned(val);
3970        Ok(out)
3971    }
3972}
3973
3974impl rusqlite::types::FromSql for MuteDuration {
3975    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
3976        // Negative values other than -1 should not be in the
3977        // database.  If found they'll be NotMuted.
3978        match i64::column_result(value)? {
3979            0 => Ok(MuteDuration::NotMuted),
3980            -1 => Ok(MuteDuration::Forever),
3981            n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
3982                Some(t) => Ok(MuteDuration::Until(t)),
3983                None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
3984            },
3985            _ => Ok(MuteDuration::NotMuted),
3986        }
3987    }
3988}
3989
3990/// Mutes the chat for a given duration or unmutes it.
3991pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
3992    set_muted_ex(context, Sync, chat_id, duration).await
3993}
3994
3995pub(crate) async fn set_muted_ex(
3996    context: &Context,
3997    sync: sync::Sync,
3998    chat_id: ChatId,
3999    duration: MuteDuration,
4000) -> Result<()> {
4001    ensure!(!chat_id.is_special(), "Invalid chat ID");
4002    context
4003        .sql
4004        .execute(
4005            "UPDATE chats SET muted_until=? WHERE id=?;",
4006            (duration, chat_id),
4007        )
4008        .await
4009        .context(format!("Failed to set mute duration for {chat_id}"))?;
4010    context.emit_event(EventType::ChatModified(chat_id));
4011    chatlist_events::emit_chatlist_item_changed(context, chat_id);
4012    if sync.into() {
4013        let chat = Chat::load_from_db(context, chat_id).await?;
4014        chat.sync(context, SyncAction::SetMuted(duration))
4015            .await
4016            .log_err(context)
4017            .ok();
4018    }
4019    Ok(())
4020}
4021
4022/// Removes contact from the chat.
4023pub async fn remove_contact_from_chat(
4024    context: &Context,
4025    chat_id: ChatId,
4026    contact_id: ContactId,
4027) -> Result<()> {
4028    ensure!(
4029        !chat_id.is_special(),
4030        "bad chat_id, can not be special chat: {}",
4031        chat_id
4032    );
4033    ensure!(
4034        !contact_id.is_special() || contact_id == ContactId::SELF,
4035        "Cannot remove special contact"
4036    );
4037
4038    let mut msg = Message::new(Viewtype::default());
4039
4040    let chat = Chat::load_from_db(context, chat_id).await?;
4041    if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
4042        if !chat.is_self_in_chat(context).await? {
4043            let err_msg = format!(
4044                "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4045            );
4046            context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4047            bail!("{}", err_msg);
4048        } else {
4049            let mut sync = Nosync;
4050
4051            if chat.is_promoted() {
4052                remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4053            } else {
4054                context
4055                    .sql
4056                    .execute(
4057                        "DELETE FROM chats_contacts
4058                         WHERE chat_id=? AND contact_id=?",
4059                        (chat_id, contact_id),
4060                    )
4061                    .await?;
4062            }
4063
4064            // We do not return an error if the contact does not exist in the database.
4065            // This allows to delete dangling references to deleted contacts
4066            // in case of the database becoming inconsistent due to a bug.
4067            if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4068                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;
4082                    if contact_id == ContactId::SELF {
4083                        res?;
4084                        set_group_explicitly_left(context, &chat.grpid).await?;
4085                    } else if let Err(e) = res {
4086                        warn!(
4087                            context,
4088                            "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}."
4089                        );
4090                    }
4091                } else {
4092                    sync = Sync;
4093                }
4094            }
4095            context.emit_event(EventType::ChatModified(chat_id));
4096            if sync.into() {
4097                chat.sync_contacts(context).await.log_err(context).ok();
4098            }
4099        }
4100    } else {
4101        bail!("Cannot remove members from non-group chats.");
4102    }
4103
4104    Ok(())
4105}
4106
4107async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4108    if !is_group_explicitly_left(context, grpid).await? {
4109        context
4110            .sql
4111            .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4112            .await?;
4113    }
4114
4115    Ok(())
4116}
4117
4118pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4119    let exists = context
4120        .sql
4121        .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4122        .await?;
4123    Ok(exists)
4124}
4125
4126/// Sets group or mailing list chat name.
4127pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4128    rename_ex(context, Sync, chat_id, new_name).await
4129}
4130
4131async fn rename_ex(
4132    context: &Context,
4133    mut sync: sync::Sync,
4134    chat_id: ChatId,
4135    new_name: &str,
4136) -> Result<()> {
4137    let new_name = sanitize_single_line(new_name);
4138    /* the function only sets the names of group chats; normal chats get their names from the contacts */
4139    let mut success = false;
4140
4141    ensure!(!new_name.is_empty(), "Invalid name");
4142    ensure!(!chat_id.is_special(), "Invalid chat ID");
4143
4144    let chat = Chat::load_from_db(context, chat_id).await?;
4145    let mut msg = Message::new(Viewtype::default());
4146
4147    if chat.typ == Chattype::Group
4148        || chat.typ == Chattype::Mailinglist
4149        || chat.typ == Chattype::OutBroadcast
4150    {
4151        if chat.name == new_name {
4152            success = true;
4153        } else if !chat.is_self_in_chat(context).await? {
4154            context.emit_event(EventType::ErrorSelfNotInGroup(
4155                "Cannot set chat name; self not in group".into(),
4156            ));
4157        } else {
4158            context
4159                .sql
4160                .execute(
4161                    "UPDATE chats SET name=? WHERE id=?;",
4162                    (new_name.to_string(), chat_id),
4163                )
4164                .await?;
4165            if chat.is_promoted()
4166                && !chat.is_mailing_list()
4167                && sanitize_single_line(&chat.name) != new_name
4168            {
4169                msg.viewtype = Viewtype::Text;
4170                msg.text =
4171                    stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4172                msg.param.set_cmd(SystemMessage::GroupNameChanged);
4173                if !chat.name.is_empty() {
4174                    msg.param.set(Param::Arg, &chat.name);
4175                }
4176                msg.id = send_msg(context, chat_id, &mut msg).await?;
4177                context.emit_msgs_changed(chat_id, msg.id);
4178                sync = Nosync;
4179            }
4180            context.emit_event(EventType::ChatModified(chat_id));
4181            chatlist_events::emit_chatlist_item_changed(context, chat_id);
4182            success = true;
4183        }
4184    }
4185
4186    if !success {
4187        bail!("Failed to set name");
4188    }
4189    if sync.into() && chat.name != new_name {
4190        let sync_name = new_name.to_string();
4191        chat.sync(context, SyncAction::Rename(sync_name))
4192            .await
4193            .log_err(context)
4194            .ok();
4195    }
4196    Ok(())
4197}
4198
4199/// Sets a new profile image for the chat.
4200///
4201/// The profile image can only be set when you are a member of the
4202/// chat.  To remove the profile image pass an empty string for the
4203/// `new_image` parameter.
4204pub async fn set_chat_profile_image(
4205    context: &Context,
4206    chat_id: ChatId,
4207    new_image: &str, // XXX use PathBuf
4208) -> Result<()> {
4209    ensure!(!chat_id.is_special(), "Invalid chat ID");
4210    let mut chat = Chat::load_from_db(context, chat_id).await?;
4211    ensure!(
4212        chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
4213        "Can only set profile image for groups / broadcasts"
4214    );
4215    ensure!(
4216        !chat.grpid.is_empty(),
4217        "Cannot set profile image for ad hoc groups"
4218    );
4219    /* we should respect this - whatever we send to the group, it gets discarded anyway! */
4220    if !chat.is_self_in_chat(context).await? {
4221        context.emit_event(EventType::ErrorSelfNotInGroup(
4222            "Cannot set chat profile image; self not in group.".into(),
4223        ));
4224        bail!("Failed to set profile image");
4225    }
4226    let mut msg = Message::new(Viewtype::Text);
4227    msg.param
4228        .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4229    if new_image.is_empty() {
4230        chat.param.remove(Param::ProfileImage);
4231        msg.param.remove(Param::Arg);
4232        msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4233    } else {
4234        let mut image_blob = BlobObject::create_and_deduplicate(
4235            context,
4236            Path::new(new_image),
4237            Path::new(new_image),
4238        )?;
4239        image_blob.recode_to_avatar_size(context).await?;
4240        chat.param.set(Param::ProfileImage, image_blob.as_name());
4241        msg.param.set(Param::Arg, image_blob.as_name());
4242        msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4243    }
4244    chat.update_param(context).await?;
4245    if chat.is_promoted() && !chat.is_mailing_list() {
4246        msg.id = send_msg(context, chat_id, &mut msg).await?;
4247        context.emit_msgs_changed(chat_id, msg.id);
4248    }
4249    context.emit_event(EventType::ChatModified(chat_id));
4250    chatlist_events::emit_chatlist_item_changed(context, chat_id);
4251    Ok(())
4252}
4253
4254/// Forwards multiple messages to a chat.
4255pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4256    ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4257    ensure!(!chat_id.is_special(), "can not forward to special chat");
4258
4259    let mut created_msgs: Vec<MsgId> = Vec::new();
4260    let mut curr_timestamp: i64;
4261
4262    chat_id
4263        .unarchive_if_not_muted(context, MessageState::Undefined)
4264        .await?;
4265    let mut chat = Chat::load_from_db(context, chat_id).await?;
4266    if let Some(reason) = chat.why_cant_send(context).await? {
4267        bail!("cannot send to {}: {}", chat_id, reason);
4268    }
4269    curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4270    let mut msgs = Vec::with_capacity(msg_ids.len());
4271    for id in msg_ids {
4272        let ts: i64 = context
4273            .sql
4274            .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4275            .await?
4276            .with_context(|| format!("No message {id}"))?;
4277        msgs.push((ts, *id));
4278    }
4279    msgs.sort_unstable();
4280    for (_, id) in msgs {
4281        let src_msg_id: MsgId = id;
4282        let mut msg = Message::load_from_db(context, src_msg_id).await?;
4283        if msg.state == MessageState::OutDraft {
4284            bail!("cannot forward drafts.");
4285        }
4286
4287        if msg.get_viewtype() != Viewtype::Sticker {
4288            msg.param
4289                .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4290        }
4291
4292        msg.param.remove(Param::GuaranteeE2ee);
4293        msg.param.remove(Param::ForcePlaintext);
4294        msg.param.remove(Param::Cmd);
4295        msg.param.remove(Param::OverrideSenderDisplayname);
4296        msg.param.remove(Param::WebxdcDocument);
4297        msg.param.remove(Param::WebxdcDocumentTimestamp);
4298        msg.param.remove(Param::WebxdcSummary);
4299        msg.param.remove(Param::WebxdcSummaryTimestamp);
4300        msg.param.remove(Param::IsEdited);
4301        msg.in_reply_to = None;
4302
4303        // do not leak data as group names; a default subject is generated by mimefactory
4304        msg.subject = "".to_string();
4305
4306        msg.state = MessageState::OutPending;
4307        msg.rfc724_mid = create_outgoing_rfc724_mid();
4308        let new_msg_id = chat
4309            .prepare_msg_raw(context, &mut msg, None, curr_timestamp)
4310            .await?;
4311
4312        curr_timestamp += 1;
4313        if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4314            context.scheduler.interrupt_smtp().await;
4315        }
4316        created_msgs.push(new_msg_id);
4317    }
4318    for msg_id in created_msgs {
4319        context.emit_msgs_changed(chat_id, msg_id);
4320    }
4321    Ok(())
4322}
4323
4324/// Save a copy of the message in "Saved Messages"
4325/// and send a sync messages so that other devices save the message as well, unless deleted there.
4326pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4327    let mut msgs = Vec::with_capacity(msg_ids.len());
4328    for id in msg_ids {
4329        let ts: i64 = context
4330            .sql
4331            .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4332            .await?
4333            .with_context(|| format!("No message {id}"))?;
4334        msgs.push((ts, *id));
4335    }
4336    msgs.sort_unstable();
4337    for (_, src_msg_id) in msgs {
4338        let dest_rfc724_mid = create_outgoing_rfc724_mid();
4339        let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4340        context
4341            .add_sync_item(SyncData::SaveMessage {
4342                src: src_rfc724_mid,
4343                dest: dest_rfc724_mid,
4344            })
4345            .await?;
4346    }
4347    context.scheduler.interrupt_inbox().await;
4348    Ok(())
4349}
4350
4351/// Saves a copy of the given message in "Saved Messages" using the given RFC724 id.
4352/// To allow UIs to have a "show in context" button,
4353/// the copy contains a reference to the original message
4354/// as well as to the original chat in case the original message gets deleted.
4355/// Returns data needed to add a `SaveMessage` sync item.
4356pub(crate) async fn save_copy_in_self_talk(
4357    context: &Context,
4358    src_msg_id: MsgId,
4359    dest_rfc724_mid: &String,
4360) -> Result<String> {
4361    let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4362    let mut msg = Message::load_from_db(context, src_msg_id).await?;
4363    msg.param.remove(Param::Cmd);
4364    msg.param.remove(Param::WebxdcDocument);
4365    msg.param.remove(Param::WebxdcDocumentTimestamp);
4366    msg.param.remove(Param::WebxdcSummary);
4367    msg.param.remove(Param::WebxdcSummaryTimestamp);
4368
4369    if !msg.original_msg_id.is_unset() {
4370        bail!("message already saved.");
4371    }
4372
4373    let copy_fields = "from_id, to_id, timestamp_sent, timestamp_rcvd, type, txt, \
4374                             mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4375    let row_id = context
4376        .sql
4377        .insert(
4378            &format!(
4379                "INSERT INTO msgs ({copy_fields}, chat_id, rfc724_mid, state, timestamp, param, starred) \
4380                            SELECT {copy_fields}, ?, ?, ?, ?, ?, ? \
4381                            FROM msgs WHERE id=?;"
4382            ),
4383            (
4384                dest_chat_id,
4385                dest_rfc724_mid,
4386                if msg.from_id == ContactId::SELF {
4387                    MessageState::OutDelivered
4388                } else {
4389                    MessageState::InSeen
4390                },
4391                create_smeared_timestamp(context),
4392                msg.param.to_string(),
4393                src_msg_id,
4394                src_msg_id,
4395            ),
4396        )
4397        .await?;
4398    let dest_msg_id = MsgId::new(row_id.try_into()?);
4399
4400    context.emit_msgs_changed(msg.chat_id, src_msg_id);
4401    context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4402    chatlist_events::emit_chatlist_changed(context);
4403    chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4404
4405    Ok(msg.rfc724_mid)
4406}
4407
4408/// Resends given messages with the same Message-ID.
4409///
4410/// This is primarily intended to make existing webxdcs available to new chat members.
4411pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4412    let mut chat_id = None;
4413    let mut msgs: Vec<Message> = Vec::new();
4414    for msg_id in msg_ids {
4415        let msg = Message::load_from_db(context, *msg_id).await?;
4416        if let Some(chat_id) = chat_id {
4417            ensure!(
4418                chat_id == msg.chat_id,
4419                "messages to resend needs to be in the same chat"
4420            );
4421        } else {
4422            chat_id = Some(msg.chat_id);
4423        }
4424        ensure!(
4425            msg.from_id == ContactId::SELF,
4426            "can resend only own messages"
4427        );
4428        ensure!(!msg.is_info(), "cannot resend info messages");
4429        msgs.push(msg)
4430    }
4431
4432    let Some(chat_id) = chat_id else {
4433        return Ok(());
4434    };
4435
4436    let chat = Chat::load_from_db(context, chat_id).await?;
4437    for mut msg in msgs {
4438        if msg.get_showpadlock() && !chat.is_protected() {
4439            msg.param.remove(Param::GuaranteeE2ee);
4440            msg.update_param(context).await?;
4441        }
4442        match msg.get_state() {
4443            // `get_state()` may return an outdated `OutPending`, so update anyway.
4444            MessageState::OutPending
4445            | MessageState::OutFailed
4446            | MessageState::OutDelivered
4447            | MessageState::OutMdnRcvd => {
4448                message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4449            }
4450            msg_state => bail!("Unexpected message state {msg_state}"),
4451        }
4452        context.emit_event(EventType::MsgsChanged {
4453            chat_id: msg.chat_id,
4454            msg_id: msg.id,
4455        });
4456        msg.timestamp_sort = create_smeared_timestamp(context);
4457        // note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it)
4458        chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4459        if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4460            continue;
4461        }
4462        if msg.viewtype == Viewtype::Webxdc {
4463            let conn_fn = |conn: &mut rusqlite::Connection| {
4464                let range = conn.query_row(
4465                    "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4466                     FROM msgs_status_updates WHERE msg_id=?",
4467                    (msg.id,),
4468                    |row| {
4469                        let min_id: StatusUpdateSerial = row.get(0)?;
4470                        let max_id: StatusUpdateSerial = row.get(1)?;
4471                        Ok((min_id, max_id))
4472                    },
4473                )?;
4474                if range.0 > range.1 {
4475                    return Ok(());
4476                };
4477                // `first_serial` must be decreased, otherwise if `Context::flush_status_updates()`
4478                // runs in parallel, it would miss the race and instead of resending just remove the
4479                // updates thinking that they have been already sent.
4480                conn.execute(
4481                    "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4482                     VALUES(?, ?, ?, '') \
4483                     ON CONFLICT(msg_id) \
4484                     DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4485                    (msg.id, range.0, range.1),
4486                )?;
4487                Ok(())
4488            };
4489            context.sql.call_write(conn_fn).await?;
4490        }
4491        context.scheduler.interrupt_smtp().await;
4492    }
4493    Ok(())
4494}
4495
4496pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4497    if context.sql.is_open().await {
4498        // no database, no chats - this is no error (needed eg. for information)
4499        let count = context
4500            .sql
4501            .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4502            .await?;
4503        Ok(count)
4504    } else {
4505        Ok(0)
4506    }
4507}
4508
4509/// Returns a tuple of `(chatid, is_protected, blocked)`.
4510pub(crate) async fn get_chat_id_by_grpid(
4511    context: &Context,
4512    grpid: &str,
4513) -> Result<Option<(ChatId, bool, Blocked)>> {
4514    context
4515        .sql
4516        .query_row_optional(
4517            "SELECT id, blocked, protected FROM chats WHERE grpid=?;",
4518            (grpid,),
4519            |row| {
4520                let chat_id = row.get::<_, ChatId>(0)?;
4521
4522                let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4523                let p = row
4524                    .get::<_, Option<ProtectionStatus>>(2)?
4525                    .unwrap_or_default();
4526                Ok((chat_id, p == ProtectionStatus::Protected, b))
4527            },
4528        )
4529        .await
4530}
4531
4532/// Adds a message to device chat.
4533///
4534/// Optional `label` can be provided to ensure that message is added only once.
4535/// If `important` is true, a notification will be sent.
4536pub async fn add_device_msg_with_importance(
4537    context: &Context,
4538    label: Option<&str>,
4539    msg: Option<&mut Message>,
4540    important: bool,
4541) -> Result<MsgId> {
4542    ensure!(
4543        label.is_some() || msg.is_some(),
4544        "device-messages need label, msg or both"
4545    );
4546    let mut chat_id = ChatId::new(0);
4547    let mut msg_id = MsgId::new_unset();
4548
4549    if let Some(label) = label {
4550        if was_device_msg_ever_added(context, label).await? {
4551            info!(context, "Device-message {label} already added.");
4552            return Ok(msg_id);
4553        }
4554    }
4555
4556    if let Some(msg) = msg {
4557        chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4558
4559        let rfc724_mid = create_outgoing_rfc724_mid();
4560        prepare_msg_blob(context, msg).await?;
4561
4562        let timestamp_sent = create_smeared_timestamp(context);
4563
4564        // makes sure, the added message is the last one,
4565        // even if the date is wrong (useful esp. when warning about bad dates)
4566        let mut timestamp_sort = timestamp_sent;
4567        if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4568            if timestamp_sort <= last_msg_time {
4569                timestamp_sort = last_msg_time + 1;
4570            }
4571        }
4572
4573        let state = MessageState::InFresh;
4574        let row_id = context
4575            .sql
4576            .insert(
4577                "INSERT INTO msgs (
4578            chat_id,
4579            from_id,
4580            to_id,
4581            timestamp,
4582            timestamp_sent,
4583            timestamp_rcvd,
4584            type,state,
4585            txt,
4586            txt_normalized,
4587            param,
4588            rfc724_mid)
4589            VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4590                (
4591                    chat_id,
4592                    ContactId::DEVICE,
4593                    ContactId::SELF,
4594                    timestamp_sort,
4595                    timestamp_sent,
4596                    timestamp_sent, // timestamp_sent equals timestamp_rcvd
4597                    msg.viewtype,
4598                    state,
4599                    &msg.text,
4600                    message::normalize_text(&msg.text),
4601                    msg.param.to_string(),
4602                    rfc724_mid,
4603                ),
4604            )
4605            .await?;
4606        context.new_msgs_notify.notify_one();
4607
4608        msg_id = MsgId::new(u32::try_from(row_id)?);
4609        if !msg.hidden {
4610            chat_id.unarchive_if_not_muted(context, state).await?;
4611        }
4612    }
4613
4614    if let Some(label) = label {
4615        context
4616            .sql
4617            .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4618            .await?;
4619    }
4620
4621    if !msg_id.is_unset() {
4622        chat_id.emit_msg_event(context, msg_id, important);
4623    }
4624
4625    Ok(msg_id)
4626}
4627
4628/// Adds a message to device chat.
4629pub async fn add_device_msg(
4630    context: &Context,
4631    label: Option<&str>,
4632    msg: Option<&mut Message>,
4633) -> Result<MsgId> {
4634    add_device_msg_with_importance(context, label, msg, false).await
4635}
4636
4637/// Returns true if device message with a given label was ever added to the device chat.
4638pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4639    ensure!(!label.is_empty(), "empty label");
4640    let exists = context
4641        .sql
4642        .exists(
4643            "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4644            (label,),
4645        )
4646        .await?;
4647
4648    Ok(exists)
4649}
4650
4651// needed on device-switches during export/import;
4652// - deletion in `msgs` with `ContactId::DEVICE` makes sure,
4653//   no wrong information are shown in the device chat
4654// - deletion in `devmsglabels` makes sure,
4655//   deleted messages are reset and useful messages can be added again
4656// - we reset the config-option `QuotaExceeding`
4657//   that is used as a helper to drive the corresponding device message.
4658pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4659    context
4660        .sql
4661        .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4662        .await?;
4663    context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4664
4665    // Insert labels for welcome messages to avoid them being re-added on reconfiguration.
4666    context
4667        .sql
4668        .execute(
4669            r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4670            (),
4671        )
4672        .await?;
4673    context
4674        .set_config_internal(Config::QuotaExceeding, None)
4675        .await?;
4676    Ok(())
4677}
4678
4679/// Adds an informational message to chat.
4680///
4681/// For example, it can be a message showing that a member was added to a group.
4682/// Doesn't fail if the chat doesn't exist.
4683#[expect(clippy::too_many_arguments)]
4684pub(crate) async fn add_info_msg_with_cmd(
4685    context: &Context,
4686    chat_id: ChatId,
4687    text: &str,
4688    cmd: SystemMessage,
4689    timestamp_sort: i64,
4690    // Timestamp to show to the user (if this is None, `timestamp_sort` will be shown to the user)
4691    timestamp_sent_rcvd: Option<i64>,
4692    parent: Option<&Message>,
4693    from_id: Option<ContactId>,
4694    added_removed_id: Option<ContactId>,
4695) -> Result<MsgId> {
4696    let rfc724_mid = create_outgoing_rfc724_mid();
4697    let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4698
4699    let mut param = Params::new();
4700    if cmd != SystemMessage::Unknown {
4701        param.set_cmd(cmd);
4702    }
4703    if let Some(contact_id) = added_removed_id {
4704        param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4705    }
4706
4707    let row_id =
4708    context.sql.insert(
4709        "INSERT INTO msgs (chat_id,from_id,to_id,timestamp,timestamp_sent,timestamp_rcvd,type,state,txt,txt_normalized,rfc724_mid,ephemeral_timer,param,mime_in_reply_to)
4710        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4711        (
4712            chat_id,
4713            from_id.unwrap_or(ContactId::INFO),
4714            ContactId::INFO,
4715            timestamp_sort,
4716            timestamp_sent_rcvd.unwrap_or(0),
4717            timestamp_sent_rcvd.unwrap_or(0),
4718            Viewtype::Text,
4719            MessageState::InNoticed,
4720            text,
4721            message::normalize_text(text),
4722            rfc724_mid,
4723            ephemeral_timer,
4724            param.to_string(),
4725            parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4726        )
4727    ).await?;
4728    context.new_msgs_notify.notify_one();
4729
4730    let msg_id = MsgId::new(row_id.try_into()?);
4731    context.emit_msgs_changed(chat_id, msg_id);
4732
4733    Ok(msg_id)
4734}
4735
4736/// Adds info message with a given text and `timestamp` to the chat.
4737pub(crate) async fn add_info_msg(
4738    context: &Context,
4739    chat_id: ChatId,
4740    text: &str,
4741    timestamp: i64,
4742) -> Result<MsgId> {
4743    add_info_msg_with_cmd(
4744        context,
4745        chat_id,
4746        text,
4747        SystemMessage::Unknown,
4748        timestamp,
4749        None,
4750        None,
4751        None,
4752        None,
4753    )
4754    .await
4755}
4756
4757pub(crate) async fn update_msg_text_and_timestamp(
4758    context: &Context,
4759    chat_id: ChatId,
4760    msg_id: MsgId,
4761    text: &str,
4762    timestamp: i64,
4763) -> Result<()> {
4764    context
4765        .sql
4766        .execute(
4767            "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4768            (text, message::normalize_text(text), timestamp, msg_id),
4769        )
4770        .await?;
4771    context.emit_msgs_changed(chat_id, msg_id);
4772    Ok(())
4773}
4774
4775/// Set chat contacts by their addresses creating the corresponding contacts if necessary.
4776async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4777    let chat = Chat::load_from_db(context, id).await?;
4778    ensure!(
4779        !chat.is_encrypted(context).await?,
4780        "Cannot add address-contacts to encrypted chat {id}"
4781    );
4782    ensure!(
4783        chat.typ == Chattype::OutBroadcast,
4784        "{id} is not a broadcast list",
4785    );
4786    let mut contacts = HashSet::new();
4787    for addr in addrs {
4788        let contact_addr = ContactAddress::new(addr)?;
4789        let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4790            .await?
4791            .0;
4792        contacts.insert(contact);
4793    }
4794    let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4795    if contacts == contacts_old {
4796        return Ok(());
4797    }
4798    context
4799        .sql
4800        .transaction(move |transaction| {
4801            transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4802
4803            // We do not care about `add_timestamp` column
4804            // because timestamps are not used for broadcast channels.
4805            let mut statement = transaction
4806                .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4807            for contact_id in &contacts {
4808                statement.execute((id, contact_id))?;
4809            }
4810            Ok(())
4811        })
4812        .await?;
4813    context.emit_event(EventType::ChatModified(id));
4814    Ok(())
4815}
4816
4817/// Set chat contacts by their fingerprints creating the corresponding contacts if necessary.
4818///
4819/// `fingerprint_addrs` is a list of pairs of fingerprint and address.
4820async fn set_contacts_by_fingerprints(
4821    context: &Context,
4822    id: ChatId,
4823    fingerprint_addrs: &[(String, String)],
4824) -> Result<()> {
4825    let chat = Chat::load_from_db(context, id).await?;
4826    ensure!(
4827        chat.is_encrypted(context).await?,
4828        "Cannot add key-contacts to unencrypted chat {id}"
4829    );
4830    ensure!(
4831        chat.typ == Chattype::OutBroadcast,
4832        "{id} is not a broadcast list",
4833    );
4834    let mut contacts = HashSet::new();
4835    for (fingerprint, addr) in fingerprint_addrs {
4836        let contact_addr = ContactAddress::new(addr)?;
4837        let contact =
4838            Contact::add_or_lookup_ex(context, "", &contact_addr, fingerprint, Origin::Hidden)
4839                .await?
4840                .0;
4841        contacts.insert(contact);
4842    }
4843    let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4844    if contacts == contacts_old {
4845        return Ok(());
4846    }
4847    context
4848        .sql
4849        .transaction(move |transaction| {
4850            transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4851
4852            // We do not care about `add_timestamp` column
4853            // because timestamps are not used for broadcast channels.
4854            let mut statement = transaction
4855                .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4856            for contact_id in &contacts {
4857                statement.execute((id, contact_id))?;
4858            }
4859            Ok(())
4860        })
4861        .await?;
4862    context.emit_event(EventType::ChatModified(id));
4863    Ok(())
4864}
4865
4866/// A cross-device chat id used for synchronisation.
4867#[derive(Debug, Serialize, Deserialize, PartialEq)]
4868pub(crate) enum SyncId {
4869    /// E-mail address of the contact.
4870    ContactAddr(String),
4871
4872    /// OpenPGP key fingerprint of the contact.
4873    ContactFingerprint(String),
4874
4875    Grpid(String),
4876    /// "Message-ID"-s, from oldest to latest. Used for ad-hoc groups.
4877    Msgids(Vec<String>),
4878
4879    // Special id for device chat.
4880    Device,
4881}
4882
4883/// An action synchronised to other devices.
4884#[derive(Debug, Serialize, Deserialize, PartialEq)]
4885pub(crate) enum SyncAction {
4886    Block,
4887    Unblock,
4888    Accept,
4889    SetVisibility(ChatVisibility),
4890    SetMuted(MuteDuration),
4891    /// Create broadcast channel with the given name.
4892    CreateBroadcast(String),
4893    Rename(String),
4894    /// Set chat contacts by their addresses.
4895    SetContacts(Vec<String>),
4896    /// Set chat contacts by their fingerprints.
4897    ///
4898    /// The list is a list of pairs of fingerprint and address.
4899    SetPgpContacts(Vec<(String, String)>),
4900    Delete,
4901}
4902
4903impl Context {
4904    /// Executes [`SyncData::AlterChat`] item sent by other device.
4905    pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4906        let chat_id = match id {
4907            SyncId::ContactAddr(addr) => {
4908                if let SyncAction::Rename(to) = action {
4909                    Contact::create_ex(self, Nosync, to, addr).await?;
4910                    return Ok(());
4911                }
4912                let addr = ContactAddress::new(addr).context("Invalid address")?;
4913                let (contact_id, _) =
4914                    Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4915                match action {
4916                    SyncAction::Block => {
4917                        return contact::set_blocked(self, Nosync, contact_id, true).await;
4918                    }
4919                    SyncAction::Unblock => {
4920                        return contact::set_blocked(self, Nosync, contact_id, false).await;
4921                    }
4922                    _ => (),
4923                }
4924                // Use `Request` so that even if the program crashes, the user doesn't have to look
4925                // into the blocked contacts.
4926                ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4927                    .await?
4928                    .id
4929            }
4930            SyncId::ContactFingerprint(fingerprint) => {
4931                let name = "";
4932                let addr = "";
4933                let (contact_id, _) =
4934                    Contact::add_or_lookup_ex(self, name, addr, fingerprint, Origin::Hidden)
4935                        .await?;
4936                match action {
4937                    SyncAction::Rename(to) => {
4938                        contact_id.set_name_ex(self, Nosync, to).await?;
4939                        self.emit_event(EventType::ContactsChanged(Some(contact_id)));
4940                        return Ok(());
4941                    }
4942                    SyncAction::Block => {
4943                        return contact::set_blocked(self, Nosync, contact_id, true).await;
4944                    }
4945                    SyncAction::Unblock => {
4946                        return contact::set_blocked(self, Nosync, contact_id, false).await;
4947                    }
4948                    _ => (),
4949                }
4950                ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4951                    .await?
4952                    .id
4953            }
4954            SyncId::Grpid(grpid) => {
4955                if let SyncAction::CreateBroadcast(name) = action {
4956                    create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
4957                    return Ok(());
4958                }
4959                get_chat_id_by_grpid(self, grpid)
4960                    .await?
4961                    .with_context(|| format!("No chat for grpid '{grpid}'"))?
4962                    .0
4963            }
4964            SyncId::Msgids(msgids) => {
4965                let msg = message::get_by_rfc724_mids(self, msgids)
4966                    .await?
4967                    .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4968                ChatId::lookup_by_message(&msg)
4969                    .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4970            }
4971            SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4972        };
4973        match action {
4974            SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4975            SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4976            SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4977            SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4978            SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4979            SyncAction::CreateBroadcast(_) => {
4980                Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4981            }
4982            SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4983            SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4984            SyncAction::SetPgpContacts(fingerprint_addrs) => {
4985                set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
4986            }
4987            SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4988        }
4989    }
4990
4991    /// Emits the appropriate `MsgsChanged` event. Should be called if the number of unnoticed
4992    /// archived chats could decrease. In general we don't want to make an extra db query to know if
4993    /// a noticed chat is archived. Emitting events should be cheap, a false-positive `MsgsChanged`
4994    /// is ok.
4995    pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4996        self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4997    }
4998}
4999
5000#[cfg(test)]
5001mod chat_tests;