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::{Context as _, Result, anyhow, bail, ensure};
13use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line};
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::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
32use crate::events::EventType;
33use crate::location;
34use crate::log::{LogExt, error, info, warn};
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 IsNoneOrEmpty, SystemTime, buf_compress, create_id, create_outgoing_rfc724_mid,
45 create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset,
46 smeared_time, time, truncate_msg_text,
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 InBroadcast,
128
129 NotAMember,
131
132 MissingKey,
134}
135
136impl fmt::Display for CantSendReason {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::SpecialChat => write!(f, "the chat is a special chat"),
140 Self::DeviceChat => write!(f, "the chat is a device chat"),
141 Self::ContactRequest => write!(
142 f,
143 "contact request chat should be accepted before sending messages"
144 ),
145 Self::ProtectionBroken => write!(
146 f,
147 "accept that the encryption isn't verified anymore before sending messages"
148 ),
149 Self::ReadOnlyMailingList => {
150 write!(f, "mailing list does not have a know post address")
151 }
152 Self::InBroadcast => {
153 write!(f, "Broadcast channel is read-only")
154 }
155 Self::NotAMember => write!(f, "not a member of the chat"),
156 Self::MissingKey => write!(f, "key is missing"),
157 }
158 }
159}
160
161#[derive(
166 Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
167)]
168pub struct ChatId(u32);
169
170impl ChatId {
171 pub const fn new(id: u32) -> ChatId {
173 ChatId(id)
174 }
175
176 pub fn is_unset(self) -> bool {
180 self.0 == 0
181 }
182
183 pub fn is_special(self) -> bool {
187 (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
188 }
189
190 pub fn is_trash(self) -> bool {
197 self == DC_CHAT_ID_TRASH
198 }
199
200 pub fn is_archived_link(self) -> bool {
207 self == DC_CHAT_ID_ARCHIVED_LINK
208 }
209
210 pub fn is_alldone_hint(self) -> bool {
219 self == DC_CHAT_ID_ALLDONE_HINT
220 }
221
222 pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
224 if msg.chat_id == DC_CHAT_ID_TRASH {
225 return None;
226 }
227 if msg.download_state == DownloadState::Undecipherable {
228 return None;
229 }
230 Some(msg.chat_id)
231 }
232
233 pub async fn lookup_by_contact(
238 context: &Context,
239 contact_id: ContactId,
240 ) -> Result<Option<Self>> {
241 let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
242 else {
243 return Ok(None);
244 };
245
246 let chat_id = match chat_id_blocked.blocked {
247 Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
248 Blocked::Yes => None,
249 };
250 Ok(chat_id)
251 }
252
253 pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
261 ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
262 .await
263 .map(|chat| chat.id)
264 }
265
266 pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
271 ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
272 }
273
274 pub(crate) async fn create_for_contact_with_blocked(
278 context: &Context,
279 contact_id: ContactId,
280 create_blocked: Blocked,
281 ) -> Result<Self> {
282 let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
283 Some(chat) => {
284 if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
285 return Ok(chat.id);
286 }
287 chat.id.set_blocked(context, Blocked::Not).await?;
288 chat.id
289 }
290 None => {
291 if Contact::real_exists_by_id(context, contact_id).await?
292 || contact_id == ContactId::SELF
293 {
294 let chat_id =
295 ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
296 .await
297 .map(|chat| chat.id)?;
298 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
299 chat_id
300 } else {
301 warn!(
302 context,
303 "Cannot create chat, contact {contact_id} does not exist."
304 );
305 bail!("Can not create chat for non-existing contact");
306 }
307 }
308 };
309 context.emit_msgs_changed_without_ids();
310 chatlist_events::emit_chatlist_changed(context);
311 chatlist_events::emit_chatlist_item_changed(context, chat_id);
312 Ok(chat_id)
313 }
314
315 #[expect(clippy::too_many_arguments)]
318 pub(crate) async fn create_multiuser_record(
319 context: &Context,
320 chattype: Chattype,
321 grpid: &str,
322 grpname: &str,
323 create_blocked: Blocked,
324 create_protected: ProtectionStatus,
325 param: Option<String>,
326 timestamp: i64,
327 ) -> Result<Self> {
328 let grpname = sanitize_single_line(grpname);
329 let timestamp = cmp::min(timestamp, smeared_time(context));
330 let row_id =
331 context.sql.insert(
332 "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
333 (
334 chattype,
335 &grpname,
336 grpid,
337 create_blocked,
338 timestamp,
339 create_protected,
340 param.unwrap_or_default(),
341 ),
342 ).await?;
343
344 let chat_id = ChatId::new(u32::try_from(row_id)?);
345
346 if create_protected == ProtectionStatus::Protected {
347 chat_id
348 .add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
349 .await?;
350 }
351
352 info!(
353 context,
354 "Created group/mailinglist '{}' grpid={} as {}, blocked={}, protected={create_protected}.",
355 &grpname,
356 grpid,
357 chat_id,
358 create_blocked,
359 );
360
361 Ok(chat_id)
362 }
363
364 async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
365 context
366 .sql
367 .execute(
368 "UPDATE contacts
369 SET selfavatar_sent=?
370 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
371 (timestamp, self),
372 )
373 .await?;
374 Ok(())
375 }
376
377 pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
381 if self.is_special() {
382 bail!("ignoring setting of Block-status for {}", self);
383 }
384 let count = context
385 .sql
386 .execute(
387 "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
388 (new_blocked, self),
389 )
390 .await?;
391 Ok(count > 0)
392 }
393
394 pub async fn block(self, context: &Context) -> Result<()> {
396 self.block_ex(context, Sync).await
397 }
398
399 pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
400 let chat = Chat::load_from_db(context, self).await?;
401 let mut delete = false;
402
403 match chat.typ {
404 Chattype::OutBroadcast => {
405 bail!("Can't block chat of type {:?}", chat.typ)
406 }
407 Chattype::Single => {
408 for contact_id in get_chat_contacts(context, self).await? {
409 if contact_id != ContactId::SELF {
410 info!(
411 context,
412 "Blocking the contact {contact_id} to block 1:1 chat."
413 );
414 contact::set_blocked(context, Nosync, contact_id, true).await?;
415 }
416 }
417 }
418 Chattype::Group => {
419 info!(context, "Can't block groups yet, deleting the chat.");
420 delete = true;
421 }
422 Chattype::Mailinglist | Chattype::InBroadcast => {
423 if self.set_blocked(context, Blocked::Yes).await? {
424 context.emit_event(EventType::ChatModified(self));
425 }
426 }
427 }
428 chatlist_events::emit_chatlist_changed(context);
429
430 if sync.into() {
431 chat.sync(context, SyncAction::Block)
433 .await
434 .log_err(context)
435 .ok();
436 }
437 if delete {
438 self.delete_ex(context, Nosync).await?;
439 }
440 Ok(())
441 }
442
443 pub async fn unblock(self, context: &Context) -> Result<()> {
445 self.unblock_ex(context, Sync).await
446 }
447
448 pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
449 self.set_blocked(context, Blocked::Not).await?;
450
451 chatlist_events::emit_chatlist_changed(context);
452
453 if sync.into() {
454 let chat = Chat::load_from_db(context, self).await?;
455 chat.sync(context, SyncAction::Unblock)
459 .await
460 .log_err(context)
461 .ok();
462 }
463
464 Ok(())
465 }
466
467 pub async fn accept(self, context: &Context) -> Result<()> {
471 self.accept_ex(context, Sync).await
472 }
473
474 pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
475 let chat = Chat::load_from_db(context, self).await?;
476
477 match chat.typ {
478 Chattype::Single
479 if chat.blocked == Blocked::Not
480 && chat.protected == ProtectionStatus::ProtectionBroken =>
481 {
482 chat.id
485 .inner_set_protection(context, ProtectionStatus::Unprotected)
486 .await?;
487 }
488 Chattype::Single | Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast => {
489 for contact_id in get_chat_contacts(context, self).await? {
494 if contact_id != ContactId::SELF {
495 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
496 .await?;
497 }
498 }
499 }
500 Chattype::Mailinglist => {
501 }
503 }
504
505 if self.set_blocked(context, Blocked::Not).await? {
506 context.emit_event(EventType::ChatModified(self));
507 chatlist_events::emit_chatlist_item_changed(context, self);
508 }
509
510 if sync.into() {
511 chat.sync(context, SyncAction::Accept)
512 .await
513 .log_err(context)
514 .ok();
515 }
516 Ok(())
517 }
518
519 pub(crate) async fn inner_set_protection(
523 self,
524 context: &Context,
525 protect: ProtectionStatus,
526 ) -> Result<bool> {
527 ensure!(!self.is_special(), "Invalid chat-id {self}.");
528
529 let chat = Chat::load_from_db(context, self).await?;
530
531 if protect == chat.protected {
532 info!(context, "Protection status unchanged for {}.", self);
533 return Ok(false);
534 }
535
536 match protect {
537 ProtectionStatus::Protected => match chat.typ {
538 Chattype::Single
539 | Chattype::Group
540 | Chattype::OutBroadcast
541 | Chattype::InBroadcast => {}
542 Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
543 },
544 ProtectionStatus::Unprotected | ProtectionStatus::ProtectionBroken => {}
545 };
546
547 context
548 .sql
549 .execute("UPDATE chats SET protected=? WHERE id=?;", (protect, self))
550 .await?;
551
552 context.emit_event(EventType::ChatModified(self));
553 chatlist_events::emit_chatlist_item_changed(context, self);
554
555 self.reset_gossiped_timestamp(context).await?;
557
558 Ok(true)
559 }
560
561 pub(crate) async fn add_protection_msg(
569 self,
570 context: &Context,
571 protect: ProtectionStatus,
572 contact_id: Option<ContactId>,
573 timestamp_sort: i64,
574 ) -> Result<()> {
575 if contact_id == Some(ContactId::SELF) {
576 return Ok(());
581 }
582
583 let text = context.stock_protection_msg(protect, contact_id).await;
584 let cmd = match protect {
585 ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
586 ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
587 ProtectionStatus::ProtectionBroken => SystemMessage::ChatProtectionDisabled,
588 };
589 add_info_msg_with_cmd(
590 context,
591 self,
592 &text,
593 cmd,
594 timestamp_sort,
595 None,
596 None,
597 None,
598 None,
599 )
600 .await?;
601
602 Ok(())
603 }
604
605 async fn set_protection_for_timestamp_sort(
610 self,
611 context: &Context,
612 protect: ProtectionStatus,
613 timestamp_sort: i64,
614 contact_id: Option<ContactId>,
615 ) -> Result<()> {
616 let protection_status_modified = self
617 .inner_set_protection(context, protect)
618 .await
619 .with_context(|| format!("Cannot set protection for {self}"))?;
620 if protection_status_modified {
621 self.add_protection_msg(context, protect, contact_id, timestamp_sort)
622 .await?;
623 chatlist_events::emit_chatlist_item_changed(context, self);
624 }
625 Ok(())
626 }
627
628 pub(crate) async fn set_protection(
632 self,
633 context: &Context,
634 protect: ProtectionStatus,
635 timestamp_sent: i64,
636 contact_id: Option<ContactId>,
637 ) -> Result<()> {
638 let sort_to_bottom = true;
639 let (received, incoming) = (false, false);
640 let ts = self
641 .calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
642 .await?
643 .saturating_add(1);
646 self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
647 .await
648 }
649
650 pub(crate) async fn set_protection_for_contact(
655 context: &Context,
656 contact_id: ContactId,
657 timestamp: i64,
658 ) -> Result<()> {
659 let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
660 .await
661 .with_context(|| format!("can't create chat for {contact_id}"))?;
662 chat_id
663 .set_protection(
664 context,
665 ProtectionStatus::Protected,
666 timestamp,
667 Some(contact_id),
668 )
669 .await?;
670 Ok(())
671 }
672
673 pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
675 self.set_visibility_ex(context, Sync, visibility).await
676 }
677
678 pub(crate) async fn set_visibility_ex(
679 self,
680 context: &Context,
681 sync: sync::Sync,
682 visibility: ChatVisibility,
683 ) -> Result<()> {
684 ensure!(
685 !self.is_special(),
686 "bad chat_id, can not be special chat: {}",
687 self
688 );
689
690 context
691 .sql
692 .transaction(move |transaction| {
693 if visibility == ChatVisibility::Archived {
694 transaction.execute(
695 "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
696 (MessageState::InNoticed, self, MessageState::InFresh),
697 )?;
698 }
699 transaction.execute(
700 "UPDATE chats SET archived=? WHERE id=?;",
701 (visibility, self),
702 )?;
703 Ok(())
704 })
705 .await?;
706
707 if visibility == ChatVisibility::Archived {
708 start_chat_ephemeral_timers(context, self).await?;
709 }
710
711 context.emit_msgs_changed_without_ids();
712 chatlist_events::emit_chatlist_changed(context);
713 chatlist_events::emit_chatlist_item_changed(context, self);
714
715 if sync.into() {
716 let chat = Chat::load_from_db(context, self).await?;
717 chat.sync(context, SyncAction::SetVisibility(visibility))
718 .await
719 .log_err(context)
720 .ok();
721 }
722 Ok(())
723 }
724
725 pub async fn unarchive_if_not_muted(
733 self,
734 context: &Context,
735 msg_state: MessageState,
736 ) -> Result<()> {
737 if msg_state != MessageState::InFresh {
738 context
739 .sql
740 .execute(
741 "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
742 AND NOT(muted_until=-1 OR muted_until>?)",
743 (self, time()),
744 )
745 .await?;
746 return Ok(());
747 }
748 let chat = Chat::load_from_db(context, self).await?;
749 if chat.visibility != ChatVisibility::Archived {
750 return Ok(());
751 }
752 if chat.is_muted() {
753 let unread_cnt = context
754 .sql
755 .count(
756 "SELECT COUNT(*)
757 FROM msgs
758 WHERE state=?
759 AND hidden=0
760 AND chat_id=?",
761 (MessageState::InFresh, self),
762 )
763 .await?;
764 if unread_cnt == 1 {
765 context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
767 }
768 return Ok(());
769 }
770 context
771 .sql
772 .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
773 .await?;
774 Ok(())
775 }
776
777 pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
780 if important {
781 debug_assert!(!msg_id.is_unset());
782
783 context.emit_incoming_msg(self, msg_id);
784 } else {
785 context.emit_msgs_changed(self, msg_id);
786 }
787 }
788
789 pub async fn delete(self, context: &Context) -> Result<()> {
791 self.delete_ex(context, Sync).await
792 }
793
794 pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
795 ensure!(
796 !self.is_special(),
797 "bad chat_id, can not be a special chat: {}",
798 self
799 );
800
801 let chat = Chat::load_from_db(context, self).await?;
802 let delete_msgs_target = context.get_delete_msgs_target().await?;
803 let sync_id = match sync {
804 Nosync => None,
805 Sync => chat.get_sync_id(context).await?,
806 };
807
808 context
809 .sql
810 .transaction(|transaction| {
811 transaction.execute(
812 "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
813 (delete_msgs_target, self,),
814 )?;
815 transaction.execute(
816 "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
817 (self,),
818 )?;
819 transaction.execute(
820 "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
821 (self,),
822 )?;
823 transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
824 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
825 transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
826 Ok(())
827 })
828 .await?;
829
830 context.emit_event(EventType::ChatDeleted { chat_id: self });
831 context.emit_msgs_changed_without_ids();
832
833 if let Some(id) = sync_id {
834 self::sync(context, id, SyncAction::Delete)
835 .await
836 .log_err(context)
837 .ok();
838 }
839
840 if chat.is_self_talk() {
841 let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
842 add_device_msg(context, None, Some(&mut msg)).await?;
843 }
844 chatlist_events::emit_chatlist_changed(context);
845
846 context
847 .set_config_internal(Config::LastHousekeeping, None)
848 .await?;
849 context.scheduler.interrupt_inbox().await;
850
851 Ok(())
852 }
853
854 pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
858 if self.is_special() {
859 return Ok(());
860 }
861
862 let changed = match &mut msg {
863 None => self.maybe_delete_draft(context).await?,
864 Some(msg) => self.do_set_draft(context, msg).await?,
865 };
866
867 if changed {
868 if msg.is_some() {
869 match self.get_draft_msg_id(context).await? {
870 Some(msg_id) => context.emit_msgs_changed(self, msg_id),
871 None => context.emit_msgs_changed_without_msg_id(self),
872 }
873 } else {
874 context.emit_msgs_changed_without_msg_id(self)
875 }
876 }
877
878 Ok(())
879 }
880
881 async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
883 let msg_id: Option<MsgId> = context
884 .sql
885 .query_get_value(
886 "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
887 (self, MessageState::OutDraft),
888 )
889 .await?;
890 Ok(msg_id)
891 }
892
893 pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
895 if self.is_special() {
896 return Ok(None);
897 }
898 match self.get_draft_msg_id(context).await? {
899 Some(draft_msg_id) => {
900 let msg = Message::load_from_db(context, draft_msg_id).await?;
901 Ok(Some(msg))
902 }
903 None => Ok(None),
904 }
905 }
906
907 async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
911 Ok(context
912 .sql
913 .execute(
914 "DELETE FROM msgs WHERE chat_id=? AND state=?",
915 (self, MessageState::OutDraft),
916 )
917 .await?
918 > 0)
919 }
920
921 async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
924 match msg.viewtype {
925 Viewtype::Unknown => bail!("Can not set draft of unknown type."),
926 Viewtype::Text => {
927 if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
928 bail!("No text and no quote in draft");
929 }
930 }
931 _ => {
932 if msg.viewtype == Viewtype::File {
933 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
934 .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
939 {
940 msg.viewtype = better_type;
941 }
942 }
943 if msg.viewtype == Viewtype::Vcard {
944 let blob = msg
945 .param
946 .get_file_blob(context)?
947 .context("no file stored in params")?;
948 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
949 }
950 }
951 }
952
953 msg.state = MessageState::OutDraft;
956 msg.chat_id = self;
957
958 if !msg.id.is_special() {
960 if let Some(old_draft) = self.get_draft(context).await? {
961 if old_draft.id == msg.id
962 && old_draft.chat_id == self
963 && old_draft.state == MessageState::OutDraft
964 {
965 let affected_rows = context
966 .sql.execute(
967 "UPDATE msgs
968 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
969 WHERE id=?7
970 AND (type <> ?2
971 OR txt <> ?3
972 OR txt_normalized <> ?4
973 OR param <> ?5
974 OR mime_in_reply_to <> ?6);",
975 (
976 time(),
977 msg.viewtype,
978 &msg.text,
979 message::normalize_text(&msg.text),
980 msg.param.to_string(),
981 msg.in_reply_to.as_deref().unwrap_or_default(),
982 msg.id,
983 ),
984 ).await?;
985 return Ok(affected_rows > 0);
986 }
987 }
988 }
989
990 let row_id = context
991 .sql
992 .transaction(|transaction| {
993 transaction.execute(
995 "DELETE FROM msgs WHERE chat_id=? AND state=?",
996 (self, MessageState::OutDraft),
997 )?;
998
999 transaction.execute(
1001 "INSERT INTO msgs (
1002 chat_id,
1003 rfc724_mid,
1004 from_id,
1005 timestamp,
1006 type,
1007 state,
1008 txt,
1009 txt_normalized,
1010 param,
1011 hidden,
1012 mime_in_reply_to)
1013 VALUES (?,?,?,?,?,?,?,?,?,?,?);",
1014 (
1015 self,
1016 &msg.rfc724_mid,
1017 ContactId::SELF,
1018 time(),
1019 msg.viewtype,
1020 MessageState::OutDraft,
1021 &msg.text,
1022 message::normalize_text(&msg.text),
1023 msg.param.to_string(),
1024 1,
1025 msg.in_reply_to.as_deref().unwrap_or_default(),
1026 ),
1027 )?;
1028
1029 Ok(transaction.last_insert_rowid())
1030 })
1031 .await?;
1032 msg.id = MsgId::new(row_id.try_into()?);
1033 Ok(true)
1034 }
1035
1036 pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
1038 let count = context
1039 .sql
1040 .count(
1041 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
1042 (self,),
1043 )
1044 .await?;
1045 Ok(count)
1046 }
1047
1048 pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
1050 let count = if self.is_archived_link() {
1061 context
1062 .sql
1063 .count(
1064 "SELECT COUNT(DISTINCT(m.chat_id))
1065 FROM msgs m
1066 LEFT JOIN chats c ON m.chat_id=c.id
1067 WHERE m.state=10
1068 and m.hidden=0
1069 AND m.chat_id>9
1070 AND c.blocked=0
1071 AND c.archived=1
1072 ",
1073 (),
1074 )
1075 .await?
1076 } else {
1077 context
1078 .sql
1079 .count(
1080 "SELECT COUNT(*)
1081 FROM msgs
1082 WHERE state=?
1083 AND hidden=0
1084 AND chat_id=?;",
1085 (MessageState::InFresh, self),
1086 )
1087 .await?
1088 };
1089 Ok(count)
1090 }
1091
1092 pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
1093 Ok(context
1094 .sql
1095 .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
1096 .await?
1097 .unwrap_or(0))
1098 }
1099
1100 pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
1103 let timestamp = context
1104 .sql
1105 .query_get_value(
1106 "SELECT MAX(timestamp)
1107 FROM msgs
1108 WHERE chat_id=?
1109 HAVING COUNT(*) > 0",
1110 (self,),
1111 )
1112 .await?;
1113 Ok(timestamp)
1114 }
1115
1116 pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
1122 let intersection: Vec<(ChatId, f64)> = context
1124 .sql
1125 .query_map(
1126 "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
1127 FROM chats_contacts as x
1128 JOIN chats_contacts as y
1129 WHERE x.contact_id > 9
1130 AND y.contact_id > 9
1131 AND x.add_timestamp >= x.remove_timestamp
1132 AND y.add_timestamp >= y.remove_timestamp
1133 AND x.chat_id=?
1134 AND y.chat_id<>x.chat_id
1135 AND y.chat_id>?
1136 GROUP BY y.chat_id",
1137 (self, DC_CHAT_ID_LAST_SPECIAL),
1138 |row| {
1139 let chat_id: ChatId = row.get(0)?;
1140 let intersection: f64 = row.get(1)?;
1141 Ok((chat_id, intersection))
1142 },
1143 |rows| {
1144 rows.collect::<std::result::Result<Vec<_>, _>>()
1145 .map_err(Into::into)
1146 },
1147 )
1148 .await
1149 .context("failed to calculate member set intersections")?;
1150
1151 let chat_size: HashMap<ChatId, f64> = context
1152 .sql
1153 .query_map(
1154 "SELECT chat_id, count(*) AS n
1155 FROM chats_contacts
1156 WHERE contact_id > ? AND chat_id > ?
1157 AND add_timestamp >= remove_timestamp
1158 GROUP BY chat_id",
1159 (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
1160 |row| {
1161 let chat_id: ChatId = row.get(0)?;
1162 let size: f64 = row.get(1)?;
1163 Ok((chat_id, size))
1164 },
1165 |rows| {
1166 rows.collect::<std::result::Result<HashMap<ChatId, f64>, _>>()
1167 .map_err(Into::into)
1168 },
1169 )
1170 .await
1171 .context("failed to count chat member sizes")?;
1172
1173 let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
1174 let mut chats_with_metrics = Vec::new();
1175 for (chat_id, intersection_size) in intersection {
1176 if intersection_size > 0.0 {
1177 let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
1178 let union_size = our_chat_size + other_chat_size - intersection_size;
1179 let metric = intersection_size / union_size;
1180 chats_with_metrics.push((chat_id, metric))
1181 }
1182 }
1183 chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
1184 metric2
1185 .partial_cmp(metric1)
1186 .unwrap_or(chat_id2.cmp(chat_id1))
1187 });
1188
1189 let mut res = Vec::new();
1191 let now = time();
1192 for (chat_id, metric) in chats_with_metrics {
1193 if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
1194 if now > chat_timestamp + 42 * 24 * 3600 {
1195 continue;
1197 }
1198 }
1199
1200 if metric < 0.1 {
1201 break;
1203 }
1204
1205 let chat = Chat::load_from_db(context, chat_id).await?;
1206 if chat.typ != Chattype::Group {
1207 continue;
1208 }
1209
1210 match chat.visibility {
1211 ChatVisibility::Normal | ChatVisibility::Pinned => {}
1212 ChatVisibility::Archived => continue,
1213 }
1214
1215 res.push((chat_id, metric));
1216 if res.len() >= 5 {
1217 break;
1218 }
1219 }
1220
1221 Ok(res)
1222 }
1223
1224 pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1228 let chat_ids: Vec<ChatId> = self
1229 .get_similar_chat_ids(context)
1230 .await
1231 .context("failed to get similar chat IDs")?
1232 .into_iter()
1233 .map(|(chat_id, _metric)| chat_id)
1234 .collect();
1235 let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1236 Ok(chatlist)
1237 }
1238
1239 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1240 let res: Option<String> = context
1241 .sql
1242 .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1243 .await?;
1244 Ok(res
1245 .map(|s| s.parse().unwrap_or_default())
1246 .unwrap_or_default())
1247 }
1248
1249 pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1251 let param = self.get_param(context).await?;
1252 let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1253 Ok(unpromoted)
1254 }
1255
1256 pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1258 let promoted = !self.is_unpromoted(context).await?;
1259 Ok(promoted)
1260 }
1261
1262 pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1264 Ok(self.get_param(context).await?.exists(Param::Selftalk))
1265 }
1266
1267 pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1269 Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1270 }
1271
1272 async fn parent_query<T, F>(
1273 self,
1274 context: &Context,
1275 fields: &str,
1276 state_out_min: MessageState,
1277 f: F,
1278 ) -> Result<Option<T>>
1279 where
1280 F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1281 T: Send + 'static,
1282 {
1283 let sql = &context.sql;
1284 let query = format!(
1285 "SELECT {fields} \
1286 FROM msgs \
1287 WHERE chat_id=? \
1288 AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1289 AND NOT hidden \
1290 AND download_state={} \
1291 AND from_id != {} \
1292 ORDER BY timestamp DESC, id DESC \
1293 LIMIT 1;",
1294 MessageState::InFresh as u32,
1295 MessageState::InSeen as u32,
1296 state_out_min as u32,
1297 DownloadState::Done as u32,
1300 ContactId::INFO.to_u32(),
1303 );
1304 sql.query_row_optional(&query, (self,), f).await
1305 }
1306
1307 async fn get_parent_mime_headers(
1308 self,
1309 context: &Context,
1310 state_out_min: MessageState,
1311 ) -> Result<Option<(String, String, String)>> {
1312 self.parent_query(
1313 context,
1314 "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1315 state_out_min,
1316 |row: &rusqlite::Row| {
1317 let rfc724_mid: String = row.get(0)?;
1318 let mime_in_reply_to: String = row.get(1)?;
1319 let mime_references: String = row.get(2)?;
1320 Ok((rfc724_mid, mime_in_reply_to, mime_references))
1321 },
1322 )
1323 .await
1324 }
1325
1326 pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1334 let chat = Chat::load_from_db(context, self).await?;
1335 if !chat.is_encrypted(context).await? {
1336 return Ok(stock_str::encr_none(context).await);
1337 }
1338
1339 let mut ret = stock_str::e2e_available(context).await + "\n";
1340
1341 for contact_id in get_chat_contacts(context, self)
1342 .await?
1343 .iter()
1344 .filter(|&contact_id| !contact_id.is_special())
1345 {
1346 let contact = Contact::get_by_id(context, *contact_id).await?;
1347 let addr = contact.get_addr();
1348 debug_assert!(contact.is_key_contact());
1349 let fingerprint = contact
1350 .fingerprint()
1351 .context("Contact does not have a fingerprint in encrypted chat")?;
1352 if contact.public_key(context).await?.is_some() {
1353 ret += &format!("\n{addr}\n{fingerprint}\n");
1354 } else {
1355 ret += &format!("\n{addr}\n(key missing)\n{fingerprint}\n");
1356 }
1357 }
1358
1359 Ok(ret.trim().to_string())
1360 }
1361
1362 pub fn to_u32(self) -> u32 {
1367 self.0
1368 }
1369
1370 pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1371 context
1372 .sql
1373 .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1374 .await?;
1375 Ok(())
1376 }
1377
1378 pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
1380 let protection_status = context
1381 .sql
1382 .query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
1383 .await?
1384 .unwrap_or_default();
1385 Ok(protection_status)
1386 }
1387
1388 pub(crate) async fn calc_sort_timestamp(
1397 self,
1398 context: &Context,
1399 message_timestamp: i64,
1400 always_sort_to_bottom: bool,
1401 received: bool,
1402 incoming: bool,
1403 ) -> Result<i64> {
1404 let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1405
1406 let last_msg_time: Option<i64> = if always_sort_to_bottom {
1407 context
1413 .sql
1414 .query_get_value(
1415 "SELECT MAX(timestamp)
1416 FROM msgs
1417 WHERE chat_id=? AND state!=?
1418 HAVING COUNT(*) > 0",
1419 (self, MessageState::OutDraft),
1420 )
1421 .await?
1422 } else if received {
1423 context
1434 .sql
1435 .query_row_optional(
1436 "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1437 FROM msgs
1438 WHERE chat_id=? AND hidden=0 AND state>?
1439 HAVING COUNT(*) > 0",
1440 (MessageState::InSeen, self, MessageState::InFresh),
1441 |row| {
1442 let ts: i64 = row.get(0)?;
1443 let ts_sent_seen: i64 = row.get(1)?;
1444 Ok((ts, ts_sent_seen))
1445 },
1446 )
1447 .await?
1448 .and_then(|(ts, ts_sent_seen)| {
1449 match incoming || ts_sent_seen <= message_timestamp {
1450 true => Some(ts),
1451 false => None,
1452 }
1453 })
1454 } else {
1455 None
1456 };
1457
1458 if let Some(last_msg_time) = last_msg_time {
1459 if last_msg_time > sort_timestamp {
1460 sort_timestamp = last_msg_time;
1461 }
1462 }
1463
1464 Ok(sort_timestamp)
1465 }
1466}
1467
1468impl std::fmt::Display for ChatId {
1469 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1470 if self.is_trash() {
1471 write!(f, "Chat#Trash")
1472 } else if self.is_archived_link() {
1473 write!(f, "Chat#ArchivedLink")
1474 } else if self.is_alldone_hint() {
1475 write!(f, "Chat#AlldoneHint")
1476 } else if self.is_special() {
1477 write!(f, "Chat#Special{}", self.0)
1478 } else {
1479 write!(f, "Chat#{}", self.0)
1480 }
1481 }
1482}
1483
1484impl rusqlite::types::ToSql for ChatId {
1489 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
1490 let val = rusqlite::types::Value::Integer(i64::from(self.0));
1491 let out = rusqlite::types::ToSqlOutput::Owned(val);
1492 Ok(out)
1493 }
1494}
1495
1496impl rusqlite::types::FromSql for ChatId {
1498 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1499 i64::column_result(value).and_then(|val| {
1500 if 0 <= val && val <= i64::from(u32::MAX) {
1501 Ok(ChatId::new(val as u32))
1502 } else {
1503 Err(rusqlite::types::FromSqlError::OutOfRange(val))
1504 }
1505 })
1506 }
1507}
1508
1509#[derive(Debug, Clone, Deserialize, Serialize)]
1514pub struct Chat {
1515 pub id: ChatId,
1517
1518 pub typ: Chattype,
1520
1521 pub name: String,
1523
1524 pub visibility: ChatVisibility,
1526
1527 pub grpid: String,
1530
1531 pub blocked: Blocked,
1533
1534 pub param: Params,
1536
1537 is_sending_locations: bool,
1539
1540 pub mute_duration: MuteDuration,
1542
1543 pub(crate) protected: ProtectionStatus,
1545}
1546
1547impl Chat {
1548 pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1550 let mut chat = context
1551 .sql
1552 .query_row(
1553 "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1554 c.blocked, c.locations_send_until, c.muted_until, c.protected
1555 FROM chats c
1556 WHERE c.id=?;",
1557 (chat_id,),
1558 |row| {
1559 let c = Chat {
1560 id: chat_id,
1561 typ: row.get(0)?,
1562 name: row.get::<_, String>(1)?,
1563 grpid: row.get::<_, String>(2)?,
1564 param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1565 visibility: row.get(4)?,
1566 blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1567 is_sending_locations: row.get(6)?,
1568 mute_duration: row.get(7)?,
1569 protected: row.get(8)?,
1570 };
1571 Ok(c)
1572 },
1573 )
1574 .await
1575 .context(format!("Failed loading chat {chat_id} from database"))?;
1576
1577 if chat.id.is_archived_link() {
1578 chat.name = stock_str::archived_chats(context).await;
1579 } else {
1580 if chat.typ == Chattype::Single && chat.name.is_empty() {
1581 let mut chat_name = "Err [Name not found]".to_owned();
1584 match get_chat_contacts(context, chat.id).await {
1585 Ok(contacts) => {
1586 if let Some(contact_id) = contacts.first() {
1587 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1588 contact.get_display_name().clone_into(&mut chat_name);
1589 }
1590 }
1591 }
1592 Err(err) => {
1593 error!(
1594 context,
1595 "Failed to load contacts for {}: {:#}.", chat.id, err
1596 );
1597 }
1598 }
1599 chat.name = chat_name;
1600 }
1601 if chat.param.exists(Param::Selftalk) {
1602 chat.name = stock_str::saved_messages(context).await;
1603 } else if chat.param.exists(Param::Devicetalk) {
1604 chat.name = stock_str::device_messages(context).await;
1605 }
1606 }
1607
1608 Ok(chat)
1609 }
1610
1611 pub fn is_self_talk(&self) -> bool {
1613 self.param.exists(Param::Selftalk)
1614 }
1615
1616 pub fn is_device_talk(&self) -> bool {
1618 self.param.exists(Param::Devicetalk)
1619 }
1620
1621 pub fn is_mailing_list(&self) -> bool {
1623 self.typ == Chattype::Mailinglist
1624 }
1625
1626 pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1630 self.why_cant_send_ex(context, &|_| false).await
1631 }
1632
1633 pub(crate) async fn why_cant_send_ex(
1634 &self,
1635 context: &Context,
1636 skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1637 ) -> Result<Option<CantSendReason>> {
1638 use CantSendReason::*;
1639 if self.id.is_special() {
1642 let reason = SpecialChat;
1643 if !skip_fn(&reason) {
1644 return Ok(Some(reason));
1645 }
1646 }
1647 if self.is_device_talk() {
1648 let reason = DeviceChat;
1649 if !skip_fn(&reason) {
1650 return Ok(Some(reason));
1651 }
1652 }
1653 if self.is_contact_request() {
1654 let reason = ContactRequest;
1655 if !skip_fn(&reason) {
1656 return Ok(Some(reason));
1657 }
1658 }
1659 if self.is_protection_broken() {
1660 let reason = ProtectionBroken;
1661 if !skip_fn(&reason) {
1662 return Ok(Some(reason));
1663 }
1664 }
1665 if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1666 let reason = ReadOnlyMailingList;
1667 if !skip_fn(&reason) {
1668 return Ok(Some(reason));
1669 }
1670 }
1671 if self.typ == Chattype::InBroadcast {
1672 let reason = InBroadcast;
1673 if !skip_fn(&reason) {
1674 return Ok(Some(reason));
1675 }
1676 }
1677
1678 let reason = NotAMember;
1680 if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1681 return Ok(Some(reason));
1682 }
1683
1684 let reason = MissingKey;
1685 if !skip_fn(&reason) && self.typ == Chattype::Single {
1686 let contact_ids = get_chat_contacts(context, self.id).await?;
1687 if let Some(contact_id) = contact_ids.first() {
1688 let contact = Contact::get_by_id(context, *contact_id).await?;
1689 if contact.is_key_contact() && contact.public_key(context).await?.is_none() {
1690 return Ok(Some(reason));
1691 }
1692 }
1693 }
1694
1695 Ok(None)
1696 }
1697
1698 pub async fn can_send(&self, context: &Context) -> Result<bool> {
1702 Ok(self.why_cant_send(context).await?.is_none())
1703 }
1704
1705 pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1709 match self.typ {
1710 Chattype::Single | Chattype::OutBroadcast | Chattype::Mailinglist => Ok(true),
1711 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1712 Chattype::InBroadcast => Ok(false),
1713 }
1714 }
1715
1716 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1717 context
1718 .sql
1719 .execute(
1720 "UPDATE chats SET param=? WHERE id=?",
1721 (self.param.to_string(), self.id),
1722 )
1723 .await?;
1724 Ok(())
1725 }
1726
1727 pub fn get_id(&self) -> ChatId {
1729 self.id
1730 }
1731
1732 pub fn get_type(&self) -> Chattype {
1734 self.typ
1735 }
1736
1737 pub fn get_name(&self) -> &str {
1739 &self.name
1740 }
1741
1742 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1744 self.param.get(Param::ListPost)
1745 }
1746
1747 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1749 if self.id.is_archived_link() {
1750 return Ok(Some(get_archive_icon(context).await?));
1753 } else if self.is_device_talk() {
1754 return Ok(Some(get_device_icon(context).await?));
1755 } else if self.is_self_talk() {
1756 return Ok(Some(get_saved_messages_icon(context).await?));
1757 } else if self.typ == Chattype::Single {
1758 let contacts = get_chat_contacts(context, self.id).await?;
1762 if let Some(contact_id) = contacts.first() {
1763 let contact = Contact::get_by_id(context, *contact_id).await?;
1764 return contact.get_profile_image(context).await;
1765 }
1766 } else if !self.is_encrypted(context).await? {
1767 return Ok(Some(get_abs_path(
1769 context,
1770 Path::new(&get_address_contact_icon(context).await?),
1771 )));
1772 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1773 if !image_rel.is_empty() {
1775 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1776 }
1777 }
1778 Ok(None)
1779 }
1780
1781 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1786 let mut color = 0;
1787
1788 if self.typ == Chattype::Single {
1789 let contacts = get_chat_contacts(context, self.id).await?;
1790 if let Some(contact_id) = contacts.first() {
1791 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1792 color = contact.get_color();
1793 }
1794 }
1795 } else {
1796 color = str_to_color(&self.name);
1797 }
1798
1799 Ok(color)
1800 }
1801
1802 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1807 let draft = match self.id.get_draft(context).await? {
1808 Some(message) => message.text,
1809 _ => String::new(),
1810 };
1811 Ok(ChatInfo {
1812 id: self.id,
1813 type_: self.typ as u32,
1814 name: self.name.clone(),
1815 archived: self.visibility == ChatVisibility::Archived,
1816 param: self.param.to_string(),
1817 is_sending_locations: self.is_sending_locations,
1818 color: self.get_color(context).await?,
1819 profile_image: self
1820 .get_profile_image(context)
1821 .await?
1822 .unwrap_or_else(std::path::PathBuf::new),
1823 draft,
1824 is_muted: self.is_muted(),
1825 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1826 })
1827 }
1828
1829 pub fn get_visibility(&self) -> ChatVisibility {
1831 self.visibility
1832 }
1833
1834 pub fn is_contact_request(&self) -> bool {
1839 self.blocked == Blocked::Request
1840 }
1841
1842 pub fn is_unpromoted(&self) -> bool {
1844 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1845 }
1846
1847 pub fn is_promoted(&self) -> bool {
1850 !self.is_unpromoted()
1851 }
1852
1853 pub fn is_protected(&self) -> bool {
1864 self.protected == ProtectionStatus::Protected
1865 }
1866
1867 pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
1869 let is_encrypted = self.is_protected()
1870 || match self.typ {
1871 Chattype::Single => {
1872 let chat_contact_ids = get_chat_contacts(context, self.id).await?;
1873 if let Some(contact_id) = chat_contact_ids.first() {
1874 if *contact_id == ContactId::DEVICE {
1875 true
1876 } else {
1877 let contact = Contact::get_by_id(context, *contact_id).await?;
1878 contact.is_key_contact()
1879 }
1880 } else {
1881 true
1882 }
1883 }
1884 Chattype::Group => {
1885 !self.grpid.is_empty()
1887 }
1888 Chattype::Mailinglist => false,
1889 Chattype::OutBroadcast | Chattype::InBroadcast => true,
1890 };
1891 Ok(is_encrypted)
1892 }
1893
1894 pub fn is_protection_broken(&self) -> bool {
1908 match self.protected {
1909 ProtectionStatus::Protected => false,
1910 ProtectionStatus::Unprotected => false,
1911 ProtectionStatus::ProtectionBroken => true,
1912 }
1913 }
1914
1915 pub fn is_sending_locations(&self) -> bool {
1917 self.is_sending_locations
1918 }
1919
1920 pub fn is_muted(&self) -> bool {
1922 match self.mute_duration {
1923 MuteDuration::NotMuted => false,
1924 MuteDuration::Forever => true,
1925 MuteDuration::Until(when) => when > SystemTime::now(),
1926 }
1927 }
1928
1929 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1931 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1932 Ok(member_list_timestamp)
1933 } else {
1934 Ok(self.id.created_timestamp(context).await?)
1935 }
1936 }
1937
1938 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1944 let now = time();
1945 let member_list_ts = self.member_list_timestamp(context).await?;
1946 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1947 >= member_list_ts.saturating_add(60 * 24 * 3600);
1948 Ok(is_stale)
1949 }
1950
1951 async fn prepare_msg_raw(
1957 &mut self,
1958 context: &Context,
1959 msg: &mut Message,
1960 update_msg_id: Option<MsgId>,
1961 timestamp: i64,
1962 ) -> Result<MsgId> {
1963 let mut to_id = 0;
1964 let mut location_id = 0;
1965
1966 if msg.rfc724_mid.is_empty() {
1967 msg.rfc724_mid = create_outgoing_rfc724_mid();
1968 }
1969
1970 if self.typ == Chattype::Single {
1971 if let Some(id) = context
1972 .sql
1973 .query_get_value(
1974 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
1975 (self.id,),
1976 )
1977 .await?
1978 {
1979 to_id = id;
1980 } else {
1981 error!(
1982 context,
1983 "Cannot send message, contact for {} not found.", self.id,
1984 );
1985 bail!("Cannot set message, contact for {} not found.", self.id);
1986 }
1987 } else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
1988 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
1989 {
1990 msg.param.set_int(Param::AttachGroupImage, 1);
1991 self.param
1992 .remove(Param::Unpromoted)
1993 .set_i64(Param::GroupNameTimestamp, timestamp);
1994 self.update_param(context).await?;
1995 context
2001 .sync_qr_code_tokens(Some(self.grpid.as_str()))
2002 .await
2003 .log_err(context)
2004 .ok();
2005 }
2006
2007 let is_bot = context.get_config_bool(Config::Bot).await?;
2008 msg.param
2009 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2010
2011 let new_references;
2015 if self.is_self_talk() {
2016 new_references = String::new();
2019 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2020 self
2026 .id
2027 .get_parent_mime_headers(context, MessageState::OutPending)
2028 .await?
2029 {
2030 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2034 msg.in_reply_to = Some(parent_rfc724_mid.clone());
2035 }
2036
2037 let parent_references = if parent_references.is_empty() {
2047 parent_in_reply_to
2048 } else {
2049 parent_references
2050 };
2051
2052 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2055 references_vec.reverse();
2056
2057 if !parent_rfc724_mid.is_empty()
2058 && !references_vec.contains(&parent_rfc724_mid.as_str())
2059 {
2060 references_vec.push(&parent_rfc724_mid)
2061 }
2062
2063 if references_vec.is_empty() {
2064 new_references = msg.rfc724_mid.clone();
2067 } else {
2068 new_references = references_vec.join(" ");
2069 }
2070 } else {
2071 new_references = msg.rfc724_mid.clone();
2077 }
2078
2079 if msg.param.exists(Param::SetLatitude) {
2081 if let Ok(row_id) = context
2082 .sql
2083 .insert(
2084 "INSERT INTO locations \
2085 (timestamp,from_id,chat_id, latitude,longitude,independent)\
2086 VALUES (?,?,?, ?,?,1);",
2087 (
2088 timestamp,
2089 ContactId::SELF,
2090 self.id,
2091 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2092 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2093 ),
2094 )
2095 .await
2096 {
2097 location_id = row_id;
2098 }
2099 }
2100
2101 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2102 EphemeralTimer::Disabled
2103 } else {
2104 self.id.get_ephemeral_timer(context).await?
2105 };
2106 let ephemeral_timestamp = match ephemeral_timer {
2107 EphemeralTimer::Disabled => 0,
2108 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2109 };
2110
2111 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2112 let new_mime_headers = if msg.has_html() {
2113 if msg.param.exists(Param::Forwarded) {
2114 msg.get_id().get_html(context).await?
2115 } else {
2116 msg.param.get(Param::SendHtml).map(|s| s.to_string())
2117 }
2118 } else {
2119 None
2120 };
2121 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2122 let html_part = MimePart::new("text/html", s);
2123 let mut buffer = Vec::new();
2124 let cursor = Cursor::new(&mut buffer);
2125 html_part.write_part(cursor).ok();
2126 String::from_utf8_lossy(&buffer).to_string()
2127 });
2128 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2129 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2133 false => None,
2134 });
2135 let new_mime_headers = match new_mime_headers {
2136 Some(h) => Some(tokio::task::block_in_place(move || {
2137 buf_compress(h.as_bytes())
2138 })?),
2139 None => None,
2140 };
2141
2142 msg.chat_id = self.id;
2143 msg.from_id = ContactId::SELF;
2144 msg.timestamp_sort = timestamp;
2145
2146 if let Some(update_msg_id) = update_msg_id {
2148 context
2149 .sql
2150 .execute(
2151 "UPDATE msgs
2152 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2153 state=?, txt=?, txt_normalized=?, subject=?, param=?,
2154 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2155 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2156 ephemeral_timestamp=?
2157 WHERE id=?;",
2158 params_slice![
2159 msg.rfc724_mid,
2160 msg.chat_id,
2161 msg.from_id,
2162 to_id,
2163 msg.timestamp_sort,
2164 msg.viewtype,
2165 msg.state,
2166 msg_text,
2167 message::normalize_text(&msg_text),
2168 &msg.subject,
2169 msg.param.to_string(),
2170 msg.hidden,
2171 msg.in_reply_to.as_deref().unwrap_or_default(),
2172 new_references,
2173 new_mime_headers.is_some(),
2174 new_mime_headers.unwrap_or_default(),
2175 location_id as i32,
2176 ephemeral_timer,
2177 ephemeral_timestamp,
2178 update_msg_id
2179 ],
2180 )
2181 .await?;
2182 msg.id = update_msg_id;
2183 } else {
2184 let raw_id = context
2185 .sql
2186 .insert(
2187 "INSERT INTO msgs (
2188 rfc724_mid,
2189 chat_id,
2190 from_id,
2191 to_id,
2192 timestamp,
2193 type,
2194 state,
2195 txt,
2196 txt_normalized,
2197 subject,
2198 param,
2199 hidden,
2200 mime_in_reply_to,
2201 mime_references,
2202 mime_modified,
2203 mime_headers,
2204 mime_compressed,
2205 location_id,
2206 ephemeral_timer,
2207 ephemeral_timestamp)
2208 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2209 params_slice![
2210 msg.rfc724_mid,
2211 msg.chat_id,
2212 msg.from_id,
2213 to_id,
2214 msg.timestamp_sort,
2215 msg.viewtype,
2216 msg.state,
2217 msg_text,
2218 message::normalize_text(&msg_text),
2219 &msg.subject,
2220 msg.param.to_string(),
2221 msg.hidden,
2222 msg.in_reply_to.as_deref().unwrap_or_default(),
2223 new_references,
2224 new_mime_headers.is_some(),
2225 new_mime_headers.unwrap_or_default(),
2226 location_id as i32,
2227 ephemeral_timer,
2228 ephemeral_timestamp
2229 ],
2230 )
2231 .await?;
2232 context.new_msgs_notify.notify_one();
2233 msg.id = MsgId::new(u32::try_from(raw_id)?);
2234
2235 maybe_set_logging_xdc(context, msg, self.id).await?;
2236 context
2237 .update_webxdc_integration_database(msg, context)
2238 .await?;
2239 }
2240 context.scheduler.interrupt_ephemeral_task().await;
2241 Ok(msg.id)
2242 }
2243
2244 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2246 if self.is_encrypted(context).await? {
2247 let fingerprint_addrs = context
2248 .sql
2249 .query_map(
2250 "SELECT c.fingerprint, c.addr
2251 FROM contacts c INNER JOIN chats_contacts cc
2252 ON c.id=cc.contact_id
2253 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2254 (self.id,),
2255 |row| {
2256 let fingerprint = row.get(0)?;
2257 let addr = row.get(1)?;
2258 Ok((fingerprint, addr))
2259 },
2260 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2261 )
2262 .await?;
2263 self.sync(context, SyncAction::SetPgpContacts(fingerprint_addrs))
2264 .await?;
2265 } else {
2266 let addrs = context
2267 .sql
2268 .query_map(
2269 "SELECT c.addr \
2270 FROM contacts c INNER JOIN chats_contacts cc \
2271 ON c.id=cc.contact_id \
2272 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2273 (self.id,),
2274 |row| row.get::<_, String>(0),
2275 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2276 )
2277 .await?;
2278 self.sync(context, SyncAction::SetContacts(addrs)).await?;
2279 }
2280 Ok(())
2281 }
2282
2283 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2285 match self.typ {
2286 Chattype::Single => {
2287 if self.is_device_talk() {
2288 return Ok(Some(SyncId::Device));
2289 }
2290
2291 let mut r = None;
2292 for contact_id in get_chat_contacts(context, self.id).await? {
2293 if contact_id == ContactId::SELF && !self.is_self_talk() {
2294 continue;
2295 }
2296 if r.is_some() {
2297 return Ok(None);
2298 }
2299 let contact = Contact::get_by_id(context, contact_id).await?;
2300 if let Some(fingerprint) = contact.fingerprint() {
2301 r = Some(SyncId::ContactFingerprint(fingerprint.hex()));
2302 } else {
2303 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2304 }
2305 }
2306 Ok(r)
2307 }
2308 Chattype::OutBroadcast
2309 | Chattype::InBroadcast
2310 | Chattype::Group
2311 | Chattype::Mailinglist => {
2312 if !self.grpid.is_empty() {
2313 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2314 }
2315
2316 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2317 .id
2318 .get_parent_mime_headers(context, MessageState::OutDelivered)
2319 .await?
2320 else {
2321 warn!(
2322 context,
2323 "Chat::get_sync_id({}): No good message identifying the chat found.",
2324 self.id
2325 );
2326 return Ok(None);
2327 };
2328 Ok(Some(SyncId::Msgids(vec![
2329 parent_in_reply_to,
2330 parent_rfc724_mid,
2331 ])))
2332 }
2333 }
2334 }
2335
2336 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2338 if let Some(id) = self.get_sync_id(context).await? {
2339 sync(context, id, action).await?;
2340 }
2341 Ok(())
2342 }
2343}
2344
2345pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2346 context
2347 .add_sync_item(SyncData::AlterChat { id, action })
2348 .await?;
2349 context.scheduler.interrupt_inbox().await;
2350 Ok(())
2351}
2352
2353#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2355#[repr(i8)]
2356pub enum ChatVisibility {
2357 Normal = 0,
2359
2360 Archived = 1,
2362
2363 Pinned = 2,
2365}
2366
2367impl rusqlite::types::ToSql for ChatVisibility {
2368 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
2369 let val = rusqlite::types::Value::Integer(*self as i64);
2370 let out = rusqlite::types::ToSqlOutput::Owned(val);
2371 Ok(out)
2372 }
2373}
2374
2375impl rusqlite::types::FromSql for ChatVisibility {
2376 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2377 i64::column_result(value).map(|val| {
2378 match val {
2379 2 => ChatVisibility::Pinned,
2380 1 => ChatVisibility::Archived,
2381 0 => ChatVisibility::Normal,
2382 _ => ChatVisibility::Normal,
2384 }
2385 })
2386 }
2387}
2388
2389#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2391#[non_exhaustive]
2392pub struct ChatInfo {
2393 pub id: ChatId,
2395
2396 #[serde(rename = "type")]
2403 pub type_: u32,
2404
2405 pub name: String,
2407
2408 pub archived: bool,
2410
2411 pub param: String,
2415
2416 pub is_sending_locations: bool,
2418
2419 pub color: u32,
2423
2424 pub profile_image: std::path::PathBuf,
2429
2430 pub draft: String,
2438
2439 pub is_muted: bool,
2443
2444 pub ephemeral_timer: EphemeralTimer,
2446 }
2452
2453async fn get_asset_icon(context: &Context, name: &str, bytes: &[u8]) -> Result<PathBuf> {
2454 ensure!(name.starts_with("icon-"));
2455 if let Some(icon) = context.sql.get_raw_config(name).await? {
2456 return Ok(get_abs_path(context, Path::new(&icon)));
2457 }
2458
2459 let blob =
2460 BlobObject::create_and_deduplicate_from_bytes(context, bytes, &format!("{name}.png"))?;
2461 let icon = blob.as_name().to_string();
2462 context.sql.set_raw_config(name, Some(&icon)).await?;
2463
2464 Ok(get_abs_path(context, Path::new(&icon)))
2465}
2466
2467pub(crate) async fn get_saved_messages_icon(context: &Context) -> Result<PathBuf> {
2468 get_asset_icon(
2469 context,
2470 "icon-saved-messages",
2471 include_bytes!("../assets/icon-saved-messages.png"),
2472 )
2473 .await
2474}
2475
2476pub(crate) async fn get_device_icon(context: &Context) -> Result<PathBuf> {
2477 get_asset_icon(
2478 context,
2479 "icon-device",
2480 include_bytes!("../assets/icon-device.png"),
2481 )
2482 .await
2483}
2484
2485pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
2486 get_asset_icon(
2487 context,
2488 "icon-archive",
2489 include_bytes!("../assets/icon-archive.png"),
2490 )
2491 .await
2492}
2493
2494pub(crate) async fn get_address_contact_icon(context: &Context) -> Result<PathBuf> {
2495 get_asset_icon(
2496 context,
2497 "icon-address-contact",
2498 include_bytes!("../assets/icon-address-contact.png"),
2499 )
2500 .await
2501}
2502
2503async fn update_special_chat_name(
2504 context: &Context,
2505 contact_id: ContactId,
2506 name: String,
2507) -> Result<()> {
2508 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2509 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2510 {
2511 context
2513 .sql
2514 .execute(
2515 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2516 (&name, chat_id, &name),
2517 )
2518 .await?;
2519 }
2520 Ok(())
2521}
2522
2523pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2524 update_special_chat_name(
2525 context,
2526 ContactId::DEVICE,
2527 stock_str::device_messages(context).await,
2528 )
2529 .await?;
2530 update_special_chat_name(
2531 context,
2532 ContactId::SELF,
2533 stock_str::saved_messages(context).await,
2534 )
2535 .await?;
2536 Ok(())
2537}
2538
2539#[derive(Debug)]
2547pub(crate) struct ChatIdBlocked {
2548 pub id: ChatId,
2550
2551 pub blocked: Blocked,
2553}
2554
2555impl ChatIdBlocked {
2556 pub async fn lookup_by_contact(
2560 context: &Context,
2561 contact_id: ContactId,
2562 ) -> Result<Option<Self>> {
2563 ensure!(context.sql.is_open().await, "Database not available");
2564 ensure!(
2565 contact_id != ContactId::UNDEFINED,
2566 "Invalid contact id requested"
2567 );
2568
2569 context
2570 .sql
2571 .query_row_optional(
2572 "SELECT c.id, c.blocked
2573 FROM chats c
2574 INNER JOIN chats_contacts j
2575 ON c.id=j.chat_id
2576 WHERE c.type=100 -- 100 = Chattype::Single
2577 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2578 AND j.contact_id=?;",
2579 (contact_id,),
2580 |row| {
2581 let id: ChatId = row.get(0)?;
2582 let blocked: Blocked = row.get(1)?;
2583 Ok(ChatIdBlocked { id, blocked })
2584 },
2585 )
2586 .await
2587 }
2588
2589 pub async fn get_for_contact(
2594 context: &Context,
2595 contact_id: ContactId,
2596 create_blocked: Blocked,
2597 ) -> Result<Self> {
2598 ensure!(context.sql.is_open().await, "Database not available");
2599 ensure!(
2600 contact_id != ContactId::UNDEFINED,
2601 "Invalid contact id requested"
2602 );
2603
2604 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2605 return Ok(res);
2607 }
2608
2609 let contact = Contact::get_by_id(context, contact_id).await?;
2610 let chat_name = contact.get_display_name().to_string();
2611 let mut params = Params::new();
2612 match contact_id {
2613 ContactId::SELF => {
2614 params.set_int(Param::Selftalk, 1);
2615 }
2616 ContactId::DEVICE => {
2617 params.set_int(Param::Devicetalk, 1);
2618 }
2619 _ => (),
2620 }
2621
2622 let protected = contact_id == ContactId::SELF || contact.is_verified(context).await?;
2623 let smeared_time = create_smeared_timestamp(context);
2624
2625 let chat_id = context
2626 .sql
2627 .transaction(move |transaction| {
2628 transaction.execute(
2629 "INSERT INTO chats
2630 (type, name, param, blocked, created_timestamp, protected)
2631 VALUES(?, ?, ?, ?, ?, ?)",
2632 (
2633 Chattype::Single,
2634 chat_name,
2635 params.to_string(),
2636 create_blocked as u8,
2637 smeared_time,
2638 if protected {
2639 ProtectionStatus::Protected
2640 } else {
2641 ProtectionStatus::Unprotected
2642 },
2643 ),
2644 )?;
2645 let chat_id = ChatId::new(
2646 transaction
2647 .last_insert_rowid()
2648 .try_into()
2649 .context("chat table rowid overflows u32")?,
2650 );
2651
2652 transaction.execute(
2653 "INSERT INTO chats_contacts
2654 (chat_id, contact_id)
2655 VALUES((SELECT last_insert_rowid()), ?)",
2656 (contact_id,),
2657 )?;
2658
2659 Ok(chat_id)
2660 })
2661 .await?;
2662
2663 if protected {
2664 chat_id
2665 .add_protection_msg(
2666 context,
2667 ProtectionStatus::Protected,
2668 Some(contact_id),
2669 smeared_time,
2670 )
2671 .await?;
2672 }
2673
2674 Ok(Self {
2675 id: chat_id,
2676 blocked: create_blocked,
2677 })
2678 }
2679}
2680
2681async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2682 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
2683 } else if msg.viewtype.has_file() {
2685 let mut blob = msg
2686 .param
2687 .get_file_blob(context)?
2688 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2689 let send_as_is = msg.viewtype == Viewtype::File;
2690
2691 if msg.viewtype == Viewtype::File
2692 || msg.viewtype == Viewtype::Image
2693 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2694 {
2695 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2702 if msg.viewtype == Viewtype::Sticker {
2703 if better_type != Viewtype::Image {
2704 msg.param.set_int(Param::ForceSticker, 1);
2706 }
2707 } else if better_type != Viewtype::Webxdc
2708 || context
2709 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2710 .await
2711 .is_ok()
2712 {
2713 msg.viewtype = better_type;
2714 }
2715 }
2716 } else if msg.viewtype == Viewtype::Webxdc {
2717 context
2718 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2719 .await?;
2720 }
2721
2722 if msg.viewtype == Viewtype::Vcard {
2723 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2724 }
2725
2726 let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
2727 if !send_as_is
2728 && (msg.viewtype == Viewtype::Image
2729 || maybe_sticker && !msg.param.exists(Param::ForceSticker))
2730 {
2731 let new_name = blob
2732 .recode_to_image_size(context, msg.get_filename(), &mut maybe_sticker)
2733 .await?;
2734 msg.param.set(Param::Filename, new_name);
2735 msg.param.set(Param::File, blob.as_name());
2736
2737 if !maybe_sticker {
2738 msg.viewtype = Viewtype::Image;
2739 }
2740 }
2741
2742 if !msg.param.exists(Param::MimeType) {
2743 if let Some((_, mime)) = message::guess_msgtype_from_suffix(msg) {
2744 msg.param.set(Param::MimeType, mime);
2745 }
2746 }
2747
2748 msg.try_calc_and_set_dimensions(context).await?;
2749
2750 info!(
2751 context,
2752 "Attaching \"{}\" for message type #{}.",
2753 blob.to_abs_path().display(),
2754 msg.viewtype
2755 );
2756 } else {
2757 bail!("Cannot send messages of type #{}.", msg.viewtype);
2758 }
2759 Ok(())
2760}
2761
2762pub async fn is_contact_in_chat(
2764 context: &Context,
2765 chat_id: ChatId,
2766 contact_id: ContactId,
2767) -> Result<bool> {
2768 let exists = context
2774 .sql
2775 .exists(
2776 "SELECT COUNT(*) FROM chats_contacts
2777 WHERE chat_id=? AND contact_id=?
2778 AND add_timestamp >= remove_timestamp",
2779 (chat_id, contact_id),
2780 )
2781 .await?;
2782 Ok(exists)
2783}
2784
2785pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2792 ensure!(
2793 !chat_id.is_special(),
2794 "chat_id cannot be a special chat: {chat_id}"
2795 );
2796
2797 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2798 msg.param.remove(Param::GuaranteeE2ee);
2799 msg.param.remove(Param::ForcePlaintext);
2800 msg.update_param(context).await?;
2801 }
2802
2803 if msg.is_system_message() {
2805 msg.text = sanitize_bidi_characters(&msg.text);
2806 }
2807
2808 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2809 if !msg.hidden {
2810 context.emit_msgs_changed(msg.chat_id, msg.id);
2811 }
2812
2813 if msg.param.exists(Param::SetLatitude) {
2814 context.emit_location_changed(Some(ContactId::SELF)).await?;
2815 }
2816
2817 context.scheduler.interrupt_smtp().await;
2818 }
2819
2820 Ok(msg.id)
2821}
2822
2823pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2828 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2829 if rowids.is_empty() {
2830 return Ok(msg.id);
2831 }
2832 let mut smtp = crate::smtp::Smtp::new();
2833 for rowid in rowids {
2834 send_msg_to_smtp(context, &mut smtp, rowid)
2835 .await
2836 .context("failed to send message, queued for later sending")?;
2837 }
2838 context.emit_msgs_changed(msg.chat_id, msg.id);
2839 Ok(msg.id)
2840}
2841
2842async fn prepare_send_msg(
2846 context: &Context,
2847 chat_id: ChatId,
2848 msg: &mut Message,
2849) -> Result<Vec<i64>> {
2850 let mut chat = Chat::load_from_db(context, chat_id).await?;
2851
2852 let skip_fn = |reason: &CantSendReason| match reason {
2853 CantSendReason::ProtectionBroken | CantSendReason::ContactRequest => {
2854 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2857 }
2858 CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2862 CantSendReason::MissingKey => msg
2863 .param
2864 .get_bool(Param::ForcePlaintext)
2865 .unwrap_or_default(),
2866 _ => false,
2867 };
2868 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2869 bail!("Cannot send to {chat_id}: {reason}");
2870 }
2871
2872 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2877 if let Some(quoted_message) = msg.quoted_message(context).await? {
2878 if quoted_message.chat_id != chat_id {
2879 bail!(
2880 "Quote of message from {} cannot be sent to {chat_id}",
2881 quoted_message.chat_id
2882 );
2883 }
2884 }
2885 }
2886
2887 let update_msg_id = if msg.state == MessageState::OutDraft {
2889 msg.hidden = false;
2890 if !msg.id.is_special() && msg.chat_id == chat_id {
2891 Some(msg.id)
2892 } else {
2893 None
2894 }
2895 } else {
2896 None
2897 };
2898
2899 msg.state = MessageState::OutPending;
2901
2902 prepare_msg_blob(context, msg).await?;
2903 if !msg.hidden {
2904 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2905 }
2906 msg.id = chat
2907 .prepare_msg_raw(
2908 context,
2909 msg,
2910 update_msg_id,
2911 create_smeared_timestamp(context),
2912 )
2913 .await?;
2914 msg.chat_id = chat_id;
2915
2916 let row_ids = create_send_msg_jobs(context, msg)
2917 .await
2918 .context("Failed to create send jobs")?;
2919 Ok(row_ids)
2920}
2921
2922pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2928 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2929 msg.chat_id
2930 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2931 .await?;
2932 }
2933
2934 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2935 let mimefactory = MimeFactory::from_msg(context, msg.clone()).await?;
2936 let attach_selfavatar = mimefactory.attach_selfavatar;
2937 let mut recipients = mimefactory.recipients();
2938
2939 let from = context.get_primary_self_addr().await?;
2940 let lowercase_from = from.to_lowercase();
2941
2942 recipients.retain(|x| x.to_lowercase() != lowercase_from);
2955 if (context.get_config_bool(Config::BccSelf).await?
2956 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
2957 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
2958 {
2959 recipients.push(from);
2960 }
2961
2962 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2964 recipients.clear();
2965 }
2966
2967 if recipients.is_empty() {
2968 info!(
2970 context,
2971 "Message {} has no recipient, skipping smtp-send.", msg.id
2972 );
2973 msg.param.set_int(Param::GuaranteeE2ee, 1);
2974 msg.update_param(context).await?;
2975 msg.id.set_delivered(context).await?;
2976 msg.state = MessageState::OutDelivered;
2977 return Ok(Vec::new());
2978 }
2979
2980 let rendered_msg = match mimefactory.render(context).await {
2981 Ok(res) => Ok(res),
2982 Err(err) => {
2983 message::set_msg_failed(context, msg, &err.to_string()).await?;
2984 Err(err)
2985 }
2986 }?;
2987
2988 if needs_encryption && !rendered_msg.is_encrypted {
2989 message::set_msg_failed(
2991 context,
2992 msg,
2993 "End-to-end-encryption unavailable unexpectedly.",
2994 )
2995 .await?;
2996 bail!(
2997 "e2e encryption unavailable {} - {:?}",
2998 msg.id,
2999 needs_encryption
3000 );
3001 }
3002
3003 let now = smeared_time(context);
3004
3005 if rendered_msg.last_added_location_id.is_some() {
3006 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3007 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3008 }
3009 }
3010
3011 if attach_selfavatar {
3012 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3013 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3014 }
3015 }
3016
3017 if rendered_msg.is_encrypted && !needs_encryption {
3018 msg.param.set_int(Param::GuaranteeE2ee, 1);
3019 msg.update_param(context).await?;
3020 }
3021
3022 msg.subject.clone_from(&rendered_msg.subject);
3023 msg.update_subject(context).await?;
3024 let chunk_size = context.get_max_smtp_rcpt_to().await?;
3025 let trans_fn = |t: &mut rusqlite::Transaction| {
3026 let mut row_ids = Vec::<i64>::new();
3027 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3028 t.execute(
3029 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3030 (),
3031 )?;
3032 t.execute(
3033 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3034 (&rendered_msg.message, msg.id),
3035 )?;
3036 } else {
3037 for recipients_chunk in recipients.chunks(chunk_size) {
3038 let recipients_chunk = recipients_chunk.join(" ");
3039 let row_id = t.execute(
3040 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3041 VALUES (?1, ?2, ?3, ?4)",
3042 (
3043 &rendered_msg.rfc724_mid,
3044 recipients_chunk,
3045 &rendered_msg.message,
3046 msg.id,
3047 ),
3048 )?;
3049 row_ids.push(row_id.try_into()?);
3050 }
3051 }
3052 Ok(row_ids)
3053 };
3054 context.sql.transaction(trans_fn).await
3055}
3056
3057pub async fn send_text_msg(
3061 context: &Context,
3062 chat_id: ChatId,
3063 text_to_send: String,
3064) -> Result<MsgId> {
3065 ensure!(
3066 !chat_id.is_special(),
3067 "bad chat_id, can not be a special chat: {}",
3068 chat_id
3069 );
3070
3071 let mut msg = Message::new_text(text_to_send);
3072 send_msg(context, chat_id, &mut msg).await
3073}
3074
3075pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3077 let mut original_msg = Message::load_from_db(context, msg_id).await?;
3078 ensure!(
3079 original_msg.from_id == ContactId::SELF,
3080 "Can edit only own messages"
3081 );
3082 ensure!(!original_msg.is_info(), "Cannot edit info messages");
3083 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3084 ensure!(
3085 original_msg.viewtype != Viewtype::VideochatInvitation,
3086 "Cannot edit videochat invitations"
3087 );
3088 ensure!(
3089 !original_msg.text.is_empty(), "Cannot add text"
3091 );
3092 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3093 if original_msg.text == new_text {
3094 info!(context, "Text unchanged.");
3095 return Ok(());
3096 }
3097
3098 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3099
3100 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() {
3103 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3104 }
3105 edit_msg
3106 .param
3107 .set(Param::TextEditFor, original_msg.rfc724_mid);
3108 edit_msg.hidden = true;
3109 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3110 Ok(())
3111}
3112
3113pub(crate) async fn save_text_edit_to_db(
3114 context: &Context,
3115 original_msg: &mut Message,
3116 new_text: &str,
3117) -> Result<()> {
3118 original_msg.param.set_int(Param::IsEdited, 1);
3119 context
3120 .sql
3121 .execute(
3122 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3123 (
3124 new_text,
3125 message::normalize_text(new_text),
3126 original_msg.param.to_string(),
3127 original_msg.id,
3128 ),
3129 )
3130 .await?;
3131 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3132 Ok(())
3133}
3134
3135pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3137 ensure!(
3138 !chat_id.is_special(),
3139 "video chat invitation cannot be sent to special chat: {}",
3140 chat_id
3141 );
3142
3143 let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3144 if !instance.is_empty() {
3145 instance
3146 } else {
3147 bail!("webrtc_instance is empty");
3148 }
3149 } else {
3150 bail!("webrtc_instance not set");
3151 };
3152
3153 let instance = Message::create_webrtc_instance(&instance, &create_id());
3154
3155 let mut msg = Message::new(Viewtype::VideochatInvitation);
3156 msg.param.set(Param::WebrtcRoom, &instance);
3157 msg.text =
3158 stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3159 .await;
3160 send_msg(context, chat_id, &mut msg).await
3161}
3162
3163#[derive(Debug)]
3165pub struct MessageListOptions {
3166 pub info_only: bool,
3168
3169 pub add_daymarker: bool,
3171}
3172
3173pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3175 get_chat_msgs_ex(
3176 context,
3177 chat_id,
3178 MessageListOptions {
3179 info_only: false,
3180 add_daymarker: false,
3181 },
3182 )
3183 .await
3184}
3185
3186pub async fn get_chat_msgs_ex(
3188 context: &Context,
3189 chat_id: ChatId,
3190 options: MessageListOptions,
3191) -> Result<Vec<ChatItem>> {
3192 let MessageListOptions {
3193 info_only,
3194 add_daymarker,
3195 } = options;
3196 let process_row = if info_only {
3197 |row: &rusqlite::Row| {
3198 let params = row.get::<_, String>("param")?;
3200 let (from_id, to_id) = (
3201 row.get::<_, ContactId>("from_id")?,
3202 row.get::<_, ContactId>("to_id")?,
3203 );
3204 let is_info_msg: bool = from_id == ContactId::INFO
3205 || to_id == ContactId::INFO
3206 || match Params::from_str(¶ms) {
3207 Ok(p) => {
3208 let cmd = p.get_cmd();
3209 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3210 }
3211 _ => false,
3212 };
3213
3214 Ok((
3215 row.get::<_, i64>("timestamp")?,
3216 row.get::<_, MsgId>("id")?,
3217 !is_info_msg,
3218 ))
3219 }
3220 } else {
3221 |row: &rusqlite::Row| {
3222 Ok((
3223 row.get::<_, i64>("timestamp")?,
3224 row.get::<_, MsgId>("id")?,
3225 false,
3226 ))
3227 }
3228 };
3229 let process_rows = |rows: rusqlite::MappedRows<_>| {
3230 let mut sorted_rows = Vec::new();
3233 for row in rows {
3234 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3235 if !exclude_message {
3236 sorted_rows.push((ts, curr_id));
3237 }
3238 }
3239 sorted_rows.sort_unstable();
3240
3241 let mut ret = Vec::new();
3242 let mut last_day = 0;
3243 let cnv_to_local = gm2local_offset();
3244
3245 for (ts, curr_id) in sorted_rows {
3246 if add_daymarker {
3247 let curr_local_timestamp = ts + cnv_to_local;
3248 let curr_day = curr_local_timestamp / 86400;
3249 if curr_day != last_day {
3250 ret.push(ChatItem::DayMarker {
3251 timestamp: curr_day * 86400, });
3253 last_day = curr_day;
3254 }
3255 }
3256 ret.push(ChatItem::Message { msg_id: curr_id });
3257 }
3258 Ok(ret)
3259 };
3260
3261 let items = if info_only {
3262 context
3263 .sql
3264 .query_map(
3265 "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
3267 FROM msgs m
3268 WHERE m.chat_id=?
3269 AND m.hidden=0
3270 AND (
3271 m.param GLOB \"*S=*\"
3272 OR m.from_id == ?
3273 OR m.to_id == ?
3274 );",
3275 (chat_id, ContactId::INFO, ContactId::INFO),
3276 process_row,
3277 process_rows,
3278 )
3279 .await?
3280 } else {
3281 context
3282 .sql
3283 .query_map(
3284 "SELECT m.id AS id, m.timestamp AS timestamp
3285 FROM msgs m
3286 WHERE m.chat_id=?
3287 AND m.hidden=0;",
3288 (chat_id,),
3289 process_row,
3290 process_rows,
3291 )
3292 .await?
3293 };
3294 Ok(items)
3295}
3296
3297pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3300 if chat_id.is_archived_link() {
3303 let chat_ids_in_archive = context
3304 .sql
3305 .query_map(
3306 "SELECT DISTINCT(m.chat_id) FROM msgs m
3307 LEFT JOIN chats c ON m.chat_id=c.id
3308 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3309 (),
3310 |row| row.get::<_, ChatId>(0),
3311 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3312 )
3313 .await?;
3314 if chat_ids_in_archive.is_empty() {
3315 return Ok(());
3316 }
3317
3318 context
3319 .sql
3320 .transaction(|transaction| {
3321 let mut stmt = transaction.prepare(
3322 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3323 )?;
3324 for chat_id_in_archive in &chat_ids_in_archive {
3325 stmt.execute((chat_id_in_archive,))?;
3326 }
3327 Ok(())
3328 })
3329 .await?;
3330
3331 for chat_id_in_archive in chat_ids_in_archive {
3332 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3333 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3334 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3335 }
3336 } else {
3337 start_chat_ephemeral_timers(context, chat_id).await?;
3338
3339 let noticed_msgs_count = context
3340 .sql
3341 .execute(
3342 "UPDATE msgs
3343 SET state=?
3344 WHERE state=?
3345 AND hidden=0
3346 AND chat_id=?;",
3347 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3348 )
3349 .await?;
3350
3351 let hidden_messages = context
3354 .sql
3355 .query_map(
3356 "SELECT id, rfc724_mid FROM msgs
3357 WHERE state=?
3358 AND hidden=1
3359 AND chat_id=?
3360 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3363 let msg_id: MsgId = row.get(0)?;
3364 let rfc724_mid: String = row.get(1)?;
3365 Ok((msg_id, rfc724_mid))
3366 },
3367 |rows| {
3368 rows.collect::<std::result::Result<Vec<_>, _>>()
3369 .map_err(Into::into)
3370 },
3371 )
3372 .await?;
3373 for (msg_id, rfc724_mid) in &hidden_messages {
3374 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3375 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3376 }
3377
3378 if noticed_msgs_count == 0 {
3379 return Ok(());
3380 }
3381 }
3382
3383 context.emit_event(EventType::MsgsNoticed(chat_id));
3384 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3385 context.on_archived_chats_maybe_noticed();
3386 Ok(())
3387}
3388
3389pub(crate) async fn mark_old_messages_as_noticed(
3396 context: &Context,
3397 mut msgs: Vec<ReceivedMsg>,
3398) -> Result<()> {
3399 msgs.retain(|m| m.state.is_outgoing());
3400 if msgs.is_empty() {
3401 return Ok(());
3402 }
3403
3404 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3405 for msg in msgs {
3406 let chat_id = msg.chat_id;
3407 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3408 if msg.sort_timestamp > existing_msg.sort_timestamp {
3409 msgs_by_chat.insert(chat_id, msg);
3410 }
3411 } else {
3412 msgs_by_chat.insert(chat_id, msg);
3413 }
3414 }
3415
3416 let changed_chats = context
3417 .sql
3418 .transaction(|transaction| {
3419 let mut changed_chats = Vec::new();
3420 for (_, msg) in msgs_by_chat {
3421 let changed_rows = transaction.execute(
3422 "UPDATE msgs
3423 SET state=?
3424 WHERE state=?
3425 AND hidden=0
3426 AND chat_id=?
3427 AND timestamp<=?;",
3428 (
3429 MessageState::InNoticed,
3430 MessageState::InFresh,
3431 msg.chat_id,
3432 msg.sort_timestamp,
3433 ),
3434 )?;
3435 if changed_rows > 0 {
3436 changed_chats.push(msg.chat_id);
3437 }
3438 }
3439 Ok(changed_chats)
3440 })
3441 .await?;
3442
3443 if !changed_chats.is_empty() {
3444 info!(
3445 context,
3446 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3447 );
3448 context.on_archived_chats_maybe_noticed();
3449 }
3450
3451 for c in changed_chats {
3452 start_chat_ephemeral_timers(context, c).await?;
3453 context.emit_event(EventType::MsgsNoticed(c));
3454 chatlist_events::emit_chatlist_item_changed(context, c);
3455 }
3456
3457 Ok(())
3458}
3459
3460pub async fn get_chat_media(
3467 context: &Context,
3468 chat_id: Option<ChatId>,
3469 msg_type: Viewtype,
3470 msg_type2: Viewtype,
3471 msg_type3: Viewtype,
3472) -> Result<Vec<MsgId>> {
3473 let list = if msg_type == Viewtype::Webxdc
3474 && msg_type2 == Viewtype::Unknown
3475 && msg_type3 == Viewtype::Unknown
3476 {
3477 context
3478 .sql
3479 .query_map(
3480 "SELECT id
3481 FROM msgs
3482 WHERE (1=? OR chat_id=?)
3483 AND chat_id != ?
3484 AND type = ?
3485 AND hidden=0
3486 ORDER BY max(timestamp, timestamp_rcvd), id;",
3487 (
3488 chat_id.is_none(),
3489 chat_id.unwrap_or_else(|| ChatId::new(0)),
3490 DC_CHAT_ID_TRASH,
3491 Viewtype::Webxdc,
3492 ),
3493 |row| row.get::<_, MsgId>(0),
3494 |ids| Ok(ids.flatten().collect()),
3495 )
3496 .await?
3497 } else {
3498 context
3499 .sql
3500 .query_map(
3501 "SELECT id
3502 FROM msgs
3503 WHERE (1=? OR chat_id=?)
3504 AND chat_id != ?
3505 AND type IN (?, ?, ?)
3506 AND hidden=0
3507 ORDER BY timestamp, id;",
3508 (
3509 chat_id.is_none(),
3510 chat_id.unwrap_or_else(|| ChatId::new(0)),
3511 DC_CHAT_ID_TRASH,
3512 msg_type,
3513 if msg_type2 != Viewtype::Unknown {
3514 msg_type2
3515 } else {
3516 msg_type
3517 },
3518 if msg_type3 != Viewtype::Unknown {
3519 msg_type3
3520 } else {
3521 msg_type
3522 },
3523 ),
3524 |row| row.get::<_, MsgId>(0),
3525 |ids| Ok(ids.flatten().collect()),
3526 )
3527 .await?
3528 };
3529 Ok(list)
3530}
3531
3532pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3534 let list = context
3538 .sql
3539 .query_map(
3540 "SELECT cc.contact_id
3541 FROM chats_contacts cc
3542 LEFT JOIN contacts c
3543 ON c.id=cc.contact_id
3544 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3545 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3546 (chat_id,),
3547 |row| row.get::<_, ContactId>(0),
3548 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3549 )
3550 .await?;
3551
3552 Ok(list)
3553}
3554
3555pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3559 let now = time();
3560 let list = context
3561 .sql
3562 .query_map(
3563 "SELECT cc.contact_id
3564 FROM chats_contacts cc
3565 LEFT JOIN contacts c
3566 ON c.id=cc.contact_id
3567 WHERE cc.chat_id=?
3568 AND cc.add_timestamp < cc.remove_timestamp
3569 AND ? < cc.remove_timestamp
3570 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3571 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3572 |row| row.get::<_, ContactId>(0),
3573 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3574 )
3575 .await?;
3576
3577 Ok(list)
3578}
3579
3580pub async fn create_group_chat(
3582 context: &Context,
3583 protect: ProtectionStatus,
3584 chat_name: &str,
3585) -> Result<ChatId> {
3586 let chat_name = sanitize_single_line(chat_name);
3587 ensure!(!chat_name.is_empty(), "Invalid chat name");
3588
3589 let grpid = create_id();
3590
3591 let timestamp = create_smeared_timestamp(context);
3592 let row_id = context
3593 .sql
3594 .insert(
3595 "INSERT INTO chats
3596 (type, name, grpid, param, created_timestamp)
3597 VALUES(?, ?, ?, \'U=1\', ?);",
3598 (Chattype::Group, chat_name, grpid, timestamp),
3599 )
3600 .await?;
3601
3602 let chat_id = ChatId::new(u32::try_from(row_id)?);
3603 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3604
3605 context.emit_msgs_changed_without_ids();
3606 chatlist_events::emit_chatlist_changed(context);
3607 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3608
3609 if protect == ProtectionStatus::Protected {
3610 chat_id
3611 .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3612 .await?;
3613 }
3614
3615 if !context.get_config_bool(Config::Bot).await?
3616 && !context.get_config_bool(Config::SkipStartMessages).await?
3617 {
3618 let text = stock_str::new_group_send_first_message(context).await;
3619 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3620 }
3621
3622 Ok(chat_id)
3623}
3624
3625pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
3641 let grpid = create_id();
3642 create_broadcast_ex(context, Sync, grpid, chat_name).await
3643}
3644
3645pub(crate) async fn create_broadcast_ex(
3646 context: &Context,
3647 sync: sync::Sync,
3648 grpid: String,
3649 chat_name: String,
3650) -> Result<ChatId> {
3651 let row_id = {
3652 let chat_name = &chat_name;
3653 let grpid = &grpid;
3654 let trans_fn = |t: &mut rusqlite::Transaction| {
3655 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3656 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3657 if cnt == 1 {
3658 return Ok(t.query_row(
3659 "SELECT id FROM chats WHERE grpid=? AND type=?",
3660 (grpid, Chattype::OutBroadcast),
3661 |row| {
3662 let id: isize = row.get(0)?;
3663 Ok(id)
3664 },
3665 )?);
3666 }
3667 t.execute(
3668 "INSERT INTO chats \
3669 (type, name, grpid, param, created_timestamp) \
3670 VALUES(?, ?, ?, \'U=1\', ?);",
3671 (
3672 Chattype::OutBroadcast,
3673 &chat_name,
3674 &grpid,
3675 create_smeared_timestamp(context),
3676 ),
3677 )?;
3678 Ok(t.last_insert_rowid().try_into()?)
3679 };
3680 context.sql.transaction(trans_fn).await?
3681 };
3682 let chat_id = ChatId::new(u32::try_from(row_id)?);
3683
3684 context.emit_msgs_changed_without_ids();
3685 chatlist_events::emit_chatlist_changed(context);
3686
3687 if sync.into() {
3688 let id = SyncId::Grpid(grpid);
3689 let action = SyncAction::CreateBroadcast(chat_name);
3690 self::sync(context, id, action).await.log_err(context).ok();
3691 }
3692
3693 Ok(chat_id)
3694}
3695
3696pub(crate) async fn update_chat_contacts_table(
3698 context: &Context,
3699 timestamp: i64,
3700 id: ChatId,
3701 contacts: &HashSet<ContactId>,
3702) -> Result<()> {
3703 context
3704 .sql
3705 .transaction(move |transaction| {
3706 transaction.execute(
3710 "UPDATE chats_contacts
3711 SET remove_timestamp=MAX(add_timestamp+1, ?)
3712 WHERE chat_id=?",
3713 (timestamp, id),
3714 )?;
3715
3716 if !contacts.is_empty() {
3717 let mut statement = transaction.prepare(
3718 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3719 VALUES (?1, ?2, ?3)
3720 ON CONFLICT (chat_id, contact_id)
3721 DO UPDATE SET add_timestamp=remove_timestamp",
3722 )?;
3723
3724 for contact_id in contacts {
3725 statement.execute((id, contact_id, timestamp))?;
3729 }
3730 }
3731 Ok(())
3732 })
3733 .await?;
3734 Ok(())
3735}
3736
3737pub(crate) async fn add_to_chat_contacts_table(
3739 context: &Context,
3740 timestamp: i64,
3741 chat_id: ChatId,
3742 contact_ids: &[ContactId],
3743) -> Result<()> {
3744 context
3745 .sql
3746 .transaction(move |transaction| {
3747 let mut add_statement = transaction.prepare(
3748 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3749 ON CONFLICT (chat_id, contact_id)
3750 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3751 )?;
3752
3753 for contact_id in contact_ids {
3754 add_statement.execute((chat_id, contact_id, timestamp))?;
3755 }
3756 Ok(())
3757 })
3758 .await?;
3759
3760 Ok(())
3761}
3762
3763pub(crate) async fn remove_from_chat_contacts_table(
3766 context: &Context,
3767 chat_id: ChatId,
3768 contact_id: ContactId,
3769) -> Result<()> {
3770 let now = time();
3771 context
3772 .sql
3773 .execute(
3774 "UPDATE chats_contacts
3775 SET remove_timestamp=MAX(add_timestamp+1, ?)
3776 WHERE chat_id=? AND contact_id=?",
3777 (now, chat_id, contact_id),
3778 )
3779 .await?;
3780 Ok(())
3781}
3782
3783pub async fn add_contact_to_chat(
3786 context: &Context,
3787 chat_id: ChatId,
3788 contact_id: ContactId,
3789) -> Result<()> {
3790 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3791 Ok(())
3792}
3793
3794pub(crate) async fn add_contact_to_chat_ex(
3795 context: &Context,
3796 mut sync: sync::Sync,
3797 chat_id: ChatId,
3798 contact_id: ContactId,
3799 from_handshake: bool,
3800) -> Result<bool> {
3801 ensure!(!chat_id.is_special(), "can not add member to special chats");
3802 let contact = Contact::get_by_id(context, contact_id).await?;
3803 let mut msg = Message::new(Viewtype::default());
3804
3805 chat_id.reset_gossiped_timestamp(context).await?;
3806
3807 let mut chat = Chat::load_from_db(context, chat_id).await?;
3809 ensure!(
3810 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
3811 "{} is not a group/broadcast where one can add members",
3812 chat_id
3813 );
3814 ensure!(
3815 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3816 "invalid contact_id {} for adding to group",
3817 contact_id
3818 );
3819 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3820 ensure!(
3821 chat.typ != Chattype::OutBroadcast || contact_id != ContactId::SELF,
3822 "Cannot add SELF to broadcast channel."
3823 );
3824 ensure!(
3825 chat.is_encrypted(context).await? == contact.is_key_contact(),
3826 "Only key-contacts can be added to encrypted chats"
3827 );
3828
3829 if !chat.is_self_in_chat(context).await? {
3830 context.emit_event(EventType::ErrorSelfNotInGroup(
3831 "Cannot add contact to group; self not in group.".into(),
3832 ));
3833 bail!("can not add contact because the account is not part of the group/broadcast");
3834 }
3835
3836 let sync_qr_code_tokens;
3837 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3838 chat.param
3839 .remove(Param::Unpromoted)
3840 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3841 chat.update_param(context).await?;
3842 sync_qr_code_tokens = true;
3843 } else {
3844 sync_qr_code_tokens = false;
3845 }
3846
3847 if context.is_self_addr(contact.get_addr()).await? {
3848 warn!(
3851 context,
3852 "Invalid attempt to add self e-mail address to group."
3853 );
3854 return Ok(false);
3855 }
3856
3857 if is_contact_in_chat(context, chat_id, contact_id).await? {
3858 if !from_handshake {
3859 return Ok(true);
3860 }
3861 } else {
3862 if chat.is_protected() && !contact.is_verified(context).await? {
3864 error!(
3865 context,
3866 "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
3867 );
3868 return Ok(false);
3869 }
3870 if is_contact_in_chat(context, chat_id, contact_id).await? {
3871 return Ok(false);
3872 }
3873 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3874 }
3875 if chat.typ == Chattype::Group && chat.is_promoted() {
3876 msg.viewtype = Viewtype::Text;
3877
3878 let contact_addr = contact.get_addr().to_lowercase();
3879 msg.text = stock_str::msg_add_member_local(context, contact.id, ContactId::SELF).await;
3880 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3881 msg.param.set(Param::Arg, contact_addr);
3882 msg.param.set_int(Param::Arg2, from_handshake.into());
3883 msg.param
3884 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3885 send_msg(context, chat_id, &mut msg).await?;
3886
3887 sync = Nosync;
3888 if sync_qr_code_tokens
3894 && context
3895 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3896 .await
3897 .log_err(context)
3898 .is_ok()
3899 {
3900 context.scheduler.interrupt_inbox().await;
3901 }
3902 }
3903 context.emit_event(EventType::ChatModified(chat_id));
3904 if sync.into() {
3905 chat.sync_contacts(context).await.log_err(context).ok();
3906 }
3907 Ok(true)
3908}
3909
3910pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3916 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3917 let needs_attach = context
3918 .sql
3919 .query_map(
3920 "SELECT c.selfavatar_sent
3921 FROM chats_contacts cc
3922 LEFT JOIN contacts c ON c.id=cc.contact_id
3923 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3924 (chat_id, ContactId::SELF),
3925 |row| Ok(row.get::<_, i64>(0)),
3926 |rows| {
3927 let mut needs_attach = false;
3928 for row in rows {
3929 let row = row?;
3930 let selfavatar_sent = row?;
3931 if selfavatar_sent < timestamp_some_days_ago {
3932 needs_attach = true;
3933 }
3934 }
3935 Ok(needs_attach)
3936 },
3937 )
3938 .await?;
3939 Ok(needs_attach)
3940}
3941
3942#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3944pub enum MuteDuration {
3945 NotMuted,
3947
3948 Forever,
3950
3951 Until(std::time::SystemTime),
3953}
3954
3955impl rusqlite::types::ToSql for MuteDuration {
3956 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
3957 let duration: i64 = match &self {
3958 MuteDuration::NotMuted => 0,
3959 MuteDuration::Forever => -1,
3960 MuteDuration::Until(when) => {
3961 let duration = when
3962 .duration_since(SystemTime::UNIX_EPOCH)
3963 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
3964 i64::try_from(duration.as_secs())
3965 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
3966 }
3967 };
3968 let val = rusqlite::types::Value::Integer(duration);
3969 let out = rusqlite::types::ToSqlOutput::Owned(val);
3970 Ok(out)
3971 }
3972}
3973
3974impl rusqlite::types::FromSql for MuteDuration {
3975 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
3976 match i64::column_result(value)? {
3979 0 => Ok(MuteDuration::NotMuted),
3980 -1 => Ok(MuteDuration::Forever),
3981 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
3982 Some(t) => Ok(MuteDuration::Until(t)),
3983 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
3984 },
3985 _ => Ok(MuteDuration::NotMuted),
3986 }
3987 }
3988}
3989
3990pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
3992 set_muted_ex(context, Sync, chat_id, duration).await
3993}
3994
3995pub(crate) async fn set_muted_ex(
3996 context: &Context,
3997 sync: sync::Sync,
3998 chat_id: ChatId,
3999 duration: MuteDuration,
4000) -> Result<()> {
4001 ensure!(!chat_id.is_special(), "Invalid chat ID");
4002 context
4003 .sql
4004 .execute(
4005 "UPDATE chats SET muted_until=? WHERE id=?;",
4006 (duration, chat_id),
4007 )
4008 .await
4009 .context(format!("Failed to set mute duration for {chat_id}"))?;
4010 context.emit_event(EventType::ChatModified(chat_id));
4011 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4012 if sync.into() {
4013 let chat = Chat::load_from_db(context, chat_id).await?;
4014 chat.sync(context, SyncAction::SetMuted(duration))
4015 .await
4016 .log_err(context)
4017 .ok();
4018 }
4019 Ok(())
4020}
4021
4022pub async fn remove_contact_from_chat(
4024 context: &Context,
4025 chat_id: ChatId,
4026 contact_id: ContactId,
4027) -> Result<()> {
4028 ensure!(
4029 !chat_id.is_special(),
4030 "bad chat_id, can not be special chat: {}",
4031 chat_id
4032 );
4033 ensure!(
4034 !contact_id.is_special() || contact_id == ContactId::SELF,
4035 "Cannot remove special contact"
4036 );
4037
4038 let mut msg = Message::new(Viewtype::default());
4039
4040 let chat = Chat::load_from_db(context, chat_id).await?;
4041 if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
4042 if !chat.is_self_in_chat(context).await? {
4043 let err_msg = format!(
4044 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4045 );
4046 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4047 bail!("{}", err_msg);
4048 } else {
4049 let mut sync = Nosync;
4050
4051 if chat.is_promoted() {
4052 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4053 } else {
4054 context
4055 .sql
4056 .execute(
4057 "DELETE FROM chats_contacts
4058 WHERE chat_id=? AND contact_id=?",
4059 (chat_id, contact_id),
4060 )
4061 .await?;
4062 }
4063
4064 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4068 if chat.typ == Chattype::Group && chat.is_promoted() {
4069 msg.viewtype = Viewtype::Text;
4070 if contact_id == ContactId::SELF {
4071 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4072 } else {
4073 msg.text =
4074 stock_str::msg_del_member_local(context, contact_id, ContactId::SELF)
4075 .await;
4076 }
4077 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4078 msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
4079 msg.param
4080 .set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4081 let res = send_msg(context, chat_id, &mut msg).await;
4082 if contact_id == ContactId::SELF {
4083 res?;
4084 set_group_explicitly_left(context, &chat.grpid).await?;
4085 } else if let Err(e) = res {
4086 warn!(
4087 context,
4088 "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}."
4089 );
4090 }
4091 } else {
4092 sync = Sync;
4093 }
4094 }
4095 context.emit_event(EventType::ChatModified(chat_id));
4096 if sync.into() {
4097 chat.sync_contacts(context).await.log_err(context).ok();
4098 }
4099 }
4100 } else {
4101 bail!("Cannot remove members from non-group chats.");
4102 }
4103
4104 Ok(())
4105}
4106
4107async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4108 if !is_group_explicitly_left(context, grpid).await? {
4109 context
4110 .sql
4111 .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4112 .await?;
4113 }
4114
4115 Ok(())
4116}
4117
4118pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4119 let exists = context
4120 .sql
4121 .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4122 .await?;
4123 Ok(exists)
4124}
4125
4126pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4128 rename_ex(context, Sync, chat_id, new_name).await
4129}
4130
4131async fn rename_ex(
4132 context: &Context,
4133 mut sync: sync::Sync,
4134 chat_id: ChatId,
4135 new_name: &str,
4136) -> Result<()> {
4137 let new_name = sanitize_single_line(new_name);
4138 let mut success = false;
4140
4141 ensure!(!new_name.is_empty(), "Invalid name");
4142 ensure!(!chat_id.is_special(), "Invalid chat ID");
4143
4144 let chat = Chat::load_from_db(context, chat_id).await?;
4145 let mut msg = Message::new(Viewtype::default());
4146
4147 if chat.typ == Chattype::Group
4148 || chat.typ == Chattype::Mailinglist
4149 || chat.typ == Chattype::OutBroadcast
4150 {
4151 if chat.name == new_name {
4152 success = true;
4153 } else if !chat.is_self_in_chat(context).await? {
4154 context.emit_event(EventType::ErrorSelfNotInGroup(
4155 "Cannot set chat name; self not in group".into(),
4156 ));
4157 } else {
4158 context
4159 .sql
4160 .execute(
4161 "UPDATE chats SET name=? WHERE id=?;",
4162 (new_name.to_string(), chat_id),
4163 )
4164 .await?;
4165 if chat.is_promoted()
4166 && !chat.is_mailing_list()
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 || chat.typ == Chattype::OutBroadcast,
4213 "Can only set profile image for groups / broadcasts"
4214 );
4215 ensure!(
4216 !chat.grpid.is_empty(),
4217 "Cannot set profile image for ad hoc groups"
4218 );
4219 if !chat.is_self_in_chat(context).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 {
4288 msg.param
4289 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4290 }
4291
4292 msg.param.remove(Param::GuaranteeE2ee);
4293 msg.param.remove(Param::ForcePlaintext);
4294 msg.param.remove(Param::Cmd);
4295 msg.param.remove(Param::OverrideSenderDisplayname);
4296 msg.param.remove(Param::WebxdcDocument);
4297 msg.param.remove(Param::WebxdcDocumentTimestamp);
4298 msg.param.remove(Param::WebxdcSummary);
4299 msg.param.remove(Param::WebxdcSummaryTimestamp);
4300 msg.param.remove(Param::IsEdited);
4301 msg.in_reply_to = None;
4302
4303 msg.subject = "".to_string();
4305
4306 msg.state = MessageState::OutPending;
4307 msg.rfc724_mid = create_outgoing_rfc724_mid();
4308 let new_msg_id = chat
4309 .prepare_msg_raw(context, &mut msg, None, curr_timestamp)
4310 .await?;
4311
4312 curr_timestamp += 1;
4313 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4314 context.scheduler.interrupt_smtp().await;
4315 }
4316 created_msgs.push(new_msg_id);
4317 }
4318 for msg_id in created_msgs {
4319 context.emit_msgs_changed(chat_id, msg_id);
4320 }
4321 Ok(())
4322}
4323
4324pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4327 let mut msgs = Vec::with_capacity(msg_ids.len());
4328 for id in msg_ids {
4329 let ts: i64 = context
4330 .sql
4331 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4332 .await?
4333 .with_context(|| format!("No message {id}"))?;
4334 msgs.push((ts, *id));
4335 }
4336 msgs.sort_unstable();
4337 for (_, src_msg_id) in msgs {
4338 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4339 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4340 context
4341 .add_sync_item(SyncData::SaveMessage {
4342 src: src_rfc724_mid,
4343 dest: dest_rfc724_mid,
4344 })
4345 .await?;
4346 }
4347 context.scheduler.interrupt_inbox().await;
4348 Ok(())
4349}
4350
4351pub(crate) async fn save_copy_in_self_talk(
4357 context: &Context,
4358 src_msg_id: MsgId,
4359 dest_rfc724_mid: &String,
4360) -> Result<String> {
4361 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4362 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4363 msg.param.remove(Param::Cmd);
4364 msg.param.remove(Param::WebxdcDocument);
4365 msg.param.remove(Param::WebxdcDocumentTimestamp);
4366 msg.param.remove(Param::WebxdcSummary);
4367 msg.param.remove(Param::WebxdcSummaryTimestamp);
4368
4369 if !msg.original_msg_id.is_unset() {
4370 bail!("message already saved.");
4371 }
4372
4373 let copy_fields = "from_id, to_id, timestamp_sent, timestamp_rcvd, type, txt, \
4374 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4375 let row_id = context
4376 .sql
4377 .insert(
4378 &format!(
4379 "INSERT INTO msgs ({copy_fields}, chat_id, rfc724_mid, state, timestamp, param, starred) \
4380 SELECT {copy_fields}, ?, ?, ?, ?, ?, ? \
4381 FROM msgs WHERE id=?;"
4382 ),
4383 (
4384 dest_chat_id,
4385 dest_rfc724_mid,
4386 if msg.from_id == ContactId::SELF {
4387 MessageState::OutDelivered
4388 } else {
4389 MessageState::InSeen
4390 },
4391 create_smeared_timestamp(context),
4392 msg.param.to_string(),
4393 src_msg_id,
4394 src_msg_id,
4395 ),
4396 )
4397 .await?;
4398 let dest_msg_id = MsgId::new(row_id.try_into()?);
4399
4400 context.emit_msgs_changed(msg.chat_id, src_msg_id);
4401 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4402 chatlist_events::emit_chatlist_changed(context);
4403 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4404
4405 Ok(msg.rfc724_mid)
4406}
4407
4408pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4412 let mut chat_id = None;
4413 let mut msgs: Vec<Message> = Vec::new();
4414 for msg_id in msg_ids {
4415 let msg = Message::load_from_db(context, *msg_id).await?;
4416 if let Some(chat_id) = chat_id {
4417 ensure!(
4418 chat_id == msg.chat_id,
4419 "messages to resend needs to be in the same chat"
4420 );
4421 } else {
4422 chat_id = Some(msg.chat_id);
4423 }
4424 ensure!(
4425 msg.from_id == ContactId::SELF,
4426 "can resend only own messages"
4427 );
4428 ensure!(!msg.is_info(), "cannot resend info messages");
4429 msgs.push(msg)
4430 }
4431
4432 let Some(chat_id) = chat_id else {
4433 return Ok(());
4434 };
4435
4436 let chat = Chat::load_from_db(context, chat_id).await?;
4437 for mut msg in msgs {
4438 if msg.get_showpadlock() && !chat.is_protected() {
4439 msg.param.remove(Param::GuaranteeE2ee);
4440 msg.update_param(context).await?;
4441 }
4442 match msg.get_state() {
4443 MessageState::OutPending
4445 | MessageState::OutFailed
4446 | MessageState::OutDelivered
4447 | MessageState::OutMdnRcvd => {
4448 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4449 }
4450 msg_state => bail!("Unexpected message state {msg_state}"),
4451 }
4452 context.emit_event(EventType::MsgsChanged {
4453 chat_id: msg.chat_id,
4454 msg_id: msg.id,
4455 });
4456 msg.timestamp_sort = create_smeared_timestamp(context);
4457 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4459 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4460 continue;
4461 }
4462 if msg.viewtype == Viewtype::Webxdc {
4463 let conn_fn = |conn: &mut rusqlite::Connection| {
4464 let range = conn.query_row(
4465 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4466 FROM msgs_status_updates WHERE msg_id=?",
4467 (msg.id,),
4468 |row| {
4469 let min_id: StatusUpdateSerial = row.get(0)?;
4470 let max_id: StatusUpdateSerial = row.get(1)?;
4471 Ok((min_id, max_id))
4472 },
4473 )?;
4474 if range.0 > range.1 {
4475 return Ok(());
4476 };
4477 conn.execute(
4481 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4482 VALUES(?, ?, ?, '') \
4483 ON CONFLICT(msg_id) \
4484 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4485 (msg.id, range.0, range.1),
4486 )?;
4487 Ok(())
4488 };
4489 context.sql.call_write(conn_fn).await?;
4490 }
4491 context.scheduler.interrupt_smtp().await;
4492 }
4493 Ok(())
4494}
4495
4496pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4497 if context.sql.is_open().await {
4498 let count = context
4500 .sql
4501 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4502 .await?;
4503 Ok(count)
4504 } else {
4505 Ok(0)
4506 }
4507}
4508
4509pub(crate) async fn get_chat_id_by_grpid(
4511 context: &Context,
4512 grpid: &str,
4513) -> Result<Option<(ChatId, bool, Blocked)>> {
4514 context
4515 .sql
4516 .query_row_optional(
4517 "SELECT id, blocked, protected FROM chats WHERE grpid=?;",
4518 (grpid,),
4519 |row| {
4520 let chat_id = row.get::<_, ChatId>(0)?;
4521
4522 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4523 let p = row
4524 .get::<_, Option<ProtectionStatus>>(2)?
4525 .unwrap_or_default();
4526 Ok((chat_id, p == ProtectionStatus::Protected, b))
4527 },
4528 )
4529 .await
4530}
4531
4532pub async fn add_device_msg_with_importance(
4537 context: &Context,
4538 label: Option<&str>,
4539 msg: Option<&mut Message>,
4540 important: bool,
4541) -> Result<MsgId> {
4542 ensure!(
4543 label.is_some() || msg.is_some(),
4544 "device-messages need label, msg or both"
4545 );
4546 let mut chat_id = ChatId::new(0);
4547 let mut msg_id = MsgId::new_unset();
4548
4549 if let Some(label) = label {
4550 if was_device_msg_ever_added(context, label).await? {
4551 info!(context, "Device-message {label} already added.");
4552 return Ok(msg_id);
4553 }
4554 }
4555
4556 if let Some(msg) = msg {
4557 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4558
4559 let rfc724_mid = create_outgoing_rfc724_mid();
4560 prepare_msg_blob(context, msg).await?;
4561
4562 let timestamp_sent = create_smeared_timestamp(context);
4563
4564 let mut timestamp_sort = timestamp_sent;
4567 if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4568 if timestamp_sort <= last_msg_time {
4569 timestamp_sort = last_msg_time + 1;
4570 }
4571 }
4572
4573 let state = MessageState::InFresh;
4574 let row_id = context
4575 .sql
4576 .insert(
4577 "INSERT INTO msgs (
4578 chat_id,
4579 from_id,
4580 to_id,
4581 timestamp,
4582 timestamp_sent,
4583 timestamp_rcvd,
4584 type,state,
4585 txt,
4586 txt_normalized,
4587 param,
4588 rfc724_mid)
4589 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4590 (
4591 chat_id,
4592 ContactId::DEVICE,
4593 ContactId::SELF,
4594 timestamp_sort,
4595 timestamp_sent,
4596 timestamp_sent, msg.viewtype,
4598 state,
4599 &msg.text,
4600 message::normalize_text(&msg.text),
4601 msg.param.to_string(),
4602 rfc724_mid,
4603 ),
4604 )
4605 .await?;
4606 context.new_msgs_notify.notify_one();
4607
4608 msg_id = MsgId::new(u32::try_from(row_id)?);
4609 if !msg.hidden {
4610 chat_id.unarchive_if_not_muted(context, state).await?;
4611 }
4612 }
4613
4614 if let Some(label) = label {
4615 context
4616 .sql
4617 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4618 .await?;
4619 }
4620
4621 if !msg_id.is_unset() {
4622 chat_id.emit_msg_event(context, msg_id, important);
4623 }
4624
4625 Ok(msg_id)
4626}
4627
4628pub async fn add_device_msg(
4630 context: &Context,
4631 label: Option<&str>,
4632 msg: Option<&mut Message>,
4633) -> Result<MsgId> {
4634 add_device_msg_with_importance(context, label, msg, false).await
4635}
4636
4637pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4639 ensure!(!label.is_empty(), "empty label");
4640 let exists = context
4641 .sql
4642 .exists(
4643 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4644 (label,),
4645 )
4646 .await?;
4647
4648 Ok(exists)
4649}
4650
4651pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4659 context
4660 .sql
4661 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4662 .await?;
4663 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4664
4665 context
4667 .sql
4668 .execute(
4669 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4670 (),
4671 )
4672 .await?;
4673 context
4674 .set_config_internal(Config::QuotaExceeding, None)
4675 .await?;
4676 Ok(())
4677}
4678
4679#[expect(clippy::too_many_arguments)]
4684pub(crate) async fn add_info_msg_with_cmd(
4685 context: &Context,
4686 chat_id: ChatId,
4687 text: &str,
4688 cmd: SystemMessage,
4689 timestamp_sort: i64,
4690 timestamp_sent_rcvd: Option<i64>,
4692 parent: Option<&Message>,
4693 from_id: Option<ContactId>,
4694 added_removed_id: Option<ContactId>,
4695) -> Result<MsgId> {
4696 let rfc724_mid = create_outgoing_rfc724_mid();
4697 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4698
4699 let mut param = Params::new();
4700 if cmd != SystemMessage::Unknown {
4701 param.set_cmd(cmd);
4702 }
4703 if let Some(contact_id) = added_removed_id {
4704 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4705 }
4706
4707 let row_id =
4708 context.sql.insert(
4709 "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)
4710 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4711 (
4712 chat_id,
4713 from_id.unwrap_or(ContactId::INFO),
4714 ContactId::INFO,
4715 timestamp_sort,
4716 timestamp_sent_rcvd.unwrap_or(0),
4717 timestamp_sent_rcvd.unwrap_or(0),
4718 Viewtype::Text,
4719 MessageState::InNoticed,
4720 text,
4721 message::normalize_text(text),
4722 rfc724_mid,
4723 ephemeral_timer,
4724 param.to_string(),
4725 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4726 )
4727 ).await?;
4728 context.new_msgs_notify.notify_one();
4729
4730 let msg_id = MsgId::new(row_id.try_into()?);
4731 context.emit_msgs_changed(chat_id, msg_id);
4732
4733 Ok(msg_id)
4734}
4735
4736pub(crate) async fn add_info_msg(
4738 context: &Context,
4739 chat_id: ChatId,
4740 text: &str,
4741 timestamp: i64,
4742) -> Result<MsgId> {
4743 add_info_msg_with_cmd(
4744 context,
4745 chat_id,
4746 text,
4747 SystemMessage::Unknown,
4748 timestamp,
4749 None,
4750 None,
4751 None,
4752 None,
4753 )
4754 .await
4755}
4756
4757pub(crate) async fn update_msg_text_and_timestamp(
4758 context: &Context,
4759 chat_id: ChatId,
4760 msg_id: MsgId,
4761 text: &str,
4762 timestamp: i64,
4763) -> Result<()> {
4764 context
4765 .sql
4766 .execute(
4767 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4768 (text, message::normalize_text(text), timestamp, msg_id),
4769 )
4770 .await?;
4771 context.emit_msgs_changed(chat_id, msg_id);
4772 Ok(())
4773}
4774
4775async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4777 let chat = Chat::load_from_db(context, id).await?;
4778 ensure!(
4779 !chat.is_encrypted(context).await?,
4780 "Cannot add address-contacts to encrypted chat {id}"
4781 );
4782 ensure!(
4783 chat.typ == Chattype::OutBroadcast,
4784 "{id} is not a broadcast list",
4785 );
4786 let mut contacts = HashSet::new();
4787 for addr in addrs {
4788 let contact_addr = ContactAddress::new(addr)?;
4789 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4790 .await?
4791 .0;
4792 contacts.insert(contact);
4793 }
4794 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4795 if contacts == contacts_old {
4796 return Ok(());
4797 }
4798 context
4799 .sql
4800 .transaction(move |transaction| {
4801 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4802
4803 let mut statement = transaction
4806 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4807 for contact_id in &contacts {
4808 statement.execute((id, contact_id))?;
4809 }
4810 Ok(())
4811 })
4812 .await?;
4813 context.emit_event(EventType::ChatModified(id));
4814 Ok(())
4815}
4816
4817async fn set_contacts_by_fingerprints(
4821 context: &Context,
4822 id: ChatId,
4823 fingerprint_addrs: &[(String, String)],
4824) -> Result<()> {
4825 let chat = Chat::load_from_db(context, id).await?;
4826 ensure!(
4827 chat.is_encrypted(context).await?,
4828 "Cannot add key-contacts to unencrypted chat {id}"
4829 );
4830 ensure!(
4831 chat.typ == Chattype::OutBroadcast,
4832 "{id} is not a broadcast list",
4833 );
4834 let mut contacts = HashSet::new();
4835 for (fingerprint, addr) in fingerprint_addrs {
4836 let contact_addr = ContactAddress::new(addr)?;
4837 let contact =
4838 Contact::add_or_lookup_ex(context, "", &contact_addr, fingerprint, Origin::Hidden)
4839 .await?
4840 .0;
4841 contacts.insert(contact);
4842 }
4843 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4844 if contacts == contacts_old {
4845 return Ok(());
4846 }
4847 context
4848 .sql
4849 .transaction(move |transaction| {
4850 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4851
4852 let mut statement = transaction
4855 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4856 for contact_id in &contacts {
4857 statement.execute((id, contact_id))?;
4858 }
4859 Ok(())
4860 })
4861 .await?;
4862 context.emit_event(EventType::ChatModified(id));
4863 Ok(())
4864}
4865
4866#[derive(Debug, Serialize, Deserialize, PartialEq)]
4868pub(crate) enum SyncId {
4869 ContactAddr(String),
4871
4872 ContactFingerprint(String),
4874
4875 Grpid(String),
4876 Msgids(Vec<String>),
4878
4879 Device,
4881}
4882
4883#[derive(Debug, Serialize, Deserialize, PartialEq)]
4885pub(crate) enum SyncAction {
4886 Block,
4887 Unblock,
4888 Accept,
4889 SetVisibility(ChatVisibility),
4890 SetMuted(MuteDuration),
4891 CreateBroadcast(String),
4893 Rename(String),
4894 SetContacts(Vec<String>),
4896 SetPgpContacts(Vec<(String, String)>),
4900 Delete,
4901}
4902
4903impl Context {
4904 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4906 let chat_id = match id {
4907 SyncId::ContactAddr(addr) => {
4908 if let SyncAction::Rename(to) = action {
4909 Contact::create_ex(self, Nosync, to, addr).await?;
4910 return Ok(());
4911 }
4912 let addr = ContactAddress::new(addr).context("Invalid address")?;
4913 let (contact_id, _) =
4914 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4915 match action {
4916 SyncAction::Block => {
4917 return contact::set_blocked(self, Nosync, contact_id, true).await;
4918 }
4919 SyncAction::Unblock => {
4920 return contact::set_blocked(self, Nosync, contact_id, false).await;
4921 }
4922 _ => (),
4923 }
4924 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4927 .await?
4928 .id
4929 }
4930 SyncId::ContactFingerprint(fingerprint) => {
4931 let name = "";
4932 let addr = "";
4933 let (contact_id, _) =
4934 Contact::add_or_lookup_ex(self, name, addr, fingerprint, Origin::Hidden)
4935 .await?;
4936 match action {
4937 SyncAction::Rename(to) => {
4938 contact_id.set_name_ex(self, Nosync, to).await?;
4939 self.emit_event(EventType::ContactsChanged(Some(contact_id)));
4940 return Ok(());
4941 }
4942 SyncAction::Block => {
4943 return contact::set_blocked(self, Nosync, contact_id, true).await;
4944 }
4945 SyncAction::Unblock => {
4946 return contact::set_blocked(self, Nosync, contact_id, false).await;
4947 }
4948 _ => (),
4949 }
4950 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4951 .await?
4952 .id
4953 }
4954 SyncId::Grpid(grpid) => {
4955 if let SyncAction::CreateBroadcast(name) = action {
4956 create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
4957 return Ok(());
4958 }
4959 get_chat_id_by_grpid(self, grpid)
4960 .await?
4961 .with_context(|| format!("No chat for grpid '{grpid}'"))?
4962 .0
4963 }
4964 SyncId::Msgids(msgids) => {
4965 let msg = message::get_by_rfc724_mids(self, msgids)
4966 .await?
4967 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4968 ChatId::lookup_by_message(&msg)
4969 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4970 }
4971 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4972 };
4973 match action {
4974 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4975 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4976 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4977 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4978 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4979 SyncAction::CreateBroadcast(_) => {
4980 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4981 }
4982 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4983 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4984 SyncAction::SetPgpContacts(fingerprint_addrs) => {
4985 set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
4986 }
4987 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4988 }
4989 }
4990
4991 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4996 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4997 }
4998}
4999
5000#[cfg(test)]
5001mod chat_tests;