deltachat/
chat.rs

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