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 rfc724_mid,
999 from_id,
1000 timestamp,
1001 type,
1002 state,
1003 txt,
1004 txt_normalized,
1005 param,
1006 hidden,
1007 mime_in_reply_to)
1008 VALUES (?,?,?,?,?,?,?,?,?,?,?);",
1009 (
1010 self,
1011 &msg.rfc724_mid,
1012 ContactId::SELF,
1013 time(),
1014 msg.viewtype,
1015 MessageState::OutDraft,
1016 &msg.text,
1017 message::normalize_text(&msg.text),
1018 msg.param.to_string(),
1019 1,
1020 msg.in_reply_to.as_deref().unwrap_or_default(),
1021 ),
1022 )?;
1023
1024 Ok(transaction.last_insert_rowid())
1025 })
1026 .await?;
1027 msg.id = MsgId::new(row_id.try_into()?);
1028 Ok(true)
1029 }
1030
1031 pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
1033 let count = context
1034 .sql
1035 .count(
1036 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
1037 (self,),
1038 )
1039 .await?;
1040 Ok(count)
1041 }
1042
1043 pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
1045 let count = if self.is_archived_link() {
1056 context
1057 .sql
1058 .count(
1059 "SELECT COUNT(DISTINCT(m.chat_id))
1060 FROM msgs m
1061 LEFT JOIN chats c ON m.chat_id=c.id
1062 WHERE m.state=10
1063 and m.hidden=0
1064 AND m.chat_id>9
1065 AND c.blocked=0
1066 AND c.archived=1
1067 ",
1068 (),
1069 )
1070 .await?
1071 } else {
1072 context
1073 .sql
1074 .count(
1075 "SELECT COUNT(*)
1076 FROM msgs
1077 WHERE state=?
1078 AND hidden=0
1079 AND chat_id=?;",
1080 (MessageState::InFresh, self),
1081 )
1082 .await?
1083 };
1084 Ok(count)
1085 }
1086
1087 pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
1088 Ok(context
1089 .sql
1090 .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
1091 .await?
1092 .unwrap_or(0))
1093 }
1094
1095 pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
1098 let timestamp = context
1099 .sql
1100 .query_get_value(
1101 "SELECT MAX(timestamp)
1102 FROM msgs
1103 WHERE chat_id=?
1104 HAVING COUNT(*) > 0",
1105 (self,),
1106 )
1107 .await?;
1108 Ok(timestamp)
1109 }
1110
1111 pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
1117 let intersection: Vec<(ChatId, f64)> = context
1119 .sql
1120 .query_map(
1121 "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
1122 FROM chats_contacts as x
1123 JOIN chats_contacts as y
1124 WHERE x.contact_id > 9
1125 AND y.contact_id > 9
1126 AND x.add_timestamp >= x.remove_timestamp
1127 AND y.add_timestamp >= y.remove_timestamp
1128 AND x.chat_id=?
1129 AND y.chat_id<>x.chat_id
1130 AND y.chat_id>?
1131 GROUP BY y.chat_id",
1132 (self, DC_CHAT_ID_LAST_SPECIAL),
1133 |row| {
1134 let chat_id: ChatId = row.get(0)?;
1135 let intersection: f64 = row.get(1)?;
1136 Ok((chat_id, intersection))
1137 },
1138 |rows| {
1139 rows.collect::<std::result::Result<Vec<_>, _>>()
1140 .map_err(Into::into)
1141 },
1142 )
1143 .await
1144 .context("failed to calculate member set intersections")?;
1145
1146 let chat_size: HashMap<ChatId, f64> = context
1147 .sql
1148 .query_map(
1149 "SELECT chat_id, count(*) AS n
1150 FROM chats_contacts
1151 WHERE contact_id > ? AND chat_id > ?
1152 AND add_timestamp >= remove_timestamp
1153 GROUP BY chat_id",
1154 (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
1155 |row| {
1156 let chat_id: ChatId = row.get(0)?;
1157 let size: f64 = row.get(1)?;
1158 Ok((chat_id, size))
1159 },
1160 |rows| {
1161 rows.collect::<std::result::Result<HashMap<ChatId, f64>, _>>()
1162 .map_err(Into::into)
1163 },
1164 )
1165 .await
1166 .context("failed to count chat member sizes")?;
1167
1168 let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
1169 let mut chats_with_metrics = Vec::new();
1170 for (chat_id, intersection_size) in intersection {
1171 if intersection_size > 0.0 {
1172 let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
1173 let union_size = our_chat_size + other_chat_size - intersection_size;
1174 let metric = intersection_size / union_size;
1175 chats_with_metrics.push((chat_id, metric))
1176 }
1177 }
1178 chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
1179 metric2
1180 .partial_cmp(metric1)
1181 .unwrap_or(chat_id2.cmp(chat_id1))
1182 });
1183
1184 let mut res = Vec::new();
1186 let now = time();
1187 for (chat_id, metric) in chats_with_metrics {
1188 if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
1189 if now > chat_timestamp + 42 * 24 * 3600 {
1190 continue;
1192 }
1193 }
1194
1195 if metric < 0.1 {
1196 break;
1198 }
1199
1200 let chat = Chat::load_from_db(context, chat_id).await?;
1201 if chat.typ != Chattype::Group {
1202 continue;
1203 }
1204
1205 match chat.visibility {
1206 ChatVisibility::Normal | ChatVisibility::Pinned => {}
1207 ChatVisibility::Archived => continue,
1208 }
1209
1210 res.push((chat_id, metric));
1211 if res.len() >= 5 {
1212 break;
1213 }
1214 }
1215
1216 Ok(res)
1217 }
1218
1219 pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1223 let chat_ids: Vec<ChatId> = self
1224 .get_similar_chat_ids(context)
1225 .await
1226 .context("failed to get similar chat IDs")?
1227 .into_iter()
1228 .map(|(chat_id, _metric)| chat_id)
1229 .collect();
1230 let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1231 Ok(chatlist)
1232 }
1233
1234 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1235 let res: Option<String> = context
1236 .sql
1237 .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1238 .await?;
1239 Ok(res
1240 .map(|s| s.parse().unwrap_or_default())
1241 .unwrap_or_default())
1242 }
1243
1244 pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1246 let param = self.get_param(context).await?;
1247 let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1248 Ok(unpromoted)
1249 }
1250
1251 pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1253 let promoted = !self.is_unpromoted(context).await?;
1254 Ok(promoted)
1255 }
1256
1257 pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1259 Ok(self.get_param(context).await?.exists(Param::Selftalk))
1260 }
1261
1262 pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1264 Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1265 }
1266
1267 async fn parent_query<T, F>(
1268 self,
1269 context: &Context,
1270 fields: &str,
1271 state_out_min: MessageState,
1272 f: F,
1273 ) -> Result<Option<T>>
1274 where
1275 F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1276 T: Send + 'static,
1277 {
1278 let sql = &context.sql;
1279 let query = format!(
1280 "SELECT {fields} \
1281 FROM msgs \
1282 WHERE chat_id=? \
1283 AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1284 AND NOT hidden \
1285 AND download_state={} \
1286 AND from_id != {} \
1287 ORDER BY timestamp DESC, id DESC \
1288 LIMIT 1;",
1289 MessageState::InFresh as u32,
1290 MessageState::InSeen as u32,
1291 state_out_min as u32,
1292 DownloadState::Done as u32,
1295 ContactId::INFO.to_u32(),
1298 );
1299 sql.query_row_optional(&query, (self,), f).await
1300 }
1301
1302 async fn get_parent_mime_headers(
1303 self,
1304 context: &Context,
1305 state_out_min: MessageState,
1306 ) -> Result<Option<(String, String, String)>> {
1307 self.parent_query(
1308 context,
1309 "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1310 state_out_min,
1311 |row: &rusqlite::Row| {
1312 let rfc724_mid: String = row.get(0)?;
1313 let mime_in_reply_to: String = row.get(1)?;
1314 let mime_references: String = row.get(2)?;
1315 Ok((rfc724_mid, mime_in_reply_to, mime_references))
1316 },
1317 )
1318 .await
1319 }
1320
1321 pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1329 let mut ret_available = String::new();
1330 let mut ret_reset = String::new();
1331
1332 for contact_id in get_chat_contacts(context, self)
1333 .await?
1334 .iter()
1335 .filter(|&contact_id| !contact_id.is_special())
1336 {
1337 let contact = Contact::get_by_id(context, *contact_id).await?;
1338 let addr = contact.get_addr();
1339 let peerstate = Peerstate::from_addr(context, addr).await?;
1340
1341 match peerstate
1342 .filter(|peerstate| peerstate.peek_key(false).is_some())
1343 .map(|peerstate| peerstate.prefer_encrypt)
1344 {
1345 Some(EncryptPreference::Mutual) | Some(EncryptPreference::NoPreference) => {
1346 ret_available += &format!("{addr}\n")
1347 }
1348 Some(EncryptPreference::Reset) | None => ret_reset += &format!("{addr}\n"),
1349 };
1350 }
1351
1352 let mut ret = String::new();
1353 if !ret_reset.is_empty() {
1354 ret += &stock_str::encr_none(context).await;
1355 ret.push(':');
1356 ret.push('\n');
1357 ret += &ret_reset;
1358 }
1359 if !ret_available.is_empty() {
1360 if !ret.is_empty() {
1361 ret.push('\n');
1362 }
1363 ret += &stock_str::e2e_available(context).await;
1364 ret.push(':');
1365 ret.push('\n');
1366 ret += &ret_available;
1367 }
1368
1369 Ok(ret.trim().to_string())
1370 }
1371
1372 pub fn to_u32(self) -> u32 {
1377 self.0
1378 }
1379
1380 pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1381 context
1382 .sql
1383 .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1384 .await?;
1385 Ok(())
1386 }
1387
1388 pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
1390 let protection_status = context
1391 .sql
1392 .query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
1393 .await?
1394 .unwrap_or_default();
1395 Ok(protection_status)
1396 }
1397
1398 pub(crate) async fn calc_sort_timestamp(
1407 self,
1408 context: &Context,
1409 message_timestamp: i64,
1410 always_sort_to_bottom: bool,
1411 received: bool,
1412 incoming: bool,
1413 ) -> Result<i64> {
1414 let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1415
1416 let last_msg_time: Option<i64> = if always_sort_to_bottom {
1417 context
1423 .sql
1424 .query_get_value(
1425 "SELECT MAX(timestamp)
1426 FROM msgs
1427 WHERE chat_id=? AND state!=?
1428 HAVING COUNT(*) > 0",
1429 (self, MessageState::OutDraft),
1430 )
1431 .await?
1432 } else if received {
1433 context
1444 .sql
1445 .query_row_optional(
1446 "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1447 FROM msgs
1448 WHERE chat_id=? AND hidden=0 AND state>?
1449 HAVING COUNT(*) > 0",
1450 (MessageState::InSeen, self, MessageState::InFresh),
1451 |row| {
1452 let ts: i64 = row.get(0)?;
1453 let ts_sent_seen: i64 = row.get(1)?;
1454 Ok((ts, ts_sent_seen))
1455 },
1456 )
1457 .await?
1458 .and_then(|(ts, ts_sent_seen)| {
1459 match incoming || ts_sent_seen <= message_timestamp {
1460 true => Some(ts),
1461 false => None,
1462 }
1463 })
1464 } else {
1465 None
1466 };
1467
1468 if let Some(last_msg_time) = last_msg_time {
1469 if last_msg_time > sort_timestamp {
1470 sort_timestamp = last_msg_time;
1471 }
1472 }
1473
1474 Ok(sort_timestamp)
1475 }
1476
1477 pub(crate) fn spawn_securejoin_wait(self, context: &Context, timeout: u64) {
1480 let context = context.clone();
1481 task::spawn(async move {
1482 tokio::time::sleep(Duration::from_secs(timeout)).await;
1483 let chat = Chat::load_from_db(&context, self).await?;
1484 chat.check_securejoin_wait(&context, 0).await?;
1485 Result::<()>::Ok(())
1486 });
1487 }
1488}
1489
1490impl std::fmt::Display for ChatId {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 if self.is_trash() {
1493 write!(f, "Chat#Trash")
1494 } else if self.is_archived_link() {
1495 write!(f, "Chat#ArchivedLink")
1496 } else if self.is_alldone_hint() {
1497 write!(f, "Chat#AlldoneHint")
1498 } else if self.is_special() {
1499 write!(f, "Chat#Special{}", self.0)
1500 } else {
1501 write!(f, "Chat#{}", self.0)
1502 }
1503 }
1504}
1505
1506impl rusqlite::types::ToSql for ChatId {
1511 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
1512 let val = rusqlite::types::Value::Integer(i64::from(self.0));
1513 let out = rusqlite::types::ToSqlOutput::Owned(val);
1514 Ok(out)
1515 }
1516}
1517
1518impl rusqlite::types::FromSql for ChatId {
1520 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1521 i64::column_result(value).and_then(|val| {
1522 if 0 <= val && val <= i64::from(u32::MAX) {
1523 Ok(ChatId::new(val as u32))
1524 } else {
1525 Err(rusqlite::types::FromSqlError::OutOfRange(val))
1526 }
1527 })
1528 }
1529}
1530
1531#[derive(Debug, Clone, Deserialize, Serialize)]
1536pub struct Chat {
1537 pub id: ChatId,
1539
1540 pub typ: Chattype,
1542
1543 pub name: String,
1545
1546 pub visibility: ChatVisibility,
1548
1549 pub grpid: String,
1552
1553 pub blocked: Blocked,
1555
1556 pub param: Params,
1558
1559 is_sending_locations: bool,
1561
1562 pub mute_duration: MuteDuration,
1564
1565 pub(crate) protected: ProtectionStatus,
1567}
1568
1569impl Chat {
1570 pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1572 let mut chat = context
1573 .sql
1574 .query_row(
1575 "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1576 c.blocked, c.locations_send_until, c.muted_until, c.protected
1577 FROM chats c
1578 WHERE c.id=?;",
1579 (chat_id,),
1580 |row| {
1581 let c = Chat {
1582 id: chat_id,
1583 typ: row.get(0)?,
1584 name: row.get::<_, String>(1)?,
1585 grpid: row.get::<_, String>(2)?,
1586 param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1587 visibility: row.get(4)?,
1588 blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1589 is_sending_locations: row.get(6)?,
1590 mute_duration: row.get(7)?,
1591 protected: row.get(8)?,
1592 };
1593 Ok(c)
1594 },
1595 )
1596 .await
1597 .context(format!("Failed loading chat {chat_id} from database"))?;
1598
1599 if chat.id.is_archived_link() {
1600 chat.name = stock_str::archived_chats(context).await;
1601 } else {
1602 if chat.typ == Chattype::Single && chat.name.is_empty() {
1603 let mut chat_name = "Err [Name not found]".to_owned();
1606 match get_chat_contacts(context, chat.id).await {
1607 Ok(contacts) => {
1608 if let Some(contact_id) = contacts.first() {
1609 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1610 contact.get_display_name().clone_into(&mut chat_name);
1611 }
1612 }
1613 }
1614 Err(err) => {
1615 error!(
1616 context,
1617 "Failed to load contacts for {}: {:#}.", chat.id, err
1618 );
1619 }
1620 }
1621 chat.name = chat_name;
1622 }
1623 if chat.param.exists(Param::Selftalk) {
1624 chat.name = stock_str::saved_messages(context).await;
1625 } else if chat.param.exists(Param::Devicetalk) {
1626 chat.name = stock_str::device_messages(context).await;
1627 }
1628 }
1629
1630 Ok(chat)
1631 }
1632
1633 pub fn is_self_talk(&self) -> bool {
1635 self.param.exists(Param::Selftalk)
1636 }
1637
1638 pub fn is_device_talk(&self) -> bool {
1640 self.param.exists(Param::Devicetalk)
1641 }
1642
1643 pub fn is_mailing_list(&self) -> bool {
1645 self.typ == Chattype::Mailinglist
1646 }
1647
1648 pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1652 self.why_cant_send_ex(context, &|_| false).await
1653 }
1654
1655 pub(crate) async fn why_cant_send_ex(
1656 &self,
1657 context: &Context,
1658 skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1659 ) -> Result<Option<CantSendReason>> {
1660 use CantSendReason::*;
1661 if self.id.is_special() {
1664 let reason = SpecialChat;
1665 if !skip_fn(&reason) {
1666 return Ok(Some(reason));
1667 }
1668 }
1669 if self.is_device_talk() {
1670 let reason = DeviceChat;
1671 if !skip_fn(&reason) {
1672 return Ok(Some(reason));
1673 }
1674 }
1675 if self.is_contact_request() {
1676 let reason = ContactRequest;
1677 if !skip_fn(&reason) {
1678 return Ok(Some(reason));
1679 }
1680 }
1681 if self.is_protection_broken() {
1682 let reason = ProtectionBroken;
1683 if !skip_fn(&reason) {
1684 return Ok(Some(reason));
1685 }
1686 }
1687 if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1688 let reason = ReadOnlyMailingList;
1689 if !skip_fn(&reason) {
1690 return Ok(Some(reason));
1691 }
1692 }
1693
1694 let reason = NotAMember;
1696 if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1697 return Ok(Some(reason));
1698 }
1699 let reason = SecurejoinWait;
1700 if !skip_fn(&reason)
1701 && self
1702 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
1703 .await?
1704 > 0
1705 {
1706 return Ok(Some(reason));
1707 }
1708 Ok(None)
1709 }
1710
1711 pub async fn can_send(&self, context: &Context) -> Result<bool> {
1715 Ok(self.why_cant_send(context).await?.is_none())
1716 }
1717
1718 pub(crate) async fn check_securejoin_wait(
1723 &self,
1724 context: &Context,
1725 timeout: u64,
1726 ) -> Result<u64> {
1727 if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
1728 return Ok(0);
1729 }
1730
1731 let (mut param_wait, mut param_timeout) = (Params::new(), Params::new());
1734 param_wait.set_cmd(SystemMessage::SecurejoinWait);
1735 param_timeout.set_cmd(SystemMessage::SecurejoinWaitTimeout);
1736 let (param_wait, param_timeout) = (param_wait.to_string(), param_timeout.to_string());
1737 let Some((param, ts_sort, ts_start)) = context
1738 .sql
1739 .query_row_optional(
1740 "SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
1741 (SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
1742 (self.id, ¶m_wait, ¶m_timeout),
1743 |row| {
1744 let param: String = row.get(0)?;
1745 let ts_sort: i64 = row.get(1)?;
1746 let ts_start: i64 = row.get(2)?;
1747 Ok((param, ts_sort, ts_start))
1748 },
1749 )
1750 .await?
1751 else {
1752 return Ok(0);
1753 };
1754 if param == param_timeout {
1755 return Ok(0);
1756 }
1757
1758 let now = time();
1759 if ts_start <= now {
1761 let timeout = ts_start
1762 .saturating_add(timeout.try_into()?)
1763 .saturating_sub(now);
1764 if timeout > 0 {
1765 return Ok(timeout as u64);
1766 }
1767 }
1768 add_info_msg_with_cmd(
1769 context,
1770 self.id,
1771 &stock_str::securejoin_takes_longer(context).await,
1772 SystemMessage::SecurejoinWaitTimeout,
1773 ts_sort,
1776 Some(now),
1777 None,
1778 None,
1779 None,
1780 )
1781 .await?;
1782 context.emit_event(EventType::ChatModified(self.id));
1783 Ok(0)
1784 }
1785
1786 pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1790 match self.typ {
1791 Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
1792 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1793 }
1794 }
1795
1796 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1797 context
1798 .sql
1799 .execute(
1800 "UPDATE chats SET param=? WHERE id=?",
1801 (self.param.to_string(), self.id),
1802 )
1803 .await?;
1804 Ok(())
1805 }
1806
1807 pub fn get_id(&self) -> ChatId {
1809 self.id
1810 }
1811
1812 pub fn get_type(&self) -> Chattype {
1814 self.typ
1815 }
1816
1817 pub fn get_name(&self) -> &str {
1819 &self.name
1820 }
1821
1822 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1824 self.param.get(Param::ListPost)
1825 }
1826
1827 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1829 if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1830 if !image_rel.is_empty() {
1831 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1832 }
1833 } else if self.id.is_archived_link() {
1834 if let Ok(image_rel) = get_archive_icon(context).await {
1835 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1836 }
1837 } else if self.typ == Chattype::Single {
1838 let contacts = get_chat_contacts(context, self.id).await?;
1839 if let Some(contact_id) = contacts.first() {
1840 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1841 return contact.get_profile_image(context).await;
1842 }
1843 }
1844 } else if self.typ == Chattype::Broadcast {
1845 if let Ok(image_rel) = get_broadcast_icon(context).await {
1846 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1847 }
1848 }
1849 Ok(None)
1850 }
1851
1852 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1857 let mut color = 0;
1858
1859 if self.typ == Chattype::Single {
1860 let contacts = get_chat_contacts(context, self.id).await?;
1861 if let Some(contact_id) = contacts.first() {
1862 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1863 color = contact.get_color();
1864 }
1865 }
1866 } else {
1867 color = str_to_color(&self.name);
1868 }
1869
1870 Ok(color)
1871 }
1872
1873 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1878 let draft = match self.id.get_draft(context).await? {
1879 Some(message) => message.text,
1880 _ => String::new(),
1881 };
1882 Ok(ChatInfo {
1883 id: self.id,
1884 type_: self.typ as u32,
1885 name: self.name.clone(),
1886 archived: self.visibility == ChatVisibility::Archived,
1887 param: self.param.to_string(),
1888 is_sending_locations: self.is_sending_locations,
1889 color: self.get_color(context).await?,
1890 profile_image: self
1891 .get_profile_image(context)
1892 .await?
1893 .unwrap_or_else(std::path::PathBuf::new),
1894 draft,
1895 is_muted: self.is_muted(),
1896 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1897 })
1898 }
1899
1900 pub fn get_visibility(&self) -> ChatVisibility {
1902 self.visibility
1903 }
1904
1905 pub fn is_contact_request(&self) -> bool {
1910 self.blocked == Blocked::Request
1911 }
1912
1913 pub fn is_unpromoted(&self) -> bool {
1915 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1916 }
1917
1918 pub fn is_promoted(&self) -> bool {
1921 !self.is_unpromoted()
1922 }
1923
1924 pub fn is_protected(&self) -> bool {
1935 self.protected == ProtectionStatus::Protected
1936 }
1937
1938 pub fn is_protection_broken(&self) -> bool {
1952 match self.protected {
1953 ProtectionStatus::Protected => false,
1954 ProtectionStatus::Unprotected => false,
1955 ProtectionStatus::ProtectionBroken => true,
1956 }
1957 }
1958
1959 pub fn is_sending_locations(&self) -> bool {
1961 self.is_sending_locations
1962 }
1963
1964 pub fn is_muted(&self) -> bool {
1966 match self.mute_duration {
1967 MuteDuration::NotMuted => false,
1968 MuteDuration::Forever => true,
1969 MuteDuration::Until(when) => when > SystemTime::now(),
1970 }
1971 }
1972
1973 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1975 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1976 Ok(member_list_timestamp)
1977 } else {
1978 Ok(self.id.created_timestamp(context).await?)
1979 }
1980 }
1981
1982 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1988 let now = time();
1989 let member_list_ts = self.member_list_timestamp(context).await?;
1990 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1991 >= member_list_ts.saturating_add(60 * 24 * 3600);
1992 Ok(is_stale)
1993 }
1994
1995 async fn prepare_msg_raw(
2001 &mut self,
2002 context: &Context,
2003 msg: &mut Message,
2004 update_msg_id: Option<MsgId>,
2005 timestamp: i64,
2006 ) -> Result<MsgId> {
2007 let mut to_id = 0;
2008 let mut location_id = 0;
2009
2010 if msg.rfc724_mid.is_empty() {
2011 msg.rfc724_mid = create_outgoing_rfc724_mid();
2012 }
2013
2014 if self.typ == Chattype::Single {
2015 if let Some(id) = context
2016 .sql
2017 .query_get_value(
2018 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
2019 (self.id,),
2020 )
2021 .await?
2022 {
2023 to_id = id;
2024 } else {
2025 error!(
2026 context,
2027 "Cannot send message, contact for {} not found.", self.id,
2028 );
2029 bail!("Cannot set message, contact for {} not found.", self.id);
2030 }
2031 } else if self.typ == Chattype::Group
2032 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
2033 {
2034 msg.param.set_int(Param::AttachGroupImage, 1);
2035 self.param
2036 .remove(Param::Unpromoted)
2037 .set_i64(Param::GroupNameTimestamp, timestamp);
2038 self.update_param(context).await?;
2039 context
2045 .sync_qr_code_tokens(Some(self.grpid.as_str()))
2046 .await
2047 .log_err(context)
2048 .ok();
2049 }
2050
2051 let is_bot = context.get_config_bool(Config::Bot).await?;
2052 msg.param
2053 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2054
2055 let new_references;
2059 if self.is_self_talk() {
2060 new_references = String::new();
2063 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2064 self
2070 .id
2071 .get_parent_mime_headers(context, MessageState::OutPending)
2072 .await?
2073 {
2074 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2078 msg.in_reply_to = Some(parent_rfc724_mid.clone());
2079 }
2080
2081 let parent_references = if parent_references.is_empty() {
2091 parent_in_reply_to
2092 } else {
2093 parent_references
2094 };
2095
2096 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2099 references_vec.reverse();
2100
2101 if !parent_rfc724_mid.is_empty()
2102 && !references_vec.contains(&parent_rfc724_mid.as_str())
2103 {
2104 references_vec.push(&parent_rfc724_mid)
2105 }
2106
2107 if references_vec.is_empty() {
2108 new_references = msg.rfc724_mid.clone();
2111 } else {
2112 new_references = references_vec.join(" ");
2113 }
2114 } else {
2115 new_references = msg.rfc724_mid.clone();
2121 }
2122
2123 if msg.param.exists(Param::SetLatitude) {
2125 if let Ok(row_id) = context
2126 .sql
2127 .insert(
2128 "INSERT INTO locations \
2129 (timestamp,from_id,chat_id, latitude,longitude,independent)\
2130 VALUES (?,?,?, ?,?,1);",
2131 (
2132 timestamp,
2133 ContactId::SELF,
2134 self.id,
2135 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2136 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2137 ),
2138 )
2139 .await
2140 {
2141 location_id = row_id;
2142 }
2143 }
2144
2145 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2146 EphemeralTimer::Disabled
2147 } else {
2148 self.id.get_ephemeral_timer(context).await?
2149 };
2150 let ephemeral_timestamp = match ephemeral_timer {
2151 EphemeralTimer::Disabled => 0,
2152 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2153 };
2154
2155 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2156 let new_mime_headers = if msg.has_html() {
2157 if msg.param.exists(Param::Forwarded) {
2158 msg.get_id().get_html(context).await?
2159 } else {
2160 msg.param.get(Param::SendHtml).map(|s| s.to_string())
2161 }
2162 } else {
2163 None
2164 };
2165 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2166 let html_part = MimePart::new("text/html", s);
2167 let mut buffer = Vec::new();
2168 let cursor = Cursor::new(&mut buffer);
2169 html_part.write_part(cursor).ok();
2170 String::from_utf8_lossy(&buffer).to_string()
2171 });
2172 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2173 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2177 false => None,
2178 });
2179 let new_mime_headers = match new_mime_headers {
2180 Some(h) => Some(tokio::task::block_in_place(move || {
2181 buf_compress(h.as_bytes())
2182 })?),
2183 None => None,
2184 };
2185
2186 msg.chat_id = self.id;
2187 msg.from_id = ContactId::SELF;
2188 msg.timestamp_sort = timestamp;
2189
2190 if let Some(update_msg_id) = update_msg_id {
2192 context
2193 .sql
2194 .execute(
2195 "UPDATE msgs
2196 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2197 state=?, txt=?, txt_normalized=?, subject=?, param=?,
2198 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2199 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2200 ephemeral_timestamp=?
2201 WHERE id=?;",
2202 params_slice![
2203 msg.rfc724_mid,
2204 msg.chat_id,
2205 msg.from_id,
2206 to_id,
2207 msg.timestamp_sort,
2208 msg.viewtype,
2209 msg.state,
2210 msg_text,
2211 message::normalize_text(&msg_text),
2212 &msg.subject,
2213 msg.param.to_string(),
2214 msg.hidden,
2215 msg.in_reply_to.as_deref().unwrap_or_default(),
2216 new_references,
2217 new_mime_headers.is_some(),
2218 new_mime_headers.unwrap_or_default(),
2219 location_id as i32,
2220 ephemeral_timer,
2221 ephemeral_timestamp,
2222 update_msg_id
2223 ],
2224 )
2225 .await?;
2226 msg.id = update_msg_id;
2227 } else {
2228 let raw_id = context
2229 .sql
2230 .insert(
2231 "INSERT INTO msgs (
2232 rfc724_mid,
2233 chat_id,
2234 from_id,
2235 to_id,
2236 timestamp,
2237 type,
2238 state,
2239 txt,
2240 txt_normalized,
2241 subject,
2242 param,
2243 hidden,
2244 mime_in_reply_to,
2245 mime_references,
2246 mime_modified,
2247 mime_headers,
2248 mime_compressed,
2249 location_id,
2250 ephemeral_timer,
2251 ephemeral_timestamp)
2252 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2253 params_slice![
2254 msg.rfc724_mid,
2255 msg.chat_id,
2256 msg.from_id,
2257 to_id,
2258 msg.timestamp_sort,
2259 msg.viewtype,
2260 msg.state,
2261 msg_text,
2262 message::normalize_text(&msg_text),
2263 &msg.subject,
2264 msg.param.to_string(),
2265 msg.hidden,
2266 msg.in_reply_to.as_deref().unwrap_or_default(),
2267 new_references,
2268 new_mime_headers.is_some(),
2269 new_mime_headers.unwrap_or_default(),
2270 location_id as i32,
2271 ephemeral_timer,
2272 ephemeral_timestamp
2273 ],
2274 )
2275 .await?;
2276 context.new_msgs_notify.notify_one();
2277 msg.id = MsgId::new(u32::try_from(raw_id)?);
2278
2279 maybe_set_logging_xdc(context, msg, self.id).await?;
2280 context
2281 .update_webxdc_integration_database(msg, context)
2282 .await?;
2283 }
2284 context.scheduler.interrupt_ephemeral_task().await;
2285 Ok(msg.id)
2286 }
2287
2288 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2290 let addrs = context
2291 .sql
2292 .query_map(
2293 "SELECT c.addr \
2294 FROM contacts c INNER JOIN chats_contacts cc \
2295 ON c.id=cc.contact_id \
2296 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2297 (self.id,),
2298 |row| row.get::<_, String>(0),
2299 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2300 )
2301 .await?;
2302 self.sync(context, SyncAction::SetContacts(addrs)).await
2303 }
2304
2305 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2307 match self.typ {
2308 Chattype::Single => {
2309 if self.is_device_talk() {
2310 return Ok(Some(SyncId::Device));
2311 }
2312
2313 let mut r = None;
2314 for contact_id in get_chat_contacts(context, self.id).await? {
2315 if contact_id == ContactId::SELF && !self.is_self_talk() {
2316 continue;
2317 }
2318 if r.is_some() {
2319 return Ok(None);
2320 }
2321 let contact = Contact::get_by_id(context, contact_id).await?;
2322 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2323 }
2324 Ok(r)
2325 }
2326 Chattype::Broadcast | Chattype::Group | Chattype::Mailinglist => {
2327 if !self.grpid.is_empty() {
2328 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2329 }
2330
2331 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2332 .id
2333 .get_parent_mime_headers(context, MessageState::OutDelivered)
2334 .await?
2335 else {
2336 warn!(
2337 context,
2338 "Chat::get_sync_id({}): No good message identifying the chat found.",
2339 self.id
2340 );
2341 return Ok(None);
2342 };
2343 Ok(Some(SyncId::Msgids(vec![
2344 parent_in_reply_to,
2345 parent_rfc724_mid,
2346 ])))
2347 }
2348 }
2349 }
2350
2351 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2353 if let Some(id) = self.get_sync_id(context).await? {
2354 sync(context, id, action).await?;
2355 }
2356 Ok(())
2357 }
2358}
2359
2360pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2361 context
2362 .add_sync_item(SyncData::AlterChat { id, action })
2363 .await?;
2364 context.scheduler.interrupt_inbox().await;
2365 Ok(())
2366}
2367
2368#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2370#[repr(i8)]
2371pub enum ChatVisibility {
2372 Normal = 0,
2374
2375 Archived = 1,
2377
2378 Pinned = 2,
2380}
2381
2382impl rusqlite::types::ToSql for ChatVisibility {
2383 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
2384 let val = rusqlite::types::Value::Integer(*self as i64);
2385 let out = rusqlite::types::ToSqlOutput::Owned(val);
2386 Ok(out)
2387 }
2388}
2389
2390impl rusqlite::types::FromSql for ChatVisibility {
2391 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2392 i64::column_result(value).map(|val| {
2393 match val {
2394 2 => ChatVisibility::Pinned,
2395 1 => ChatVisibility::Archived,
2396 0 => ChatVisibility::Normal,
2397 _ => ChatVisibility::Normal,
2399 }
2400 })
2401 }
2402}
2403
2404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2406#[non_exhaustive]
2407pub struct ChatInfo {
2408 pub id: ChatId,
2410
2411 #[serde(rename = "type")]
2418 pub type_: u32,
2419
2420 pub name: String,
2422
2423 pub archived: bool,
2425
2426 pub param: String,
2430
2431 pub is_sending_locations: bool,
2433
2434 pub color: u32,
2438
2439 pub profile_image: std::path::PathBuf,
2444
2445 pub draft: String,
2453
2454 pub is_muted: bool,
2458
2459 pub ephemeral_timer: EphemeralTimer,
2461 }
2467
2468pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<()> {
2469 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2470 ChatIdBlocked::lookup_by_contact(context, ContactId::SELF).await?
2471 {
2472 let icon = include_bytes!("../assets/icon-saved-messages.png");
2473 let blob =
2474 BlobObject::create_and_deduplicate_from_bytes(context, icon, "saved-messages.png")?;
2475 let icon = blob.as_name().to_string();
2476
2477 let mut chat = Chat::load_from_db(context, chat_id).await?;
2478 chat.param.set(Param::ProfileImage, icon);
2479 chat.update_param(context).await?;
2480 }
2481 Ok(())
2482}
2483
2484pub(crate) async fn update_device_icon(context: &Context) -> Result<()> {
2485 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2486 ChatIdBlocked::lookup_by_contact(context, ContactId::DEVICE).await?
2487 {
2488 let icon = include_bytes!("../assets/icon-device.png");
2489 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "device.png")?;
2490 let icon = blob.as_name().to_string();
2491
2492 let mut chat = Chat::load_from_db(context, chat_id).await?;
2493 chat.param.set(Param::ProfileImage, &icon);
2494 chat.update_param(context).await?;
2495
2496 let mut contact = Contact::get_by_id(context, ContactId::DEVICE).await?;
2497 contact.param.set(Param::ProfileImage, icon);
2498 contact.update_param(context).await?;
2499 }
2500 Ok(())
2501}
2502
2503pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
2504 if let Some(icon) = context.sql.get_raw_config("icon-broadcast").await? {
2505 return Ok(icon);
2506 }
2507
2508 let icon = include_bytes!("../assets/icon-broadcast.png");
2509 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "broadcast.png")?;
2510 let icon = blob.as_name().to_string();
2511 context
2512 .sql
2513 .set_raw_config("icon-broadcast", Some(&icon))
2514 .await?;
2515 Ok(icon)
2516}
2517
2518pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
2519 if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
2520 return Ok(icon);
2521 }
2522
2523 let icon = include_bytes!("../assets/icon-archive.png");
2524 let blob = BlobObject::create_and_deduplicate_from_bytes(context, icon, "archive.png")?;
2525 let icon = blob.as_name().to_string();
2526 context
2527 .sql
2528 .set_raw_config("icon-archive", Some(&icon))
2529 .await?;
2530 Ok(icon)
2531}
2532
2533async fn update_special_chat_name(
2534 context: &Context,
2535 contact_id: ContactId,
2536 name: String,
2537) -> Result<()> {
2538 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2539 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2540 {
2541 context
2543 .sql
2544 .execute(
2545 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2546 (&name, chat_id, &name),
2547 )
2548 .await?;
2549 }
2550 Ok(())
2551}
2552
2553pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2554 update_special_chat_name(
2555 context,
2556 ContactId::DEVICE,
2557 stock_str::device_messages(context).await,
2558 )
2559 .await?;
2560 update_special_chat_name(
2561 context,
2562 ContactId::SELF,
2563 stock_str::saved_messages(context).await,
2564 )
2565 .await?;
2566 Ok(())
2567}
2568
2569pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
2572 let chat_ids: Vec<ChatId> = context
2573 .sql
2574 .query_map(
2575 "SELECT chat_id FROM bobstate",
2576 (),
2577 |row| {
2578 let chat_id: ChatId = row.get(0)?;
2579 Ok(chat_id)
2580 },
2581 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2582 )
2583 .await?;
2584
2585 for chat_id in chat_ids {
2586 let chat = Chat::load_from_db(context, chat_id).await?;
2587 let timeout = chat
2588 .check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
2589 .await?;
2590 if timeout > 0 {
2591 chat_id.spawn_securejoin_wait(context, timeout);
2592 }
2593 }
2594 Ok(())
2595}
2596
2597#[derive(Debug)]
2605pub(crate) struct ChatIdBlocked {
2606 pub id: ChatId,
2608
2609 pub blocked: Blocked,
2611}
2612
2613impl ChatIdBlocked {
2614 pub async fn lookup_by_contact(
2618 context: &Context,
2619 contact_id: ContactId,
2620 ) -> Result<Option<Self>> {
2621 ensure!(context.sql.is_open().await, "Database not available");
2622 ensure!(
2623 contact_id != ContactId::UNDEFINED,
2624 "Invalid contact id requested"
2625 );
2626
2627 context
2628 .sql
2629 .query_row_optional(
2630 "SELECT c.id, c.blocked
2631 FROM chats c
2632 INNER JOIN chats_contacts j
2633 ON c.id=j.chat_id
2634 WHERE c.type=100 -- 100 = Chattype::Single
2635 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2636 AND j.contact_id=?;",
2637 (contact_id,),
2638 |row| {
2639 let id: ChatId = row.get(0)?;
2640 let blocked: Blocked = row.get(1)?;
2641 Ok(ChatIdBlocked { id, blocked })
2642 },
2643 )
2644 .await
2645 }
2646
2647 pub async fn get_for_contact(
2652 context: &Context,
2653 contact_id: ContactId,
2654 create_blocked: Blocked,
2655 ) -> Result<Self> {
2656 ensure!(context.sql.is_open().await, "Database not available");
2657 ensure!(
2658 contact_id != ContactId::UNDEFINED,
2659 "Invalid contact id requested"
2660 );
2661
2662 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2663 return Ok(res);
2665 }
2666
2667 let contact = Contact::get_by_id(context, contact_id).await?;
2668 let chat_name = contact.get_display_name().to_string();
2669 let mut params = Params::new();
2670 match contact_id {
2671 ContactId::SELF => {
2672 params.set_int(Param::Selftalk, 1);
2673 }
2674 ContactId::DEVICE => {
2675 params.set_int(Param::Devicetalk, 1);
2676 }
2677 _ => (),
2678 }
2679
2680 let protected = contact_id == ContactId::SELF || {
2681 let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
2682 peerstate.is_some_and(|p| {
2683 p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
2684 })
2685 };
2686 let smeared_time = create_smeared_timestamp(context);
2687
2688 let chat_id = context
2689 .sql
2690 .transaction(move |transaction| {
2691 transaction.execute(
2692 "INSERT INTO chats
2693 (type, name, param, blocked, created_timestamp, protected)
2694 VALUES(?, ?, ?, ?, ?, ?)",
2695 (
2696 Chattype::Single,
2697 chat_name,
2698 params.to_string(),
2699 create_blocked as u8,
2700 smeared_time,
2701 if protected {
2702 ProtectionStatus::Protected
2703 } else {
2704 ProtectionStatus::Unprotected
2705 },
2706 ),
2707 )?;
2708 let chat_id = ChatId::new(
2709 transaction
2710 .last_insert_rowid()
2711 .try_into()
2712 .context("chat table rowid overflows u32")?,
2713 );
2714
2715 transaction.execute(
2716 "INSERT INTO chats_contacts
2717 (chat_id, contact_id)
2718 VALUES((SELECT last_insert_rowid()), ?)",
2719 (contact_id,),
2720 )?;
2721
2722 Ok(chat_id)
2723 })
2724 .await?;
2725
2726 if protected {
2727 chat_id
2728 .add_protection_msg(
2729 context,
2730 ProtectionStatus::Protected,
2731 Some(contact_id),
2732 smeared_time,
2733 )
2734 .await?;
2735 }
2736
2737 match contact_id {
2738 ContactId::SELF => update_saved_messages_icon(context).await?,
2739 ContactId::DEVICE => update_device_icon(context).await?,
2740 _ => (),
2741 }
2742
2743 Ok(Self {
2744 id: chat_id,
2745 blocked: create_blocked,
2746 })
2747 }
2748}
2749
2750async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2751 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
2752 } else if msg.viewtype.has_file() {
2754 let mut blob = msg
2755 .param
2756 .get_file_blob(context)?
2757 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2758 let send_as_is = msg.viewtype == Viewtype::File;
2759
2760 if msg.viewtype == Viewtype::File
2761 || msg.viewtype == Viewtype::Image
2762 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2763 {
2764 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2771 if msg.viewtype == Viewtype::Sticker {
2772 if better_type != Viewtype::Image {
2773 msg.param.set_int(Param::ForceSticker, 1);
2775 }
2776 } else if better_type != Viewtype::Webxdc
2777 || context
2778 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2779 .await
2780 .is_ok()
2781 {
2782 msg.viewtype = better_type;
2783 }
2784 }
2785 } else if msg.viewtype == Viewtype::Webxdc {
2786 context
2787 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2788 .await?;
2789 }
2790
2791 if msg.viewtype == Viewtype::Vcard {
2792 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2793 }
2794
2795 let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
2796 if !send_as_is
2797 && (msg.viewtype == Viewtype::Image
2798 || maybe_sticker && !msg.param.exists(Param::ForceSticker))
2799 {
2800 let new_name = blob
2801 .recode_to_image_size(context, msg.get_filename(), &mut maybe_sticker)
2802 .await?;
2803 msg.param.set(Param::Filename, new_name);
2804 msg.param.set(Param::File, blob.as_name());
2805
2806 if !maybe_sticker {
2807 msg.viewtype = Viewtype::Image;
2808 }
2809 }
2810
2811 if !msg.param.exists(Param::MimeType) {
2812 if let Some((_, mime)) = message::guess_msgtype_from_suffix(msg) {
2813 msg.param.set(Param::MimeType, mime);
2814 }
2815 }
2816
2817 msg.try_calc_and_set_dimensions(context).await?;
2818
2819 info!(
2820 context,
2821 "Attaching \"{}\" for message type #{}.",
2822 blob.to_abs_path().display(),
2823 msg.viewtype
2824 );
2825 } else {
2826 bail!("Cannot send messages of type #{}.", msg.viewtype);
2827 }
2828 Ok(())
2829}
2830
2831pub async fn is_contact_in_chat(
2833 context: &Context,
2834 chat_id: ChatId,
2835 contact_id: ContactId,
2836) -> Result<bool> {
2837 let exists = context
2843 .sql
2844 .exists(
2845 "SELECT COUNT(*) FROM chats_contacts
2846 WHERE chat_id=? AND contact_id=?
2847 AND add_timestamp >= remove_timestamp",
2848 (chat_id, contact_id),
2849 )
2850 .await?;
2851 Ok(exists)
2852}
2853
2854pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2861 ensure!(
2862 !chat_id.is_special(),
2863 "chat_id cannot be a special chat: {chat_id}"
2864 );
2865
2866 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2867 msg.param.remove(Param::GuaranteeE2ee);
2868 msg.param.remove(Param::ForcePlaintext);
2869 msg.update_param(context).await?;
2870 }
2871
2872 if msg.is_system_message() {
2874 msg.text = sanitize_bidi_characters(&msg.text);
2875 }
2876
2877 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2878 if !msg.hidden {
2879 context.emit_msgs_changed(msg.chat_id, msg.id);
2880 }
2881
2882 if msg.param.exists(Param::SetLatitude) {
2883 context.emit_location_changed(Some(ContactId::SELF)).await?;
2884 }
2885
2886 context.scheduler.interrupt_smtp().await;
2887 }
2888
2889 Ok(msg.id)
2890}
2891
2892pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2897 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2898 if rowids.is_empty() {
2899 return Ok(msg.id);
2900 }
2901 let mut smtp = crate::smtp::Smtp::new();
2902 for rowid in rowids {
2903 send_msg_to_smtp(context, &mut smtp, rowid)
2904 .await
2905 .context("failed to send message, queued for later sending")?;
2906 }
2907 context.emit_msgs_changed(msg.chat_id, msg.id);
2908 Ok(msg.id)
2909}
2910
2911async fn prepare_send_msg(
2915 context: &Context,
2916 chat_id: ChatId,
2917 msg: &mut Message,
2918) -> Result<Vec<i64>> {
2919 let mut chat = Chat::load_from_db(context, chat_id).await?;
2920
2921 let skip_fn = |reason: &CantSendReason| match reason {
2922 CantSendReason::ProtectionBroken
2923 | CantSendReason::ContactRequest
2924 | CantSendReason::SecurejoinWait => {
2925 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2928 }
2929 CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2933 _ => false,
2934 };
2935 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2936 bail!("Cannot send to {chat_id}: {reason}");
2937 }
2938
2939 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2944 if let Some(quoted_message) = msg.quoted_message(context).await? {
2945 if quoted_message.chat_id != chat_id {
2946 bail!("Bad quote reply");
2947 }
2948 }
2949 }
2950
2951 let update_msg_id = if msg.state == MessageState::OutDraft {
2953 msg.hidden = false;
2954 if !msg.id.is_special() && msg.chat_id == chat_id {
2955 Some(msg.id)
2956 } else {
2957 None
2958 }
2959 } else {
2960 None
2961 };
2962
2963 msg.state = MessageState::OutPending;
2965
2966 prepare_msg_blob(context, msg).await?;
2967 if !msg.hidden {
2968 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2969 }
2970 msg.id = chat
2971 .prepare_msg_raw(
2972 context,
2973 msg,
2974 update_msg_id,
2975 create_smeared_timestamp(context),
2976 )
2977 .await?;
2978 msg.chat_id = chat_id;
2979
2980 let row_ids = create_send_msg_jobs(context, msg)
2981 .await
2982 .context("Failed to create send jobs")?;
2983 Ok(row_ids)
2984}
2985
2986pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2992 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2993 msg.chat_id
2994 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2995 .await?;
2996 }
2997
2998 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2999 let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
3000 let attach_selfavatar = mimefactory.attach_selfavatar;
3001 let mut recipients = mimefactory.recipients();
3002
3003 let from = context.get_primary_self_addr().await?;
3004 let lowercase_from = from.to_lowercase();
3005
3006 recipients.retain(|x| x.to_lowercase() != lowercase_from);
3019 if (context.get_config_bool(Config::BccSelf).await?
3020 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
3021 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
3022 {
3023 recipients.push(from);
3024 }
3025
3026 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
3028 recipients.clear();
3029 }
3030
3031 if recipients.is_empty() {
3032 info!(
3034 context,
3035 "Message {} has no recipient, skipping smtp-send.", msg.id
3036 );
3037 msg.param.set_int(Param::GuaranteeE2ee, 1);
3038 msg.update_param(context).await?;
3039 msg.id.set_delivered(context).await?;
3040 msg.state = MessageState::OutDelivered;
3041 return Ok(Vec::new());
3042 }
3043
3044 let rendered_msg = match mimefactory.render(context).await {
3045 Ok(res) => Ok(res),
3046 Err(err) => {
3047 message::set_msg_failed(context, msg, &err.to_string()).await?;
3048 Err(err)
3049 }
3050 }?;
3051
3052 if needs_encryption && !rendered_msg.is_encrypted {
3053 message::set_msg_failed(
3055 context,
3056 msg,
3057 "End-to-end-encryption unavailable unexpectedly.",
3058 )
3059 .await?;
3060 bail!(
3061 "e2e encryption unavailable {} - {:?}",
3062 msg.id,
3063 needs_encryption
3064 );
3065 }
3066
3067 let now = smeared_time(context);
3068
3069 if rendered_msg.last_added_location_id.is_some() {
3070 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3071 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3072 }
3073 }
3074
3075 if attach_selfavatar {
3076 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3077 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3078 }
3079 }
3080
3081 if rendered_msg.is_encrypted && !needs_encryption {
3082 msg.param.set_int(Param::GuaranteeE2ee, 1);
3083 msg.update_param(context).await?;
3084 }
3085
3086 msg.subject.clone_from(&rendered_msg.subject);
3087 msg.update_subject(context).await?;
3088 let chunk_size = context.get_max_smtp_rcpt_to().await?;
3089 let trans_fn = |t: &mut rusqlite::Transaction| {
3090 let mut row_ids = Vec::<i64>::new();
3091 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3092 t.execute(
3093 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3094 (),
3095 )?;
3096 t.execute(
3097 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3098 (&rendered_msg.message, msg.id),
3099 )?;
3100 } else {
3101 for recipients_chunk in recipients.chunks(chunk_size) {
3102 let recipients_chunk = recipients_chunk.join(" ");
3103 let row_id = t.execute(
3104 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3105 VALUES (?1, ?2, ?3, ?4)",
3106 (
3107 &rendered_msg.rfc724_mid,
3108 recipients_chunk,
3109 &rendered_msg.message,
3110 msg.id,
3111 ),
3112 )?;
3113 row_ids.push(row_id.try_into()?);
3114 }
3115 }
3116 Ok(row_ids)
3117 };
3118 context.sql.transaction(trans_fn).await
3119}
3120
3121pub async fn send_text_msg(
3125 context: &Context,
3126 chat_id: ChatId,
3127 text_to_send: String,
3128) -> Result<MsgId> {
3129 ensure!(
3130 !chat_id.is_special(),
3131 "bad chat_id, can not be a special chat: {}",
3132 chat_id
3133 );
3134
3135 let mut msg = Message::new_text(text_to_send);
3136 send_msg(context, chat_id, &mut msg).await
3137}
3138
3139pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3141 let mut original_msg = Message::load_from_db(context, msg_id).await?;
3142 ensure!(
3143 original_msg.from_id == ContactId::SELF,
3144 "Can edit only own messages"
3145 );
3146 ensure!(!original_msg.is_info(), "Cannot edit info messages");
3147 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3148 ensure!(
3149 original_msg.viewtype != Viewtype::VideochatInvitation,
3150 "Cannot edit videochat invitations"
3151 );
3152 ensure!(
3153 !original_msg.text.is_empty(), "Cannot add text"
3155 );
3156 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3157 if original_msg.text == new_text {
3158 info!(context, "Text unchanged.");
3159 return Ok(());
3160 }
3161
3162 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3163
3164 let mut edit_msg = Message::new_text(EDITED_PREFIX.to_owned() + &new_text); edit_msg.set_quote(context, Some(&original_msg)).await?; if original_msg.get_showpadlock() {
3167 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3168 }
3169 edit_msg
3170 .param
3171 .set(Param::TextEditFor, original_msg.rfc724_mid);
3172 edit_msg.hidden = true;
3173 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3174 Ok(())
3175}
3176
3177pub(crate) async fn save_text_edit_to_db(
3178 context: &Context,
3179 original_msg: &mut Message,
3180 new_text: &str,
3181) -> Result<()> {
3182 original_msg.param.set_int(Param::IsEdited, 1);
3183 context
3184 .sql
3185 .execute(
3186 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3187 (
3188 new_text,
3189 message::normalize_text(new_text),
3190 original_msg.param.to_string(),
3191 original_msg.id,
3192 ),
3193 )
3194 .await?;
3195 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3196 Ok(())
3197}
3198
3199pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3201 ensure!(
3202 !chat_id.is_special(),
3203 "video chat invitation cannot be sent to special chat: {}",
3204 chat_id
3205 );
3206
3207 let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3208 if !instance.is_empty() {
3209 instance
3210 } else {
3211 bail!("webrtc_instance is empty");
3212 }
3213 } else {
3214 bail!("webrtc_instance not set");
3215 };
3216
3217 let instance = Message::create_webrtc_instance(&instance, &create_id());
3218
3219 let mut msg = Message::new(Viewtype::VideochatInvitation);
3220 msg.param.set(Param::WebrtcRoom, &instance);
3221 msg.text =
3222 stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3223 .await;
3224 send_msg(context, chat_id, &mut msg).await
3225}
3226
3227#[derive(Debug)]
3229pub struct MessageListOptions {
3230 pub info_only: bool,
3232
3233 pub add_daymarker: bool,
3235}
3236
3237pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3239 get_chat_msgs_ex(
3240 context,
3241 chat_id,
3242 MessageListOptions {
3243 info_only: false,
3244 add_daymarker: false,
3245 },
3246 )
3247 .await
3248}
3249
3250pub async fn get_chat_msgs_ex(
3252 context: &Context,
3253 chat_id: ChatId,
3254 options: MessageListOptions,
3255) -> Result<Vec<ChatItem>> {
3256 let MessageListOptions {
3257 info_only,
3258 add_daymarker,
3259 } = options;
3260 let process_row = if info_only {
3261 |row: &rusqlite::Row| {
3262 let params = row.get::<_, String>("param")?;
3264 let (from_id, to_id) = (
3265 row.get::<_, ContactId>("from_id")?,
3266 row.get::<_, ContactId>("to_id")?,
3267 );
3268 let is_info_msg: bool = from_id == ContactId::INFO
3269 || to_id == ContactId::INFO
3270 || match Params::from_str(¶ms) {
3271 Ok(p) => {
3272 let cmd = p.get_cmd();
3273 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3274 }
3275 _ => false,
3276 };
3277
3278 Ok((
3279 row.get::<_, i64>("timestamp")?,
3280 row.get::<_, MsgId>("id")?,
3281 !is_info_msg,
3282 ))
3283 }
3284 } else {
3285 |row: &rusqlite::Row| {
3286 Ok((
3287 row.get::<_, i64>("timestamp")?,
3288 row.get::<_, MsgId>("id")?,
3289 false,
3290 ))
3291 }
3292 };
3293 let process_rows = |rows: rusqlite::MappedRows<_>| {
3294 let mut sorted_rows = Vec::new();
3297 for row in rows {
3298 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3299 if !exclude_message {
3300 sorted_rows.push((ts, curr_id));
3301 }
3302 }
3303 sorted_rows.sort_unstable();
3304
3305 let mut ret = Vec::new();
3306 let mut last_day = 0;
3307 let cnv_to_local = gm2local_offset();
3308
3309 for (ts, curr_id) in sorted_rows {
3310 if add_daymarker {
3311 let curr_local_timestamp = ts + cnv_to_local;
3312 let curr_day = curr_local_timestamp / 86400;
3313 if curr_day != last_day {
3314 ret.push(ChatItem::DayMarker {
3315 timestamp: curr_day * 86400, });
3317 last_day = curr_day;
3318 }
3319 }
3320 ret.push(ChatItem::Message { msg_id: curr_id });
3321 }
3322 Ok(ret)
3323 };
3324
3325 let items = if info_only {
3326 context
3327 .sql
3328 .query_map(
3329 "SELECT m.id AS id, m.timestamp AS timestamp, m.param AS param, m.from_id AS from_id, m.to_id AS to_id
3331 FROM msgs m
3332 WHERE m.chat_id=?
3333 AND m.hidden=0
3334 AND (
3335 m.param GLOB \"*S=*\"
3336 OR m.from_id == ?
3337 OR m.to_id == ?
3338 );",
3339 (chat_id, ContactId::INFO, ContactId::INFO),
3340 process_row,
3341 process_rows,
3342 )
3343 .await?
3344 } else {
3345 context
3346 .sql
3347 .query_map(
3348 "SELECT m.id AS id, m.timestamp AS timestamp
3349 FROM msgs m
3350 WHERE m.chat_id=?
3351 AND m.hidden=0;",
3352 (chat_id,),
3353 process_row,
3354 process_rows,
3355 )
3356 .await?
3357 };
3358 Ok(items)
3359}
3360
3361pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3364 if chat_id.is_archived_link() {
3367 let chat_ids_in_archive = context
3368 .sql
3369 .query_map(
3370 "SELECT DISTINCT(m.chat_id) FROM msgs m
3371 LEFT JOIN chats c ON m.chat_id=c.id
3372 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3373 (),
3374 |row| row.get::<_, ChatId>(0),
3375 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3376 )
3377 .await?;
3378 if chat_ids_in_archive.is_empty() {
3379 return Ok(());
3380 }
3381
3382 context
3383 .sql
3384 .transaction(|transaction| {
3385 let mut stmt = transaction.prepare(
3386 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3387 )?;
3388 for chat_id_in_archive in &chat_ids_in_archive {
3389 stmt.execute((chat_id_in_archive,))?;
3390 }
3391 Ok(())
3392 })
3393 .await?;
3394
3395 for chat_id_in_archive in chat_ids_in_archive {
3396 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3397 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3398 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3399 }
3400 } else {
3401 start_chat_ephemeral_timers(context, chat_id).await?;
3402
3403 let noticed_msgs_count = context
3404 .sql
3405 .execute(
3406 "UPDATE msgs
3407 SET state=?
3408 WHERE state=?
3409 AND hidden=0
3410 AND chat_id=?;",
3411 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3412 )
3413 .await?;
3414
3415 let hidden_messages = context
3418 .sql
3419 .query_map(
3420 "SELECT id, rfc724_mid FROM msgs
3421 WHERE state=?
3422 AND hidden=1
3423 AND chat_id=?
3424 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3427 let msg_id: MsgId = row.get(0)?;
3428 let rfc724_mid: String = row.get(1)?;
3429 Ok((msg_id, rfc724_mid))
3430 },
3431 |rows| {
3432 rows.collect::<std::result::Result<Vec<_>, _>>()
3433 .map_err(Into::into)
3434 },
3435 )
3436 .await?;
3437 for (msg_id, rfc724_mid) in &hidden_messages {
3438 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3439 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3440 }
3441
3442 if noticed_msgs_count == 0 {
3443 return Ok(());
3444 }
3445 }
3446
3447 context.emit_event(EventType::MsgsNoticed(chat_id));
3448 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3449 context.on_archived_chats_maybe_noticed();
3450 Ok(())
3451}
3452
3453pub(crate) async fn mark_old_messages_as_noticed(
3460 context: &Context,
3461 mut msgs: Vec<ReceivedMsg>,
3462) -> Result<()> {
3463 msgs.retain(|m| m.state.is_outgoing());
3464 if msgs.is_empty() {
3465 return Ok(());
3466 }
3467
3468 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3469 for msg in msgs {
3470 let chat_id = msg.chat_id;
3471 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3472 if msg.sort_timestamp > existing_msg.sort_timestamp {
3473 msgs_by_chat.insert(chat_id, msg);
3474 }
3475 } else {
3476 msgs_by_chat.insert(chat_id, msg);
3477 }
3478 }
3479
3480 let changed_chats = context
3481 .sql
3482 .transaction(|transaction| {
3483 let mut changed_chats = Vec::new();
3484 for (_, msg) in msgs_by_chat {
3485 let changed_rows = transaction.execute(
3486 "UPDATE msgs
3487 SET state=?
3488 WHERE state=?
3489 AND hidden=0
3490 AND chat_id=?
3491 AND timestamp<=?;",
3492 (
3493 MessageState::InNoticed,
3494 MessageState::InFresh,
3495 msg.chat_id,
3496 msg.sort_timestamp,
3497 ),
3498 )?;
3499 if changed_rows > 0 {
3500 changed_chats.push(msg.chat_id);
3501 }
3502 }
3503 Ok(changed_chats)
3504 })
3505 .await?;
3506
3507 if !changed_chats.is_empty() {
3508 info!(
3509 context,
3510 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3511 );
3512 context.on_archived_chats_maybe_noticed();
3513 }
3514
3515 for c in changed_chats {
3516 start_chat_ephemeral_timers(context, c).await?;
3517 context.emit_event(EventType::MsgsNoticed(c));
3518 chatlist_events::emit_chatlist_item_changed(context, c);
3519 }
3520
3521 Ok(())
3522}
3523
3524pub async fn get_chat_media(
3531 context: &Context,
3532 chat_id: Option<ChatId>,
3533 msg_type: Viewtype,
3534 msg_type2: Viewtype,
3535 msg_type3: Viewtype,
3536) -> Result<Vec<MsgId>> {
3537 let list = if msg_type == Viewtype::Webxdc
3538 && msg_type2 == Viewtype::Unknown
3539 && msg_type3 == Viewtype::Unknown
3540 {
3541 context
3542 .sql
3543 .query_map(
3544 "SELECT id
3545 FROM msgs
3546 WHERE (1=? OR chat_id=?)
3547 AND chat_id != ?
3548 AND type = ?
3549 AND hidden=0
3550 ORDER BY max(timestamp, timestamp_rcvd), id;",
3551 (
3552 chat_id.is_none(),
3553 chat_id.unwrap_or_else(|| ChatId::new(0)),
3554 DC_CHAT_ID_TRASH,
3555 Viewtype::Webxdc,
3556 ),
3557 |row| row.get::<_, MsgId>(0),
3558 |ids| Ok(ids.flatten().collect()),
3559 )
3560 .await?
3561 } else {
3562 context
3563 .sql
3564 .query_map(
3565 "SELECT id
3566 FROM msgs
3567 WHERE (1=? OR chat_id=?)
3568 AND chat_id != ?
3569 AND type IN (?, ?, ?)
3570 AND hidden=0
3571 ORDER BY timestamp, id;",
3572 (
3573 chat_id.is_none(),
3574 chat_id.unwrap_or_else(|| ChatId::new(0)),
3575 DC_CHAT_ID_TRASH,
3576 msg_type,
3577 if msg_type2 != Viewtype::Unknown {
3578 msg_type2
3579 } else {
3580 msg_type
3581 },
3582 if msg_type3 != Viewtype::Unknown {
3583 msg_type3
3584 } else {
3585 msg_type
3586 },
3587 ),
3588 |row| row.get::<_, MsgId>(0),
3589 |ids| Ok(ids.flatten().collect()),
3590 )
3591 .await?
3592 };
3593 Ok(list)
3594}
3595
3596pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3598 let list = context
3602 .sql
3603 .query_map(
3604 "SELECT cc.contact_id
3605 FROM chats_contacts cc
3606 LEFT JOIN contacts c
3607 ON c.id=cc.contact_id
3608 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3609 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3610 (chat_id,),
3611 |row| row.get::<_, ContactId>(0),
3612 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3613 )
3614 .await?;
3615
3616 Ok(list)
3617}
3618
3619pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3623 let now = time();
3624 let list = context
3625 .sql
3626 .query_map(
3627 "SELECT cc.contact_id
3628 FROM chats_contacts cc
3629 LEFT JOIN contacts c
3630 ON c.id=cc.contact_id
3631 WHERE cc.chat_id=?
3632 AND cc.add_timestamp < cc.remove_timestamp
3633 AND ? < cc.remove_timestamp
3634 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3635 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3636 |row| row.get::<_, ContactId>(0),
3637 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3638 )
3639 .await?;
3640
3641 Ok(list)
3642}
3643
3644pub async fn create_group_chat(
3646 context: &Context,
3647 protect: ProtectionStatus,
3648 chat_name: &str,
3649) -> Result<ChatId> {
3650 let chat_name = sanitize_single_line(chat_name);
3651 ensure!(!chat_name.is_empty(), "Invalid chat name");
3652
3653 let grpid = create_id();
3654
3655 let timestamp = create_smeared_timestamp(context);
3656 let row_id = context
3657 .sql
3658 .insert(
3659 "INSERT INTO chats
3660 (type, name, grpid, param, created_timestamp)
3661 VALUES(?, ?, ?, \'U=1\', ?);",
3662 (Chattype::Group, chat_name, grpid, timestamp),
3663 )
3664 .await?;
3665
3666 let chat_id = ChatId::new(u32::try_from(row_id)?);
3667 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3668
3669 context.emit_msgs_changed_without_ids();
3670 chatlist_events::emit_chatlist_changed(context);
3671 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3672
3673 if protect == ProtectionStatus::Protected {
3674 chat_id
3675 .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3676 .await?;
3677 }
3678
3679 if !context.get_config_bool(Config::Bot).await?
3680 && !context.get_config_bool(Config::SkipStartMessages).await?
3681 {
3682 let text = stock_str::new_group_send_first_message(context).await;
3683 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3684 }
3685
3686 Ok(chat_id)
3687}
3688
3689async fn find_unused_broadcast_list_name(context: &Context) -> Result<String> {
3691 let base_name = stock_str::broadcast_list(context).await;
3692 for attempt in 1..1000 {
3693 let better_name = if attempt > 1 {
3694 format!("{base_name} {attempt}")
3695 } else {
3696 base_name.clone()
3697 };
3698 if !context
3699 .sql
3700 .exists(
3701 "SELECT COUNT(*) FROM chats WHERE type=? AND name=?;",
3702 (Chattype::Broadcast, &better_name),
3703 )
3704 .await?
3705 {
3706 return Ok(better_name);
3707 }
3708 }
3709 Ok(base_name)
3710}
3711
3712pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
3714 let chat_name = find_unused_broadcast_list_name(context).await?;
3715 let grpid = create_id();
3716 create_broadcast_list_ex(context, Sync, grpid, chat_name).await
3717}
3718
3719pub(crate) async fn create_broadcast_list_ex(
3720 context: &Context,
3721 sync: sync::Sync,
3722 grpid: String,
3723 chat_name: String,
3724) -> Result<ChatId> {
3725 let row_id = {
3726 let chat_name = &chat_name;
3727 let grpid = &grpid;
3728 let trans_fn = |t: &mut rusqlite::Transaction| {
3729 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3730 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3731 if cnt == 1 {
3732 return Ok(t.query_row(
3733 "SELECT id FROM chats WHERE grpid=? AND type=?",
3734 (grpid, Chattype::Broadcast),
3735 |row| {
3736 let id: isize = row.get(0)?;
3737 Ok(id)
3738 },
3739 )?);
3740 }
3741 t.execute(
3742 "INSERT INTO chats \
3743 (type, name, grpid, param, created_timestamp) \
3744 VALUES(?, ?, ?, \'U=1\', ?);",
3745 (
3746 Chattype::Broadcast,
3747 &chat_name,
3748 &grpid,
3749 create_smeared_timestamp(context),
3750 ),
3751 )?;
3752 Ok(t.last_insert_rowid().try_into()?)
3753 };
3754 context.sql.transaction(trans_fn).await?
3755 };
3756 let chat_id = ChatId::new(u32::try_from(row_id)?);
3757
3758 context.emit_msgs_changed_without_ids();
3759 chatlist_events::emit_chatlist_changed(context);
3760
3761 if sync.into() {
3762 let id = SyncId::Grpid(grpid);
3763 let action = SyncAction::CreateBroadcast(chat_name);
3764 self::sync(context, id, action).await.log_err(context).ok();
3765 }
3766
3767 Ok(chat_id)
3768}
3769
3770pub(crate) async fn update_chat_contacts_table(
3772 context: &Context,
3773 timestamp: i64,
3774 id: ChatId,
3775 contacts: &HashSet<ContactId>,
3776) -> Result<()> {
3777 context
3778 .sql
3779 .transaction(move |transaction| {
3780 transaction.execute(
3784 "UPDATE chats_contacts
3785 SET remove_timestamp=MAX(add_timestamp+1, ?)
3786 WHERE chat_id=?",
3787 (timestamp, id),
3788 )?;
3789
3790 if !contacts.is_empty() {
3791 let mut statement = transaction.prepare(
3792 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3793 VALUES (?1, ?2, ?3)
3794 ON CONFLICT (chat_id, contact_id)
3795 DO UPDATE SET add_timestamp=remove_timestamp",
3796 )?;
3797
3798 for contact_id in contacts {
3799 statement.execute((id, contact_id, timestamp))?;
3803 }
3804 }
3805 Ok(())
3806 })
3807 .await?;
3808 Ok(())
3809}
3810
3811pub(crate) async fn add_to_chat_contacts_table(
3813 context: &Context,
3814 timestamp: i64,
3815 chat_id: ChatId,
3816 contact_ids: &[ContactId],
3817) -> Result<()> {
3818 context
3819 .sql
3820 .transaction(move |transaction| {
3821 let mut add_statement = transaction.prepare(
3822 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3823 ON CONFLICT (chat_id, contact_id)
3824 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3825 )?;
3826
3827 for contact_id in contact_ids {
3828 add_statement.execute((chat_id, contact_id, timestamp))?;
3829 }
3830 Ok(())
3831 })
3832 .await?;
3833
3834 Ok(())
3835}
3836
3837pub(crate) async fn remove_from_chat_contacts_table(
3840 context: &Context,
3841 chat_id: ChatId,
3842 contact_id: ContactId,
3843) -> Result<()> {
3844 let now = time();
3845 context
3846 .sql
3847 .execute(
3848 "UPDATE chats_contacts
3849 SET remove_timestamp=MAX(add_timestamp+1, ?)
3850 WHERE chat_id=? AND contact_id=?",
3851 (now, chat_id, contact_id),
3852 )
3853 .await?;
3854 Ok(())
3855}
3856
3857pub async fn add_contact_to_chat(
3860 context: &Context,
3861 chat_id: ChatId,
3862 contact_id: ContactId,
3863) -> Result<()> {
3864 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3865 Ok(())
3866}
3867
3868pub(crate) async fn add_contact_to_chat_ex(
3869 context: &Context,
3870 mut sync: sync::Sync,
3871 chat_id: ChatId,
3872 contact_id: ContactId,
3873 from_handshake: bool,
3874) -> Result<bool> {
3875 ensure!(!chat_id.is_special(), "can not add member to special chats");
3876 let contact = Contact::get_by_id(context, contact_id).await?;
3877 let mut msg = Message::new(Viewtype::default());
3878
3879 chat_id.reset_gossiped_timestamp(context).await?;
3880
3881 let mut chat = Chat::load_from_db(context, chat_id).await?;
3883 ensure!(
3884 chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast,
3885 "{} is not a group/broadcast where one can add members",
3886 chat_id
3887 );
3888 ensure!(
3889 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3890 "invalid contact_id {} for adding to group",
3891 contact_id
3892 );
3893 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3894 ensure!(
3895 chat.typ != Chattype::Broadcast || contact_id != ContactId::SELF,
3896 "Cannot add SELF to broadcast."
3897 );
3898
3899 if !chat.is_self_in_chat(context).await? {
3900 context.emit_event(EventType::ErrorSelfNotInGroup(
3901 "Cannot add contact to group; self not in group.".into(),
3902 ));
3903 bail!("can not add contact because the account is not part of the group/broadcast");
3904 }
3905
3906 let sync_qr_code_tokens;
3907 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3908 chat.param
3909 .remove(Param::Unpromoted)
3910 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3911 chat.update_param(context).await?;
3912 sync_qr_code_tokens = true;
3913 } else {
3914 sync_qr_code_tokens = false;
3915 }
3916
3917 if context.is_self_addr(contact.get_addr()).await? {
3918 warn!(
3921 context,
3922 "Invalid attempt to add self e-mail address to group."
3923 );
3924 return Ok(false);
3925 }
3926
3927 if is_contact_in_chat(context, chat_id, contact_id).await? {
3928 if !from_handshake {
3929 return Ok(true);
3930 }
3931 } else {
3932 if chat.is_protected() && !contact.is_verified(context).await? {
3934 error!(
3935 context,
3936 "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
3937 );
3938 return Ok(false);
3939 }
3940 if is_contact_in_chat(context, chat_id, contact_id).await? {
3941 return Ok(false);
3942 }
3943 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3944 }
3945 if chat.typ == Chattype::Group && chat.is_promoted() {
3946 msg.viewtype = Viewtype::Text;
3947
3948 let contact_addr = contact.get_addr().to_lowercase();
3949 msg.text = stock_str::msg_add_member_local(context, &contact_addr, ContactId::SELF).await;
3950 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3951 msg.param.set(Param::Arg, contact_addr);
3952 msg.param.set_int(Param::Arg2, from_handshake.into());
3953 msg.param
3954 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3955 send_msg(context, chat_id, &mut msg).await?;
3956
3957 sync = Nosync;
3958 if sync_qr_code_tokens
3964 && context
3965 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3966 .await
3967 .log_err(context)
3968 .is_ok()
3969 {
3970 context.scheduler.interrupt_inbox().await;
3971 }
3972 }
3973 context.emit_event(EventType::ChatModified(chat_id));
3974 if sync.into() {
3975 chat.sync_contacts(context).await.log_err(context).ok();
3976 }
3977 Ok(true)
3978}
3979
3980pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3986 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3987 let needs_attach = context
3988 .sql
3989 .query_map(
3990 "SELECT c.selfavatar_sent
3991 FROM chats_contacts cc
3992 LEFT JOIN contacts c ON c.id=cc.contact_id
3993 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3994 (chat_id, ContactId::SELF),
3995 |row| Ok(row.get::<_, i64>(0)),
3996 |rows| {
3997 let mut needs_attach = false;
3998 for row in rows {
3999 let row = row?;
4000 let selfavatar_sent = row?;
4001 if selfavatar_sent < timestamp_some_days_ago {
4002 needs_attach = true;
4003 }
4004 }
4005 Ok(needs_attach)
4006 },
4007 )
4008 .await?;
4009 Ok(needs_attach)
4010}
4011
4012#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
4014pub enum MuteDuration {
4015 NotMuted,
4017
4018 Forever,
4020
4021 Until(std::time::SystemTime),
4023}
4024
4025impl rusqlite::types::ToSql for MuteDuration {
4026 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
4027 let duration: i64 = match &self {
4028 MuteDuration::NotMuted => 0,
4029 MuteDuration::Forever => -1,
4030 MuteDuration::Until(when) => {
4031 let duration = when
4032 .duration_since(SystemTime::UNIX_EPOCH)
4033 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
4034 i64::try_from(duration.as_secs())
4035 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
4036 }
4037 };
4038 let val = rusqlite::types::Value::Integer(duration);
4039 let out = rusqlite::types::ToSqlOutput::Owned(val);
4040 Ok(out)
4041 }
4042}
4043
4044impl rusqlite::types::FromSql for MuteDuration {
4045 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
4046 match i64::column_result(value)? {
4049 0 => Ok(MuteDuration::NotMuted),
4050 -1 => Ok(MuteDuration::Forever),
4051 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
4052 Some(t) => Ok(MuteDuration::Until(t)),
4053 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
4054 },
4055 _ => Ok(MuteDuration::NotMuted),
4056 }
4057 }
4058}
4059
4060pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
4062 set_muted_ex(context, Sync, chat_id, duration).await
4063}
4064
4065pub(crate) async fn set_muted_ex(
4066 context: &Context,
4067 sync: sync::Sync,
4068 chat_id: ChatId,
4069 duration: MuteDuration,
4070) -> Result<()> {
4071 ensure!(!chat_id.is_special(), "Invalid chat ID");
4072 context
4073 .sql
4074 .execute(
4075 "UPDATE chats SET muted_until=? WHERE id=?;",
4076 (duration, chat_id),
4077 )
4078 .await
4079 .context(format!("Failed to set mute duration for {chat_id}"))?;
4080 context.emit_event(EventType::ChatModified(chat_id));
4081 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4082 if sync.into() {
4083 let chat = Chat::load_from_db(context, chat_id).await?;
4084 chat.sync(context, SyncAction::SetMuted(duration))
4085 .await
4086 .log_err(context)
4087 .ok();
4088 }
4089 Ok(())
4090}
4091
4092pub async fn remove_contact_from_chat(
4094 context: &Context,
4095 chat_id: ChatId,
4096 contact_id: ContactId,
4097) -> Result<()> {
4098 ensure!(
4099 !chat_id.is_special(),
4100 "bad chat_id, can not be special chat: {}",
4101 chat_id
4102 );
4103 ensure!(
4104 !contact_id.is_special() || contact_id == ContactId::SELF,
4105 "Cannot remove special contact"
4106 );
4107
4108 let mut msg = Message::new(Viewtype::default());
4109
4110 let chat = Chat::load_from_db(context, chat_id).await?;
4111 if chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast {
4112 if !chat.is_self_in_chat(context).await? {
4113 let err_msg = format!(
4114 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4115 );
4116 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4117 bail!("{}", err_msg);
4118 } else {
4119 let mut sync = Nosync;
4120
4121 if chat.is_promoted() {
4122 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4123 } else {
4124 context
4125 .sql
4126 .execute(
4127 "DELETE FROM chats_contacts
4128 WHERE chat_id=? AND contact_id=?",
4129 (chat_id, contact_id),
4130 )
4131 .await?;
4132 }
4133
4134 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4138 if chat.typ == Chattype::Group && chat.is_promoted() {
4139 msg.viewtype = Viewtype::Text;
4140 if contact_id == ContactId::SELF {
4141 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4142 } else {
4143 msg.text = stock_str::msg_del_member_local(
4144 context,
4145 contact.get_addr(),
4146 ContactId::SELF,
4147 )
4148 .await;
4149 }
4150 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4151 msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4152 msg.param
4153 .set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4154 let res = send_msg(context, chat_id, &mut msg).await;
4155 if contact_id == ContactId::SELF {
4156 res?;
4157 set_group_explicitly_left(context, &chat.grpid).await?;
4158 } else if let Err(e) = res {
4159 warn!(context, "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}.");
4160 }
4161 } else {
4162 sync = Sync;
4163 }
4164 }
4165 context.emit_event(EventType::ChatModified(chat_id));
4166 if sync.into() {
4167 chat.sync_contacts(context).await.log_err(context).ok();
4168 }
4169 }
4170 } else {
4171 bail!("Cannot remove members from non-group chats.");
4172 }
4173
4174 Ok(())
4175}
4176
4177async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4178 if !is_group_explicitly_left(context, grpid).await? {
4179 context
4180 .sql
4181 .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4182 .await?;
4183 }
4184
4185 Ok(())
4186}
4187
4188pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4189 let exists = context
4190 .sql
4191 .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4192 .await?;
4193 Ok(exists)
4194}
4195
4196pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4198 rename_ex(context, Sync, chat_id, new_name).await
4199}
4200
4201async fn rename_ex(
4202 context: &Context,
4203 mut sync: sync::Sync,
4204 chat_id: ChatId,
4205 new_name: &str,
4206) -> Result<()> {
4207 let new_name = sanitize_single_line(new_name);
4208 let mut success = false;
4210
4211 ensure!(!new_name.is_empty(), "Invalid name");
4212 ensure!(!chat_id.is_special(), "Invalid chat ID");
4213
4214 let chat = Chat::load_from_db(context, chat_id).await?;
4215 let mut msg = Message::new(Viewtype::default());
4216
4217 if chat.typ == Chattype::Group
4218 || chat.typ == Chattype::Mailinglist
4219 || chat.typ == Chattype::Broadcast
4220 {
4221 if chat.name == new_name {
4222 success = true;
4223 } else if !chat.is_self_in_chat(context).await? {
4224 context.emit_event(EventType::ErrorSelfNotInGroup(
4225 "Cannot set chat name; self not in group".into(),
4226 ));
4227 } else {
4228 context
4229 .sql
4230 .execute(
4231 "UPDATE chats SET name=? WHERE id=?;",
4232 (new_name.to_string(), chat_id),
4233 )
4234 .await?;
4235 if chat.is_promoted()
4236 && !chat.is_mailing_list()
4237 && chat.typ != Chattype::Broadcast
4238 && sanitize_single_line(&chat.name) != new_name
4239 {
4240 msg.viewtype = Viewtype::Text;
4241 msg.text =
4242 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4243 msg.param.set_cmd(SystemMessage::GroupNameChanged);
4244 if !chat.name.is_empty() {
4245 msg.param.set(Param::Arg, &chat.name);
4246 }
4247 msg.id = send_msg(context, chat_id, &mut msg).await?;
4248 context.emit_msgs_changed(chat_id, msg.id);
4249 sync = Nosync;
4250 }
4251 context.emit_event(EventType::ChatModified(chat_id));
4252 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4253 success = true;
4254 }
4255 }
4256
4257 if !success {
4258 bail!("Failed to set name");
4259 }
4260 if sync.into() && chat.name != new_name {
4261 let sync_name = new_name.to_string();
4262 chat.sync(context, SyncAction::Rename(sync_name))
4263 .await
4264 .log_err(context)
4265 .ok();
4266 }
4267 Ok(())
4268}
4269
4270pub async fn set_chat_profile_image(
4276 context: &Context,
4277 chat_id: ChatId,
4278 new_image: &str, ) -> Result<()> {
4280 ensure!(!chat_id.is_special(), "Invalid chat ID");
4281 let mut chat = Chat::load_from_db(context, chat_id).await?;
4282 ensure!(
4283 chat.typ == Chattype::Group || chat.typ == Chattype::Mailinglist,
4284 "Failed to set profile image; group does not exist"
4285 );
4286 if !is_contact_in_chat(context, chat_id, ContactId::SELF).await? {
4288 context.emit_event(EventType::ErrorSelfNotInGroup(
4289 "Cannot set chat profile image; self not in group.".into(),
4290 ));
4291 bail!("Failed to set profile image");
4292 }
4293 let mut msg = Message::new(Viewtype::Text);
4294 msg.param
4295 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4296 if new_image.is_empty() {
4297 chat.param.remove(Param::ProfileImage);
4298 msg.param.remove(Param::Arg);
4299 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4300 } else {
4301 let mut image_blob = BlobObject::create_and_deduplicate(
4302 context,
4303 Path::new(new_image),
4304 Path::new(new_image),
4305 )?;
4306 image_blob.recode_to_avatar_size(context).await?;
4307 chat.param.set(Param::ProfileImage, image_blob.as_name());
4308 msg.param.set(Param::Arg, image_blob.as_name());
4309 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4310 }
4311 chat.update_param(context).await?;
4312 if chat.is_promoted() && !chat.is_mailing_list() {
4313 msg.id = send_msg(context, chat_id, &mut msg).await?;
4314 context.emit_msgs_changed(chat_id, msg.id);
4315 }
4316 context.emit_event(EventType::ChatModified(chat_id));
4317 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4318 Ok(())
4319}
4320
4321pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4323 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4324 ensure!(!chat_id.is_special(), "can not forward to special chat");
4325
4326 let mut created_msgs: Vec<MsgId> = Vec::new();
4327 let mut curr_timestamp: i64;
4328
4329 chat_id
4330 .unarchive_if_not_muted(context, MessageState::Undefined)
4331 .await?;
4332 let mut chat = Chat::load_from_db(context, chat_id).await?;
4333 if let Some(reason) = chat.why_cant_send(context).await? {
4334 bail!("cannot send to {}: {}", chat_id, reason);
4335 }
4336 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4337 let mut msgs = Vec::with_capacity(msg_ids.len());
4338 for id in msg_ids {
4339 let ts: i64 = context
4340 .sql
4341 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4342 .await?
4343 .context("No message {id}")?;
4344 msgs.push((ts, *id));
4345 }
4346 msgs.sort_unstable();
4347 for (_, id) in msgs {
4348 let src_msg_id: MsgId = id;
4349 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4350 if msg.state == MessageState::OutDraft {
4351 bail!("cannot forward drafts.");
4352 }
4353
4354 if msg.get_viewtype() != Viewtype::Sticker {
4359 msg.param
4360 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4361 }
4362
4363 msg.param.remove(Param::GuaranteeE2ee);
4364 msg.param.remove(Param::ForcePlaintext);
4365 msg.param.remove(Param::Cmd);
4366 msg.param.remove(Param::OverrideSenderDisplayname);
4367 msg.param.remove(Param::WebxdcDocument);
4368 msg.param.remove(Param::WebxdcDocumentTimestamp);
4369 msg.param.remove(Param::WebxdcSummary);
4370 msg.param.remove(Param::WebxdcSummaryTimestamp);
4371 msg.param.remove(Param::IsEdited);
4372 msg.in_reply_to = None;
4373
4374 msg.subject = "".to_string();
4376
4377 msg.state = MessageState::OutPending;
4378 msg.rfc724_mid = create_outgoing_rfc724_mid();
4379 let new_msg_id = chat
4380 .prepare_msg_raw(context, &mut msg, None, curr_timestamp)
4381 .await?;
4382
4383 curr_timestamp += 1;
4384 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4385 context.scheduler.interrupt_smtp().await;
4386 }
4387 created_msgs.push(new_msg_id);
4388 }
4389 for msg_id in created_msgs {
4390 context.emit_msgs_changed(chat_id, msg_id);
4391 }
4392 Ok(())
4393}
4394
4395pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4398 for src_msg_id in msg_ids {
4399 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4400 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4401 context
4402 .add_sync_item(SyncData::SaveMessage {
4403 src: src_rfc724_mid,
4404 dest: dest_rfc724_mid,
4405 })
4406 .await?;
4407 }
4408 context.scheduler.interrupt_inbox().await;
4409 Ok(())
4410}
4411
4412pub(crate) async fn save_copy_in_self_talk(
4418 context: &Context,
4419 src_msg_id: &MsgId,
4420 dest_rfc724_mid: &String,
4421) -> Result<String> {
4422 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4423 let mut msg = Message::load_from_db(context, *src_msg_id).await?;
4424 msg.param.remove(Param::Cmd);
4425 msg.param.remove(Param::WebxdcDocument);
4426 msg.param.remove(Param::WebxdcDocumentTimestamp);
4427 msg.param.remove(Param::WebxdcSummary);
4428 msg.param.remove(Param::WebxdcSummaryTimestamp);
4429
4430 if !msg.original_msg_id.is_unset() {
4431 bail!("message already saved.");
4432 }
4433
4434 let copy_fields = "from_id, to_id, timestamp_sent, timestamp_rcvd, type, txt, \
4435 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4436 let row_id = context
4437 .sql
4438 .insert(
4439 &format!(
4440 "INSERT INTO msgs ({copy_fields}, chat_id, rfc724_mid, state, timestamp, param, starred) \
4441 SELECT {copy_fields}, ?, ?, ?, ?, ?, ? \
4442 FROM msgs WHERE id=?;"
4443 ),
4444 (
4445 dest_chat_id,
4446 dest_rfc724_mid,
4447 if msg.from_id == ContactId::SELF {
4448 MessageState::OutDelivered
4449 } else {
4450 MessageState::InSeen
4451 },
4452 create_smeared_timestamp(context),
4453 msg.param.to_string(),
4454 src_msg_id,
4455 src_msg_id,
4456 ),
4457 )
4458 .await?;
4459 let dest_msg_id = MsgId::new(row_id.try_into()?);
4460
4461 context.emit_msgs_changed(msg.chat_id, *src_msg_id);
4462 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4463 chatlist_events::emit_chatlist_changed(context);
4464 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4465
4466 Ok(msg.rfc724_mid)
4467}
4468
4469pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4473 let mut chat_id = None;
4474 let mut msgs: Vec<Message> = Vec::new();
4475 for msg_id in msg_ids {
4476 let msg = Message::load_from_db(context, *msg_id).await?;
4477 if let Some(chat_id) = chat_id {
4478 ensure!(
4479 chat_id == msg.chat_id,
4480 "messages to resend needs to be in the same chat"
4481 );
4482 } else {
4483 chat_id = Some(msg.chat_id);
4484 }
4485 ensure!(
4486 msg.from_id == ContactId::SELF,
4487 "can resend only own messages"
4488 );
4489 ensure!(!msg.is_info(), "cannot resend info messages");
4490 msgs.push(msg)
4491 }
4492
4493 let Some(chat_id) = chat_id else {
4494 return Ok(());
4495 };
4496
4497 let chat = Chat::load_from_db(context, chat_id).await?;
4498 for mut msg in msgs {
4499 if msg.get_showpadlock() && !chat.is_protected() {
4500 msg.param.remove(Param::GuaranteeE2ee);
4501 msg.update_param(context).await?;
4502 }
4503 match msg.get_state() {
4504 MessageState::OutPending
4506 | MessageState::OutFailed
4507 | MessageState::OutDelivered
4508 | MessageState::OutMdnRcvd => {
4509 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4510 }
4511 msg_state => bail!("Unexpected message state {msg_state}"),
4512 }
4513 context.emit_event(EventType::MsgsChanged {
4514 chat_id: msg.chat_id,
4515 msg_id: msg.id,
4516 });
4517 msg.timestamp_sort = create_smeared_timestamp(context);
4518 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4520 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4521 continue;
4522 }
4523 if msg.viewtype == Viewtype::Webxdc {
4524 let conn_fn = |conn: &mut rusqlite::Connection| {
4525 let range = conn.query_row(
4526 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4527 FROM msgs_status_updates WHERE msg_id=?",
4528 (msg.id,),
4529 |row| {
4530 let min_id: StatusUpdateSerial = row.get(0)?;
4531 let max_id: StatusUpdateSerial = row.get(1)?;
4532 Ok((min_id, max_id))
4533 },
4534 )?;
4535 if range.0 > range.1 {
4536 return Ok(());
4537 };
4538 conn.execute(
4542 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4543 VALUES(?, ?, ?, '') \
4544 ON CONFLICT(msg_id) \
4545 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4546 (msg.id, range.0, range.1),
4547 )?;
4548 Ok(())
4549 };
4550 context.sql.call_write(conn_fn).await?;
4551 }
4552 context.scheduler.interrupt_smtp().await;
4553 }
4554 Ok(())
4555}
4556
4557pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4558 if context.sql.is_open().await {
4559 let count = context
4561 .sql
4562 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4563 .await?;
4564 Ok(count)
4565 } else {
4566 Ok(0)
4567 }
4568}
4569
4570pub(crate) async fn get_chat_id_by_grpid(
4572 context: &Context,
4573 grpid: &str,
4574) -> Result<Option<(ChatId, bool, Blocked)>> {
4575 context
4576 .sql
4577 .query_row_optional(
4578 "SELECT id, blocked, protected FROM chats WHERE grpid=?;",
4579 (grpid,),
4580 |row| {
4581 let chat_id = row.get::<_, ChatId>(0)?;
4582
4583 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4584 let p = row
4585 .get::<_, Option<ProtectionStatus>>(2)?
4586 .unwrap_or_default();
4587 Ok((chat_id, p == ProtectionStatus::Protected, b))
4588 },
4589 )
4590 .await
4591}
4592
4593pub async fn add_device_msg_with_importance(
4598 context: &Context,
4599 label: Option<&str>,
4600 msg: Option<&mut Message>,
4601 important: bool,
4602) -> Result<MsgId> {
4603 ensure!(
4604 label.is_some() || msg.is_some(),
4605 "device-messages need label, msg or both"
4606 );
4607 let mut chat_id = ChatId::new(0);
4608 let mut msg_id = MsgId::new_unset();
4609
4610 if let Some(label) = label {
4611 if was_device_msg_ever_added(context, label).await? {
4612 info!(context, "Device-message {label} already added.");
4613 return Ok(msg_id);
4614 }
4615 }
4616
4617 if let Some(msg) = msg {
4618 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4619
4620 let rfc724_mid = create_outgoing_rfc724_mid();
4621 prepare_msg_blob(context, msg).await?;
4622
4623 let timestamp_sent = create_smeared_timestamp(context);
4624
4625 let mut timestamp_sort = timestamp_sent;
4628 if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4629 if timestamp_sort <= last_msg_time {
4630 timestamp_sort = last_msg_time + 1;
4631 }
4632 }
4633
4634 let state = MessageState::InFresh;
4635 let row_id = context
4636 .sql
4637 .insert(
4638 "INSERT INTO msgs (
4639 chat_id,
4640 from_id,
4641 to_id,
4642 timestamp,
4643 timestamp_sent,
4644 timestamp_rcvd,
4645 type,state,
4646 txt,
4647 txt_normalized,
4648 param,
4649 rfc724_mid)
4650 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4651 (
4652 chat_id,
4653 ContactId::DEVICE,
4654 ContactId::SELF,
4655 timestamp_sort,
4656 timestamp_sent,
4657 timestamp_sent, msg.viewtype,
4659 state,
4660 &msg.text,
4661 message::normalize_text(&msg.text),
4662 msg.param.to_string(),
4663 rfc724_mid,
4664 ),
4665 )
4666 .await?;
4667 context.new_msgs_notify.notify_one();
4668
4669 msg_id = MsgId::new(u32::try_from(row_id)?);
4670 if !msg.hidden {
4671 chat_id.unarchive_if_not_muted(context, state).await?;
4672 }
4673 }
4674
4675 if let Some(label) = label {
4676 context
4677 .sql
4678 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4679 .await?;
4680 }
4681
4682 if !msg_id.is_unset() {
4683 chat_id.emit_msg_event(context, msg_id, important);
4684 }
4685
4686 Ok(msg_id)
4687}
4688
4689pub async fn add_device_msg(
4691 context: &Context,
4692 label: Option<&str>,
4693 msg: Option<&mut Message>,
4694) -> Result<MsgId> {
4695 add_device_msg_with_importance(context, label, msg, false).await
4696}
4697
4698pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4700 ensure!(!label.is_empty(), "empty label");
4701 let exists = context
4702 .sql
4703 .exists(
4704 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4705 (label,),
4706 )
4707 .await?;
4708
4709 Ok(exists)
4710}
4711
4712pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4720 context
4721 .sql
4722 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4723 .await?;
4724 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4725
4726 context
4728 .sql
4729 .execute(
4730 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4731 (),
4732 )
4733 .await?;
4734 context
4735 .set_config_internal(Config::QuotaExceeding, None)
4736 .await?;
4737 Ok(())
4738}
4739
4740#[expect(clippy::too_many_arguments)]
4745pub(crate) async fn add_info_msg_with_cmd(
4746 context: &Context,
4747 chat_id: ChatId,
4748 text: &str,
4749 cmd: SystemMessage,
4750 timestamp_sort: i64,
4751 timestamp_sent_rcvd: Option<i64>,
4753 parent: Option<&Message>,
4754 from_id: Option<ContactId>,
4755 added_removed_id: Option<ContactId>,
4756) -> Result<MsgId> {
4757 let rfc724_mid = create_outgoing_rfc724_mid();
4758 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4759
4760 let mut param = Params::new();
4761 if cmd != SystemMessage::Unknown {
4762 param.set_cmd(cmd);
4763 }
4764 if let Some(contact_id) = added_removed_id {
4765 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4766 }
4767
4768 let row_id =
4769 context.sql.insert(
4770 "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)
4771 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4772 (
4773 chat_id,
4774 from_id.unwrap_or(ContactId::INFO),
4775 ContactId::INFO,
4776 timestamp_sort,
4777 timestamp_sent_rcvd.unwrap_or(0),
4778 timestamp_sent_rcvd.unwrap_or(0),
4779 Viewtype::Text,
4780 MessageState::InNoticed,
4781 text,
4782 message::normalize_text(text),
4783 rfc724_mid,
4784 ephemeral_timer,
4785 param.to_string(),
4786 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4787 )
4788 ).await?;
4789 context.new_msgs_notify.notify_one();
4790
4791 let msg_id = MsgId::new(row_id.try_into()?);
4792 context.emit_msgs_changed(chat_id, msg_id);
4793
4794 Ok(msg_id)
4795}
4796
4797pub(crate) async fn add_info_msg(
4799 context: &Context,
4800 chat_id: ChatId,
4801 text: &str,
4802 timestamp: i64,
4803) -> Result<MsgId> {
4804 add_info_msg_with_cmd(
4805 context,
4806 chat_id,
4807 text,
4808 SystemMessage::Unknown,
4809 timestamp,
4810 None,
4811 None,
4812 None,
4813 None,
4814 )
4815 .await
4816}
4817
4818pub(crate) async fn update_msg_text_and_timestamp(
4819 context: &Context,
4820 chat_id: ChatId,
4821 msg_id: MsgId,
4822 text: &str,
4823 timestamp: i64,
4824) -> Result<()> {
4825 context
4826 .sql
4827 .execute(
4828 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4829 (text, message::normalize_text(text), timestamp, msg_id),
4830 )
4831 .await?;
4832 context.emit_msgs_changed(chat_id, msg_id);
4833 Ok(())
4834}
4835
4836async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4838 let chat = Chat::load_from_db(context, id).await?;
4839 ensure!(
4840 chat.typ == Chattype::Broadcast,
4841 "{id} is not a broadcast list",
4842 );
4843 let mut contacts = HashSet::new();
4844 for addr in addrs {
4845 let contact_addr = ContactAddress::new(addr)?;
4846 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4847 .await?
4848 .0;
4849 contacts.insert(contact);
4850 }
4851 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4852 if contacts == contacts_old {
4853 return Ok(());
4854 }
4855 context
4856 .sql
4857 .transaction(move |transaction| {
4858 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4859
4860 let mut statement = transaction
4863 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4864 for contact_id in &contacts {
4865 statement.execute((id, contact_id))?;
4866 }
4867 Ok(())
4868 })
4869 .await?;
4870 context.emit_event(EventType::ChatModified(id));
4871 Ok(())
4872}
4873
4874#[derive(Debug, Serialize, Deserialize, PartialEq)]
4876pub(crate) enum SyncId {
4877 ContactAddr(String),
4878 Grpid(String),
4879 Msgids(Vec<String>),
4881
4882 Device,
4884}
4885
4886#[derive(Debug, Serialize, Deserialize, PartialEq)]
4888pub(crate) enum SyncAction {
4889 Block,
4890 Unblock,
4891 Accept,
4892 SetVisibility(ChatVisibility),
4893 SetMuted(MuteDuration),
4894 CreateBroadcast(String),
4896 Rename(String),
4897 SetContacts(Vec<String>),
4899 Delete,
4900}
4901
4902impl Context {
4903 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4905 let chat_id = match id {
4906 SyncId::ContactAddr(addr) => {
4907 if let SyncAction::Rename(to) = action {
4908 Contact::create_ex(self, Nosync, to, addr).await?;
4909 return Ok(());
4910 }
4911 let addr = ContactAddress::new(addr).context("Invalid address")?;
4912 let (contact_id, _) =
4913 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4914 match action {
4915 SyncAction::Block => {
4916 return contact::set_blocked(self, Nosync, contact_id, true).await
4917 }
4918 SyncAction::Unblock => {
4919 return contact::set_blocked(self, Nosync, contact_id, false).await
4920 }
4921 _ => (),
4922 }
4923 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4926 .await?
4927 .id
4928 }
4929 SyncId::Grpid(grpid) => {
4930 if let SyncAction::CreateBroadcast(name) = action {
4931 create_broadcast_list_ex(self, Nosync, grpid.clone(), name.clone()).await?;
4932 return Ok(());
4933 }
4934 get_chat_id_by_grpid(self, grpid)
4935 .await?
4936 .with_context(|| format!("No chat for grpid '{grpid}'"))?
4937 .0
4938 }
4939 SyncId::Msgids(msgids) => {
4940 let msg = message::get_by_rfc724_mids(self, msgids)
4941 .await?
4942 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4943 ChatId::lookup_by_message(&msg)
4944 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4945 }
4946 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4947 };
4948 match action {
4949 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4950 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4951 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4952 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4953 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4954 SyncAction::CreateBroadcast(_) => {
4955 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4956 }
4957 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4958 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4959 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4960 }
4961 }
4962
4963 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4968 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4969 }
4970}
4971
4972#[cfg(test)]
4973mod chat_tests;