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