1use std::cmp;
4use std::collections::{HashMap, HashSet};
5use std::fmt;
6use std::io::Cursor;
7use std::marker::Sync;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::time::Duration;
11
12use anyhow::{anyhow, bail, ensure, Context as _, Result};
13use deltachat_contact_tools::{sanitize_bidi_characters, sanitize_single_line, ContactAddress};
14use deltachat_derive::{FromSql, ToSql};
15use mail_builder::mime::MimePart;
16use serde::{Deserialize, Serialize};
17use strum_macros::EnumIter;
18use tokio::task;
19
20use crate::aheader::EncryptPreference;
21use crate::blob::BlobObject;
22use crate::chatlist::Chatlist;
23use crate::color::str_to_color;
24use crate::config::Config;
25use crate::constants::{
26 self, Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK,
27 DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS, EDITED_PREFIX,
28 TIMESTAMP_SENT_TOLERANCE,
29};
30use crate::contact::{self, Contact, ContactId, Origin};
31use crate::context::Context;
32use crate::debug_logging::maybe_set_logging_xdc;
33use crate::download::DownloadState;
34use crate::ephemeral::{start_chat_ephemeral_timers, Timer as EphemeralTimer};
35use crate::events::EventType;
36use crate::location;
37use crate::log::LogExt;
38use crate::message::{self, Message, MessageState, MsgId, Viewtype};
39use crate::mimefactory::MimeFactory;
40use crate::mimeparser::SystemMessage;
41use crate::param::{Param, Params};
42use crate::peerstate::Peerstate;
43use crate::receive_imf::ReceivedMsg;
44use crate::smtp::send_msg_to_smtp;
45use crate::stock_str;
46use crate::sync::{self, Sync::*, SyncData};
47use crate::tools::{
48 buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
49 create_smeared_timestamps, get_abs_path, gm2local_offset, smeared_time, time,
50 truncate_msg_text, IsNoneOrEmpty, SystemTime,
51};
52use crate::webxdc::StatusUpdateSerial;
53use crate::{chatlist_events, imap};
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub enum ChatItem {
58 Message {
60 msg_id: MsgId,
62 },
63
64 DayMarker {
67 timestamp: i64,
69 },
70}
71
72#[derive(
74 Debug,
75 Default,
76 Display,
77 Clone,
78 Copy,
79 PartialEq,
80 Eq,
81 FromPrimitive,
82 ToPrimitive,
83 FromSql,
84 ToSql,
85 IntoStaticStr,
86 Serialize,
87 Deserialize,
88)]
89#[repr(u32)]
90pub enum ProtectionStatus {
91 #[default]
93 Unprotected = 0,
94
95 Protected = 1,
99
100 ProtectionBroken = 3, }
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub(crate) enum CantSendReason {
114 SpecialChat,
116
117 DeviceChat,
119
120 ContactRequest,
122
123 ProtectionBroken,
126
127 ReadOnlyMailingList,
129
130 NotAMember,
132
133 SecurejoinWait,
135}
136
137impl fmt::Display for CantSendReason {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 Self::SpecialChat => write!(f, "the chat is a special chat"),
141 Self::DeviceChat => write!(f, "the chat is a device chat"),
142 Self::ContactRequest => write!(
143 f,
144 "contact request chat should be accepted before sending messages"
145 ),
146 Self::ProtectionBroken => write!(
147 f,
148 "accept that the encryption isn't verified anymore before sending messages"
149 ),
150 Self::ReadOnlyMailingList => {
151 write!(f, "mailing list does not have a know post address")
152 }
153 Self::NotAMember => write!(f, "not a member of the chat"),
154 Self::SecurejoinWait => write!(f, "awaiting SecureJoin for 1:1 chat"),
155 }
156 }
157}
158
159#[derive(
164 Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
165)]
166pub struct ChatId(u32);
167
168impl ChatId {
169 pub const fn new(id: u32) -> ChatId {
171 ChatId(id)
172 }
173
174 pub fn is_unset(self) -> bool {
178 self.0 == 0
179 }
180
181 pub fn is_special(self) -> bool {
185 (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
186 }
187
188 pub fn is_trash(self) -> bool {
195 self == DC_CHAT_ID_TRASH
196 }
197
198 pub fn is_archived_link(self) -> bool {
205 self == DC_CHAT_ID_ARCHIVED_LINK
206 }
207
208 pub fn is_alldone_hint(self) -> bool {
217 self == DC_CHAT_ID_ALLDONE_HINT
218 }
219
220 pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
222 if msg.chat_id == DC_CHAT_ID_TRASH {
223 return None;
224 }
225 if msg.download_state == DownloadState::Undecipherable {
226 return None;
227 }
228 Some(msg.chat_id)
229 }
230
231 pub async fn lookup_by_contact(
236 context: &Context,
237 contact_id: ContactId,
238 ) -> Result<Option<Self>> {
239 let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
240 else {
241 return Ok(None);
242 };
243
244 let chat_id = match chat_id_blocked.blocked {
245 Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
246 Blocked::Yes => None,
247 };
248 Ok(chat_id)
249 }
250
251 pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
259 ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
260 .await
261 .map(|chat| chat.id)
262 }
263
264 pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
269 ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
270 }
271
272 pub(crate) async fn create_for_contact_with_blocked(
276 context: &Context,
277 contact_id: ContactId,
278 create_blocked: Blocked,
279 ) -> Result<Self> {
280 let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
281 Some(chat) => {
282 if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
283 return Ok(chat.id);
284 }
285 chat.id.set_blocked(context, Blocked::Not).await?;
286 chat.id
287 }
288 None => {
289 if Contact::real_exists_by_id(context, contact_id).await?
290 || contact_id == ContactId::SELF
291 {
292 let chat_id =
293 ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
294 .await
295 .map(|chat| chat.id)?;
296 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
297 chat_id
298 } else {
299 warn!(
300 context,
301 "Cannot create chat, contact {contact_id} does not exist."
302 );
303 bail!("Can not create chat for non-existing contact");
304 }
305 }
306 };
307 context.emit_msgs_changed_without_ids();
308 chatlist_events::emit_chatlist_changed(context);
309 chatlist_events::emit_chatlist_item_changed(context, chat_id);
310 Ok(chat_id)
311 }
312
313 #[expect(clippy::too_many_arguments)]
316 pub(crate) async fn create_multiuser_record(
317 context: &Context,
318 chattype: Chattype,
319 grpid: &str,
320 grpname: &str,
321 create_blocked: Blocked,
322 create_protected: ProtectionStatus,
323 param: Option<String>,
324 timestamp: i64,
325 ) -> Result<Self> {
326 let grpname = sanitize_single_line(grpname);
327 let timestamp = cmp::min(timestamp, smeared_time(context));
328 let row_id =
329 context.sql.insert(
330 "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
331 (
332 chattype,
333 &grpname,
334 grpid,
335 create_blocked,
336 timestamp,
337 create_protected,
338 param.unwrap_or_default(),
339 ),
340 ).await?;
341
342 let chat_id = ChatId::new(u32::try_from(row_id)?);
343
344 if create_protected == ProtectionStatus::Protected {
345 chat_id
346 .add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
347 .await?;
348 }
349
350 info!(
351 context,
352 "Created group/mailinglist '{}' grpid={} as {}, blocked={}, protected={create_protected}.",
353 &grpname,
354 grpid,
355 chat_id,
356 create_blocked,
357 );
358
359 Ok(chat_id)
360 }
361
362 async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
363 context
364 .sql
365 .execute(
366 "UPDATE contacts
367 SET selfavatar_sent=?
368 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
369 (timestamp, self),
370 )
371 .await?;
372 Ok(())
373 }
374
375 pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
379 if self.is_special() {
380 bail!("ignoring setting of Block-status for {}", self);
381 }
382 let count = context
383 .sql
384 .execute(
385 "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
386 (new_blocked, self),
387 )
388 .await?;
389 Ok(count > 0)
390 }
391
392 pub async fn block(self, context: &Context) -> Result<()> {
394 self.block_ex(context, Sync).await
395 }
396
397 pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
398 let chat = Chat::load_from_db(context, self).await?;
399 let mut delete = false;
400
401 match chat.typ {
402 Chattype::Broadcast => {
403 bail!("Can't block chat of type {:?}", chat.typ)
404 }
405 Chattype::Single => {
406 for contact_id in get_chat_contacts(context, self).await? {
407 if contact_id != ContactId::SELF {
408 info!(
409 context,
410 "Blocking the contact {contact_id} to block 1:1 chat."
411 );
412 contact::set_blocked(context, Nosync, contact_id, true).await?;
413 }
414 }
415 }
416 Chattype::Group => {
417 info!(context, "Can't block groups yet, deleting the chat.");
418 delete = true;
419 }
420 Chattype::Mailinglist => {
421 if self.set_blocked(context, Blocked::Yes).await? {
422 context.emit_event(EventType::ChatModified(self));
423 }
424 }
425 }
426 chatlist_events::emit_chatlist_changed(context);
427
428 if sync.into() {
429 chat.sync(context, SyncAction::Block)
431 .await
432 .log_err(context)
433 .ok();
434 }
435 if delete {
436 self.delete_ex(context, Nosync).await?;
437 }
438 Ok(())
439 }
440
441 pub async fn unblock(self, context: &Context) -> Result<()> {
443 self.unblock_ex(context, Sync).await
444 }
445
446 pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
447 self.set_blocked(context, Blocked::Not).await?;
448
449 chatlist_events::emit_chatlist_changed(context);
450
451 if sync.into() {
452 let chat = Chat::load_from_db(context, self).await?;
453 chat.sync(context, SyncAction::Unblock)
457 .await
458 .log_err(context)
459 .ok();
460 }
461
462 Ok(())
463 }
464
465 pub async fn accept(self, context: &Context) -> Result<()> {
469 self.accept_ex(context, Sync).await
470 }
471
472 pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
473 let chat = Chat::load_from_db(context, self).await?;
474
475 match chat.typ {
476 Chattype::Single
477 if chat.blocked == Blocked::Not
478 && chat.protected == ProtectionStatus::ProtectionBroken =>
479 {
480 chat.id
483 .inner_set_protection(context, ProtectionStatus::Unprotected)
484 .await?;
485 }
486 Chattype::Single | Chattype::Group | Chattype::Broadcast => {
487 for contact_id in get_chat_contacts(context, self).await? {
492 if contact_id != ContactId::SELF {
493 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
494 .await?;
495 }
496 }
497 }
498 Chattype::Mailinglist => {
499 }
501 }
502
503 if self.set_blocked(context, Blocked::Not).await? {
504 context.emit_event(EventType::ChatModified(self));
505 chatlist_events::emit_chatlist_item_changed(context, self);
506 }
507
508 if sync.into() {
509 chat.sync(context, SyncAction::Accept)
510 .await
511 .log_err(context)
512 .ok();
513 }
514 Ok(())
515 }
516
517 pub(crate) async fn inner_set_protection(
521 self,
522 context: &Context,
523 protect: ProtectionStatus,
524 ) -> Result<bool> {
525 ensure!(!self.is_special(), "Invalid chat-id {self}.");
526
527 let chat = Chat::load_from_db(context, self).await?;
528
529 if protect == chat.protected {
530 info!(context, "Protection status unchanged for {}.", self);
531 return Ok(false);
532 }
533
534 match protect {
535 ProtectionStatus::Protected => match chat.typ {
536 Chattype::Single | Chattype::Group | Chattype::Broadcast => {}
537 Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
538 },
539 ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {}
540 };
541
542 context
543 .sql
544 .execute("UPDATE chats SET protected=? WHERE id=?;", (protect, self))
545 .await?;
546
547 context.emit_event(EventType::ChatModified(self));
548 chatlist_events::emit_chatlist_item_changed(context, self);
549
550 self.reset_gossiped_timestamp(context).await?;
552
553 Ok(true)
554 }
555
556 pub(crate) async fn add_protection_msg(
564 self,
565 context: &Context,
566 protect: ProtectionStatus,
567 contact_id: Option<ContactId>,
568 timestamp_sort: i64,
569 ) -> Result<()> {
570 if contact_id == Some(ContactId::SELF) {
571 return Ok(());
576 }
577
578 let text = context.stock_protection_msg(protect, contact_id).await;
579 let cmd = match protect {
580 ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
581 ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
582 ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled,
583 };
584 add_info_msg_with_cmd(
585 context,
586 self,
587 &text,
588 cmd,
589 timestamp_sort,
590 None,
591 None,
592 None,
593 None,
594 )
595 .await?;
596
597 Ok(())
598 }
599
600 async fn set_protection_for_timestamp_sort(
605 self,
606 context: &Context,
607 protect: ProtectionStatus,
608 timestamp_sort: i64,
609 contact_id: Option<ContactId>,
610 ) -> Result<()> {
611 let protection_status_modified = self
612 .inner_set_protection(context, protect)
613 .await
614 .with_context(|| format!("Cannot set protection for {self}"))?;
615 if protection_status_modified {
616 self.add_protection_msg(context, protect, contact_id, timestamp_sort)
617 .await?;
618 chatlist_events::emit_chatlist_item_changed(context, self);
619 }
620 Ok(())
621 }
622
623 pub(crate) async fn set_protection(
627 self,
628 context: &Context,
629 protect: ProtectionStatus,
630 timestamp_sent: i64,
631 contact_id: Option<ContactId>,
632 ) -> Result<()> {
633 let sort_to_bottom = true;
634 let (received, incoming) = (false, false);
635 let ts = self
636 .calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
637 .await?
638 .saturating_add(1);
641 self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
642 .await
643 }
644
645 pub(crate) async fn set_protection_for_contact(
650 context: &Context,
651 contact_id: ContactId,
652 timestamp: i64,
653 ) -> Result<()> {
654 let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
655 .await
656 .with_context(|| format!("can't create chat for {}", contact_id))?;
657 chat_id
658 .set_protection(
659 context,
660 ProtectionStatus::Protected,
661 timestamp,
662 Some(contact_id),
663 )
664 .await?;
665 Ok(())
666 }
667
668 pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
670 self.set_visibility_ex(context, Sync, visibility).await
671 }
672
673 pub(crate) async fn set_visibility_ex(
674 self,
675 context: &Context,
676 sync: sync::Sync,
677 visibility: ChatVisibility,
678 ) -> Result<()> {
679 ensure!(
680 !self.is_special(),
681 "bad chat_id, can not be special chat: {}",
682 self
683 );
684
685 context
686 .sql
687 .transaction(move |transaction| {
688 if visibility == ChatVisibility::Archived {
689 transaction.execute(
690 "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
691 (MessageState::InNoticed, self, MessageState::InFresh),
692 )?;
693 }
694 transaction.execute(
695 "UPDATE chats SET archived=? WHERE id=?;",
696 (visibility, self),
697 )?;
698 Ok(())
699 })
700 .await?;
701
702 if visibility == ChatVisibility::Archived {
703 start_chat_ephemeral_timers(context, self).await?;
704 }
705
706 context.emit_msgs_changed_without_ids();
707 chatlist_events::emit_chatlist_changed(context);
708 chatlist_events::emit_chatlist_item_changed(context, self);
709
710 if sync.into() {
711 let chat = Chat::load_from_db(context, self).await?;
712 chat.sync(context, SyncAction::SetVisibility(visibility))
713 .await
714 .log_err(context)
715 .ok();
716 }
717 Ok(())
718 }
719
720 pub async fn unarchive_if_not_muted(
728 self,
729 context: &Context,
730 msg_state: MessageState,
731 ) -> Result<()> {
732 if msg_state != MessageState::InFresh {
733 context
734 .sql
735 .execute(
736 "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
737 AND NOT(muted_until=-1 OR muted_until>?)",
738 (self, time()),
739 )
740 .await?;
741 return Ok(());
742 }
743 let chat = Chat::load_from_db(context, self).await?;
744 if chat.visibility != ChatVisibility::Archived {
745 return Ok(());
746 }
747 if chat.is_muted() {
748 let unread_cnt = context
749 .sql
750 .count(
751 "SELECT COUNT(*)
752 FROM msgs
753 WHERE state=?
754 AND hidden=0
755 AND chat_id=?",
756 (MessageState::InFresh, self),
757 )
758 .await?;
759 if unread_cnt == 1 {
760 context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
762 }
763 return Ok(());
764 }
765 context
766 .sql
767 .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
768 .await?;
769 Ok(())
770 }
771
772 pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
775 if important {
776 debug_assert!(!msg_id.is_unset());
777
778 context.emit_incoming_msg(self, msg_id);
779 } else {
780 context.emit_msgs_changed(self, msg_id);
781 }
782 }
783
784 pub async fn delete(self, context: &Context) -> Result<()> {
786 self.delete_ex(context, Sync).await
787 }
788
789 pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
790 ensure!(
791 !self.is_special(),
792 "bad chat_id, can not be a special chat: {}",
793 self
794 );
795
796 let chat = Chat::load_from_db(context, self).await?;
797 let delete_msgs_target = context.get_delete_msgs_target().await?;
798 let sync_id = match sync {
799 Nosync => None,
800 Sync => chat.get_sync_id(context).await?,
801 };
802
803 context
804 .sql
805 .transaction(|transaction| {
806 transaction.execute(
807 "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
808 (delete_msgs_target, self,),
809 )?;
810 transaction.execute(
811 "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
812 (self,),
813 )?;
814 transaction.execute(
815 "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
816 (self,),
817 )?;
818 transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
819 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
820 transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
821 Ok(())
822 })
823 .await?;
824
825 context.emit_event(EventType::ChatDeleted { chat_id: self });
826 context.emit_msgs_changed_without_ids();
827
828 if let Some(id) = sync_id {
829 self::sync(context, id, SyncAction::Delete)
830 .await
831 .log_err(context)
832 .ok();
833 }
834
835 if chat.is_self_talk() {
836 let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
837 add_device_msg(context, None, Some(&mut msg)).await?;
838 }
839 chatlist_events::emit_chatlist_changed(context);
840
841 context
842 .set_config_internal(Config::LastHousekeeping, None)
843 .await?;
844 context.scheduler.interrupt_inbox().await;
845
846 Ok(())
847 }
848
849 pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
853 if self.is_special() {
854 return Ok(());
855 }
856
857 let changed = match &mut msg {
858 None => self.maybe_delete_draft(context).await?,
859 Some(msg) => self.do_set_draft(context, msg).await?,
860 };
861
862 if changed {
863 if msg.is_some() {
864 match self.get_draft_msg_id(context).await? {
865 Some(msg_id) => context.emit_msgs_changed(self, msg_id),
866 None => context.emit_msgs_changed_without_msg_id(self),
867 }
868 } else {
869 context.emit_msgs_changed_without_msg_id(self)
870 }
871 }
872
873 Ok(())
874 }
875
876 async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
878 let msg_id: Option<MsgId> = context
879 .sql
880 .query_get_value(
881 "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
882 (self, MessageState::OutDraft),
883 )
884 .await?;
885 Ok(msg_id)
886 }
887
888 pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
890 if self.is_special() {
891 return Ok(None);
892 }
893 match self.get_draft_msg_id(context).await? {
894 Some(draft_msg_id) => {
895 let msg = Message::load_from_db(context, draft_msg_id).await?;
896 Ok(Some(msg))
897 }
898 None => Ok(None),
899 }
900 }
901
902 async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
906 Ok(context
907 .sql
908 .execute(
909 "DELETE FROM msgs WHERE chat_id=? AND state=?",
910 (self, MessageState::OutDraft),
911 )
912 .await?
913 > 0)
914 }
915
916 async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
919 match msg.viewtype {
920 Viewtype::Unknown => bail!("Can not set draft of unknown type."),
921 Viewtype::Text => {
922 if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
923 bail!("No text and no quote in draft");
924 }
925 }
926 _ => {
927 if msg.viewtype == Viewtype::File {
928 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
929 .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
934 {
935 msg.viewtype = better_type;
936 }
937 }
938 if msg.viewtype == Viewtype::Vcard {
939 let blob = msg
940 .param
941 .get_file_blob(context)?
942 .context("no file stored in params")?;
943 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
944 }
945 }
946 }
947
948 msg.state = MessageState::OutDraft;
951 msg.chat_id = self;
952
953 if !msg.id.is_special() {
955 if let Some(old_draft) = self.get_draft(context).await? {
956 if old_draft.id == msg.id
957 && old_draft.chat_id == self
958 && old_draft.state == MessageState::OutDraft
959 {
960 let affected_rows = context
961 .sql.execute(
962 "UPDATE msgs
963 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
964 WHERE id=?7
965 AND (type <> ?2
966 OR txt <> ?3
967 OR txt_normalized <> ?4
968 OR param <> ?5
969 OR mime_in_reply_to <> ?6);",
970 (
971 time(),
972 msg.viewtype,
973 &msg.text,
974 message::normalize_text(&msg.text),
975 msg.param.to_string(),
976 msg.in_reply_to.as_deref().unwrap_or_default(),
977 msg.id,
978 ),
979 ).await?;
980 return Ok(affected_rows > 0);
981 }
982 }
983 }
984
985 let row_id = context
986 .sql
987 .transaction(|transaction| {
988 transaction.execute(
990 "DELETE FROM msgs WHERE chat_id=? AND state=?",
991 (self, MessageState::OutDraft),
992 )?;
993
994 transaction.execute(
996 "INSERT INTO msgs (
997 chat_id,
998 from_id,
999 timestamp,
1000 type,
1001 state,
1002 txt,
1003 txt_normalized,
1004 param,
1005 hidden,
1006 mime_in_reply_to)
1007 VALUES (?,?,?,?,?,?,?,?,?,?);",
1008 (
1009 self,
1010 ContactId::SELF,
1011 time(),
1012 msg.viewtype,
1013 MessageState::OutDraft,
1014 &msg.text,
1015 message::normalize_text(&msg.text),
1016 msg.param.to_string(),
1017 1,
1018 msg.in_reply_to.as_deref().unwrap_or_default(),
1019 ),
1020 )?;
1021
1022 Ok(transaction.last_insert_rowid())
1023 })
1024 .await?;
1025 msg.id = MsgId::new(row_id.try_into()?);
1026 Ok(true)
1027 }
1028
1029 pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
1031 let count = context
1032 .sql
1033 .count(
1034 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
1035 (self,),
1036 )
1037 .await?;
1038 Ok(count)
1039 }
1040
1041 pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
1043 let count = if self.is_archived_link() {
1054 context
1055 .sql
1056 .count(
1057 "SELECT COUNT(DISTINCT(m.chat_id))
1058 FROM msgs m
1059 LEFT JOIN chats c ON m.chat_id=c.id
1060 WHERE m.state=10
1061 and m.hidden=0
1062 AND m.chat_id>9
1063 AND c.blocked=0
1064 AND c.archived=1
1065 ",
1066 (),
1067 )
1068 .await?
1069 } else {
1070 context
1071 .sql
1072 .count(
1073 "SELECT COUNT(*)
1074 FROM msgs
1075 WHERE state=?
1076 AND hidden=0
1077 AND chat_id=?;",
1078 (MessageState::InFresh, self),
1079 )
1080 .await?
1081 };
1082 Ok(count)
1083 }
1084
1085 pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
1086 Ok(context
1087 .sql
1088 .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
1089 .await?
1090 .unwrap_or(0))
1091 }
1092
1093 pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
1096 let timestamp = context
1097 .sql
1098 .query_get_value(
1099 "SELECT MAX(timestamp)
1100 FROM msgs
1101 WHERE chat_id=?
1102 HAVING COUNT(*) > 0",
1103 (self,),
1104 )
1105 .await?;
1106 Ok(timestamp)
1107 }
1108
1109 pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
1115 let intersection: Vec<(ChatId, f64)> = context
1117 .sql
1118 .query_map(
1119 "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
1120 FROM chats_contacts as x
1121 JOIN chats_contacts as y
1122 WHERE x.contact_id > 9
1123 AND y.contact_id > 9
1124 AND x.add_timestamp >= x.remove_timestamp
1125 AND y.add_timestamp >= y.remove_timestamp
1126 AND x.chat_id=?
1127 AND y.chat_id<>x.chat_id
1128 AND y.chat_id>?
1129 GROUP BY y.chat_id",
1130 (self, DC_CHAT_ID_LAST_SPECIAL),
1131 |row| {
1132 let chat_id: ChatId = row.get(0)?;
1133 let intersection: f64 = row.get(1)?;
1134 Ok((chat_id, intersection))
1135 },
1136 |rows| {
1137 rows.collect::<std::result::Result<Vec<_>, _>>()
1138 .map_err(Into::into)
1139 },
1140 )
1141 .await
1142 .context("failed to calculate member set intersections")?;
1143
1144 let chat_size: HashMap<ChatId, f64> = context
1145 .sql
1146 .query_map(
1147 "SELECT chat_id, count(*) AS n
1148 FROM chats_contacts
1149 WHERE contact_id > ? AND chat_id > ?
1150 AND add_timestamp >= remove_timestamp
1151 GROUP BY chat_id",
1152 (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
1153 |row| {
1154 let chat_id: ChatId = row.get(0)?;
1155 let size: f64 = row.get(1)?;
1156 Ok((chat_id, size))
1157 },
1158 |rows| {
1159 rows.collect::<std::result::Result<HashMap<ChatId, f64>, _>>()
1160 .map_err(Into::into)
1161 },
1162 )
1163 .await
1164 .context("failed to count chat member sizes")?;
1165
1166 let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
1167 let mut chats_with_metrics = Vec::new();
1168 for (chat_id, intersection_size) in intersection {
1169 if intersection_size > 0.0 {
1170 let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
1171 let union_size = our_chat_size + other_chat_size - intersection_size;
1172 let metric = intersection_size / union_size;
1173 chats_with_metrics.push((chat_id, metric))
1174 }
1175 }
1176 chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
1177 metric2
1178 .partial_cmp(metric1)
1179 .unwrap_or(chat_id2.cmp(chat_id1))
1180 });
1181
1182 let mut res = Vec::new();
1184 let now = time();
1185 for (chat_id, metric) in chats_with_metrics {
1186 if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
1187 if now > chat_timestamp + 42 * 24 * 3600 {
1188 continue;
1190 }
1191 }
1192
1193 if metric < 0.1 {
1194 break;
1196 }
1197
1198 let chat = Chat::load_from_db(context, chat_id).await?;
1199 if chat.typ != Chattype::Group {
1200 continue;
1201 }
1202
1203 match chat.visibility {
1204 ChatVisibility::Normal | ChatVisibility::Pinned => {}
1205 ChatVisibility::Archived => continue,
1206 }
1207
1208 res.push((chat_id, metric));
1209 if res.len() >= 5 {
1210 break;
1211 }
1212 }
1213
1214 Ok(res)
1215 }
1216
1217 pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1221 let chat_ids: Vec<ChatId> = self
1222 .get_similar_chat_ids(context)
1223 .await
1224 .context("failed to get similar chat IDs")?
1225 .into_iter()
1226 .map(|(chat_id, _metric)| chat_id)
1227 .collect();
1228 let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1229 Ok(chatlist)
1230 }
1231
1232 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1233 let res: Option<String> = context
1234 .sql
1235 .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1236 .await?;
1237 Ok(res
1238 .map(|s| s.parse().unwrap_or_default())
1239 .unwrap_or_default())
1240 }
1241
1242 pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1244 let param = self.get_param(context).await?;
1245 let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1246 Ok(unpromoted)
1247 }
1248
1249 pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1251 let promoted = !self.is_unpromoted(context).await?;
1252 Ok(promoted)
1253 }
1254
1255 pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1257 Ok(self.get_param(context).await?.exists(Param::Selftalk))
1258 }
1259
1260 pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1262 Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1263 }
1264
1265 async fn parent_query<T, F>(
1266 self,
1267 context: &Context,
1268 fields: &str,
1269 state_out_min: MessageState,
1270 f: F,
1271 ) -> Result<Option<T>>
1272 where
1273 F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1274 T: Send + 'static,
1275 {
1276 let sql = &context.sql;
1277 let query = format!(
1278 "SELECT {fields} \
1279 FROM msgs \
1280 WHERE chat_id=? \
1281 AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1282 AND NOT hidden \
1283 AND download_state={} \
1284 AND from_id != {} \
1285 ORDER BY timestamp DESC, id DESC \
1286 LIMIT 1;",
1287 MessageState::InFresh as u32,
1288 MessageState::InSeen as u32,
1289 state_out_min as u32,
1290 DownloadState::Done as u32,
1293 ContactId::INFO.to_u32(),
1296 );
1297 sql.query_row_optional(&query, (self,), f).await
1298 }
1299
1300 async fn get_parent_mime_headers(
1301 self,
1302 context: &Context,
1303 state_out_min: MessageState,
1304 ) -> Result<Option<(String, String, String)>> {
1305 self.parent_query(
1306 context,
1307 "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1308 state_out_min,
1309 |row: &rusqlite::Row| {
1310 let rfc724_mid: String = row.get(0)?;
1311 let mime_in_reply_to: String = row.get(1)?;
1312 let mime_references: String = row.get(2)?;
1313 Ok((rfc724_mid, mime_in_reply_to, mime_references))
1314 },
1315 )
1316 .await
1317 }
1318
1319 pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1327 let mut ret_available = String::new();
1328 let mut ret_reset = String::new();
1329
1330 for contact_id in get_chat_contacts(context, self)
1331 .await?
1332 .iter()
1333 .filter(|&contact_id| !contact_id.is_special())
1334 {
1335 let contact = Contact::get_by_id(context, *contact_id).await?;
1336 let addr = contact.get_addr();
1337 let peerstate = Peerstate::from_addr(context, addr).await?;
1338
1339 match peerstate
1340 .filter(|peerstate| peerstate.peek_key(false).is_some())
1341 .map(|peerstate| peerstate.prefer_encrypt)
1342 {
1343 Some(EncryptPreference::Mutual) | Some(EncryptPreference::NoPreference) => {
1344 ret_available += &format!("{addr}\n")
1345 }
1346 Some(EncryptPreference::Reset) | None => ret_reset += &format!("{addr}\n"),
1347 };
1348 }
1349
1350 let mut ret = String::new();
1351 if !ret_reset.is_empty() {
1352 ret += &stock_str::encr_none(context).await;
1353 ret.push(':');
1354 ret.push('\n');
1355 ret += &ret_reset;
1356 }
1357 if !ret_available.is_empty() {
1358 if !ret.is_empty() {
1359 ret.push('\n');
1360 }
1361 ret += &stock_str::e2e_available(context).await;
1362 ret.push(':');
1363 ret.push('\n');
1364 ret += &ret_available;
1365 }
1366
1367 Ok(ret.trim().to_string())
1368 }
1369
1370 pub fn to_u32(self) -> u32 {
1375 self.0
1376 }
1377
1378 pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1379 context
1380 .sql
1381 .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1382 .await?;
1383 Ok(())
1384 }
1385
1386 pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
1388 let protection_status = context
1389 .sql
1390 .query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
1391 .await?
1392 .unwrap_or_default();
1393 Ok(protection_status)
1394 }
1395
1396 pub(crate) async fn calc_sort_timestamp(
1405 self,
1406 context: &Context,
1407 message_timestamp: i64,
1408 always_sort_to_bottom: bool,
1409 received: bool,
1410 incoming: bool,
1411 ) -> Result<i64> {
1412 let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1413
1414 let last_msg_time: Option<i64> = if always_sort_to_bottom {
1415 context
1421 .sql
1422 .query_get_value(
1423 "SELECT MAX(timestamp)
1424 FROM msgs
1425 WHERE chat_id=? AND state!=?
1426 HAVING COUNT(*) > 0",
1427 (self, MessageState::OutDraft),
1428 )
1429 .await?
1430 } else if received {
1431 context
1442 .sql
1443 .query_row_optional(
1444 "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1445 FROM msgs
1446 WHERE chat_id=? AND hidden=0 AND state>?
1447 HAVING COUNT(*) > 0",
1448 (MessageState::InSeen, self, MessageState::InFresh),
1449 |row| {
1450 let ts: i64 = row.get(0)?;
1451 let ts_sent_seen: i64 = row.get(1)?;
1452 Ok((ts, ts_sent_seen))
1453 },
1454 )
1455 .await?
1456 .and_then(|(ts, ts_sent_seen)| {
1457 match incoming || ts_sent_seen <= message_timestamp {
1458 true => Some(ts),
1459 false => None,
1460 }
1461 })
1462 } else {
1463 None
1464 };
1465
1466 if let Some(last_msg_time) = last_msg_time {
1467 if last_msg_time > sort_timestamp {
1468 sort_timestamp = last_msg_time;
1469 }
1470 }
1471
1472 Ok(sort_timestamp)
1473 }
1474
1475 pub(crate) fn spawn_securejoin_wait(self, context: &Context, timeout: u64) {
1478 let context = context.clone();
1479 task::spawn(async move {
1480 tokio::time::sleep(Duration::from_secs(timeout)).await;
1481 let chat = Chat::load_from_db(&context, self).await?;
1482 chat.check_securejoin_wait(&context, 0).await?;
1483 Result::<()>::Ok(())
1484 });
1485 }
1486}
1487
1488impl std::fmt::Display for ChatId {
1489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490 if self.is_trash() {
1491 write!(f, "Chat#Trash")
1492 } else if self.is_archived_link() {
1493 write!(f, "Chat#ArchivedLink")
1494 } else if self.is_alldone_hint() {
1495 write!(f, "Chat#AlldoneHint")
1496 } else if self.is_special() {
1497 write!(f, "Chat#Special{}", self.0)
1498 } else {
1499 write!(f, "Chat#{}", self.0)
1500 }
1501 }
1502}
1503
1504impl rusqlite::types::ToSql for ChatId {
1509 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
1510 let val = rusqlite::types::Value::Integer(i64::from(self.0));
1511 let out = rusqlite::types::ToSqlOutput::Owned(val);
1512 Ok(out)
1513 }
1514}
1515
1516impl rusqlite::types::FromSql for ChatId {
1518 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1519 i64::column_result(value).and_then(|val| {
1520 if 0 <= val && val <= i64::from(u32::MAX) {
1521 Ok(ChatId::new(val as u32))
1522 } else {
1523 Err(rusqlite::types::FromSqlError::OutOfRange(val))
1524 }
1525 })
1526 }
1527}
1528
1529#[derive(Debug, Clone, Deserialize, Serialize)]
1534pub struct Chat {
1535 pub id: ChatId,
1537
1538 pub typ: Chattype,
1540
1541 pub name: String,
1543
1544 pub visibility: ChatVisibility,
1546
1547 pub grpid: String,
1550
1551 pub blocked: Blocked,
1553
1554 pub param: Params,
1556
1557 is_sending_locations: bool,
1559
1560 pub mute_duration: MuteDuration,
1562
1563 pub(crate) protected: ProtectionStatus,
1565}
1566
1567impl Chat {
1568 pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1570 let mut chat = context
1571 .sql
1572 .query_row(
1573 "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1574 c.blocked, c.locations_send_until, c.muted_until, c.protected
1575 FROM chats c
1576 WHERE c.id=?;",
1577 (chat_id,),
1578 |row| {
1579 let c = Chat {
1580 id: chat_id,
1581 typ: row.get(0)?,
1582 name: row.get::<_, String>(1)?,
1583 grpid: row.get::<_, String>(2)?,
1584 param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1585 visibility: row.get(4)?,
1586 blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1587 is_sending_locations: row.get(6)?,
1588 mute_duration: row.get(7)?,
1589 protected: row.get(8)?,
1590 };
1591 Ok(c)
1592 },
1593 )
1594 .await
1595 .context(format!("Failed loading chat {chat_id} from database"))?;
1596
1597 if chat.id.is_archived_link() {
1598 chat.name = stock_str::archived_chats(context).await;
1599 } else {
1600 if chat.typ == Chattype::Single && chat.name.is_empty() {
1601 let mut chat_name = "Err [Name not found]".to_owned();
1604 match get_chat_contacts(context, chat.id).await {
1605 Ok(contacts) => {
1606 if let Some(contact_id) = contacts.first() {
1607 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1608 contact.get_display_name().clone_into(&mut chat_name);
1609 }
1610 }
1611 }
1612 Err(err) => {
1613 error!(
1614 context,
1615 "Failed to load contacts for {}: {:#}.", chat.id, err
1616 );
1617 }
1618 }
1619 chat.name = chat_name;
1620 }
1621 if chat.param.exists(Param::Selftalk) {
1622 chat.name = stock_str::saved_messages(context).await;
1623 } else if chat.param.exists(Param::Devicetalk) {
1624 chat.name = stock_str::device_messages(context).await;
1625 }
1626 }
1627
1628 Ok(chat)
1629 }
1630
1631 pub fn is_self_talk(&self) -> bool {
1633 self.param.exists(Param::Selftalk)
1634 }
1635
1636 pub fn is_device_talk(&self) -> bool {
1638 self.param.exists(Param::Devicetalk)
1639 }
1640
1641 pub fn is_mailing_list(&self) -> bool {
1643 self.typ == Chattype::Mailinglist
1644 }
1645
1646 pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1650 self.why_cant_send_ex(context, &|_| false).await
1651 }
1652
1653 pub(crate) async fn why_cant_send_ex(
1654 &self,
1655 context: &Context,
1656 skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1657 ) -> Result<Option<CantSendReason>> {
1658 use CantSendReason::*;
1659 if self.id.is_special() {
1662 let reason = SpecialChat;
1663 if !skip_fn(&reason) {
1664 return Ok(Some(reason));
1665 }
1666 }
1667 if self.is_device_talk() {
1668 let reason = DeviceChat;
1669 if !skip_fn(&reason) {
1670 return Ok(Some(reason));
1671 }
1672 }
1673 if self.is_contact_request() {
1674 let reason = ContactRequest;
1675 if !skip_fn(&reason) {
1676 return Ok(Some(reason));
1677 }
1678 }
1679 if self.is_protection_broken() {
1680 let reason = ProtectionBroken;
1681 if !skip_fn(&reason) {
1682 return Ok(Some(reason));
1683 }
1684 }
1685 if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1686 let reason = ReadOnlyMailingList;
1687 if !skip_fn(&reason) {
1688 return Ok(Some(reason));
1689 }
1690 }
1691
1692 let reason = NotAMember;
1694 if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1695 return Ok(Some(reason));
1696 }
1697 let reason = SecurejoinWait;
1698 if !skip_fn(&reason) {
1699 let (can_write, _) = self
1700 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
1701 .await?;
1702 if !can_write {
1703 return Ok(Some(reason));
1704 }
1705 }
1706 Ok(None)
1707 }
1708
1709 pub async fn can_send(&self, context: &Context) -> Result<bool> {
1713 Ok(self.why_cant_send(context).await?.is_none())
1714 }
1715
1716 pub(crate) async fn check_securejoin_wait(
1722 &self,
1723 context: &Context,
1724 timeout: u64,
1725 ) -> Result<(bool, u64)> {
1726 if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
1727 return Ok((true, 0));
1728 }
1729
1730 let (mut param_wait, mut param_timeout) = (Params::new(), Params::new());
1733 param_wait.set_cmd(SystemMessage::SecurejoinWait);
1734 param_timeout.set_cmd(SystemMessage::SecurejoinWaitTimeout);
1735 let (param_wait, param_timeout) = (param_wait.to_string(), param_timeout.to_string());
1736 let Some((param, ts_sort, ts_start)) = context
1737 .sql
1738 .query_row_optional(
1739 "SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
1740 (SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
1741 (self.id, ¶m_wait, ¶m_timeout),
1742 |row| {
1743 let param: String = row.get(0)?;
1744 let ts_sort: i64 = row.get(1)?;
1745 let ts_start: i64 = row.get(2)?;
1746 Ok((param, ts_sort, ts_start))
1747 },
1748 )
1749 .await?
1750 else {
1751 return Ok((true, 0));
1752 };
1753
1754 if param == param_timeout {
1755 return Ok((false, 0));
1756 }
1757
1758 let now = time();
1759 if ts_start <= now {
1761 let timeout = ts_start
1762 .saturating_add(timeout.try_into()?)
1763 .saturating_sub(now);
1764 if timeout > 0 {
1765 return Ok((false, timeout as u64));
1766 }
1767 }
1768
1769 add_info_msg_with_cmd(
1770 context,
1771 self.id,
1772 &stock_str::securejoin_takes_longer(context).await,
1773 SystemMessage::SecurejoinWaitTimeout,
1774 ts_sort,
1777 Some(now),
1778 None,
1779 None,
1780 None,
1781 )
1782 .await?;
1783
1784 Ok((false, 0))
1785 }
1786
1787 pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1791 match self.typ {
1792 Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
1793 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1794 }
1795 }
1796
1797 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1798 context
1799 .sql
1800 .execute(
1801 "UPDATE chats SET param=? WHERE id=?",
1802 (self.param.to_string(), self.id),
1803 )
1804 .await?;
1805 Ok(())
1806 }
1807
1808 pub fn get_id(&self) -> ChatId {
1810 self.id
1811 }
1812
1813 pub fn get_type(&self) -> Chattype {
1815 self.typ
1816 }
1817
1818 pub fn get_name(&self) -> &str {
1820 &self.name
1821 }
1822
1823 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1825 self.param.get(Param::ListPost)
1826 }
1827
1828 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1830 if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1831 if !image_rel.is_empty() {
1832 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1833 }
1834 } else if self.id.is_archived_link() {
1835 if let Ok(image_rel) = get_archive_icon(context).await {
1836 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1837 }
1838 } else if self.typ == Chattype::Single {
1839 let contacts = get_chat_contacts(context, self.id).await?;
1840 if let Some(contact_id) = contacts.first() {
1841 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1842 return contact.get_profile_image(context).await;
1843 }
1844 }
1845 } else if self.typ == Chattype::Broadcast {
1846 if let Ok(image_rel) = get_broadcast_icon(context).await {
1847 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1848 }
1849 }
1850 Ok(None)
1851 }
1852
1853 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1858 let mut color = 0;
1859
1860 if self.typ == Chattype::Single {
1861 let contacts = get_chat_contacts(context, self.id).await?;
1862 if let Some(contact_id) = contacts.first() {
1863 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1864 color = contact.get_color();
1865 }
1866 }
1867 } else {
1868 color = str_to_color(&self.name);
1869 }
1870
1871 Ok(color)
1872 }
1873
1874 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1879 let draft = match self.id.get_draft(context).await? {
1880 Some(message) => message.text,
1881 _ => String::new(),
1882 };
1883 Ok(ChatInfo {
1884 id: self.id,
1885 type_: self.typ as u32,
1886 name: self.name.clone(),
1887 archived: self.visibility == ChatVisibility::Archived,
1888 param: self.param.to_string(),
1889 is_sending_locations: self.is_sending_locations,
1890 color: self.get_color(context).await?,
1891 profile_image: self
1892 .get_profile_image(context)
1893 .await?
1894 .unwrap_or_else(std::path::PathBuf::new),
1895 draft,
1896 is_muted: self.is_muted(),
1897 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1898 })
1899 }
1900
1901 pub fn get_visibility(&self) -> ChatVisibility {
1903 self.visibility
1904 }
1905
1906 pub fn is_contact_request(&self) -> bool {
1911 self.blocked == Blocked::Request
1912 }
1913
1914 pub fn is_unpromoted(&self) -> bool {
1916 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1917 }
1918
1919 pub fn is_promoted(&self) -> bool {
1922 !self.is_unpromoted()
1923 }
1924
1925 pub fn is_protected(&self) -> bool {
1936 self.protected == ProtectionStatus::Protected
1937 }
1938
1939 pub fn is_protection_broken(&self) -> bool {
1953 match self.protected {
1954 ProtectionStatus::Protected => false,
1955 ProtectionStatus::Unprotected => false,
1956 ProtectionStatus::ProtectionBroken => true,
1957 }
1958 }
1959
1960 pub fn is_sending_locations(&self) -> bool {
1962 self.is_sending_locations
1963 }
1964
1965 pub fn is_muted(&self) -> bool {
1967 match self.mute_duration {
1968 MuteDuration::NotMuted => false,
1969 MuteDuration::Forever => true,
1970 MuteDuration::Until(when) => when > SystemTime::now(),
1971 }
1972 }
1973
1974 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1976 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1977 Ok(member_list_timestamp)
1978 } else {
1979 Ok(self.id.created_timestamp(context).await?)
1980 }
1981 }
1982
1983 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1989 let now = time();
1990 let member_list_ts = self.member_list_timestamp(context).await?;
1991 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1992 >= member_list_ts.saturating_add(60 * 24 * 3600);
1993 Ok(is_stale)
1994 }
1995
1996 async fn prepare_msg_raw(
2002 &mut self,
2003 context: &Context,
2004 msg: &mut Message,
2005 update_msg_id: Option<MsgId>,
2006 timestamp: i64,
2007 ) -> Result<MsgId> {
2008 let mut to_id = 0;
2009 let mut location_id = 0;
2010
2011 let new_rfc724_mid = create_outgoing_rfc724_mid();
2012
2013 if self.typ == Chattype::Single {
2014 if let Some(id) = context
2015 .sql
2016 .query_get_value(
2017 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
2018 (self.id,),
2019 )
2020 .await?
2021 {
2022 to_id = id;
2023 } else {
2024 error!(
2025 context,
2026 "Cannot send message, contact for {} not found.", self.id,
2027 );
2028 bail!("Cannot set message, contact for {} not found.", self.id);
2029 }
2030 } else if self.typ == Chattype::Group
2031 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
2032 {
2033 msg.param.set_int(Param::AttachGroupImage, 1);
2034 self.param
2035 .remove(Param::Unpromoted)
2036 .set_i64(Param::GroupNameTimestamp, timestamp);
2037 self.update_param(context).await?;
2038 context
2044 .sync_qr_code_tokens(Some(self.grpid.as_str()))
2045 .await
2046 .log_err(context)
2047 .ok();
2048 }
2049
2050 let is_bot = context.get_config_bool(Config::Bot).await?;
2051 msg.param
2052 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2053
2054 let new_references;
2058 if self.is_self_talk() {
2059 new_references = String::new();
2062 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2063 self
2069 .id
2070 .get_parent_mime_headers(context, MessageState::OutPending)
2071 .await?
2072 {
2073 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2077 msg.in_reply_to = Some(parent_rfc724_mid.clone());
2078 }
2079
2080 let parent_references = if parent_references.is_empty() {
2090 parent_in_reply_to
2091 } else {
2092 parent_references
2093 };
2094
2095 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2098 references_vec.reverse();
2099
2100 if !parent_rfc724_mid.is_empty()
2101 && !references_vec.contains(&parent_rfc724_mid.as_str())
2102 {
2103 references_vec.push(&parent_rfc724_mid)
2104 }
2105
2106 if references_vec.is_empty() {
2107 new_references = new_rfc724_mid.clone();
2110 } else {
2111 new_references = references_vec.join(" ");
2112 }
2113 } else {
2114 new_references = new_rfc724_mid.clone();
2120 }
2121
2122 if msg.param.exists(Param::SetLatitude) {
2124 if let Ok(row_id) = context
2125 .sql
2126 .insert(
2127 "INSERT INTO locations \
2128 (timestamp,from_id,chat_id, latitude,longitude,independent)\
2129 VALUES (?,?,?, ?,?,1);",
2130 (
2131 timestamp,
2132 ContactId::SELF,
2133 self.id,
2134 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2135 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2136 ),
2137 )
2138 .await
2139 {
2140 location_id = row_id;
2141 }
2142 }
2143
2144 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2145 EphemeralTimer::Disabled
2146 } else {
2147 self.id.get_ephemeral_timer(context).await?
2148 };
2149 let ephemeral_timestamp = match ephemeral_timer {
2150 EphemeralTimer::Disabled => 0,
2151 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2152 };
2153
2154 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2155 let new_mime_headers = if msg.has_html() {
2156 if msg.param.exists(Param::Forwarded) {
2157 msg.get_id().get_html(context).await?
2158 } else {
2159 msg.param.get(Param::SendHtml).map(|s| s.to_string())
2160 }
2161 } else {
2162 None
2163 };
2164 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2165 let html_part = MimePart::new("text/html", s);
2166 let mut buffer = Vec::new();
2167 let cursor = Cursor::new(&mut buffer);
2168 html_part.write_part(cursor).ok();
2169 String::from_utf8_lossy(&buffer).to_string()
2170 });
2171 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2172 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2176 false => None,
2177 });
2178 let new_mime_headers = match new_mime_headers {
2179 Some(h) => Some(tokio::task::block_in_place(move || {
2180 buf_compress(h.as_bytes())
2181 })?),
2182 None => None,
2183 };
2184
2185 msg.chat_id = self.id;
2186 msg.from_id = ContactId::SELF;
2187 msg.rfc724_mid = new_rfc724_mid;
2188 msg.timestamp_sort = timestamp;
2189
2190 if let Some(update_msg_id) = update_msg_id {
2192 context
2193 .sql
2194 .execute(
2195 "UPDATE msgs
2196 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2197 state=?, txt=?, txt_normalized=?, subject=?, param=?,
2198 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2199 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2200 ephemeral_timestamp=?
2201 WHERE id=?;",
2202 params_slice![
2203 msg.rfc724_mid,
2204 msg.chat_id,
2205 msg.from_id,
2206 to_id,
2207 msg.timestamp_sort,
2208 msg.viewtype,
2209 msg.state,
2210 msg_text,
2211 message::normalize_text(&msg_text),
2212 &msg.subject,
2213 msg.param.to_string(),
2214 msg.hidden,
2215 msg.in_reply_to.as_deref().unwrap_or_default(),
2216 new_references,
2217 new_mime_headers.is_some(),
2218 new_mime_headers.unwrap_or_default(),
2219 location_id as i32,
2220 ephemeral_timer,
2221 ephemeral_timestamp,
2222 update_msg_id
2223 ],
2224 )
2225 .await?;
2226 msg.id = update_msg_id;
2227 } else {
2228 let raw_id = context
2229 .sql
2230 .insert(
2231 "INSERT INTO msgs (
2232 rfc724_mid,
2233 chat_id,
2234 from_id,
2235 to_id,
2236 timestamp,
2237 type,
2238 state,
2239 txt,
2240 txt_normalized,
2241 subject,
2242 param,
2243 hidden,
2244 mime_in_reply_to,
2245 mime_references,
2246 mime_modified,
2247 mime_headers,
2248 mime_compressed,
2249 location_id,
2250 ephemeral_timer,
2251 ephemeral_timestamp)
2252 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2253 params_slice![
2254 msg.rfc724_mid,
2255 msg.chat_id,
2256 msg.from_id,
2257 to_id,
2258 msg.timestamp_sort,
2259 msg.viewtype,
2260 msg.state,
2261 msg_text,
2262 message::normalize_text(&msg_text),
2263 &msg.subject,
2264 msg.param.to_string(),
2265 msg.hidden,
2266 msg.in_reply_to.as_deref().unwrap_or_default(),
2267 new_references,
2268 new_mime_headers.is_some(),
2269 new_mime_headers.unwrap_or_default(),
2270 location_id as i32,
2271 ephemeral_timer,
2272 ephemeral_timestamp
2273 ],
2274 )
2275 .await?;
2276 context.new_msgs_notify.notify_one();
2277 msg.id = MsgId::new(u32::try_from(raw_id)?);
2278
2279 maybe_set_logging_xdc(context, msg, self.id).await?;
2280 context
2281 .update_webxdc_integration_database(msg, context)
2282 .await?;
2283 }
2284 context.scheduler.interrupt_ephemeral_task().await;
2285 Ok(msg.id)
2286 }
2287
2288 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2290 let addrs = context
2291 .sql
2292 .query_map(
2293 "SELECT c.addr \
2294 FROM contacts c INNER JOIN chats_contacts cc \
2295 ON c.id=cc.contact_id \
2296 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2297 (self.id,),
2298 |row| row.get::<_, String>(0),
2299 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2300 )
2301 .await?;
2302 self.sync(context, SyncAction::SetContacts(addrs)).await
2303 }
2304
2305 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2307 match self.typ {
2308 Chattype::Single => {
2309 if self.is_device_talk() {
2310 return Ok(Some(SyncId::Device));
2311 }
2312
2313 let mut r = None;
2314 for contact_id in get_chat_contacts(context, self.id).await? {
2315 if contact_id == ContactId::SELF && !self.is_self_talk() {
2316 continue;
2317 }
2318 if r.is_some() {
2319 return Ok(None);
2320 }
2321 let contact = Contact::get_by_id(context, contact_id).await?;
2322 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2323 }
2324 Ok(r)
2325 }
2326 Chattype::Broadcast | Chattype::Group | Chattype::Mailinglist => {
2327 if !self.grpid.is_empty() {
2328 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2329 }
2330
2331 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2332 .id
2333 .get_parent_mime_headers(context, MessageState::OutDelivered)
2334 .await?
2335 else {
2336 warn!(
2337 context,
2338 "Chat::get_sync_id({}): No good message identifying the chat found.",
2339 self.id
2340 );
2341 return Ok(None);
2342 };
2343 Ok(Some(SyncId::Msgids(vec![
2344 parent_in_reply_to,
2345 parent_rfc724_mid,
2346 ])))
2347 }
2348 }
2349 }
2350
2351 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2353 if let Some(id) = self.get_sync_id(context).await? {
2354 sync(context, id, action).await?;
2355 }
2356 Ok(())
2357 }
2358}
2359
2360pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2361 context
2362 .add_sync_item(SyncData::AlterChat { id, action })
2363 .await?;
2364 context.scheduler.interrupt_inbox().await;
2365 Ok(())
2366}
2367
2368#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2370#[repr(i8)]
2371pub enum ChatVisibility {
2372 Normal = 0,
2374
2375 Archived = 1,
2377
2378 Pinned = 2,
2380}
2381
2382impl rusqlite::types::ToSql for ChatVisibility {
2383 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
2384 let val = rusqlite::types::Value::Integer(*self as i64);
2385 let out = rusqlite::types::ToSqlOutput::Owned(val);
2386 Ok(out)
2387 }
2388}
2389
2390impl rusqlite::types::FromSql for ChatVisibility {
2391 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2392 i64::column_result(value).map(|val| {
2393 match val {
2394 2 => ChatVisibility::Pinned,
2395 1 => ChatVisibility::Archived,
2396 0 => ChatVisibility::Normal,
2397 _ => ChatVisibility::Normal,
2399 }
2400 })
2401 }
2402}
2403
2404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2406#[non_exhaustive]
2407pub struct ChatInfo {
2408 pub id: ChatId,
2410
2411 #[serde(rename = "type")]
2418 pub type_: u32,
2419
2420 pub name: String,
2422
2423 pub archived: bool,
2425
2426 pub param: String,
2430
2431 pub is_sending_locations: bool,
2433
2434 pub color: u32,
2438
2439 pub profile_image: std::path::PathBuf,
2444
2445 pub draft: String,
2453
2454 pub is_muted: bool,
2458
2459 pub ephemeral_timer: EphemeralTimer,
2461 }
2467
2468pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<()> {
2469 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2470 ChatIdBlocked::lookup_by_contact(context, ContactId::SELF).await?
2471 {
2472 let icon = include_bytes!("../assets/icon-saved-messages.png");
2473 let blob =
2474 BlobObject::create_and_deduplicate_from_bytes(context, icon, "saved-messages.png")?;
2475 let icon = blob.as_name().to_string();
2476
2477 let mut chat = Chat::load_from_db(context, chat_id).await?;
2478 chat.param.set(Param::ProfileImage, icon);
2479 chat.update_param(context).await?;
2480 }
2481 Ok(())
2482}
2483
2484pub(crate) async fn update_device_icon(context: &Context) -> Result<()> {
2485 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2486 ChatIdBlocked::lookup_by_contact(context, ContactId::DEVICE).await?
2487 {
2488 let icon = include_bytes!("../assets/icon-device.png");
2489 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "device.png")?;
2490 let icon = blob.as_name().to_string();
2491
2492 let mut chat = Chat::load_from_db(context, chat_id).await?;
2493 chat.param.set(Param::ProfileImage, &icon);
2494 chat.update_param(context).await?;
2495
2496 let mut contact = Contact::get_by_id(context, ContactId::DEVICE).await?;
2497 contact.param.set(Param::ProfileImage, icon);
2498 contact.update_param(context).await?;
2499 }
2500 Ok(())
2501}
2502
2503pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
2504 if let Some(icon) = context.sql.get_raw_config("icon-broadcast").await? {
2505 return Ok(icon);
2506 }
2507
2508 let icon = include_bytes!("../assets/icon-broadcast.png");
2509 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "broadcast.png")?;
2510 let icon = blob.as_name().to_string();
2511 context
2512 .sql
2513 .set_raw_config("icon-broadcast", Some(&icon))
2514 .await?;
2515 Ok(icon)
2516}
2517
2518pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
2519 if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
2520 return Ok(icon);
2521 }
2522
2523 let icon = include_bytes!("../assets/icon-archive.png");
2524 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "archive.png")?;
2525 let icon = blob.as_name().to_string();
2526 context
2527 .sql
2528 .set_raw_config("icon-archive", Some(&icon))
2529 .await?;
2530 Ok(icon)
2531}
2532
2533async fn update_special_chat_name(
2534 context: &Context,
2535 contact_id: ContactId,
2536 name: String,
2537) -> Result<()> {
2538 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2539 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2540 {
2541 context
2543 .sql
2544 .execute(
2545 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2546 (&name, chat_id, &name),
2547 )
2548 .await?;
2549 }
2550 Ok(())
2551}
2552
2553pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2554 update_special_chat_name(
2555 context,
2556 ContactId::DEVICE,
2557 stock_str::device_messages(context).await,
2558 )
2559 .await?;
2560 update_special_chat_name(
2561 context,
2562 ContactId::SELF,
2563 stock_str::saved_messages(context).await,
2564 )
2565 .await?;
2566 Ok(())
2567}
2568
2569pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
2572 let chat_ids: Vec<ChatId> = context
2573 .sql
2574 .query_map(
2575 "SELECT chat_id FROM bobstate",
2576 (),
2577 |row| {
2578 let chat_id: ChatId = row.get(0)?;
2579 Ok(chat_id)
2580 },
2581 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2582 )
2583 .await?;
2584
2585 for chat_id in chat_ids {
2586 let chat = Chat::load_from_db(context, chat_id).await?;
2587 let (_, timeout) = chat
2588 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
2589 .await?;
2590 if timeout > 0 {
2591 chat_id.spawn_securejoin_wait(context, timeout);
2592 }
2593 }
2594 Ok(())
2595}
2596
2597#[derive(Debug)]
2605pub(crate) struct ChatIdBlocked {
2606 pub id: ChatId,
2608
2609 pub blocked: Blocked,
2611}
2612
2613impl ChatIdBlocked {
2614 pub async fn lookup_by_contact(
2618 context: &Context,
2619 contact_id: ContactId,
2620 ) -> Result<Option<Self>> {
2621 ensure!(context.sql.is_open().await, "Database not available");
2622 ensure!(
2623 contact_id != ContactId::UNDEFINED,
2624 "Invalid contact id requested"
2625 );
2626
2627 context
2628 .sql
2629 .query_row_optional(
2630 "SELECT c.id, c.blocked
2631 FROM chats c
2632 INNER JOIN chats_contacts j
2633 ON c.id=j.chat_id
2634 WHERE c.type=100 -- 100 = Chattype::Single
2635 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2636 AND j.contact_id=?;",
2637 (contact_id,),
2638 |row| {
2639 let id: ChatId = row.get(0)?;
2640 let blocked: Blocked = row.get(1)?;
2641 Ok(ChatIdBlocked { id, blocked })
2642 },
2643 )
2644 .await
2645 }
2646
2647 pub async fn get_for_contact(
2652 context: &Context,
2653 contact_id: ContactId,
2654 create_blocked: Blocked,
2655 ) -> Result<Self> {
2656 ensure!(context.sql.is_open().await, "Database not available");
2657 ensure!(
2658 contact_id != ContactId::UNDEFINED,
2659 "Invalid contact id requested"
2660 );
2661
2662 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2663 return Ok(res);
2665 }
2666
2667 let contact = Contact::get_by_id(context, contact_id).await?;
2668 let chat_name = contact.get_display_name().to_string();
2669 let mut params = Params::new();
2670 match contact_id {
2671 ContactId::SELF => {
2672 params.set_int(Param::Selftalk, 1);
2673 }
2674 ContactId::DEVICE => {
2675 params.set_int(Param::Devicetalk, 1);
2676 }
2677 _ => (),
2678 }
2679
2680 let protected = contact_id == ContactId::SELF || {
2681 let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
2682 peerstate.is_some_and(|p| {
2683 p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
2684 })
2685 };
2686 let smeared_time = create_smeared_timestamp(context);
2687
2688 let chat_id = context
2689 .sql
2690 .transaction(move |transaction| {
2691 transaction.execute(
2692 "INSERT INTO chats
2693 (type, name, param, blocked, created_timestamp, protected)
2694 VALUES(?, ?, ?, ?, ?, ?)",
2695 (
2696 Chattype::Single,
2697 chat_name,
2698 params.to_string(),
2699 create_blocked as u8,
2700 smeared_time,
2701 if protected {
2702 ProtectionStatus::Protected
2703 } else {
2704 ProtectionStatus::Unprotected
2705 },
2706 ),
2707 )?;
2708 let chat_id = ChatId::new(
2709 transaction
2710 .last_insert_rowid()
2711 .try_into()
2712 .context("chat table rowid overflows u32")?,
2713 );
2714
2715 transaction.execute(
2716 "INSERT INTO chats_contacts
2717 (chat_id, contact_id)
2718 VALUES((SELECT last_insert_rowid()), ?)",
2719 (contact_id,),
2720 )?;
2721
2722 Ok(chat_id)
2723 })
2724 .await?;
2725
2726 if protected {
2727 chat_id
2728 .add_protection_msg(
2729 context,
2730 ProtectionStatus::Protected,
2731 Some(contact_id),
2732 smeared_time,
2733 )
2734 .await?;
2735 }
2736
2737 match contact_id {
2738 ContactId::SELF => update_saved_messages_icon(context).await?,
2739 ContactId::DEVICE => update_device_icon(context).await?,
2740 _ => (),
2741 }
2742
2743 Ok(Self {
2744 id: chat_id,
2745 blocked: create_blocked,
2746 })
2747 }
2748}
2749
2750async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2751 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
2752 } else if msg.viewtype.has_file() {
2754 let mut blob = msg
2755 .param
2756 .get_file_blob(context)?
2757 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2758 let send_as_is = msg.viewtype == Viewtype::File;
2759
2760 if msg.viewtype == Viewtype::File
2761 || msg.viewtype == Viewtype::Image
2762 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2763 {
2764 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2771 if msg.viewtype == Viewtype::Sticker {
2772 if better_type != Viewtype::Image {
2773 msg.param.set_int(Param::ForceSticker, 1);
2775 }
2776 } else if better_type != Viewtype::Webxdc
2777 || context
2778 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2779 .await
2780 .is_ok()
2781 {
2782 msg.viewtype = better_type;
2783 }
2784 }
2785 } else if msg.viewtype == Viewtype::Webxdc {
2786 context
2787 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2788 .await?;
2789 }
2790
2791 if msg.viewtype == Viewtype::Vcard {
2792 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2793 }
2794
2795 let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
2796 if !send_as_is
2797 && (msg.viewtype == Viewtype::Image
2798 || maybe_sticker && !msg.param.exists(Param::ForceSticker))
2799 {
2800 let new_name = blob
2801 .recode_to_image_size(context, msg.get_filename(), &mut maybe_sticker)
2802 .await?;
2803 msg.param.set(Param::Filename, new_name);
2804 msg.param.set(Param::File, blob.as_name());
2805
2806 if !maybe_sticker {
2807 msg.viewtype = Viewtype::Image;
2808 }
2809 }
2810
2811 if !msg.param.exists(Param::MimeType) {
2812 if let Some((_, mime)) = message::guess_msgtype_from_suffix(msg) {
2813 msg.param.set(Param::MimeType, mime);
2814 }
2815 }
2816
2817 msg.try_calc_and_set_dimensions(context).await?;
2818
2819 info!(
2820 context,
2821 "Attaching \"{}\" for message type #{}.",
2822 blob.to_abs_path().display(),
2823 msg.viewtype
2824 );
2825 } else {
2826 bail!("Cannot send messages of type #{}.", msg.viewtype);
2827 }
2828 Ok(())
2829}
2830
2831pub async fn is_contact_in_chat(
2833 context: &Context,
2834 chat_id: ChatId,
2835 contact_id: ContactId,
2836) -> Result<bool> {
2837 let exists = context
2843 .sql
2844 .exists(
2845 "SELECT COUNT(*) FROM chats_contacts
2846 WHERE chat_id=? AND contact_id=?
2847 AND add_timestamp >= remove_timestamp",
2848 (chat_id, contact_id),
2849 )
2850 .await?;
2851 Ok(exists)
2852}
2853
2854pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2861 ensure!(
2862 !chat_id.is_special(),
2863 "chat_id cannot be a special chat: {chat_id}"
2864 );
2865
2866 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2867 msg.param.remove(Param::GuaranteeE2ee);
2868 msg.param.remove(Param::ForcePlaintext);
2869 msg.update_param(context).await?;
2870 }
2871
2872 if msg.is_system_message() {
2874 msg.text = sanitize_bidi_characters(&msg.text);
2875 }
2876
2877 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2878 if !msg.hidden {
2879 context.emit_msgs_changed(msg.chat_id, msg.id);
2880 }
2881
2882 if msg.param.exists(Param::SetLatitude) {
2883 context.emit_location_changed(Some(ContactId::SELF)).await?;
2884 }
2885
2886 context.scheduler.interrupt_smtp().await;
2887 }
2888
2889 Ok(msg.id)
2890}
2891
2892pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2897 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2898 if rowids.is_empty() {
2899 return Ok(msg.id);
2900 }
2901 let mut smtp = crate::smtp::Smtp::new();
2902 for rowid in rowids {
2903 send_msg_to_smtp(context, &mut smtp, rowid)
2904 .await
2905 .context("failed to send message, queued for later sending")?;
2906 }
2907 context.emit_msgs_changed(msg.chat_id, msg.id);
2908 Ok(msg.id)
2909}
2910
2911async fn prepare_send_msg(
2915 context: &Context,
2916 chat_id: ChatId,
2917 msg: &mut Message,
2918) -> Result<Vec<i64>> {
2919 let mut chat = Chat::load_from_db(context, chat_id).await?;
2920
2921 let skip_fn = |reason: &CantSendReason| match reason {
2922 CantSendReason::ProtectionBroken
2923 | CantSendReason::ContactRequest
2924 | CantSendReason::SecurejoinWait => {
2925 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2928 }
2929 CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2933 _ => false,
2934 };
2935 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2936 bail!("Cannot send to {chat_id}: {reason}");
2937 }
2938
2939 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2944 if let Some(quoted_message) = msg.quoted_message(context).await? {
2945 if quoted_message.chat_id != chat_id {
2946 bail!("Bad quote reply");
2947 }
2948 }
2949 }
2950
2951 let update_msg_id = if msg.state == MessageState::OutDraft {
2953 msg.hidden = false;
2954 if !msg.id.is_special() && msg.chat_id == chat_id {
2955 Some(msg.id)
2956 } else {
2957 None
2958 }
2959 } else {
2960 None
2961 };
2962
2963 msg.state = MessageState::OutPending;
2965
2966 prepare_msg_blob(context, msg).await?;
2967 if !msg.hidden {
2968 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2969 }
2970 msg.id = chat
2971 .prepare_msg_raw(
2972 context,
2973 msg,
2974 update_msg_id,
2975 create_smeared_timestamp(context),
2976 )
2977 .await?;
2978 msg.chat_id = chat_id;
2979
2980 let row_ids = create_send_msg_jobs(context, msg)
2981 .await
2982 .context("Failed to create send jobs")?;
2983 Ok(row_ids)
2984}
2985
2986pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2992 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2993 msg.chat_id
2994 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2995 .await?;
2996 }
2997
2998 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2999 let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
3000 let attach_selfavatar = mimefactory.attach_selfavatar;
3001 let mut recipients = mimefactory.recipients();
3002
3003 let from = context.get_primary_self_addr().await?;
3004 let lowercase_from = from.to_lowercase();
3005
3006 recipients.retain(|x| x.to_lowercase() != lowercase_from);
3019 if (context.get_config_bool(Config::BccSelf).await?
3020 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
3021 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
3022 {
3023 recipients.push(from);
3024 }
3025
3026 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
3028 recipients.clear();
3029 }
3030
3031 if recipients.is_empty() {
3032 info!(
3034 context,
3035 "Message {} has no recipient, skipping smtp-send.", msg.id
3036 );
3037 msg.param.set_int(Param::GuaranteeE2ee, 1);
3038 msg.update_param(context).await?;
3039 msg.id.set_delivered(context).await?;
3040 msg.state = MessageState::OutDelivered;
3041 return Ok(Vec::new());
3042 }
3043
3044 let rendered_msg = match mimefactory.render(context).await {
3045 Ok(res) => Ok(res),
3046 Err(err) => {
3047 message::set_msg_failed(context, msg, &err.to_string()).await?;
3048 Err(err)
3049 }
3050 }?;
3051
3052 if needs_encryption && !rendered_msg.is_encrypted {
3053 message::set_msg_failed(
3055 context,
3056 msg,
3057 "End-to-end-encryption unavailable unexpectedly.",
3058 )
3059 .await?;
3060 bail!(
3061 "e2e encryption unavailable {} - {:?}",
3062 msg.id,
3063 needs_encryption
3064 );
3065 }
3066
3067 let now = smeared_time(context);
3068
3069 if rendered_msg.last_added_location_id.is_some() {
3070 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3071 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3072 }
3073 }
3074
3075 if attach_selfavatar {
3076 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3077 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3078 }
3079 }
3080
3081 if rendered_msg.is_encrypted && !needs_encryption {
3082 msg.param.set_int(Param::GuaranteeE2ee, 1);
3083 msg.update_param(context).await?;
3084 }
3085
3086 msg.subject.clone_from(&rendered_msg.subject);
3087 msg.update_subject(context).await?;
3088 let chunk_size = context.get_max_smtp_rcpt_to().await?;
3089 let trans_fn = |t: &mut rusqlite::Transaction| {
3090 let mut row_ids = Vec::<i64>::new();
3091 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3092 t.execute(
3093 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3094 (),
3095 )?;
3096 t.execute(
3097 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3098 (&rendered_msg.message, msg.id),
3099 )?;
3100 } else {
3101 for recipients_chunk in recipients.chunks(chunk_size) {
3102 let recipients_chunk = recipients_chunk.join(" ");
3103 let row_id = t.execute(
3104 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3105 VALUES (?1, ?2, ?3, ?4)",
3106 (
3107 &rendered_msg.rfc724_mid,
3108 recipients_chunk,
3109 &rendered_msg.message,
3110 msg.id,
3111 ),
3112 )?;
3113 row_ids.push(row_id.try_into()?);
3114 }
3115 }
3116 Ok(row_ids)
3117 };
3118 context.sql.transaction(trans_fn).await
3119}
3120
3121pub async fn send_text_msg(
3125 context: &Context,
3126 chat_id: ChatId,
3127 text_to_send: String,
3128) -> Result<MsgId> {
3129 ensure!(
3130 !chat_id.is_special(),
3131 "bad chat_id, can not be a special chat: {}",
3132 chat_id
3133 );
3134
3135 let mut msg = Message::new_text(text_to_send);
3136 send_msg(context, chat_id, &mut msg).await
3137}
3138
3139pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3141 let mut original_msg = Message::load_from_db(context, msg_id).await?;
3142 ensure!(
3143 original_msg.from_id == ContactId::SELF,
3144 "Can edit only own messages"
3145 );
3146 ensure!(!original_msg.is_info(), "Cannot edit info messages");
3147 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3148 ensure!(
3149 original_msg.viewtype != Viewtype::VideochatInvitation,
3150 "Cannot edit videochat invitations"
3151 );
3152 ensure!(
3153 !original_msg.text.is_empty(), "Cannot add text"
3155 );
3156 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3157 if original_msg.text == new_text {
3158 info!(context, "Text unchanged.");
3159 return Ok(());
3160 }
3161
3162 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3163
3164 let mut edit_msg = Message::new_text(EDITED_PREFIX.to_owned() + &new_text); edit_msg.set_quote(context, Some(&original_msg)).await?; if original_msg.get_showpadlock() {
3167 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3168 }
3169 edit_msg
3170 .param
3171 .set(Param::TextEditFor, original_msg.rfc724_mid);
3172 edit_msg.hidden = true;
3173 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3174 Ok(())
3175}
3176
3177pub(crate) async fn save_text_edit_to_db(
3178 context: &Context,
3179 original_msg: &mut Message,
3180 new_text: &str,
3181) -> Result<()> {
3182 original_msg.param.set_int(Param::IsEdited, 1);
3183 context
3184 .sql
3185 .execute(
3186 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3187 (
3188 new_text,
3189 message::normalize_text(new_text),
3190 original_msg.param.to_string(),
3191 original_msg.id,
3192 ),
3193 )
3194 .await?;
3195 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3196 Ok(())
3197}
3198
3199pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3201 ensure!(
3202 !chat_id.is_special(),
3203 "video chat invitation cannot be sent to special chat: {}",
3204 chat_id
3205 );
3206
3207 let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3208 if !instance.is_empty() {
3209 instance
3210 } else {
3211 bail!("webrtc_instance is empty");
3212 }
3213 } else {
3214 bail!("webrtc_instance not set");
3215 };
3216
3217 let instance = Message::create_webrtc_instance(&instance, &create_id());
3218
3219 let mut msg = Message::new(Viewtype::VideochatInvitation);
3220 msg.param.set(Param::WebrtcRoom, &instance);
3221 msg.text =
3222 stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3223 .await;
3224 send_msg(context, chat_id, &mut msg).await
3225}
3226
3227#[derive(Debug)]
3229pub struct MessageListOptions {
3230 pub info_only: bool,
3232
3233 pub add_daymarker: bool,
3235}
3236
3237pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3239 get_chat_msgs_ex(
3240 context,
3241 chat_id,
3242 MessageListOptions {
3243 info_only: false,
3244 add_daymarker: false,
3245 },
3246 )
3247 .await
3248}
3249
3250pub async fn get_chat_msgs_ex(
3252 context: &Context,
3253 chat_id: ChatId,
3254 options: MessageListOptions,
3255) -> Result<Vec<ChatItem>> {
3256 let MessageListOptions {
3257 info_only,
3258 add_daymarker,
3259 } = options;
3260 let process_row = if info_only {
3261 |row: &rusqlite::Row| {
3262 let params = row.get::<_, String>("param")?;
3264 let (from_id, to_id) = (
3265 row.get::<_, ContactId>("from_id")?,
3266 row.get::<_, ContactId>("to_id")?,
3267 );
3268 let is_info_msg: bool = from_id == ContactId::INFO
3269 || to_id == ContactId::INFO
3270 || match Params::from_str(¶ms) {
3271 Ok(p) => {
3272 let cmd = p.get_cmd();
3273 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3274 }
3275 _ => false,
3276 };
3277
3278 Ok((
3279 row.get::<_, i64>("timestamp")?,
3280 row.get::<_, MsgId>("id")?,
3281 !is_info_msg,
3282 ))
3283 }
3284 } else {
3285 |row: &rusqlite::Row| {
3286 Ok((
3287 row.get::<_, i64>("timestamp")?,
3288 row.get::<_, MsgId>("id")?,
3289 false,
3290 ))
3291 }
3292 };
3293 let process_rows = |rows: rusqlite::MappedRows<_>| {
3294 let mut sorted_rows = Vec::new();
3297 for row in rows {
3298 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3299 if !exclude_message {
3300 sorted_rows.push((ts, curr_id));
3301 }
3302 }
3303 sorted_rows.sort_unstable();
3304
3305 let mut ret = Vec::new();
3306 let mut last_day = 0;
3307 let cnv_to_local = gm2local_offset();
3308
3309 for (ts, curr_id) in sorted_rows {
3310 if add_daymarker {
3311 let curr_local_timestamp = ts + cnv_to_local;
3312 let curr_day = curr_local_timestamp / 86400;
3313 if curr_day != last_day {
3314 ret.push(ChatItem::DayMarker {
3315 timestamp: curr_day * 86400, });
3317 last_day = curr_day;
3318 }
3319 }
3320 ret.push(ChatItem::Message { msg_id: curr_id });
3321 }
3322 Ok(ret)
3323 };
3324
3325 let items = if info_only {
3326 context
3327 .sql
3328 .query_map(
3329 "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
3331 FROM msgs m
3332 WHERE m.chat_id=?
3333 AND m.hidden=0
3334 AND (
3335 m.param GLOB \"*S=*\"
3336 OR m.from_id == ?
3337 OR m.to_id == ?
3338 );",
3339 (chat_id, ContactId::INFO, ContactId::INFO),
3340 process_row,
3341 process_rows,
3342 )
3343 .await?
3344 } else {
3345 context
3346 .sql
3347 .query_map(
3348 "SELECT m.id AS id, m.timestamp AS timestamp
3349 FROM msgs m
3350 WHERE m.chat_id=?
3351 AND m.hidden=0;",
3352 (chat_id,),
3353 process_row,
3354 process_rows,
3355 )
3356 .await?
3357 };
3358 Ok(items)
3359}
3360
3361pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3364 if chat_id.is_archived_link() {
3367 let chat_ids_in_archive = context
3368 .sql
3369 .query_map(
3370 "SELECT DISTINCT(m.chat_id) FROM msgs m
3371 LEFT JOIN chats c ON m.chat_id=c.id
3372 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3373 (),
3374 |row| row.get::<_, ChatId>(0),
3375 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3376 )
3377 .await?;
3378 if chat_ids_in_archive.is_empty() {
3379 return Ok(());
3380 }
3381
3382 context
3383 .sql
3384 .transaction(|transaction| {
3385 let mut stmt = transaction.prepare(
3386 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3387 )?;
3388 for chat_id_in_archive in &chat_ids_in_archive {
3389 stmt.execute((chat_id_in_archive,))?;
3390 }
3391 Ok(())
3392 })
3393 .await?;
3394
3395 for chat_id_in_archive in chat_ids_in_archive {
3396 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3397 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3398 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3399 }
3400 } else {
3401 start_chat_ephemeral_timers(context, chat_id).await?;
3402
3403 let noticed_msgs_count = context
3404 .sql
3405 .execute(
3406 "UPDATE msgs
3407 SET state=?
3408 WHERE state=?
3409 AND hidden=0
3410 AND chat_id=?;",
3411 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3412 )
3413 .await?;
3414
3415 let hidden_messages = context
3418 .sql
3419 .query_map(
3420 "SELECT id, rfc724_mid FROM msgs
3421 WHERE state=?
3422 AND hidden=1
3423 AND chat_id=?
3424 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3427 let msg_id: MsgId = row.get(0)?;
3428 let rfc724_mid: String = row.get(1)?;
3429 Ok((msg_id, rfc724_mid))
3430 },
3431 |rows| {
3432 rows.collect::<std::result::Result<Vec<_>, _>>()
3433 .map_err(Into::into)
3434 },
3435 )
3436 .await?;
3437 for (msg_id, rfc724_mid) in &hidden_messages {
3438 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3439 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3440 }
3441
3442 if noticed_msgs_count == 0 {
3443 return Ok(());
3444 }
3445 }
3446
3447 context.emit_event(EventType::MsgsNoticed(chat_id));
3448 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3449 context.on_archived_chats_maybe_noticed();
3450 Ok(())
3451}
3452
3453pub(crate) async fn mark_old_messages_as_noticed(
3460 context: &Context,
3461 mut msgs: Vec<ReceivedMsg>,
3462) -> Result<()> {
3463 msgs.retain(|m| m.state.is_outgoing());
3464 if msgs.is_empty() {
3465 return Ok(());
3466 }
3467
3468 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3469 for msg in msgs {
3470 let chat_id = msg.chat_id;
3471 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3472 if msg.sort_timestamp > existing_msg.sort_timestamp {
3473 msgs_by_chat.insert(chat_id, msg);
3474 }
3475 } else {
3476 msgs_by_chat.insert(chat_id, msg);
3477 }
3478 }
3479
3480 let changed_chats = context
3481 .sql
3482 .transaction(|transaction| {
3483 let mut changed_chats = Vec::new();
3484 for (_, msg) in msgs_by_chat {
3485 let changed_rows = transaction.execute(
3486 "UPDATE msgs
3487 SET state=?
3488 WHERE state=?
3489 AND hidden=0
3490 AND chat_id=?
3491 AND timestamp<=?;",
3492 (
3493 MessageState::InNoticed,
3494 MessageState::InFresh,
3495 msg.chat_id,
3496 msg.sort_timestamp,
3497 ),
3498 )?;
3499 if changed_rows > 0 {
3500 changed_chats.push(msg.chat_id);
3501 }
3502 }
3503 Ok(changed_chats)
3504 })
3505 .await?;
3506
3507 if !changed_chats.is_empty() {
3508 info!(
3509 context,
3510 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3511 );
3512 context.on_archived_chats_maybe_noticed();
3513 }
3514
3515 for c in changed_chats {
3516 start_chat_ephemeral_timers(context, c).await?;
3517 context.emit_event(EventType::MsgsNoticed(c));
3518 chatlist_events::emit_chatlist_item_changed(context, c);
3519 }
3520
3521 Ok(())
3522}
3523
3524pub async fn get_chat_media(
3531 context: &Context,
3532 chat_id: Option<ChatId>,
3533 msg_type: Viewtype,
3534 msg_type2: Viewtype,
3535 msg_type3: Viewtype,
3536) -> Result<Vec<MsgId>> {
3537 let list = context
3539 .sql
3540 .query_map(
3541 "SELECT id
3542 FROM msgs
3543 WHERE (1=? OR chat_id=?)
3544 AND chat_id != ?
3545 AND (type=? OR type=? OR type=?)
3546 AND hidden=0
3547 ORDER BY timestamp, id;",
3548 (
3549 chat_id.is_none(),
3550 chat_id.unwrap_or_else(|| ChatId::new(0)),
3551 DC_CHAT_ID_TRASH,
3552 msg_type,
3553 if msg_type2 != Viewtype::Unknown {
3554 msg_type2
3555 } else {
3556 msg_type
3557 },
3558 if msg_type3 != Viewtype::Unknown {
3559 msg_type3
3560 } else {
3561 msg_type
3562 },
3563 ),
3564 |row| row.get::<_, MsgId>(0),
3565 |ids| Ok(ids.flatten().collect()),
3566 )
3567 .await?;
3568 Ok(list)
3569}
3570
3571pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3573 let list = context
3577 .sql
3578 .query_map(
3579 "SELECT cc.contact_id
3580 FROM chats_contacts cc
3581 LEFT JOIN contacts c
3582 ON c.id=cc.contact_id
3583 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3584 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3585 (chat_id,),
3586 |row| row.get::<_, ContactId>(0),
3587 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3588 )
3589 .await?;
3590
3591 Ok(list)
3592}
3593
3594pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3598 let now = time();
3599 let list = context
3600 .sql
3601 .query_map(
3602 "SELECT cc.contact_id
3603 FROM chats_contacts cc
3604 LEFT JOIN contacts c
3605 ON c.id=cc.contact_id
3606 WHERE cc.chat_id=?
3607 AND cc.add_timestamp < cc.remove_timestamp
3608 AND ? < cc.remove_timestamp
3609 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3610 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3611 |row| row.get::<_, ContactId>(0),
3612 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3613 )
3614 .await?;
3615
3616 Ok(list)
3617}
3618
3619pub async fn create_group_chat(
3621 context: &Context,
3622 protect: ProtectionStatus,
3623 chat_name: &str,
3624) -> Result<ChatId> {
3625 let chat_name = sanitize_single_line(chat_name);
3626 ensure!(!chat_name.is_empty(), "Invalid chat name");
3627
3628 let grpid = create_id();
3629
3630 let timestamp = create_smeared_timestamp(context);
3631 let row_id = context
3632 .sql
3633 .insert(
3634 "INSERT INTO chats
3635 (type, name, grpid, param, created_timestamp)
3636 VALUES(?, ?, ?, \'U=1\', ?);",
3637 (Chattype::Group, chat_name, grpid, timestamp),
3638 )
3639 .await?;
3640
3641 let chat_id = ChatId::new(u32::try_from(row_id)?);
3642 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3643
3644 context.emit_msgs_changed_without_ids();
3645 chatlist_events::emit_chatlist_changed(context);
3646 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3647
3648 if protect == ProtectionStatus::Protected {
3649 chat_id
3650 .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3651 .await?;
3652 }
3653
3654 if !context.get_config_bool(Config::Bot).await?
3655 && !context.get_config_bool(Config::SkipStartMessages).await?
3656 {
3657 let text = stock_str::new_group_send_first_message(context).await;
3658 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3659 }
3660
3661 Ok(chat_id)
3662}
3663
3664async fn find_unused_broadcast_list_name(context: &Context) -> Result<String> {
3666 let base_name = stock_str::broadcast_list(context).await;
3667 for attempt in 1..1000 {
3668 let better_name = if attempt > 1 {
3669 format!("{base_name} {attempt}")
3670 } else {
3671 base_name.clone()
3672 };
3673 if !context
3674 .sql
3675 .exists(
3676 "SELECT COUNT(*) FROM chats WHERE type=? AND name=?;",
3677 (Chattype::Broadcast, &better_name),
3678 )
3679 .await?
3680 {
3681 return Ok(better_name);
3682 }
3683 }
3684 Ok(base_name)
3685}
3686
3687pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
3689 let chat_name = find_unused_broadcast_list_name(context).await?;
3690 let grpid = create_id();
3691 create_broadcast_list_ex(context, Sync, grpid, chat_name).await
3692}
3693
3694pub(crate) async fn create_broadcast_list_ex(
3695 context: &Context,
3696 sync: sync::Sync,
3697 grpid: String,
3698 chat_name: String,
3699) -> Result<ChatId> {
3700 let row_id = {
3701 let chat_name = &chat_name;
3702 let grpid = &grpid;
3703 let trans_fn = |t: &mut rusqlite::Transaction| {
3704 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3705 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3706 if cnt == 1 {
3707 return Ok(t.query_row(
3708 "SELECT id FROM chats WHERE grpid=? AND type=?",
3709 (grpid, Chattype::Broadcast),
3710 |row| {
3711 let id: isize = row.get(0)?;
3712 Ok(id)
3713 },
3714 )?);
3715 }
3716 t.execute(
3717 "INSERT INTO chats \
3718 (type, name, grpid, param, created_timestamp) \
3719 VALUES(?, ?, ?, \'U=1\', ?);",
3720 (
3721 Chattype::Broadcast,
3722 &chat_name,
3723 &grpid,
3724 create_smeared_timestamp(context),
3725 ),
3726 )?;
3727 Ok(t.last_insert_rowid().try_into()?)
3728 };
3729 context.sql.transaction(trans_fn).await?
3730 };
3731 let chat_id = ChatId::new(u32::try_from(row_id)?);
3732
3733 context.emit_msgs_changed_without_ids();
3734 chatlist_events::emit_chatlist_changed(context);
3735
3736 if sync.into() {
3737 let id = SyncId::Grpid(grpid);
3738 let action = SyncAction::CreateBroadcast(chat_name);
3739 self::sync(context, id, action).await.log_err(context).ok();
3740 }
3741
3742 Ok(chat_id)
3743}
3744
3745pub(crate) async fn update_chat_contacts_table(
3747 context: &Context,
3748 timestamp: i64,
3749 id: ChatId,
3750 contacts: &HashSet<ContactId>,
3751) -> Result<()> {
3752 context
3753 .sql
3754 .transaction(move |transaction| {
3755 transaction.execute(
3759 "UPDATE chats_contacts
3760 SET remove_timestamp=MAX(add_timestamp+1, ?)
3761 WHERE chat_id=?",
3762 (timestamp, id),
3763 )?;
3764
3765 if !contacts.is_empty() {
3766 let mut statement = transaction.prepare(
3767 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3768 VALUES (?1, ?2, ?3)
3769 ON CONFLICT (chat_id, contact_id)
3770 DO UPDATE SET add_timestamp=remove_timestamp",
3771 )?;
3772
3773 for contact_id in contacts {
3774 statement.execute((id, contact_id, timestamp))?;
3778 }
3779 }
3780 Ok(())
3781 })
3782 .await?;
3783 Ok(())
3784}
3785
3786pub(crate) async fn add_to_chat_contacts_table(
3788 context: &Context,
3789 timestamp: i64,
3790 chat_id: ChatId,
3791 contact_ids: &[ContactId],
3792) -> Result<()> {
3793 context
3794 .sql
3795 .transaction(move |transaction| {
3796 let mut add_statement = transaction.prepare(
3797 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3798 ON CONFLICT (chat_id, contact_id)
3799 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3800 )?;
3801
3802 for contact_id in contact_ids {
3803 add_statement.execute((chat_id, contact_id, timestamp))?;
3804 }
3805 Ok(())
3806 })
3807 .await?;
3808
3809 Ok(())
3810}
3811
3812pub(crate) async fn remove_from_chat_contacts_table(
3815 context: &Context,
3816 chat_id: ChatId,
3817 contact_id: ContactId,
3818) -> Result<()> {
3819 let now = time();
3820 context
3821 .sql
3822 .execute(
3823 "UPDATE chats_contacts
3824 SET remove_timestamp=MAX(add_timestamp+1, ?)
3825 WHERE chat_id=? AND contact_id=?",
3826 (now, chat_id, contact_id),
3827 )
3828 .await?;
3829 Ok(())
3830}
3831
3832pub async fn add_contact_to_chat(
3835 context: &Context,
3836 chat_id: ChatId,
3837 contact_id: ContactId,
3838) -> Result<()> {
3839 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3840 Ok(())
3841}
3842
3843pub(crate) async fn add_contact_to_chat_ex(
3844 context: &Context,
3845 mut sync: sync::Sync,
3846 chat_id: ChatId,
3847 contact_id: ContactId,
3848 from_handshake: bool,
3849) -> Result<bool> {
3850 ensure!(!chat_id.is_special(), "can not add member to special chats");
3851 let contact = Contact::get_by_id(context, contact_id).await?;
3852 let mut msg = Message::default();
3853
3854 chat_id.reset_gossiped_timestamp(context).await?;
3855
3856 let mut chat = Chat::load_from_db(context, chat_id).await?;
3858 ensure!(
3859 chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast,
3860 "{} is not a group/broadcast where one can add members",
3861 chat_id
3862 );
3863 ensure!(
3864 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3865 "invalid contact_id {} for adding to group",
3866 contact_id
3867 );
3868 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3869 ensure!(
3870 chat.typ != Chattype::Broadcast || contact_id != ContactId::SELF,
3871 "Cannot add SELF to broadcast."
3872 );
3873
3874 if !chat.is_self_in_chat(context).await? {
3875 context.emit_event(EventType::ErrorSelfNotInGroup(
3876 "Cannot add contact to group; self not in group.".into(),
3877 ));
3878 bail!("can not add contact because the account is not part of the group/broadcast");
3879 }
3880
3881 let sync_qr_code_tokens;
3882 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3883 chat.param
3884 .remove(Param::Unpromoted)
3885 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3886 chat.update_param(context).await?;
3887 sync_qr_code_tokens = true;
3888 } else {
3889 sync_qr_code_tokens = false;
3890 }
3891
3892 if context.is_self_addr(contact.get_addr()).await? {
3893 warn!(
3896 context,
3897 "Invalid attempt to add self e-mail address to group."
3898 );
3899 return Ok(false);
3900 }
3901
3902 if is_contact_in_chat(context, chat_id, contact_id).await? {
3903 if !from_handshake {
3904 return Ok(true);
3905 }
3906 } else {
3907 if chat.is_protected() && !contact.is_verified(context).await? {
3909 error!(
3910 context,
3911 "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
3912 );
3913 return Ok(false);
3914 }
3915 if is_contact_in_chat(context, chat_id, contact_id).await? {
3916 return Ok(false);
3917 }
3918 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3919 }
3920 if chat.typ == Chattype::Group && chat.is_promoted() {
3921 msg.viewtype = Viewtype::Text;
3922
3923 let contact_addr = contact.get_addr().to_lowercase();
3924 msg.text = stock_str::msg_add_member_local(context, &contact_addr, ContactId::SELF).await;
3925 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3926 msg.param.set(Param::Arg, contact_addr);
3927 msg.param.set_int(Param::Arg2, from_handshake.into());
3928 msg.param
3929 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3930 send_msg(context, chat_id, &mut msg).await?;
3931
3932 sync = Nosync;
3933 if sync_qr_code_tokens
3939 && context
3940 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3941 .await
3942 .log_err(context)
3943 .is_ok()
3944 {
3945 context.scheduler.interrupt_inbox().await;
3946 }
3947 }
3948 context.emit_event(EventType::ChatModified(chat_id));
3949 if sync.into() {
3950 chat.sync_contacts(context).await.log_err(context).ok();
3951 }
3952 Ok(true)
3953}
3954
3955pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3961 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3962 let needs_attach = context
3963 .sql
3964 .query_map(
3965 "SELECT c.selfavatar_sent
3966 FROM chats_contacts cc
3967 LEFT JOIN contacts c ON c.id=cc.contact_id
3968 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3969 (chat_id, ContactId::SELF),
3970 |row| Ok(row.get::<_, i64>(0)),
3971 |rows| {
3972 let mut needs_attach = false;
3973 for row in rows {
3974 let row = row?;
3975 let selfavatar_sent = row?;
3976 if selfavatar_sent < timestamp_some_days_ago {
3977 needs_attach = true;
3978 }
3979 }
3980 Ok(needs_attach)
3981 },
3982 )
3983 .await?;
3984 Ok(needs_attach)
3985}
3986
3987#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3989pub enum MuteDuration {
3990 NotMuted,
3992
3993 Forever,
3995
3996 Until(std::time::SystemTime),
3998}
3999
4000impl rusqlite::types::ToSql for MuteDuration {
4001 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
4002 let duration: i64 = match &self {
4003 MuteDuration::NotMuted => 0,
4004 MuteDuration::Forever => -1,
4005 MuteDuration::Until(when) => {
4006 let duration = when
4007 .duration_since(SystemTime::UNIX_EPOCH)
4008 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
4009 i64::try_from(duration.as_secs())
4010 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
4011 }
4012 };
4013 let val = rusqlite::types::Value::Integer(duration);
4014 let out = rusqlite::types::ToSqlOutput::Owned(val);
4015 Ok(out)
4016 }
4017}
4018
4019impl rusqlite::types::FromSql for MuteDuration {
4020 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
4021 match i64::column_result(value)? {
4024 0 => Ok(MuteDuration::NotMuted),
4025 -1 => Ok(MuteDuration::Forever),
4026 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
4027 Some(t) => Ok(MuteDuration::Until(t)),
4028 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
4029 },
4030 _ => Ok(MuteDuration::NotMuted),
4031 }
4032 }
4033}
4034
4035pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
4037 set_muted_ex(context, Sync, chat_id, duration).await
4038}
4039
4040pub(crate) async fn set_muted_ex(
4041 context: &Context,
4042 sync: sync::Sync,
4043 chat_id: ChatId,
4044 duration: MuteDuration,
4045) -> Result<()> {
4046 ensure!(!chat_id.is_special(), "Invalid chat ID");
4047 context
4048 .sql
4049 .execute(
4050 "UPDATE chats SET muted_until=? WHERE id=?;",
4051 (duration, chat_id),
4052 )
4053 .await
4054 .context(format!("Failed to set mute duration for {chat_id}"))?;
4055 context.emit_event(EventType::ChatModified(chat_id));
4056 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4057 if sync.into() {
4058 let chat = Chat::load_from_db(context, chat_id).await?;
4059 chat.sync(context, SyncAction::SetMuted(duration))
4060 .await
4061 .log_err(context)
4062 .ok();
4063 }
4064 Ok(())
4065}
4066
4067pub async fn remove_contact_from_chat(
4069 context: &Context,
4070 chat_id: ChatId,
4071 contact_id: ContactId,
4072) -> Result<()> {
4073 ensure!(
4074 !chat_id.is_special(),
4075 "bad chat_id, can not be special chat: {}",
4076 chat_id
4077 );
4078 ensure!(
4079 !contact_id.is_special() || contact_id == ContactId::SELF,
4080 "Cannot remove special contact"
4081 );
4082
4083 let mut msg = Message::default();
4084
4085 let chat = Chat::load_from_db(context, chat_id).await?;
4086 if chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast {
4087 if !chat.is_self_in_chat(context).await? {
4088 let err_msg = format!(
4089 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4090 );
4091 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4092 bail!("{}", err_msg);
4093 } else {
4094 let mut sync = Nosync;
4095
4096 if chat.is_promoted() {
4097 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4098 } else {
4099 context
4100 .sql
4101 .execute(
4102 "DELETE FROM chats_contacts
4103 WHERE chat_id=? AND contact_id=?",
4104 (chat_id, contact_id),
4105 )
4106 .await?;
4107 }
4108
4109 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4113 if chat.typ == Chattype::Group && chat.is_promoted() {
4114 msg.viewtype = Viewtype::Text;
4115 if contact_id == ContactId::SELF {
4116 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4117 } else {
4118 msg.text = stock_str::msg_del_member_local(
4119 context,
4120 contact.get_addr(),
4121 ContactId::SELF,
4122 )
4123 .await;
4124 }
4125 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4126 msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4127 msg.param
4128 .set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4129 let res = send_msg(context, chat_id, &mut msg).await;
4130 if contact_id == ContactId::SELF {
4131 res?;
4132 set_group_explicitly_left(context, &chat.grpid).await?;
4133 } else if let Err(e) = res {
4134 warn!(context, "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}.");
4135 }
4136 } else {
4137 sync = Sync;
4138 }
4139 }
4140 context.emit_event(EventType::ChatModified(chat_id));
4141 if sync.into() {
4142 chat.sync_contacts(context).await.log_err(context).ok();
4143 }
4144 }
4145 } else {
4146 bail!("Cannot remove members from non-group chats.");
4147 }
4148
4149 Ok(())
4150}
4151
4152async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4153 if !is_group_explicitly_left(context, grpid).await? {
4154 context
4155 .sql
4156 .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4157 .await?;
4158 }
4159
4160 Ok(())
4161}
4162
4163pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4164 let exists = context
4165 .sql
4166 .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4167 .await?;
4168 Ok(exists)
4169}
4170
4171pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4173 rename_ex(context, Sync, chat_id, new_name).await
4174}
4175
4176async fn rename_ex(
4177 context: &Context,
4178 mut sync: sync::Sync,
4179 chat_id: ChatId,
4180 new_name: &str,
4181) -> Result<()> {
4182 let new_name = sanitize_single_line(new_name);
4183 let mut success = false;
4185
4186 ensure!(!new_name.is_empty(), "Invalid name");
4187 ensure!(!chat_id.is_special(), "Invalid chat ID");
4188
4189 let chat = Chat::load_from_db(context, chat_id).await?;
4190 let mut msg = Message::default();
4191
4192 if chat.typ == Chattype::Group
4193 || chat.typ == Chattype::Mailinglist
4194 || chat.typ == Chattype::Broadcast
4195 {
4196 if chat.name == new_name {
4197 success = true;
4198 } else if !chat.is_self_in_chat(context).await? {
4199 context.emit_event(EventType::ErrorSelfNotInGroup(
4200 "Cannot set chat name; self not in group".into(),
4201 ));
4202 } else {
4203 context
4204 .sql
4205 .execute(
4206 "UPDATE chats SET name=? WHERE id=?;",
4207 (new_name.to_string(), chat_id),
4208 )
4209 .await?;
4210 if chat.is_promoted()
4211 && !chat.is_mailing_list()
4212 && chat.typ != Chattype::Broadcast
4213 && sanitize_single_line(&chat.name) != new_name
4214 {
4215 msg.viewtype = Viewtype::Text;
4216 msg.text =
4217 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4218 msg.param.set_cmd(SystemMessage::GroupNameChanged);
4219 if !chat.name.is_empty() {
4220 msg.param.set(Param::Arg, &chat.name);
4221 }
4222 msg.id = send_msg(context, chat_id, &mut msg).await?;
4223 context.emit_msgs_changed(chat_id, msg.id);
4224 sync = Nosync;
4225 }
4226 context.emit_event(EventType::ChatModified(chat_id));
4227 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4228 success = true;
4229 }
4230 }
4231
4232 if !success {
4233 bail!("Failed to set name");
4234 }
4235 if sync.into() && chat.name != new_name {
4236 let sync_name = new_name.to_string();
4237 chat.sync(context, SyncAction::Rename(sync_name))
4238 .await
4239 .log_err(context)
4240 .ok();
4241 }
4242 Ok(())
4243}
4244
4245pub async fn set_chat_profile_image(
4251 context: &Context,
4252 chat_id: ChatId,
4253 new_image: &str, ) -> Result<()> {
4255 ensure!(!chat_id.is_special(), "Invalid chat ID");
4256 let mut chat = Chat::load_from_db(context, chat_id).await?;
4257 ensure!(
4258 chat.typ == Chattype::Group || chat.typ == Chattype::Mailinglist,
4259 "Failed to set profile image; group does not exist"
4260 );
4261 if !is_contact_in_chat(context, chat_id, ContactId::SELF).await? {
4263 context.emit_event(EventType::ErrorSelfNotInGroup(
4264 "Cannot set chat profile image; self not in group.".into(),
4265 ));
4266 bail!("Failed to set profile image");
4267 }
4268 let mut msg = Message::new(Viewtype::Text);
4269 msg.param
4270 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4271 if new_image.is_empty() {
4272 chat.param.remove(Param::ProfileImage);
4273 msg.param.remove(Param::Arg);
4274 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4275 } else {
4276 let mut image_blob = BlobObject::create_and_deduplicate(
4277 context,
4278 Path::new(new_image),
4279 Path::new(new_image),
4280 )?;
4281 image_blob.recode_to_avatar_size(context).await?;
4282 chat.param.set(Param::ProfileImage, image_blob.as_name());
4283 msg.param.set(Param::Arg, image_blob.as_name());
4284 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4285 }
4286 chat.update_param(context).await?;
4287 if chat.is_promoted() && !chat.is_mailing_list() {
4288 msg.id = send_msg(context, chat_id, &mut msg).await?;
4289 context.emit_msgs_changed(chat_id, msg.id);
4290 }
4291 context.emit_event(EventType::ChatModified(chat_id));
4292 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4293 Ok(())
4294}
4295
4296pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4298 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4299 ensure!(!chat_id.is_special(), "can not forward to special chat");
4300
4301 let mut created_msgs: Vec<MsgId> = Vec::new();
4302 let mut curr_timestamp: i64;
4303
4304 chat_id
4305 .unarchive_if_not_muted(context, MessageState::Undefined)
4306 .await?;
4307 let mut chat = Chat::load_from_db(context, chat_id).await?;
4308 if let Some(reason) = chat.why_cant_send(context).await? {
4309 bail!("cannot send to {}: {}", chat_id, reason);
4310 }
4311 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4312 let mut msgs = Vec::with_capacity(msg_ids.len());
4313 for id in msg_ids {
4314 let ts: i64 = context
4315 .sql
4316 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4317 .await?
4318 .context("No message {id}")?;
4319 msgs.push((ts, *id));
4320 }
4321 msgs.sort_unstable();
4322 for (_, id) in msgs {
4323 let src_msg_id: MsgId = id;
4324 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4325 if msg.state == MessageState::OutDraft {
4326 bail!("cannot forward drafts.");
4327 }
4328
4329 if msg.get_viewtype() != Viewtype::Sticker {
4334 msg.param
4335 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4336 }
4337
4338 msg.param.remove(Param::GuaranteeE2ee);
4339 msg.param.remove(Param::ForcePlaintext);
4340 msg.param.remove(Param::Cmd);
4341 msg.param.remove(Param::OverrideSenderDisplayname);
4342 msg.param.remove(Param::WebxdcDocument);
4343 msg.param.remove(Param::WebxdcDocumentTimestamp);
4344 msg.param.remove(Param::WebxdcSummary);
4345 msg.param.remove(Param::WebxdcSummaryTimestamp);
4346 msg.param.remove(Param::IsEdited);
4347 msg.in_reply_to = None;
4348
4349 msg.subject = "".to_string();
4351
4352 msg.state = MessageState::OutPending;
4353 let new_msg_id = chat
4354 .prepare_msg_raw(context, &mut msg, None, curr_timestamp)
4355 .await?;
4356 curr_timestamp += 1;
4357 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4358 context.scheduler.interrupt_smtp().await;
4359 }
4360 created_msgs.push(new_msg_id);
4361 }
4362 for msg_id in created_msgs {
4363 context.emit_msgs_changed(chat_id, msg_id);
4364 }
4365 Ok(())
4366}
4367
4368pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4371 for src_msg_id in msg_ids {
4372 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4373 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4374 context
4375 .add_sync_item(SyncData::SaveMessage {
4376 src: src_rfc724_mid,
4377 dest: dest_rfc724_mid,
4378 })
4379 .await?;
4380 }
4381 context.scheduler.interrupt_inbox().await;
4382 Ok(())
4383}
4384
4385pub(crate) async fn save_copy_in_self_talk(
4391 context: &Context,
4392 src_msg_id: &MsgId,
4393 dest_rfc724_mid: &String,
4394) -> Result<String> {
4395 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4396 let mut msg = Message::load_from_db(context, *src_msg_id).await?;
4397 msg.param.remove(Param::Cmd);
4398 msg.param.remove(Param::WebxdcDocument);
4399 msg.param.remove(Param::WebxdcDocumentTimestamp);
4400 msg.param.remove(Param::WebxdcSummary);
4401 msg.param.remove(Param::WebxdcSummaryTimestamp);
4402
4403 if !msg.original_msg_id.is_unset() {
4404 bail!("message already saved.");
4405 }
4406
4407 let copy_fields = "from_id, to_id, timestamp_sent, timestamp_rcvd, type, txt, \
4408 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4409 let row_id = context
4410 .sql
4411 .insert(
4412 &format!(
4413 "INSERT INTO msgs ({copy_fields}, chat_id, rfc724_mid, state, timestamp, param, starred) \
4414 SELECT {copy_fields}, ?, ?, ?, ?, ?, ? \
4415 FROM msgs WHERE id=?;"
4416 ),
4417 (
4418 dest_chat_id,
4419 dest_rfc724_mid,
4420 if msg.from_id == ContactId::SELF {
4421 MessageState::OutDelivered
4422 } else {
4423 MessageState::InSeen
4424 },
4425 create_smeared_timestamp(context),
4426 msg.param.to_string(),
4427 src_msg_id,
4428 src_msg_id,
4429 ),
4430 )
4431 .await?;
4432 let dest_msg_id = MsgId::new(row_id.try_into()?);
4433
4434 context.emit_msgs_changed(msg.chat_id, *src_msg_id);
4435 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4436 chatlist_events::emit_chatlist_changed(context);
4437 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4438
4439 Ok(msg.rfc724_mid)
4440}
4441
4442pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4446 let mut chat_id = None;
4447 let mut msgs: Vec<Message> = Vec::new();
4448 for msg_id in msg_ids {
4449 let msg = Message::load_from_db(context, *msg_id).await?;
4450 if let Some(chat_id) = chat_id {
4451 ensure!(
4452 chat_id == msg.chat_id,
4453 "messages to resend needs to be in the same chat"
4454 );
4455 } else {
4456 chat_id = Some(msg.chat_id);
4457 }
4458 ensure!(
4459 msg.from_id == ContactId::SELF,
4460 "can resend only own messages"
4461 );
4462 ensure!(!msg.is_info(), "cannot resend info messages");
4463 msgs.push(msg)
4464 }
4465
4466 let Some(chat_id) = chat_id else {
4467 return Ok(());
4468 };
4469
4470 let chat = Chat::load_from_db(context, chat_id).await?;
4471 for mut msg in msgs {
4472 if msg.get_showpadlock() && !chat.is_protected() {
4473 msg.param.remove(Param::GuaranteeE2ee);
4474 msg.update_param(context).await?;
4475 }
4476 match msg.get_state() {
4477 MessageState::OutPending
4479 | MessageState::OutFailed
4480 | MessageState::OutDelivered
4481 | MessageState::OutMdnRcvd => {
4482 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4483 }
4484 msg_state => bail!("Unexpected message state {msg_state}"),
4485 }
4486 context.emit_event(EventType::MsgsChanged {
4487 chat_id: msg.chat_id,
4488 msg_id: msg.id,
4489 });
4490 msg.timestamp_sort = create_smeared_timestamp(context);
4491 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4493 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4494 continue;
4495 }
4496 if msg.viewtype == Viewtype::Webxdc {
4497 let conn_fn = |conn: &mut rusqlite::Connection| {
4498 let range = conn.query_row(
4499 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4500 FROM msgs_status_updates WHERE msg_id=?",
4501 (msg.id,),
4502 |row| {
4503 let min_id: StatusUpdateSerial = row.get(0)?;
4504 let max_id: StatusUpdateSerial = row.get(1)?;
4505 Ok((min_id, max_id))
4506 },
4507 )?;
4508 if range.0 > range.1 {
4509 return Ok(());
4510 };
4511 conn.execute(
4515 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4516 VALUES(?, ?, ?, '') \
4517 ON CONFLICT(msg_id) \
4518 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4519 (msg.id, range.0, range.1),
4520 )?;
4521 Ok(())
4522 };
4523 context.sql.call_write(conn_fn).await?;
4524 }
4525 context.scheduler.interrupt_smtp().await;
4526 }
4527 Ok(())
4528}
4529
4530pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4531 if context.sql.is_open().await {
4532 let count = context
4534 .sql
4535 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4536 .await?;
4537 Ok(count)
4538 } else {
4539 Ok(0)
4540 }
4541}
4542
4543pub(crate) async fn get_chat_id_by_grpid(
4545 context: &Context,
4546 grpid: &str,
4547) -> Result<Option<(ChatId, bool, Blocked)>> {
4548 context
4549 .sql
4550 .query_row_optional(
4551 "SELECT id, blocked, protected FROM chats WHERE grpid=?;",
4552 (grpid,),
4553 |row| {
4554 let chat_id = row.get::<_, ChatId>(0)?;
4555
4556 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4557 let p = row
4558 .get::<_, Option<ProtectionStatus>>(2)?
4559 .unwrap_or_default();
4560 Ok((chat_id, p == ProtectionStatus::Protected, b))
4561 },
4562 )
4563 .await
4564}
4565
4566pub async fn add_device_msg_with_importance(
4571 context: &Context,
4572 label: Option<&str>,
4573 msg: Option<&mut Message>,
4574 important: bool,
4575) -> Result<MsgId> {
4576 ensure!(
4577 label.is_some() || msg.is_some(),
4578 "device-messages need label, msg or both"
4579 );
4580 let mut chat_id = ChatId::new(0);
4581 let mut msg_id = MsgId::new_unset();
4582
4583 if let Some(label) = label {
4584 if was_device_msg_ever_added(context, label).await? {
4585 info!(context, "Device-message {label} already added.");
4586 return Ok(msg_id);
4587 }
4588 }
4589
4590 if let Some(msg) = msg {
4591 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4592
4593 let rfc724_mid = create_outgoing_rfc724_mid();
4594 prepare_msg_blob(context, msg).await?;
4595
4596 let timestamp_sent = create_smeared_timestamp(context);
4597
4598 let mut timestamp_sort = timestamp_sent;
4601 if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4602 if timestamp_sort <= last_msg_time {
4603 timestamp_sort = last_msg_time + 1;
4604 }
4605 }
4606
4607 let state = MessageState::InFresh;
4608 let row_id = context
4609 .sql
4610 .insert(
4611 "INSERT INTO msgs (
4612 chat_id,
4613 from_id,
4614 to_id,
4615 timestamp,
4616 timestamp_sent,
4617 timestamp_rcvd,
4618 type,state,
4619 txt,
4620 txt_normalized,
4621 param,
4622 rfc724_mid)
4623 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4624 (
4625 chat_id,
4626 ContactId::DEVICE,
4627 ContactId::SELF,
4628 timestamp_sort,
4629 timestamp_sent,
4630 timestamp_sent, msg.viewtype,
4632 state,
4633 &msg.text,
4634 message::normalize_text(&msg.text),
4635 msg.param.to_string(),
4636 rfc724_mid,
4637 ),
4638 )
4639 .await?;
4640 context.new_msgs_notify.notify_one();
4641
4642 msg_id = MsgId::new(u32::try_from(row_id)?);
4643 if !msg.hidden {
4644 chat_id.unarchive_if_not_muted(context, state).await?;
4645 }
4646 }
4647
4648 if let Some(label) = label {
4649 context
4650 .sql
4651 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4652 .await?;
4653 }
4654
4655 if !msg_id.is_unset() {
4656 chat_id.emit_msg_event(context, msg_id, important);
4657 }
4658
4659 Ok(msg_id)
4660}
4661
4662pub async fn add_device_msg(
4664 context: &Context,
4665 label: Option<&str>,
4666 msg: Option<&mut Message>,
4667) -> Result<MsgId> {
4668 add_device_msg_with_importance(context, label, msg, false).await
4669}
4670
4671pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4673 ensure!(!label.is_empty(), "empty label");
4674 let exists = context
4675 .sql
4676 .exists(
4677 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4678 (label,),
4679 )
4680 .await?;
4681
4682 Ok(exists)
4683}
4684
4685pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4693 context
4694 .sql
4695 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4696 .await?;
4697 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4698
4699 context
4701 .sql
4702 .execute(
4703 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4704 (),
4705 )
4706 .await?;
4707 context
4708 .set_config_internal(Config::QuotaExceeding, None)
4709 .await?;
4710 Ok(())
4711}
4712
4713#[expect(clippy::too_many_arguments)]
4718pub(crate) async fn add_info_msg_with_cmd(
4719 context: &Context,
4720 chat_id: ChatId,
4721 text: &str,
4722 cmd: SystemMessage,
4723 timestamp_sort: i64,
4724 timestamp_sent_rcvd: Option<i64>,
4726 parent: Option<&Message>,
4727 from_id: Option<ContactId>,
4728 added_removed_id: Option<ContactId>,
4729) -> Result<MsgId> {
4730 let rfc724_mid = create_outgoing_rfc724_mid();
4731 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4732
4733 let mut param = Params::new();
4734 if cmd != SystemMessage::Unknown {
4735 param.set_cmd(cmd);
4736 }
4737 if let Some(contact_id) = added_removed_id {
4738 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4739 }
4740
4741 let row_id =
4742 context.sql.insert(
4743 "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)
4744 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4745 (
4746 chat_id,
4747 from_id.unwrap_or(ContactId::INFO),
4748 ContactId::INFO,
4749 timestamp_sort,
4750 timestamp_sent_rcvd.unwrap_or(0),
4751 timestamp_sent_rcvd.unwrap_or(0),
4752 Viewtype::Text,
4753 MessageState::InNoticed,
4754 text,
4755 message::normalize_text(text),
4756 rfc724_mid,
4757 ephemeral_timer,
4758 param.to_string(),
4759 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4760 )
4761 ).await?;
4762 context.new_msgs_notify.notify_one();
4763
4764 let msg_id = MsgId::new(row_id.try_into()?);
4765 context.emit_msgs_changed(chat_id, msg_id);
4766
4767 Ok(msg_id)
4768}
4769
4770pub(crate) async fn add_info_msg(
4772 context: &Context,
4773 chat_id: ChatId,
4774 text: &str,
4775 timestamp: i64,
4776) -> Result<MsgId> {
4777 add_info_msg_with_cmd(
4778 context,
4779 chat_id,
4780 text,
4781 SystemMessage::Unknown,
4782 timestamp,
4783 None,
4784 None,
4785 None,
4786 None,
4787 )
4788 .await
4789}
4790
4791pub(crate) async fn update_msg_text_and_timestamp(
4792 context: &Context,
4793 chat_id: ChatId,
4794 msg_id: MsgId,
4795 text: &str,
4796 timestamp: i64,
4797) -> Result<()> {
4798 context
4799 .sql
4800 .execute(
4801 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4802 (text, message::normalize_text(text), timestamp, msg_id),
4803 )
4804 .await?;
4805 context.emit_msgs_changed(chat_id, msg_id);
4806 Ok(())
4807}
4808
4809async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4811 let chat = Chat::load_from_db(context, id).await?;
4812 ensure!(
4813 chat.typ == Chattype::Broadcast,
4814 "{id} is not a broadcast list",
4815 );
4816 let mut contacts = HashSet::new();
4817 for addr in addrs {
4818 let contact_addr = ContactAddress::new(addr)?;
4819 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4820 .await?
4821 .0;
4822 contacts.insert(contact);
4823 }
4824 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4825 if contacts == contacts_old {
4826 return Ok(());
4827 }
4828 context
4829 .sql
4830 .transaction(move |transaction| {
4831 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4832
4833 let mut statement = transaction
4836 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4837 for contact_id in &contacts {
4838 statement.execute((id, contact_id))?;
4839 }
4840 Ok(())
4841 })
4842 .await?;
4843 context.emit_event(EventType::ChatModified(id));
4844 Ok(())
4845}
4846
4847#[derive(Debug, Serialize, Deserialize, PartialEq)]
4849pub(crate) enum SyncId {
4850 ContactAddr(String),
4851 Grpid(String),
4852 Msgids(Vec<String>),
4854
4855 Device,
4857}
4858
4859#[derive(Debug, Serialize, Deserialize, PartialEq)]
4861pub(crate) enum SyncAction {
4862 Block,
4863 Unblock,
4864 Accept,
4865 SetVisibility(ChatVisibility),
4866 SetMuted(MuteDuration),
4867 CreateBroadcast(String),
4869 Rename(String),
4870 SetContacts(Vec<String>),
4872 Delete,
4873}
4874
4875impl Context {
4876 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4878 let chat_id = match id {
4879 SyncId::ContactAddr(addr) => {
4880 if let SyncAction::Rename(to) = action {
4881 Contact::create_ex(self, Nosync, to, addr).await?;
4882 return Ok(());
4883 }
4884 let addr = ContactAddress::new(addr).context("Invalid address")?;
4885 let (contact_id, _) =
4886 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4887 match action {
4888 SyncAction::Block => {
4889 return contact::set_blocked(self, Nosync, contact_id, true).await
4890 }
4891 SyncAction::Unblock => {
4892 return contact::set_blocked(self, Nosync, contact_id, false).await
4893 }
4894 _ => (),
4895 }
4896 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4899 .await?
4900 .id
4901 }
4902 SyncId::Grpid(grpid) => {
4903 if let SyncAction::CreateBroadcast(name) = action {
4904 create_broadcast_list_ex(self, Nosync, grpid.clone(), name.clone()).await?;
4905 return Ok(());
4906 }
4907 get_chat_id_by_grpid(self, grpid)
4908 .await?
4909 .with_context(|| format!("No chat for grpid '{grpid}'"))?
4910 .0
4911 }
4912 SyncId::Msgids(msgids) => {
4913 let msg = message::get_by_rfc724_mids(self, msgids)
4914 .await?
4915 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4916 ChatId::lookup_by_message(&msg)
4917 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4918 }
4919 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4920 };
4921 match action {
4922 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4923 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4924 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4925 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4926 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4927 SyncAction::CreateBroadcast(_) => {
4928 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4929 }
4930 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4931 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4932 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4933 }
4934 }
4935
4936 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4941 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4942 }
4943}
4944
4945#[cfg(test)]
4946mod chat_tests;