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