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