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