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 && self
1700 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
1701 .await?
1702 > 0
1703 {
1704 return Ok(Some(reason));
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(
1721 &self,
1722 context: &Context,
1723 timeout: u64,
1724 ) -> Result<u64> {
1725 if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
1726 return Ok(0);
1727 }
1728
1729 let (mut param_wait, mut param_timeout) = (Params::new(), Params::new());
1732 param_wait.set_cmd(SystemMessage::SecurejoinWait);
1733 param_timeout.set_cmd(SystemMessage::SecurejoinWaitTimeout);
1734 let (param_wait, param_timeout) = (param_wait.to_string(), param_timeout.to_string());
1735 let Some((param, ts_sort, ts_start)) = context
1736 .sql
1737 .query_row_optional(
1738 "SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
1739 (SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
1740 (self.id, ¶m_wait, ¶m_timeout),
1741 |row| {
1742 let param: String = row.get(0)?;
1743 let ts_sort: i64 = row.get(1)?;
1744 let ts_start: i64 = row.get(2)?;
1745 Ok((param, ts_sort, ts_start))
1746 },
1747 )
1748 .await?
1749 else {
1750 return Ok(0);
1751 };
1752 if param == param_timeout {
1753 return Ok(0);
1754 }
1755
1756 let now = time();
1757 if ts_start <= now {
1759 let timeout = ts_start
1760 .saturating_add(timeout.try_into()?)
1761 .saturating_sub(now);
1762 if timeout > 0 {
1763 return Ok(timeout as u64);
1764 }
1765 }
1766 add_info_msg_with_cmd(
1767 context,
1768 self.id,
1769 &stock_str::securejoin_takes_longer(context).await,
1770 SystemMessage::SecurejoinWaitTimeout,
1771 ts_sort,
1774 Some(now),
1775 None,
1776 None,
1777 None,
1778 )
1779 .await?;
1780 context.emit_event(EventType::ChatModified(self.id));
1781 Ok(0)
1782 }
1783
1784 pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1788 match self.typ {
1789 Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
1790 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1791 }
1792 }
1793
1794 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1795 context
1796 .sql
1797 .execute(
1798 "UPDATE chats SET param=? WHERE id=?",
1799 (self.param.to_string(), self.id),
1800 )
1801 .await?;
1802 Ok(())
1803 }
1804
1805 pub fn get_id(&self) -> ChatId {
1807 self.id
1808 }
1809
1810 pub fn get_type(&self) -> Chattype {
1812 self.typ
1813 }
1814
1815 pub fn get_name(&self) -> &str {
1817 &self.name
1818 }
1819
1820 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1822 self.param.get(Param::ListPost)
1823 }
1824
1825 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1827 if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1828 if !image_rel.is_empty() {
1829 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1830 }
1831 } else if self.id.is_archived_link() {
1832 if let Ok(image_rel) = get_archive_icon(context).await {
1833 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1834 }
1835 } else if self.typ == Chattype::Single {
1836 let contacts = get_chat_contacts(context, self.id).await?;
1837 if let Some(contact_id) = contacts.first() {
1838 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1839 return contact.get_profile_image(context).await;
1840 }
1841 }
1842 } else if self.typ == Chattype::Broadcast {
1843 if let Ok(image_rel) = get_broadcast_icon(context).await {
1844 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1845 }
1846 }
1847 Ok(None)
1848 }
1849
1850 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1855 let mut color = 0;
1856
1857 if self.typ == Chattype::Single {
1858 let contacts = get_chat_contacts(context, self.id).await?;
1859 if let Some(contact_id) = contacts.first() {
1860 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1861 color = contact.get_color();
1862 }
1863 }
1864 } else {
1865 color = str_to_color(&self.name);
1866 }
1867
1868 Ok(color)
1869 }
1870
1871 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1876 let draft = match self.id.get_draft(context).await? {
1877 Some(message) => message.text,
1878 _ => String::new(),
1879 };
1880 Ok(ChatInfo {
1881 id: self.id,
1882 type_: self.typ as u32,
1883 name: self.name.clone(),
1884 archived: self.visibility == ChatVisibility::Archived,
1885 param: self.param.to_string(),
1886 is_sending_locations: self.is_sending_locations,
1887 color: self.get_color(context).await?,
1888 profile_image: self
1889 .get_profile_image(context)
1890 .await?
1891 .unwrap_or_else(std::path::PathBuf::new),
1892 draft,
1893 is_muted: self.is_muted(),
1894 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1895 })
1896 }
1897
1898 pub fn get_visibility(&self) -> ChatVisibility {
1900 self.visibility
1901 }
1902
1903 pub fn is_contact_request(&self) -> bool {
1908 self.blocked == Blocked::Request
1909 }
1910
1911 pub fn is_unpromoted(&self) -> bool {
1913 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1914 }
1915
1916 pub fn is_promoted(&self) -> bool {
1919 !self.is_unpromoted()
1920 }
1921
1922 pub fn is_protected(&self) -> bool {
1933 self.protected == ProtectionStatus::Protected
1934 }
1935
1936 pub fn is_protection_broken(&self) -> bool {
1950 match self.protected {
1951 ProtectionStatus::Protected => false,
1952 ProtectionStatus::Unprotected => false,
1953 ProtectionStatus::ProtectionBroken => true,
1954 }
1955 }
1956
1957 pub fn is_sending_locations(&self) -> bool {
1959 self.is_sending_locations
1960 }
1961
1962 pub fn is_muted(&self) -> bool {
1964 match self.mute_duration {
1965 MuteDuration::NotMuted => false,
1966 MuteDuration::Forever => true,
1967 MuteDuration::Until(when) => when > SystemTime::now(),
1968 }
1969 }
1970
1971 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1973 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1974 Ok(member_list_timestamp)
1975 } else {
1976 Ok(self.id.created_timestamp(context).await?)
1977 }
1978 }
1979
1980 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1986 let now = time();
1987 let member_list_ts = self.member_list_timestamp(context).await?;
1988 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1989 >= member_list_ts.saturating_add(60 * 24 * 3600);
1990 Ok(is_stale)
1991 }
1992
1993 async fn prepare_msg_raw(
1999 &mut self,
2000 context: &Context,
2001 msg: &mut Message,
2002 update_msg_id: Option<MsgId>,
2003 timestamp: i64,
2004 ) -> Result<MsgId> {
2005 let mut to_id = 0;
2006 let mut location_id = 0;
2007
2008 if msg.rfc724_mid.is_empty() {
2009 msg.rfc724_mid = create_outgoing_rfc724_mid();
2010 }
2011
2012 if self.typ == Chattype::Single {
2013 if let Some(id) = context
2014 .sql
2015 .query_get_value(
2016 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
2017 (self.id,),
2018 )
2019 .await?
2020 {
2021 to_id = id;
2022 } else {
2023 error!(
2024 context,
2025 "Cannot send message, contact for {} not found.", self.id,
2026 );
2027 bail!("Cannot set message, contact for {} not found.", self.id);
2028 }
2029 } else if self.typ == Chattype::Group
2030 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
2031 {
2032 msg.param.set_int(Param::AttachGroupImage, 1);
2033 self.param
2034 .remove(Param::Unpromoted)
2035 .set_i64(Param::GroupNameTimestamp, timestamp);
2036 self.update_param(context).await?;
2037 context
2043 .sync_qr_code_tokens(Some(self.grpid.as_str()))
2044 .await
2045 .log_err(context)
2046 .ok();
2047 }
2048
2049 let is_bot = context.get_config_bool(Config::Bot).await?;
2050 msg.param
2051 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2052
2053 let new_references;
2057 if self.is_self_talk() {
2058 new_references = String::new();
2061 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2062 self
2068 .id
2069 .get_parent_mime_headers(context, MessageState::OutPending)
2070 .await?
2071 {
2072 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2076 msg.in_reply_to = Some(parent_rfc724_mid.clone());
2077 }
2078
2079 let parent_references = if parent_references.is_empty() {
2089 parent_in_reply_to
2090 } else {
2091 parent_references
2092 };
2093
2094 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2097 references_vec.reverse();
2098
2099 if !parent_rfc724_mid.is_empty()
2100 && !references_vec.contains(&parent_rfc724_mid.as_str())
2101 {
2102 references_vec.push(&parent_rfc724_mid)
2103 }
2104
2105 if references_vec.is_empty() {
2106 new_references = msg.rfc724_mid.clone();
2109 } else {
2110 new_references = references_vec.join(" ");
2111 }
2112 } else {
2113 new_references = msg.rfc724_mid.clone();
2119 }
2120
2121 if msg.param.exists(Param::SetLatitude) {
2123 if let Ok(row_id) = context
2124 .sql
2125 .insert(
2126 "INSERT INTO locations \
2127 (timestamp,from_id,chat_id, latitude,longitude,independent)\
2128 VALUES (?,?,?, ?,?,1);",
2129 (
2130 timestamp,
2131 ContactId::SELF,
2132 self.id,
2133 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2134 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2135 ),
2136 )
2137 .await
2138 {
2139 location_id = row_id;
2140 }
2141 }
2142
2143 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2144 EphemeralTimer::Disabled
2145 } else {
2146 self.id.get_ephemeral_timer(context).await?
2147 };
2148 let ephemeral_timestamp = match ephemeral_timer {
2149 EphemeralTimer::Disabled => 0,
2150 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2151 };
2152
2153 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2154 let new_mime_headers = if msg.has_html() {
2155 if msg.param.exists(Param::Forwarded) {
2156 msg.get_id().get_html(context).await?
2157 } else {
2158 msg.param.get(Param::SendHtml).map(|s| s.to_string())
2159 }
2160 } else {
2161 None
2162 };
2163 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2164 let html_part = MimePart::new("text/html", s);
2165 let mut buffer = Vec::new();
2166 let cursor = Cursor::new(&mut buffer);
2167 html_part.write_part(cursor).ok();
2168 String::from_utf8_lossy(&buffer).to_string()
2169 });
2170 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2171 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2175 false => None,
2176 });
2177 let new_mime_headers = match new_mime_headers {
2178 Some(h) => Some(tokio::task::block_in_place(move || {
2179 buf_compress(h.as_bytes())
2180 })?),
2181 None => None,
2182 };
2183
2184 msg.chat_id = self.id;
2185 msg.from_id = ContactId::SELF;
2186 msg.timestamp_sort = timestamp;
2187
2188 if let Some(update_msg_id) = update_msg_id {
2190 context
2191 .sql
2192 .execute(
2193 "UPDATE msgs
2194 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2195 state=?, txt=?, txt_normalized=?, subject=?, param=?,
2196 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2197 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2198 ephemeral_timestamp=?
2199 WHERE id=?;",
2200 params_slice![
2201 msg.rfc724_mid,
2202 msg.chat_id,
2203 msg.from_id,
2204 to_id,
2205 msg.timestamp_sort,
2206 msg.viewtype,
2207 msg.state,
2208 msg_text,
2209 message::normalize_text(&msg_text),
2210 &msg.subject,
2211 msg.param.to_string(),
2212 msg.hidden,
2213 msg.in_reply_to.as_deref().unwrap_or_default(),
2214 new_references,
2215 new_mime_headers.is_some(),
2216 new_mime_headers.unwrap_or_default(),
2217 location_id as i32,
2218 ephemeral_timer,
2219 ephemeral_timestamp,
2220 update_msg_id
2221 ],
2222 )
2223 .await?;
2224 msg.id = update_msg_id;
2225 } else {
2226 let raw_id = context
2227 .sql
2228 .insert(
2229 "INSERT INTO msgs (
2230 rfc724_mid,
2231 chat_id,
2232 from_id,
2233 to_id,
2234 timestamp,
2235 type,
2236 state,
2237 txt,
2238 txt_normalized,
2239 subject,
2240 param,
2241 hidden,
2242 mime_in_reply_to,
2243 mime_references,
2244 mime_modified,
2245 mime_headers,
2246 mime_compressed,
2247 location_id,
2248 ephemeral_timer,
2249 ephemeral_timestamp)
2250 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2251 params_slice![
2252 msg.rfc724_mid,
2253 msg.chat_id,
2254 msg.from_id,
2255 to_id,
2256 msg.timestamp_sort,
2257 msg.viewtype,
2258 msg.state,
2259 msg_text,
2260 message::normalize_text(&msg_text),
2261 &msg.subject,
2262 msg.param.to_string(),
2263 msg.hidden,
2264 msg.in_reply_to.as_deref().unwrap_or_default(),
2265 new_references,
2266 new_mime_headers.is_some(),
2267 new_mime_headers.unwrap_or_default(),
2268 location_id as i32,
2269 ephemeral_timer,
2270 ephemeral_timestamp
2271 ],
2272 )
2273 .await?;
2274 context.new_msgs_notify.notify_one();
2275 msg.id = MsgId::new(u32::try_from(raw_id)?);
2276
2277 maybe_set_logging_xdc(context, msg, self.id).await?;
2278 context
2279 .update_webxdc_integration_database(msg, context)
2280 .await?;
2281 }
2282 context.scheduler.interrupt_ephemeral_task().await;
2283 Ok(msg.id)
2284 }
2285
2286 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2288 let addrs = context
2289 .sql
2290 .query_map(
2291 "SELECT c.addr \
2292 FROM contacts c INNER JOIN chats_contacts cc \
2293 ON c.id=cc.contact_id \
2294 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2295 (self.id,),
2296 |row| row.get::<_, String>(0),
2297 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2298 )
2299 .await?;
2300 self.sync(context, SyncAction::SetContacts(addrs)).await
2301 }
2302
2303 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2305 match self.typ {
2306 Chattype::Single => {
2307 if self.is_device_talk() {
2308 return Ok(Some(SyncId::Device));
2309 }
2310
2311 let mut r = None;
2312 for contact_id in get_chat_contacts(context, self.id).await? {
2313 if contact_id == ContactId::SELF && !self.is_self_talk() {
2314 continue;
2315 }
2316 if r.is_some() {
2317 return Ok(None);
2318 }
2319 let contact = Contact::get_by_id(context, contact_id).await?;
2320 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2321 }
2322 Ok(r)
2323 }
2324 Chattype::Broadcast | Chattype::Group | Chattype::Mailinglist => {
2325 if !self.grpid.is_empty() {
2326 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2327 }
2328
2329 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2330 .id
2331 .get_parent_mime_headers(context, MessageState::OutDelivered)
2332 .await?
2333 else {
2334 warn!(
2335 context,
2336 "Chat::get_sync_id({}): No good message identifying the chat found.",
2337 self.id
2338 );
2339 return Ok(None);
2340 };
2341 Ok(Some(SyncId::Msgids(vec![
2342 parent_in_reply_to,
2343 parent_rfc724_mid,
2344 ])))
2345 }
2346 }
2347 }
2348
2349 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2351 if let Some(id) = self.get_sync_id(context).await? {
2352 sync(context, id, action).await?;
2353 }
2354 Ok(())
2355 }
2356}
2357
2358pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2359 context
2360 .add_sync_item(SyncData::AlterChat { id, action })
2361 .await?;
2362 context.scheduler.interrupt_inbox().await;
2363 Ok(())
2364}
2365
2366#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2368#[repr(i8)]
2369pub enum ChatVisibility {
2370 Normal = 0,
2372
2373 Archived = 1,
2375
2376 Pinned = 2,
2378}
2379
2380impl rusqlite::types::ToSql for ChatVisibility {
2381 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
2382 let val = rusqlite::types::Value::Integer(*self as i64);
2383 let out = rusqlite::types::ToSqlOutput::Owned(val);
2384 Ok(out)
2385 }
2386}
2387
2388impl rusqlite::types::FromSql for ChatVisibility {
2389 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2390 i64::column_result(value).map(|val| {
2391 match val {
2392 2 => ChatVisibility::Pinned,
2393 1 => ChatVisibility::Archived,
2394 0 => ChatVisibility::Normal,
2395 _ => ChatVisibility::Normal,
2397 }
2398 })
2399 }
2400}
2401
2402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2404#[non_exhaustive]
2405pub struct ChatInfo {
2406 pub id: ChatId,
2408
2409 #[serde(rename = "type")]
2416 pub type_: u32,
2417
2418 pub name: String,
2420
2421 pub archived: bool,
2423
2424 pub param: String,
2428
2429 pub is_sending_locations: bool,
2431
2432 pub color: u32,
2436
2437 pub profile_image: std::path::PathBuf,
2442
2443 pub draft: String,
2451
2452 pub is_muted: bool,
2456
2457 pub ephemeral_timer: EphemeralTimer,
2459 }
2465
2466pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<()> {
2467 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2468 ChatIdBlocked::lookup_by_contact(context, ContactId::SELF).await?
2469 {
2470 let icon = include_bytes!("../assets/icon-saved-messages.png");
2471 let blob =
2472 BlobObject::create_and_deduplicate_from_bytes(context, icon, "saved-messages.png")?;
2473 let icon = blob.as_name().to_string();
2474
2475 let mut chat = Chat::load_from_db(context, chat_id).await?;
2476 chat.param.set(Param::ProfileImage, icon);
2477 chat.update_param(context).await?;
2478 }
2479 Ok(())
2480}
2481
2482pub(crate) async fn update_device_icon(context: &Context) -> Result<()> {
2483 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2484 ChatIdBlocked::lookup_by_contact(context, ContactId::DEVICE).await?
2485 {
2486 let icon = include_bytes!("../assets/icon-device.png");
2487 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "device.png")?;
2488 let icon = blob.as_name().to_string();
2489
2490 let mut chat = Chat::load_from_db(context, chat_id).await?;
2491 chat.param.set(Param::ProfileImage, &icon);
2492 chat.update_param(context).await?;
2493
2494 let mut contact = Contact::get_by_id(context, ContactId::DEVICE).await?;
2495 contact.param.set(Param::ProfileImage, icon);
2496 contact.update_param(context).await?;
2497 }
2498 Ok(())
2499}
2500
2501pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
2502 if let Some(icon) = context.sql.get_raw_config("icon-broadcast").await? {
2503 return Ok(icon);
2504 }
2505
2506 let icon = include_bytes!("../assets/icon-broadcast.png");
2507 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "broadcast.png")?;
2508 let icon = blob.as_name().to_string();
2509 context
2510 .sql
2511 .set_raw_config("icon-broadcast", Some(&icon))
2512 .await?;
2513 Ok(icon)
2514}
2515
2516pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
2517 if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
2518 return Ok(icon);
2519 }
2520
2521 let icon = include_bytes!("../assets/icon-archive.png");
2522 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "archive.png")?;
2523 let icon = blob.as_name().to_string();
2524 context
2525 .sql
2526 .set_raw_config("icon-archive", Some(&icon))
2527 .await?;
2528 Ok(icon)
2529}
2530
2531async fn update_special_chat_name(
2532 context: &Context,
2533 contact_id: ContactId,
2534 name: String,
2535) -> Result<()> {
2536 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2537 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2538 {
2539 context
2541 .sql
2542 .execute(
2543 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2544 (&name, chat_id, &name),
2545 )
2546 .await?;
2547 }
2548 Ok(())
2549}
2550
2551pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2552 update_special_chat_name(
2553 context,
2554 ContactId::DEVICE,
2555 stock_str::device_messages(context).await,
2556 )
2557 .await?;
2558 update_special_chat_name(
2559 context,
2560 ContactId::SELF,
2561 stock_str::saved_messages(context).await,
2562 )
2563 .await?;
2564 Ok(())
2565}
2566
2567pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
2570 let chat_ids: Vec<ChatId> = context
2571 .sql
2572 .query_map(
2573 "SELECT chat_id FROM bobstate",
2574 (),
2575 |row| {
2576 let chat_id: ChatId = row.get(0)?;
2577 Ok(chat_id)
2578 },
2579 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2580 )
2581 .await?;
2582
2583 for chat_id in chat_ids {
2584 let chat = Chat::load_from_db(context, chat_id).await?;
2585 let timeout = chat
2586 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
2587 .await?;
2588 if timeout > 0 {
2589 chat_id.spawn_securejoin_wait(context, timeout);
2590 }
2591 }
2592 Ok(())
2593}
2594
2595#[derive(Debug)]
2603pub(crate) struct ChatIdBlocked {
2604 pub id: ChatId,
2606
2607 pub blocked: Blocked,
2609}
2610
2611impl ChatIdBlocked {
2612 pub async fn lookup_by_contact(
2616 context: &Context,
2617 contact_id: ContactId,
2618 ) -> Result<Option<Self>> {
2619 ensure!(context.sql.is_open().await, "Database not available");
2620 ensure!(
2621 contact_id != ContactId::UNDEFINED,
2622 "Invalid contact id requested"
2623 );
2624
2625 context
2626 .sql
2627 .query_row_optional(
2628 "SELECT c.id, c.blocked
2629 FROM chats c
2630 INNER JOIN chats_contacts j
2631 ON c.id=j.chat_id
2632 WHERE c.type=100 -- 100 = Chattype::Single
2633 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2634 AND j.contact_id=?;",
2635 (contact_id,),
2636 |row| {
2637 let id: ChatId = row.get(0)?;
2638 let blocked: Blocked = row.get(1)?;
2639 Ok(ChatIdBlocked { id, blocked })
2640 },
2641 )
2642 .await
2643 }
2644
2645 pub async fn get_for_contact(
2650 context: &Context,
2651 contact_id: ContactId,
2652 create_blocked: Blocked,
2653 ) -> Result<Self> {
2654 ensure!(context.sql.is_open().await, "Database not available");
2655 ensure!(
2656 contact_id != ContactId::UNDEFINED,
2657 "Invalid contact id requested"
2658 );
2659
2660 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2661 return Ok(res);
2663 }
2664
2665 let contact = Contact::get_by_id(context, contact_id).await?;
2666 let chat_name = contact.get_display_name().to_string();
2667 let mut params = Params::new();
2668 match contact_id {
2669 ContactId::SELF => {
2670 params.set_int(Param::Selftalk, 1);
2671 }
2672 ContactId::DEVICE => {
2673 params.set_int(Param::Devicetalk, 1);
2674 }
2675 _ => (),
2676 }
2677
2678 let protected = contact_id == ContactId::SELF || {
2679 let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
2680 peerstate.is_some_and(|p| {
2681 p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
2682 })
2683 };
2684 let smeared_time = create_smeared_timestamp(context);
2685
2686 let chat_id = context
2687 .sql
2688 .transaction(move |transaction| {
2689 transaction.execute(
2690 "INSERT INTO chats
2691 (type, name, param, blocked, created_timestamp, protected)
2692 VALUES(?, ?, ?, ?, ?, ?)",
2693 (
2694 Chattype::Single,
2695 chat_name,
2696 params.to_string(),
2697 create_blocked as u8,
2698 smeared_time,
2699 if protected {
2700 ProtectionStatus::Protected
2701 } else {
2702 ProtectionStatus::Unprotected
2703 },
2704 ),
2705 )?;
2706 let chat_id = ChatId::new(
2707 transaction
2708 .last_insert_rowid()
2709 .try_into()
2710 .context("chat table rowid overflows u32")?,
2711 );
2712
2713 transaction.execute(
2714 "INSERT INTO chats_contacts
2715 (chat_id, contact_id)
2716 VALUES((SELECT last_insert_rowid()), ?)",
2717 (contact_id,),
2718 )?;
2719
2720 Ok(chat_id)
2721 })
2722 .await?;
2723
2724 if protected {
2725 chat_id
2726 .add_protection_msg(
2727 context,
2728 ProtectionStatus::Protected,
2729 Some(contact_id),
2730 smeared_time,
2731 )
2732 .await?;
2733 }
2734
2735 match contact_id {
2736 ContactId::SELF => update_saved_messages_icon(context).await?,
2737 ContactId::DEVICE => update_device_icon(context).await?,
2738 _ => (),
2739 }
2740
2741 Ok(Self {
2742 id: chat_id,
2743 blocked: create_blocked,
2744 })
2745 }
2746}
2747
2748async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2749 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
2750 } else if msg.viewtype.has_file() {
2752 let mut blob = msg
2753 .param
2754 .get_file_blob(context)?
2755 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2756 let send_as_is = msg.viewtype == Viewtype::File;
2757
2758 if msg.viewtype == Viewtype::File
2759 || msg.viewtype == Viewtype::Image
2760 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2761 {
2762 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2769 if msg.viewtype == Viewtype::Sticker {
2770 if better_type != Viewtype::Image {
2771 msg.param.set_int(Param::ForceSticker, 1);
2773 }
2774 } else if better_type != Viewtype::Webxdc
2775 || context
2776 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2777 .await
2778 .is_ok()
2779 {
2780 msg.viewtype = better_type;
2781 }
2782 }
2783 } else if msg.viewtype == Viewtype::Webxdc {
2784 context
2785 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2786 .await?;
2787 }
2788
2789 if msg.viewtype == Viewtype::Vcard {
2790 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2791 }
2792
2793 let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
2794 if !send_as_is
2795 && (msg.viewtype == Viewtype::Image
2796 || maybe_sticker && !msg.param.exists(Param::ForceSticker))
2797 {
2798 let new_name = blob
2799 .recode_to_image_size(context, msg.get_filename(), &mut maybe_sticker)
2800 .await?;
2801 msg.param.set(Param::Filename, new_name);
2802 msg.param.set(Param::File, blob.as_name());
2803
2804 if !maybe_sticker {
2805 msg.viewtype = Viewtype::Image;
2806 }
2807 }
2808
2809 if !msg.param.exists(Param::MimeType) {
2810 if let Some((_, mime)) = message::guess_msgtype_from_suffix(msg) {
2811 msg.param.set(Param::MimeType, mime);
2812 }
2813 }
2814
2815 msg.try_calc_and_set_dimensions(context).await?;
2816
2817 info!(
2818 context,
2819 "Attaching \"{}\" for message type #{}.",
2820 blob.to_abs_path().display(),
2821 msg.viewtype
2822 );
2823 } else {
2824 bail!("Cannot send messages of type #{}.", msg.viewtype);
2825 }
2826 Ok(())
2827}
2828
2829pub async fn is_contact_in_chat(
2831 context: &Context,
2832 chat_id: ChatId,
2833 contact_id: ContactId,
2834) -> Result<bool> {
2835 let exists = context
2841 .sql
2842 .exists(
2843 "SELECT COUNT(*) FROM chats_contacts
2844 WHERE chat_id=? AND contact_id=?
2845 AND add_timestamp >= remove_timestamp",
2846 (chat_id, contact_id),
2847 )
2848 .await?;
2849 Ok(exists)
2850}
2851
2852pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2859 ensure!(
2860 !chat_id.is_special(),
2861 "chat_id cannot be a special chat: {chat_id}"
2862 );
2863
2864 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2865 msg.param.remove(Param::GuaranteeE2ee);
2866 msg.param.remove(Param::ForcePlaintext);
2867 msg.update_param(context).await?;
2868 }
2869
2870 if msg.is_system_message() {
2872 msg.text = sanitize_bidi_characters(&msg.text);
2873 }
2874
2875 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2876 if !msg.hidden {
2877 context.emit_msgs_changed(msg.chat_id, msg.id);
2878 }
2879
2880 if msg.param.exists(Param::SetLatitude) {
2881 context.emit_location_changed(Some(ContactId::SELF)).await?;
2882 }
2883
2884 context.scheduler.interrupt_smtp().await;
2885 }
2886
2887 Ok(msg.id)
2888}
2889
2890pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2895 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2896 if rowids.is_empty() {
2897 return Ok(msg.id);
2898 }
2899 let mut smtp = crate::smtp::Smtp::new();
2900 for rowid in rowids {
2901 send_msg_to_smtp(context, &mut smtp, rowid)
2902 .await
2903 .context("failed to send message, queued for later sending")?;
2904 }
2905 context.emit_msgs_changed(msg.chat_id, msg.id);
2906 Ok(msg.id)
2907}
2908
2909async fn prepare_send_msg(
2913 context: &Context,
2914 chat_id: ChatId,
2915 msg: &mut Message,
2916) -> Result<Vec<i64>> {
2917 let mut chat = Chat::load_from_db(context, chat_id).await?;
2918
2919 let skip_fn = |reason: &CantSendReason| match reason {
2920 CantSendReason::ProtectionBroken
2921 | CantSendReason::ContactRequest
2922 | CantSendReason::SecurejoinWait => {
2923 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2926 }
2927 CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2931 _ => false,
2932 };
2933 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2934 bail!("Cannot send to {chat_id}: {reason}");
2935 }
2936
2937 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2942 if let Some(quoted_message) = msg.quoted_message(context).await? {
2943 if quoted_message.chat_id != chat_id {
2944 bail!("Bad quote reply");
2945 }
2946 }
2947 }
2948
2949 let update_msg_id = if msg.state == MessageState::OutDraft {
2951 msg.hidden = false;
2952 if !msg.id.is_special() && msg.chat_id == chat_id {
2953 Some(msg.id)
2954 } else {
2955 None
2956 }
2957 } else {
2958 None
2959 };
2960
2961 msg.state = MessageState::OutPending;
2963
2964 prepare_msg_blob(context, msg).await?;
2965 if !msg.hidden {
2966 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2967 }
2968 msg.id = chat
2969 .prepare_msg_raw(
2970 context,
2971 msg,
2972 update_msg_id,
2973 create_smeared_timestamp(context),
2974 )
2975 .await?;
2976 msg.chat_id = chat_id;
2977
2978 let row_ids = create_send_msg_jobs(context, msg)
2979 .await
2980 .context("Failed to create send jobs")?;
2981 Ok(row_ids)
2982}
2983
2984pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2990 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2991 msg.chat_id
2992 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2993 .await?;
2994 }
2995
2996 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2997 let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
2998 let attach_selfavatar = mimefactory.attach_selfavatar;
2999 let mut recipients = mimefactory.recipients();
3000
3001 let from = context.get_primary_self_addr().await?;
3002 let lowercase_from = from.to_lowercase();
3003
3004 recipients.retain(|x| x.to_lowercase() != lowercase_from);
3017 if (context.get_config_bool(Config::BccSelf).await?
3018 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
3019 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
3020 {
3021 recipients.push(from);
3022 }
3023
3024 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
3026 recipients.clear();
3027 }
3028
3029 if recipients.is_empty() {
3030 info!(
3032 context,
3033 "Message {} has no recipient, skipping smtp-send.", msg.id
3034 );
3035 msg.param.set_int(Param::GuaranteeE2ee, 1);
3036 msg.update_param(context).await?;
3037 msg.id.set_delivered(context).await?;
3038 msg.state = MessageState::OutDelivered;
3039 return Ok(Vec::new());
3040 }
3041
3042 let rendered_msg = match mimefactory.render(context).await {
3043 Ok(res) => Ok(res),
3044 Err(err) => {
3045 message::set_msg_failed(context, msg, &err.to_string()).await?;
3046 Err(err)
3047 }
3048 }?;
3049
3050 if needs_encryption && !rendered_msg.is_encrypted {
3051 message::set_msg_failed(
3053 context,
3054 msg,
3055 "End-to-end-encryption unavailable unexpectedly.",
3056 )
3057 .await?;
3058 bail!(
3059 "e2e encryption unavailable {} - {:?}",
3060 msg.id,
3061 needs_encryption
3062 );
3063 }
3064
3065 let now = smeared_time(context);
3066
3067 if rendered_msg.last_added_location_id.is_some() {
3068 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3069 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3070 }
3071 }
3072
3073 if attach_selfavatar {
3074 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3075 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3076 }
3077 }
3078
3079 if rendered_msg.is_encrypted && !needs_encryption {
3080 msg.param.set_int(Param::GuaranteeE2ee, 1);
3081 msg.update_param(context).await?;
3082 }
3083
3084 msg.subject.clone_from(&rendered_msg.subject);
3085 msg.update_subject(context).await?;
3086 let chunk_size = context.get_max_smtp_rcpt_to().await?;
3087 let trans_fn = |t: &mut rusqlite::Transaction| {
3088 let mut row_ids = Vec::<i64>::new();
3089 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3090 t.execute(
3091 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3092 (),
3093 )?;
3094 t.execute(
3095 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3096 (&rendered_msg.message, msg.id),
3097 )?;
3098 } else {
3099 for recipients_chunk in recipients.chunks(chunk_size) {
3100 let recipients_chunk = recipients_chunk.join(" ");
3101 let row_id = t.execute(
3102 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3103 VALUES (?1, ?2, ?3, ?4)",
3104 (
3105 &rendered_msg.rfc724_mid,
3106 recipients_chunk,
3107 &rendered_msg.message,
3108 msg.id,
3109 ),
3110 )?;
3111 row_ids.push(row_id.try_into()?);
3112 }
3113 }
3114 Ok(row_ids)
3115 };
3116 context.sql.transaction(trans_fn).await
3117}
3118
3119pub async fn send_text_msg(
3123 context: &Context,
3124 chat_id: ChatId,
3125 text_to_send: String,
3126) -> Result<MsgId> {
3127 ensure!(
3128 !chat_id.is_special(),
3129 "bad chat_id, can not be a special chat: {}",
3130 chat_id
3131 );
3132
3133 let mut msg = Message::new_text(text_to_send);
3134 send_msg(context, chat_id, &mut msg).await
3135}
3136
3137pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3139 let mut original_msg = Message::load_from_db(context, msg_id).await?;
3140 ensure!(
3141 original_msg.from_id == ContactId::SELF,
3142 "Can edit only own messages"
3143 );
3144 ensure!(!original_msg.is_info(), "Cannot edit info messages");
3145 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3146 ensure!(
3147 original_msg.viewtype != Viewtype::VideochatInvitation,
3148 "Cannot edit videochat invitations"
3149 );
3150 ensure!(
3151 !original_msg.text.is_empty(), "Cannot add text"
3153 );
3154 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3155 if original_msg.text == new_text {
3156 info!(context, "Text unchanged.");
3157 return Ok(());
3158 }
3159
3160 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3161
3162 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() {
3165 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3166 }
3167 edit_msg
3168 .param
3169 .set(Param::TextEditFor, original_msg.rfc724_mid);
3170 edit_msg.hidden = true;
3171 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3172 Ok(())
3173}
3174
3175pub(crate) async fn save_text_edit_to_db(
3176 context: &Context,
3177 original_msg: &mut Message,
3178 new_text: &str,
3179) -> Result<()> {
3180 original_msg.param.set_int(Param::IsEdited, 1);
3181 context
3182 .sql
3183 .execute(
3184 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3185 (
3186 new_text,
3187 message::normalize_text(new_text),
3188 original_msg.param.to_string(),
3189 original_msg.id,
3190 ),
3191 )
3192 .await?;
3193 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3194 Ok(())
3195}
3196
3197pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3199 ensure!(
3200 !chat_id.is_special(),
3201 "video chat invitation cannot be sent to special chat: {}",
3202 chat_id
3203 );
3204
3205 let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3206 if !instance.is_empty() {
3207 instance
3208 } else {
3209 bail!("webrtc_instance is empty");
3210 }
3211 } else {
3212 bail!("webrtc_instance not set");
3213 };
3214
3215 let instance = Message::create_webrtc_instance(&instance, &create_id());
3216
3217 let mut msg = Message::new(Viewtype::VideochatInvitation);
3218 msg.param.set(Param::WebrtcRoom, &instance);
3219 msg.text =
3220 stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3221 .await;
3222 send_msg(context, chat_id, &mut msg).await
3223}
3224
3225#[derive(Debug)]
3227pub struct MessageListOptions {
3228 pub info_only: bool,
3230
3231 pub add_daymarker: bool,
3233}
3234
3235pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3237 get_chat_msgs_ex(
3238 context,
3239 chat_id,
3240 MessageListOptions {
3241 info_only: false,
3242 add_daymarker: false,
3243 },
3244 )
3245 .await
3246}
3247
3248pub async fn get_chat_msgs_ex(
3250 context: &Context,
3251 chat_id: ChatId,
3252 options: MessageListOptions,
3253) -> Result<Vec<ChatItem>> {
3254 let MessageListOptions {
3255 info_only,
3256 add_daymarker,
3257 } = options;
3258 let process_row = if info_only {
3259 |row: &rusqlite::Row| {
3260 let params = row.get::<_, String>("param")?;
3262 let (from_id, to_id) = (
3263 row.get::<_, ContactId>("from_id")?,
3264 row.get::<_, ContactId>("to_id")?,
3265 );
3266 let is_info_msg: bool = from_id == ContactId::INFO
3267 || to_id == ContactId::INFO
3268 || match Params::from_str(¶ms) {
3269 Ok(p) => {
3270 let cmd = p.get_cmd();
3271 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3272 }
3273 _ => false,
3274 };
3275
3276 Ok((
3277 row.get::<_, i64>("timestamp")?,
3278 row.get::<_, MsgId>("id")?,
3279 !is_info_msg,
3280 ))
3281 }
3282 } else {
3283 |row: &rusqlite::Row| {
3284 Ok((
3285 row.get::<_, i64>("timestamp")?,
3286 row.get::<_, MsgId>("id")?,
3287 false,
3288 ))
3289 }
3290 };
3291 let process_rows = |rows: rusqlite::MappedRows<_>| {
3292 let mut sorted_rows = Vec::new();
3295 for row in rows {
3296 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3297 if !exclude_message {
3298 sorted_rows.push((ts, curr_id));
3299 }
3300 }
3301 sorted_rows.sort_unstable();
3302
3303 let mut ret = Vec::new();
3304 let mut last_day = 0;
3305 let cnv_to_local = gm2local_offset();
3306
3307 for (ts, curr_id) in sorted_rows {
3308 if add_daymarker {
3309 let curr_local_timestamp = ts + cnv_to_local;
3310 let curr_day = curr_local_timestamp / 86400;
3311 if curr_day != last_day {
3312 ret.push(ChatItem::DayMarker {
3313 timestamp: curr_day * 86400, });
3315 last_day = curr_day;
3316 }
3317 }
3318 ret.push(ChatItem::Message { msg_id: curr_id });
3319 }
3320 Ok(ret)
3321 };
3322
3323 let items = if info_only {
3324 context
3325 .sql
3326 .query_map(
3327 "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
3329 FROM msgs m
3330 WHERE m.chat_id=?
3331 AND m.hidden=0
3332 AND (
3333 m.param GLOB \"*S=*\"
3334 OR m.from_id == ?
3335 OR m.to_id == ?
3336 );",
3337 (chat_id, ContactId::INFO, ContactId::INFO),
3338 process_row,
3339 process_rows,
3340 )
3341 .await?
3342 } else {
3343 context
3344 .sql
3345 .query_map(
3346 "SELECT m.id AS id, m.timestamp AS timestamp
3347 FROM msgs m
3348 WHERE m.chat_id=?
3349 AND m.hidden=0;",
3350 (chat_id,),
3351 process_row,
3352 process_rows,
3353 )
3354 .await?
3355 };
3356 Ok(items)
3357}
3358
3359pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3362 if chat_id.is_archived_link() {
3365 let chat_ids_in_archive = context
3366 .sql
3367 .query_map(
3368 "SELECT DISTINCT(m.chat_id) FROM msgs m
3369 LEFT JOIN chats c ON m.chat_id=c.id
3370 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3371 (),
3372 |row| row.get::<_, ChatId>(0),
3373 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3374 )
3375 .await?;
3376 if chat_ids_in_archive.is_empty() {
3377 return Ok(());
3378 }
3379
3380 context
3381 .sql
3382 .transaction(|transaction| {
3383 let mut stmt = transaction.prepare(
3384 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3385 )?;
3386 for chat_id_in_archive in &chat_ids_in_archive {
3387 stmt.execute((chat_id_in_archive,))?;
3388 }
3389 Ok(())
3390 })
3391 .await?;
3392
3393 for chat_id_in_archive in chat_ids_in_archive {
3394 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3395 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3396 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3397 }
3398 } else {
3399 start_chat_ephemeral_timers(context, chat_id).await?;
3400
3401 let noticed_msgs_count = context
3402 .sql
3403 .execute(
3404 "UPDATE msgs
3405 SET state=?
3406 WHERE state=?
3407 AND hidden=0
3408 AND chat_id=?;",
3409 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3410 )
3411 .await?;
3412
3413 let hidden_messages = context
3416 .sql
3417 .query_map(
3418 "SELECT id, rfc724_mid FROM msgs
3419 WHERE state=?
3420 AND hidden=1
3421 AND chat_id=?
3422 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3425 let msg_id: MsgId = row.get(0)?;
3426 let rfc724_mid: String = row.get(1)?;
3427 Ok((msg_id, rfc724_mid))
3428 },
3429 |rows| {
3430 rows.collect::<std::result::Result<Vec<_>, _>>()
3431 .map_err(Into::into)
3432 },
3433 )
3434 .await?;
3435 for (msg_id, rfc724_mid) in &hidden_messages {
3436 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3437 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3438 }
3439
3440 if noticed_msgs_count == 0 {
3441 return Ok(());
3442 }
3443 }
3444
3445 context.emit_event(EventType::MsgsNoticed(chat_id));
3446 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3447 context.on_archived_chats_maybe_noticed();
3448 Ok(())
3449}
3450
3451pub(crate) async fn mark_old_messages_as_noticed(
3458 context: &Context,
3459 mut msgs: Vec<ReceivedMsg>,
3460) -> Result<()> {
3461 msgs.retain(|m| m.state.is_outgoing());
3462 if msgs.is_empty() {
3463 return Ok(());
3464 }
3465
3466 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3467 for msg in msgs {
3468 let chat_id = msg.chat_id;
3469 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3470 if msg.sort_timestamp > existing_msg.sort_timestamp {
3471 msgs_by_chat.insert(chat_id, msg);
3472 }
3473 } else {
3474 msgs_by_chat.insert(chat_id, msg);
3475 }
3476 }
3477
3478 let changed_chats = context
3479 .sql
3480 .transaction(|transaction| {
3481 let mut changed_chats = Vec::new();
3482 for (_, msg) in msgs_by_chat {
3483 let changed_rows = transaction.execute(
3484 "UPDATE msgs
3485 SET state=?
3486 WHERE state=?
3487 AND hidden=0
3488 AND chat_id=?
3489 AND timestamp<=?;",
3490 (
3491 MessageState::InNoticed,
3492 MessageState::InFresh,
3493 msg.chat_id,
3494 msg.sort_timestamp,
3495 ),
3496 )?;
3497 if changed_rows > 0 {
3498 changed_chats.push(msg.chat_id);
3499 }
3500 }
3501 Ok(changed_chats)
3502 })
3503 .await?;
3504
3505 if !changed_chats.is_empty() {
3506 info!(
3507 context,
3508 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3509 );
3510 context.on_archived_chats_maybe_noticed();
3511 }
3512
3513 for c in changed_chats {
3514 start_chat_ephemeral_timers(context, c).await?;
3515 context.emit_event(EventType::MsgsNoticed(c));
3516 chatlist_events::emit_chatlist_item_changed(context, c);
3517 }
3518
3519 Ok(())
3520}
3521
3522pub async fn get_chat_media(
3529 context: &Context,
3530 chat_id: Option<ChatId>,
3531 msg_type: Viewtype,
3532 msg_type2: Viewtype,
3533 msg_type3: Viewtype,
3534) -> Result<Vec<MsgId>> {
3535 let list = context
3537 .sql
3538 .query_map(
3539 "SELECT id
3540 FROM msgs
3541 WHERE (1=? OR chat_id=?)
3542 AND chat_id != ?
3543 AND (type=? OR type=? OR type=?)
3544 AND hidden=0
3545 ORDER BY timestamp, id;",
3546 (
3547 chat_id.is_none(),
3548 chat_id.unwrap_or_else(|| ChatId::new(0)),
3549 DC_CHAT_ID_TRASH,
3550 msg_type,
3551 if msg_type2 != Viewtype::Unknown {
3552 msg_type2
3553 } else {
3554 msg_type
3555 },
3556 if msg_type3 != Viewtype::Unknown {
3557 msg_type3
3558 } else {
3559 msg_type
3560 },
3561 ),
3562 |row| row.get::<_, MsgId>(0),
3563 |ids| Ok(ids.flatten().collect()),
3564 )
3565 .await?;
3566 Ok(list)
3567}
3568
3569pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3571 let list = context
3575 .sql
3576 .query_map(
3577 "SELECT cc.contact_id
3578 FROM chats_contacts cc
3579 LEFT JOIN contacts c
3580 ON c.id=cc.contact_id
3581 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3582 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3583 (chat_id,),
3584 |row| row.get::<_, ContactId>(0),
3585 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3586 )
3587 .await?;
3588
3589 Ok(list)
3590}
3591
3592pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3596 let now = time();
3597 let list = context
3598 .sql
3599 .query_map(
3600 "SELECT cc.contact_id
3601 FROM chats_contacts cc
3602 LEFT JOIN contacts c
3603 ON c.id=cc.contact_id
3604 WHERE cc.chat_id=?
3605 AND cc.add_timestamp < cc.remove_timestamp
3606 AND ? < cc.remove_timestamp
3607 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3608 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3609 |row| row.get::<_, ContactId>(0),
3610 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3611 )
3612 .await?;
3613
3614 Ok(list)
3615}
3616
3617pub async fn create_group_chat(
3619 context: &Context,
3620 protect: ProtectionStatus,
3621 chat_name: &str,
3622) -> Result<ChatId> {
3623 let chat_name = sanitize_single_line(chat_name);
3624 ensure!(!chat_name.is_empty(), "Invalid chat name");
3625
3626 let grpid = create_id();
3627
3628 let timestamp = create_smeared_timestamp(context);
3629 let row_id = context
3630 .sql
3631 .insert(
3632 "INSERT INTO chats
3633 (type, name, grpid, param, created_timestamp)
3634 VALUES(?, ?, ?, \'U=1\', ?);",
3635 (Chattype::Group, chat_name, grpid, timestamp),
3636 )
3637 .await?;
3638
3639 let chat_id = ChatId::new(u32::try_from(row_id)?);
3640 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3641
3642 context.emit_msgs_changed_without_ids();
3643 chatlist_events::emit_chatlist_changed(context);
3644 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3645
3646 if protect == ProtectionStatus::Protected {
3647 chat_id
3648 .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3649 .await?;
3650 }
3651
3652 if !context.get_config_bool(Config::Bot).await?
3653 && !context.get_config_bool(Config::SkipStartMessages).await?
3654 {
3655 let text = stock_str::new_group_send_first_message(context).await;
3656 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3657 }
3658
3659 Ok(chat_id)
3660}
3661
3662async fn find_unused_broadcast_list_name(context: &Context) -> Result<String> {
3664 let base_name = stock_str::broadcast_list(context).await;
3665 for attempt in 1..1000 {
3666 let better_name = if attempt > 1 {
3667 format!("{base_name} {attempt}")
3668 } else {
3669 base_name.clone()
3670 };
3671 if !context
3672 .sql
3673 .exists(
3674 "SELECT COUNT(*) FROM chats WHERE type=? AND name=?;",
3675 (Chattype::Broadcast, &better_name),
3676 )
3677 .await?
3678 {
3679 return Ok(better_name);
3680 }
3681 }
3682 Ok(base_name)
3683}
3684
3685pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
3687 let chat_name = find_unused_broadcast_list_name(context).await?;
3688 let grpid = create_id();
3689 create_broadcast_list_ex(context, Sync, grpid, chat_name).await
3690}
3691
3692pub(crate) async fn create_broadcast_list_ex(
3693 context: &Context,
3694 sync: sync::Sync,
3695 grpid: String,
3696 chat_name: String,
3697) -> Result<ChatId> {
3698 let row_id = {
3699 let chat_name = &chat_name;
3700 let grpid = &grpid;
3701 let trans_fn = |t: &mut rusqlite::Transaction| {
3702 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3703 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3704 if cnt == 1 {
3705 return Ok(t.query_row(
3706 "SELECT id FROM chats WHERE grpid=? AND type=?",
3707 (grpid, Chattype::Broadcast),
3708 |row| {
3709 let id: isize = row.get(0)?;
3710 Ok(id)
3711 },
3712 )?);
3713 }
3714 t.execute(
3715 "INSERT INTO chats \
3716 (type, name, grpid, param, created_timestamp) \
3717 VALUES(?, ?, ?, \'U=1\', ?);",
3718 (
3719 Chattype::Broadcast,
3720 &chat_name,
3721 &grpid,
3722 create_smeared_timestamp(context),
3723 ),
3724 )?;
3725 Ok(t.last_insert_rowid().try_into()?)
3726 };
3727 context.sql.transaction(trans_fn).await?
3728 };
3729 let chat_id = ChatId::new(u32::try_from(row_id)?);
3730
3731 context.emit_msgs_changed_without_ids();
3732 chatlist_events::emit_chatlist_changed(context);
3733
3734 if sync.into() {
3735 let id = SyncId::Grpid(grpid);
3736 let action = SyncAction::CreateBroadcast(chat_name);
3737 self::sync(context, id, action).await.log_err(context).ok();
3738 }
3739
3740 Ok(chat_id)
3741}
3742
3743pub(crate) async fn update_chat_contacts_table(
3745 context: &Context,
3746 timestamp: i64,
3747 id: ChatId,
3748 contacts: &HashSet<ContactId>,
3749) -> Result<()> {
3750 context
3751 .sql
3752 .transaction(move |transaction| {
3753 transaction.execute(
3757 "UPDATE chats_contacts
3758 SET remove_timestamp=MAX(add_timestamp+1, ?)
3759 WHERE chat_id=?",
3760 (timestamp, id),
3761 )?;
3762
3763 if !contacts.is_empty() {
3764 let mut statement = transaction.prepare(
3765 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3766 VALUES (?1, ?2, ?3)
3767 ON CONFLICT (chat_id, contact_id)
3768 DO UPDATE SET add_timestamp=remove_timestamp",
3769 )?;
3770
3771 for contact_id in contacts {
3772 statement.execute((id, contact_id, timestamp))?;
3776 }
3777 }
3778 Ok(())
3779 })
3780 .await?;
3781 Ok(())
3782}
3783
3784pub(crate) async fn add_to_chat_contacts_table(
3786 context: &Context,
3787 timestamp: i64,
3788 chat_id: ChatId,
3789 contact_ids: &[ContactId],
3790) -> Result<()> {
3791 context
3792 .sql
3793 .transaction(move |transaction| {
3794 let mut add_statement = transaction.prepare(
3795 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3796 ON CONFLICT (chat_id, contact_id)
3797 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3798 )?;
3799
3800 for contact_id in contact_ids {
3801 add_statement.execute((chat_id, contact_id, timestamp))?;
3802 }
3803 Ok(())
3804 })
3805 .await?;
3806
3807 Ok(())
3808}
3809
3810pub(crate) async fn remove_from_chat_contacts_table(
3813 context: &Context,
3814 chat_id: ChatId,
3815 contact_id: ContactId,
3816) -> Result<()> {
3817 let now = time();
3818 context
3819 .sql
3820 .execute(
3821 "UPDATE chats_contacts
3822 SET remove_timestamp=MAX(add_timestamp+1, ?)
3823 WHERE chat_id=? AND contact_id=?",
3824 (now, chat_id, contact_id),
3825 )
3826 .await?;
3827 Ok(())
3828}
3829
3830pub async fn add_contact_to_chat(
3833 context: &Context,
3834 chat_id: ChatId,
3835 contact_id: ContactId,
3836) -> Result<()> {
3837 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3838 Ok(())
3839}
3840
3841pub(crate) async fn add_contact_to_chat_ex(
3842 context: &Context,
3843 mut sync: sync::Sync,
3844 chat_id: ChatId,
3845 contact_id: ContactId,
3846 from_handshake: bool,
3847) -> Result<bool> {
3848 ensure!(!chat_id.is_special(), "can not add member to special chats");
3849 let contact = Contact::get_by_id(context, contact_id).await?;
3850 let mut msg = Message::new(Viewtype::default());
3851
3852 chat_id.reset_gossiped_timestamp(context).await?;
3853
3854 let mut chat = Chat::load_from_db(context, chat_id).await?;
3856 ensure!(
3857 chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast,
3858 "{} is not a group/broadcast where one can add members",
3859 chat_id
3860 );
3861 ensure!(
3862 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3863 "invalid contact_id {} for adding to group",
3864 contact_id
3865 );
3866 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3867 ensure!(
3868 chat.typ != Chattype::Broadcast || contact_id != ContactId::SELF,
3869 "Cannot add SELF to broadcast."
3870 );
3871
3872 if !chat.is_self_in_chat(context).await? {
3873 context.emit_event(EventType::ErrorSelfNotInGroup(
3874 "Cannot add contact to group; self not in group.".into(),
3875 ));
3876 bail!("can not add contact because the account is not part of the group/broadcast");
3877 }
3878
3879 let sync_qr_code_tokens;
3880 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3881 chat.param
3882 .remove(Param::Unpromoted)
3883 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3884 chat.update_param(context).await?;
3885 sync_qr_code_tokens = true;
3886 } else {
3887 sync_qr_code_tokens = false;
3888 }
3889
3890 if context.is_self_addr(contact.get_addr()).await? {
3891 warn!(
3894 context,
3895 "Invalid attempt to add self e-mail address to group."
3896 );
3897 return Ok(false);
3898 }
3899
3900 if is_contact_in_chat(context, chat_id, contact_id).await? {
3901 if !from_handshake {
3902 return Ok(true);
3903 }
3904 } else {
3905 if chat.is_protected() && !contact.is_verified(context).await? {
3907 error!(
3908 context,
3909 "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
3910 );
3911 return Ok(false);
3912 }
3913 if is_contact_in_chat(context, chat_id, contact_id).await? {
3914 return Ok(false);
3915 }
3916 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3917 }
3918 if chat.typ == Chattype::Group && chat.is_promoted() {
3919 msg.viewtype = Viewtype::Text;
3920
3921 let contact_addr = contact.get_addr().to_lowercase();
3922 msg.text = stock_str::msg_add_member_local(context, &contact_addr, ContactId::SELF).await;
3923 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3924 msg.param.set(Param::Arg, contact_addr);
3925 msg.param.set_int(Param::Arg2, from_handshake.into());
3926 msg.param
3927 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3928 send_msg(context, chat_id, &mut msg).await?;
3929
3930 sync = Nosync;
3931 if sync_qr_code_tokens
3937 && context
3938 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3939 .await
3940 .log_err(context)
3941 .is_ok()
3942 {
3943 context.scheduler.interrupt_inbox().await;
3944 }
3945 }
3946 context.emit_event(EventType::ChatModified(chat_id));
3947 if sync.into() {
3948 chat.sync_contacts(context).await.log_err(context).ok();
3949 }
3950 Ok(true)
3951}
3952
3953pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3959 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3960 let needs_attach = context
3961 .sql
3962 .query_map(
3963 "SELECT c.selfavatar_sent
3964 FROM chats_contacts cc
3965 LEFT JOIN contacts c ON c.id=cc.contact_id
3966 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3967 (chat_id, ContactId::SELF),
3968 |row| Ok(row.get::<_, i64>(0)),
3969 |rows| {
3970 let mut needs_attach = false;
3971 for row in rows {
3972 let row = row?;
3973 let selfavatar_sent = row?;
3974 if selfavatar_sent < timestamp_some_days_ago {
3975 needs_attach = true;
3976 }
3977 }
3978 Ok(needs_attach)
3979 },
3980 )
3981 .await?;
3982 Ok(needs_attach)
3983}
3984
3985#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3987pub enum MuteDuration {
3988 NotMuted,
3990
3991 Forever,
3993
3994 Until(std::time::SystemTime),
3996}
3997
3998impl rusqlite::types::ToSql for MuteDuration {
3999 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
4000 let duration: i64 = match &self {
4001 MuteDuration::NotMuted => 0,
4002 MuteDuration::Forever => -1,
4003 MuteDuration::Until(when) => {
4004 let duration = when
4005 .duration_since(SystemTime::UNIX_EPOCH)
4006 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
4007 i64::try_from(duration.as_secs())
4008 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
4009 }
4010 };
4011 let val = rusqlite::types::Value::Integer(duration);
4012 let out = rusqlite::types::ToSqlOutput::Owned(val);
4013 Ok(out)
4014 }
4015}
4016
4017impl rusqlite::types::FromSql for MuteDuration {
4018 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
4019 match i64::column_result(value)? {
4022 0 => Ok(MuteDuration::NotMuted),
4023 -1 => Ok(MuteDuration::Forever),
4024 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
4025 Some(t) => Ok(MuteDuration::Until(t)),
4026 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
4027 },
4028 _ => Ok(MuteDuration::NotMuted),
4029 }
4030 }
4031}
4032
4033pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
4035 set_muted_ex(context, Sync, chat_id, duration).await
4036}
4037
4038pub(crate) async fn set_muted_ex(
4039 context: &Context,
4040 sync: sync::Sync,
4041 chat_id: ChatId,
4042 duration: MuteDuration,
4043) -> Result<()> {
4044 ensure!(!chat_id.is_special(), "Invalid chat ID");
4045 context
4046 .sql
4047 .execute(
4048 "UPDATE chats SET muted_until=? WHERE id=?;",
4049 (duration, chat_id),
4050 )
4051 .await
4052 .context(format!("Failed to set mute duration for {chat_id}"))?;
4053 context.emit_event(EventType::ChatModified(chat_id));
4054 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4055 if sync.into() {
4056 let chat = Chat::load_from_db(context, chat_id).await?;
4057 chat.sync(context, SyncAction::SetMuted(duration))
4058 .await
4059 .log_err(context)
4060 .ok();
4061 }
4062 Ok(())
4063}
4064
4065pub async fn remove_contact_from_chat(
4067 context: &Context,
4068 chat_id: ChatId,
4069 contact_id: ContactId,
4070) -> Result<()> {
4071 ensure!(
4072 !chat_id.is_special(),
4073 "bad chat_id, can not be special chat: {}",
4074 chat_id
4075 );
4076 ensure!(
4077 !contact_id.is_special() || contact_id == ContactId::SELF,
4078 "Cannot remove special contact"
4079 );
4080
4081 let mut msg = Message::new(Viewtype::default());
4082
4083 let chat = Chat::load_from_db(context, chat_id).await?;
4084 if chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast {
4085 if !chat.is_self_in_chat(context).await? {
4086 let err_msg = format!(
4087 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4088 );
4089 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4090 bail!("{}", err_msg);
4091 } else {
4092 let mut sync = Nosync;
4093
4094 if chat.is_promoted() {
4095 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4096 } else {
4097 context
4098 .sql
4099 .execute(
4100 "DELETE FROM chats_contacts
4101 WHERE chat_id=? AND contact_id=?",
4102 (chat_id, contact_id),
4103 )
4104 .await?;
4105 }
4106
4107 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4111 if chat.typ == Chattype::Group && chat.is_promoted() {
4112 msg.viewtype = Viewtype::Text;
4113 if contact_id == ContactId::SELF {
4114 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4115 } else {
4116 msg.text = stock_str::msg_del_member_local(
4117 context,
4118 contact.get_addr(),
4119 ContactId::SELF,
4120 )
4121 .await;
4122 }
4123 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4124 msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4125 msg.param
4126 .set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4127 let res = send_msg(context, chat_id, &mut msg).await;
4128 if contact_id == ContactId::SELF {
4129 res?;
4130 set_group_explicitly_left(context, &chat.grpid).await?;
4131 } else if let Err(e) = res {
4132 warn!(context, "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}.");
4133 }
4134 } else {
4135 sync = Sync;
4136 }
4137 }
4138 context.emit_event(EventType::ChatModified(chat_id));
4139 if sync.into() {
4140 chat.sync_contacts(context).await.log_err(context).ok();
4141 }
4142 }
4143 } else {
4144 bail!("Cannot remove members from non-group chats.");
4145 }
4146
4147 Ok(())
4148}
4149
4150async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4151 if !is_group_explicitly_left(context, grpid).await? {
4152 context
4153 .sql
4154 .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4155 .await?;
4156 }
4157
4158 Ok(())
4159}
4160
4161pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4162 let exists = context
4163 .sql
4164 .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4165 .await?;
4166 Ok(exists)
4167}
4168
4169pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4171 rename_ex(context, Sync, chat_id, new_name).await
4172}
4173
4174async fn rename_ex(
4175 context: &Context,
4176 mut sync: sync::Sync,
4177 chat_id: ChatId,
4178 new_name: &str,
4179) -> Result<()> {
4180 let new_name = sanitize_single_line(new_name);
4181 let mut success = false;
4183
4184 ensure!(!new_name.is_empty(), "Invalid name");
4185 ensure!(!chat_id.is_special(), "Invalid chat ID");
4186
4187 let chat = Chat::load_from_db(context, chat_id).await?;
4188 let mut msg = Message::new(Viewtype::default());
4189
4190 if chat.typ == Chattype::Group
4191 || chat.typ == Chattype::Mailinglist
4192 || chat.typ == Chattype::Broadcast
4193 {
4194 if chat.name == new_name {
4195 success = true;
4196 } else if !chat.is_self_in_chat(context).await? {
4197 context.emit_event(EventType::ErrorSelfNotInGroup(
4198 "Cannot set chat name; self not in group".into(),
4199 ));
4200 } else {
4201 context
4202 .sql
4203 .execute(
4204 "UPDATE chats SET name=? WHERE id=?;",
4205 (new_name.to_string(), chat_id),
4206 )
4207 .await?;
4208 if chat.is_promoted()
4209 && !chat.is_mailing_list()
4210 && chat.typ != Chattype::Broadcast
4211 && sanitize_single_line(&chat.name) != new_name
4212 {
4213 msg.viewtype = Viewtype::Text;
4214 msg.text =
4215 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4216 msg.param.set_cmd(SystemMessage::GroupNameChanged);
4217 if !chat.name.is_empty() {
4218 msg.param.set(Param::Arg, &chat.name);
4219 }
4220 msg.id = send_msg(context, chat_id, &mut msg).await?;
4221 context.emit_msgs_changed(chat_id, msg.id);
4222 sync = Nosync;
4223 }
4224 context.emit_event(EventType::ChatModified(chat_id));
4225 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4226 success = true;
4227 }
4228 }
4229
4230 if !success {
4231 bail!("Failed to set name");
4232 }
4233 if sync.into() && chat.name != new_name {
4234 let sync_name = new_name.to_string();
4235 chat.sync(context, SyncAction::Rename(sync_name))
4236 .await
4237 .log_err(context)
4238 .ok();
4239 }
4240 Ok(())
4241}
4242
4243pub async fn set_chat_profile_image(
4249 context: &Context,
4250 chat_id: ChatId,
4251 new_image: &str, ) -> Result<()> {
4253 ensure!(!chat_id.is_special(), "Invalid chat ID");
4254 let mut chat = Chat::load_from_db(context, chat_id).await?;
4255 ensure!(
4256 chat.typ == Chattype::Group || chat.typ == Chattype::Mailinglist,
4257 "Failed to set profile image; group does not exist"
4258 );
4259 if !is_contact_in_chat(context, chat_id, ContactId::SELF).await? {
4261 context.emit_event(EventType::ErrorSelfNotInGroup(
4262 "Cannot set chat profile image; self not in group.".into(),
4263 ));
4264 bail!("Failed to set profile image");
4265 }
4266 let mut msg = Message::new(Viewtype::Text);
4267 msg.param
4268 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4269 if new_image.is_empty() {
4270 chat.param.remove(Param::ProfileImage);
4271 msg.param.remove(Param::Arg);
4272 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4273 } else {
4274 let mut image_blob = BlobObject::create_and_deduplicate(
4275 context,
4276 Path::new(new_image),
4277 Path::new(new_image),
4278 )?;
4279 image_blob.recode_to_avatar_size(context).await?;
4280 chat.param.set(Param::ProfileImage, image_blob.as_name());
4281 msg.param.set(Param::Arg, image_blob.as_name());
4282 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4283 }
4284 chat.update_param(context).await?;
4285 if chat.is_promoted() && !chat.is_mailing_list() {
4286 msg.id = send_msg(context, chat_id, &mut msg).await?;
4287 context.emit_msgs_changed(chat_id, msg.id);
4288 }
4289 context.emit_event(EventType::ChatModified(chat_id));
4290 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4291 Ok(())
4292}
4293
4294pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4296 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4297 ensure!(!chat_id.is_special(), "can not forward to special chat");
4298
4299 let mut created_msgs: Vec<MsgId> = Vec::new();
4300 let mut curr_timestamp: i64;
4301
4302 chat_id
4303 .unarchive_if_not_muted(context, MessageState::Undefined)
4304 .await?;
4305 let mut chat = Chat::load_from_db(context, chat_id).await?;
4306 if let Some(reason) = chat.why_cant_send(context).await? {
4307 bail!("cannot send to {}: {}", chat_id, reason);
4308 }
4309 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4310 let mut msgs = Vec::with_capacity(msg_ids.len());
4311 for id in msg_ids {
4312 let ts: i64 = context
4313 .sql
4314 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4315 .await?
4316 .context("No message {id}")?;
4317 msgs.push((ts, *id));
4318 }
4319 msgs.sort_unstable();
4320 for (_, id) in msgs {
4321 let src_msg_id: MsgId = id;
4322 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4323 if msg.state == MessageState::OutDraft {
4324 bail!("cannot forward drafts.");
4325 }
4326
4327 if msg.get_viewtype() != Viewtype::Sticker {
4332 msg.param
4333 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4334 }
4335
4336 msg.param.remove(Param::GuaranteeE2ee);
4337 msg.param.remove(Param::ForcePlaintext);
4338 msg.param.remove(Param::Cmd);
4339 msg.param.remove(Param::OverrideSenderDisplayname);
4340 msg.param.remove(Param::WebxdcDocument);
4341 msg.param.remove(Param::WebxdcDocumentTimestamp);
4342 msg.param.remove(Param::WebxdcSummary);
4343 msg.param.remove(Param::WebxdcSummaryTimestamp);
4344 msg.param.remove(Param::IsEdited);
4345 msg.in_reply_to = None;
4346
4347 msg.subject = "".to_string();
4349
4350 msg.state = MessageState::OutPending;
4351 msg.rfc724_mid = create_outgoing_rfc724_mid();
4352 let new_msg_id = chat
4353 .prepare_msg_raw(context, &mut msg, None, curr_timestamp)
4354 .await?;
4355
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;